-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpcm.py
executable file
·143 lines (113 loc) · 4.39 KB
/
pcm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# pcm.py
# Converts between .wav files and 1-bit pcm data. (pcm = pulse-code modulation)
import argparse
import os
import struct
import wave
BASE_SAMPLE_RATE = 16384
def convert_to_wav(filenames=[]):
"""
Converts a file containing 1-bit pcm data into a .wav file.
"""
for filename in filenames:
with open(filename, 'rb') as pcm_file:
# Generate array of on/off pcm values.
samples = []
byte = pcm_file.read(1)
while byte != "":
byte, = struct.unpack('B', byte)
samples.append((byte & 0xf0) >> 4)
samples.append(byte & 0xf)
byte = pcm_file.read(1)
# Write a .wav file using the pcm data.
name, extension = os.path.splitext(filename)
wav_filename = name + '.wav'
wave_file = wave.open(wav_filename, 'w')
wave_file.setframerate(BASE_SAMPLE_RATE)
wave_file.setnchannels(1)
wave_file.setsampwidth(1)
for value in samples:
value = max(16 * value - 1, 0)
packed_value = struct.pack('B', value)
wave_file.writeframesraw(packed_value)
wave_file.close()
def convert_to_pcm(filenames=[]):
"""
Converts a .wav file into 1-bit pcm data.
Samples in the .wav file are simply clamped to on/off.
This currently works correctly on .wav files with the following attributes:
1. Sample Width = 1 or 2 bytes (Some wave files use 3 bytes per sample...)
2. Arbitrary sample sample_rate
3. Mono or Stereo (1 or 2 channels)
"""
for filename in filenames:
samples, sample_bins = get_wav_samples(filename)
# Generate a list of clamped samples
clamped_samples = [sample_bins.index(sample) for sample in samples]
# The pcm data must be a multiple of 2, so pad the clamped samples with 0.
while len(clamped_samples) % 2 != 0:
clamped_samples.append(0)
# Pack the 4-bit samples together.
packed_samples = bytearray()
for i in xrange(0, len(clamped_samples), 2):
# Read 2 pcm values to pack one byte.
packed_value = (clamped_samples[i] << 4) | clamped_samples[i + 1]
packed_samples.append(packed_value)
# Open the output .pcm file, and write all 1-bit samples.
name, extension = os.path.splitext(filename)
pcm_filename = name + '.pcm'
with open(pcm_filename, 'wb') as out_file:
out_file.write(packed_samples)
def get_wav_samples(filename):
"""
Reads the given .wav file and returns a list of its samples after re-sampling
to BASE_SAMPLE_RATE.
Also returns the average sample amplitude.
"""
wav_file = wave.open(filename, 'r')
sample_width = wav_file.getsampwidth()
sample_count = wav_file.getnframes()
sample_rate = wav_file.getframerate()
num_channels = wav_file.getnchannels()
samples = bytearray(wav_file.readframes(sample_count))
# Unpack the values based on the sample byte width.
unpacked_samples = []
for i in xrange(0, len(samples), sample_width):
if sample_width == 1:
fmt = 'B'
elif sample_width == 2:
fmt = 'h'
else:
# todo: support 3-byte sample width
raise Exception, "Unsupported sample width: " + str(sample_width)
value, = struct.unpack(fmt, samples[i:i + sample_width])
unpacked_samples.append(value)
# Only keep the samples from the first audio channel.
unpacked_samples = unpacked_samples[::num_channels]
# Approximate the BASE_SAMPLE_RATE.
# Also find the average amplitude of the samples.
resampled_samples = []
total_value = 0
interval = float(sample_rate) / BASE_SAMPLE_RATE
index = 0
while index < sample_count:
sample = unpacked_samples[int(index)]
total_value += sample
resampled_samples.append(sample)
index += interval
sample_bins = sorted(set(resampled_samples))
return resampled_samples, sample_bins
def main():
ap = argparse.ArgumentParser()
ap.add_argument('mode')
ap.add_argument('filenames', nargs='*')
args = ap.parse_args()
method = {
'wav': convert_to_wav,
'pcm': convert_to_pcm,
}.get(args.mode, None)
if method == None:
raise Exception, "Unknown conversion method!"
method(args.filenames)
if __name__ == "__main__":
main()