A sound fragment represents a sequence of signed integer sound samples encoded in a bytes-like object. audioop
supports representations of 1, 2, 3 or 4 bytes per sample.
A single sample can be converted with struct.pack
(let's use 0, 1, 2, -1, 42 as examples):
from struct import pack
for sample in [0, 1, 2, -1, 42]:
print(f'sample value {sample}, 1 byte/sample:', pack('b', sample))
print(f' {sample}, 2 byte/sample:', pack('h', sample))
print(f' {sample}, 4 byte/sample:', pack('i', sample))
This prints:
sample value 0, 1 byte/sample: b'\x00'
0, 2 byte/sample: b'\x00\x00'
0, 4 byte/sample: b'\x00\x00\x00\x00'
sample value 1, 1 byte/sample: b'\x01'
1, 2 byte/sample: b'\x01\x00'
1, 4 byte/sample: b'\x01\x00\x00\x00'
sample value 2, 1 byte/sample: b'\x02'
2, 2 byte/sample: b'\x02\x00'
2, 4 byte/sample: b'\x02\x00\x00\x00'
sample value -1, 1 byte/sample: b'\xff'
-1, 2 byte/sample: b'\xff\xff'
-1, 4 byte/sample: b'\xff\xff\xff\xff'
sample value 42, 1 byte/sample: b'*'
42, 2 byte/sample: b'*\x00'
42, 4 byte/sample: b'*\x00\x00\x00'
Let's assume we want to convert some sound samples we have in Python (signed) integer representation to a sound fragment using 2 byte per sample (allowing input sample values between -32768 to 32767; that is -2**15
to 2**15-1
) like used in an audio CD:
import audioop
import array
samples = [0, 1000, 32767, 1, -1, -32768, -1000] # 7 samples of "music"
fragment = array.array('h', samples).tobytes()
print(f'Fragment {fragment} of length {len(fragment)}')
# convert back with audioop function
print([audioop.getsample(fragment, 2, i) for i in range(len(fragment) // 2)])
This prints:
Fragment b'\x00\x00\xe8\x03\xff\x7f\x01\x00\xff\xff\x00\x80\x18\xfc' of length 14
[0, 1000, 32767, 1, -1, -32768, -1000]
As last example, write a 3 second stereo sine wave as .wav
file and read it again:
import audioop
import wave
from array import array
from math import sin, pi
bytes_per_sample = 2
duration = 3. # seconds
sample_rate = 16000. # Hz
frequency = 440. # Hz
max_amplitude = 2**(bytes_per_sample * 8 - 1) - 1
amp = max_amplitude * 0.8
time = [i / sample_rate for i in range(int(sample_rate * duration))]
samples = [int(round(amp * sin(2 * pi * frequency * t))) for t in time]
fragment_mono = array('h', samples).tobytes()
fragment_stereo = audioop.tostereo(fragment_mono, bytes_per_sample, 1, 1)
with wave.open('sine_440hz_stereo.wav', 'wb') as wav:
wav.setnchannels(2) # stereo
wav.setsampwidth(bytes_per_sample)
wav.setframerate(sample_rate)
wav.writeframes(fragment_stereo)
# read wave file again
with wave.open('sine_440hz_stereo.wav', 'rb') as wav:
fragment = wav.readframes(wav.getnframes())
# test whether written fragment and read fragment are same
assert fragment == fragment_stereo