[Tutor] Musical note on python
eryksun
eryksun at gmail.com
Thu Sep 13 21:03:45 CEST 2012
On Thu, Sep 13, 2012 at 11:48 AM, D.V.N.Sarma డి.వి.ఎన్.శర్మ
<dvnsarma at gmail.com> wrote:
>
> As far as programming volume is concerned winsound.Beep has only frequency
> and duration. pyaudio module appears to have provision for volume control.
You should be able to add a wave header to a raw byte string. For example:
import wave
import winsound
from cStringIO import StringIO
def get_wave(data):
f = StringIO()
w = wave.open(f, 'w')
w.setnchannels(1) # mono
w.setsampwidth(2) # 2 bytes
w.setframerate(48000) # samples/second
w.writeframes(data)
return f.getvalue()
Then play the sound like this (_untested_):
wave_data = get_wave(data)
windsound.PlaySound(wave_data, winsound.SND_MEMORY)
I've modified my previous script to add simple polyphonic sound. I
included a basic square wave and a sawtooth wave, plus a random
polyphonic wave. It's the kind of sound you might hear in a cheesy
greeting card. Also, since you're looking to use winsound.PlaySound(),
I changed the byte packing to use a little endian signed short
(int16). I think that's what Windows wave files use.
My demo below still uses pyaudio, which wraps the cross-platform
PortAudio library. To me it seems like a better solution than relying
on the standard library (e.g. the standard lib uses OSS on Linux; I
don't even have that installed).
Hopefully it performs OK on your computer. Using NumPy arrays would
speed up the number crunching.
import pyaudio
from math import sin, pi
from struct import pack
from random import sample, randrange
def polywave(freqs, amps, f0, fs):
"""create a polyphonic waveform
freqs: sequence of frequencies (Hz)
amps: sequence of amplitudes
f0: fundamental frequency (Hz)
fs: samples/second
output is normalized to the range [-1,1].
"""
N = int(fs / f0)
rad_step = 2 * pi / fs # radians / (Hz * sample)
wave = [0.0] * N
for f, a in zip(freqs, amps):
for n in xrange(N):
wave[n] += a * sin(rad_step * f * n)
maxamp = abs(max(wave, key=lambda x: abs(x)))
return [samp / maxamp for samp in wave]
def packwave(wave, vol=1, duration=None, fs=None):
"""pack a waveform as bytes (int16)
wave: sample sequence, |mag| <= 1
vol: optional volume scale, |mag| <= 1
duration: optional loop time (seconds)
fs: samples/second
fs is required to set duration.
"""
scale = min(vol * 32767, 32767)
ncycles = 1
if not (duration is None or fs is None):
ncycles = int(round(1.0 * duration * fs / len(wave)))
data = b''.join(pack('<h', scale * samp) for samp in wave)
return data * ncycles
def make_square(num, f0, fs):
"""generate square wave components
num: number of harmonics
f0: funamental frequency (Hz)
fs: samples/second
fs/2 is the upper bound.
"""
stop = min(2*num, int(fs / (2 * f0)))
freqs = [n * f0 for n in xrange(1, stop, 2)]
amps = [1.0 / n for n in xrange(1, stop, 2)]
return freqs, amps
def make_saw(num, f0, fs):
"""generate sawtooth wave components
num: number of harmonics
f0: funamental frequency (Hz)
fs: samples/second
fs/2 is the upper bound.
"""
stop = min(num + 1, int(fs / (2 * f0)))
freqs = [n * f0 for n in xrange(1, stop)]
amps = [(-1.0) ** (n + 1) / n for n in xrange(1, stop)]
return freqs, amps
def make_rand(num, f0, fs):
"""generate wave with random harmonics/amplitudes"""
ftop = min(fs // 2, 12000)
nmax = int(ftop / f0)
num = min(num, nmax)
freqs = [n * f0 for n in sample(xrange(1, nmax+1), num)]
amps = [randrange(32768)/32767.0 for n in xrange(num)]
return freqs, amps
def play(data, stream):
chunks = (data[i:i+1024] for i in xrange(0, len(data), 1024))
for chunk in chunks:
stream.write(chunk)
if __name__ == "__main__":
from time import sleep
fs = 48000
p = pyaudio.PyAudio()
stream = p.open(
format=pyaudio.paInt16,
channels=1,
rate=fs,
frames_per_buffer=fs//4,
output=True)
# http://en.wikipedia.org/wiki/
# Equal_temperament#Calculating_absolute_frequencies
note = lambda n: 440 * (2**(1/12.)) ** (-21 + n)
scale = [note(n) for n in range(12)]
rscale = [note(13-n) for n in range(12)]
vols = [0.2 + 0.05*n for n in range(len(scale))]
rvols = list(reversed(vols))
def play_scale(scale, vols, wave_func, master_vol=1):
duration = 0.5
nharmonics = 30
for f0, vol in zip(scale, vols):
freqs, amps = wave_func(nharmonics, f0, fs)
wave = polywave(freqs, amps, f0, fs)
data = packwave(wave, master_vol * vol, duration, fs)
play(data, stream)
sleep(0.5)
play_scale(scale, vols, make_square, 0.5)
play_scale(rscale, rvols, make_saw, 0.5)
play_scale(scale, vols, make_rand, 0.75)
play_scale(rscale, rvols, make_rand, 0.75)
stream.close()
p.terminate()
More information about the Tutor
mailing list