Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 213 lines (199 sloc) 6.8 KB
#!/usr/bin/env python
"""usage:
$ play_mt [melody] [options]
or
>>> from play_mt import play
>>> play(melody,timing,**options)
"""
from __future__ import division
import math
import Nsound
# setup
samplerate = 44100 # per second
nchannels = 2 # per sample
bits = 16 # per channel?
clipping_freq = 16.0
sine = Nsound.Sine(samplerate)
# playback
out = Nsound.AudioStream(samplerate,nchannels)
playback = Nsound.AudioPlayback(samplerate,nchannels,bits)
# instruments
bass = Nsound.GuitarBass(samplerate)
organ = Nsound.OrganPipe(samplerate) # requires dual channel output
clarinet = Nsound.Clarinet(samplerate)
kicker = Nsound.DrumKickBass(samplerate,1000,40)
bd01 = Nsound.DrumBD01(samplerate)
hat = Nsound.Hat(samplerate)
flute = Nsound.FluteSlide(samplerate)
# play a given melody with the given timing
def generate_audio(melody,timing,bpm=200,swing=False,transpose=0,instrument=bass,loop=False):
"""play(melody,timing,bpm=200,swing=False,transpose=0,instrument=bass):
melody = [pitch,pitch,pitch,...]
timing = ('.' + ' ' * (len(note)-1)) * len(song)
bpm = beats per minute
swing = play two beats as 2+1 beats
transpose = number of semitones to transpose by (12 = 1 octave)
instrument = Nsound-compatible instrument to use
"""
if not timing: timing = '.' * len(melody)
if transpose: pitch_multiplier = 2**(float(transpose)/12)
beat_length = 60/float(bpm)
if swing: beat_length *= 2/3
loop = int(loop)
if loop == 1: loop = -1 # infinite loopings
elif loop == 0: loop = +1 # play once
playhead = 0
while loop:
loop -= 1
for pos in xrange(len(timing)):
beat = timing[pos]
if beat.isspace(): continue
duration = 1
if swing and not pos % 2: duration += 1
for x in xrange(pos+1,len(timing)):
if not timing[x].isspace(): break
duration += 1
if swing and not x % 2: duration += 1
if playhead < len(melody): pitch = melody[playhead]
if transpose: pitch *= pitch_multiplier
duration *= beat_length
playhead += 1
yield instrument.play(duration,pitch).__div__(math.log(pitch/clipping_freq,2))
# play something
def play(*args,**kwargs):
for sample in generate_audio(*args,**kwargs): sample >> playback
# play something more than once
def loop(*args,**kwargs):
"""loop(*args,**kwargs,alternate_swing=False,repeat=1)"""
try: alternate = kwargs.pop('alternate_swing')
except KeyError: alternate = False
try: repeat = kwargs.pop('repeat')
except KeyError: repeat = -1
while repeat:
if not alternate: play(*args,**kwargs)
else:
kwargs['swing'] = False
play(*args,**kwargs)
kwargs['swing'] = True
play(*args,**kwargs)
repeat -= 1
# notes for melody use
a4 = 440
# (note)(octave) = (a4)*2**(octave-4)*2**(note-(a))
for octave in range(8):
for key,count in zip(['c','cs','d','ds','e','f','fs','g','gs','a','as','b'],range(12)):
exec(str(key)+str(octave)+' = a4*2**'+str((octave-4)+(count-9)/12))
# some test melodies tommy made up
m1 = [c1,g1,a1,g1,a1,b1,c2,g1,a1,g1,a1,b1,c2,g1,a1,g1,e1,d1,g1,e1,d1]
t1 = '. . . ..... . ..... . ... ...'
#
m2a = [c2,d2,e2,d2,c2,b1,c2]
t2a = '....... '
m2b = [c2,d2,e2,d2,c2,c3,c2]
t2b = '....... '
m2 = m2a + m2b
t2 = t2a + t2b
#
m3a = [c2,c3,c2,b1,c2,a2,f1,e1,d1,g1,e1,d1]
t3a = '......... ...'
m3b = [c1,c3,c2,b2,c2,a2,f1,e1,d1,g1,e1,d1]
t3b = '......... ...'
#
m4a = [a1,b1,c2]*4
t4a = '. . . .. .......'
m4b = [f1,g1,a1]*4
t4b = t4a
m4c = [e1,gs1,a1,e1,e1,f1,e1,d1,c1,b0]
t4c = '. . . .. .....'
m4d = [a0,b0,c1,d1,e1,f1,e1,f1,e1,f1,e1,f1]
t4d = '. . . .. .......'
m4 = m4a + m4b + m4c + m4d
t4 = t4a + t4b + t4c + t4d
#
m5 = [a1,c2,a1,c2,a1,gs2,a2,c2,g2,f2,a1,e2]
t5 = '. . .. . . . .. . . . '
#
m6 = [a1,b1,c2,d2,e2,ds2,e2,f2,e2]
t6 = '. . . . . .. . . '
#
m7 = [a1,e2,ds2,e2,c2,b1,c2,a1,g1,b1,c2,d2,c2,b1]
t7 = '. . .. . ... . .. . .. '
# timing, currently unused except for fade-in
crotchet = 0.7 # seconds
quaver = crotchet / 2**1
semiquaver = crotchet / 2**2
minim = crotchet * 2**1
bar = crotchet * 2**2
# play some test melodies
def play_test_melodies():
# ensure clean fade-in
sine.silence(quaver) >> playback
try:
loop(m1,t1, transpose=12, repeat=2)
loop(m2,t2,bpm=200,transpose=0,alternate_swing=True,repeat=1)
loop(m3a,t3a,swing=True,repeat=1)
loop(m3b,t3b,swing=True,repeat=1)
loop(m4,t4,swing=True,repeat=2)
loop(m5,t5,repeat=1)
loop(m6,t6,bpm=133*2,transpose=2,repeat=2)
loop(m7,t7,repeat=2)
loop(m4,t4)
except KeyboardInterrupt: exit()
def play_bugle_song():
t = '... ... ... ... ... ... ... ... '
m = [g2,g2,c3,g2,c3,e3,g2,c3,e3,g2,c3,e3,g2,c3,e3,g3,e3,c3,e3,c3,g2,g2,g2,c3]
loop(m,t,swing=True)
# parse command line arguments for use as function arguments
def parse_argv_for(function):
from sys import argv
import json
f = function.func_code
defaults = function.func_defaults
nargs = f.co_argcount
args = []
kwargs = {}
for n in range(nargs):
name = f.co_varnames[n]
m = n + len(defaults) - nargs
if m < 0: # has no default value
# obtain from argv
try: args.append(argv[n+1])
except IndexError: args.append(None)
else: # has a default value
value = defaults[m]
if len(argv) > 1+n and not argv[1+n].startswith('--'):
value = argv[1+n]
kwargs[name] = value
for arg in argv[1:]:
if arg.startswith('--'+name):
if '=' in arg:
value = arg.split('=')[1]
try: value = json.loads(value)
except ValueError: pass
else: value = True
kwargs[name]=value
return args,kwargs
# parse and play from command line
def play_from_command_line():
import sys
if len(sys.argv) < 2: play_test_melodies(); exit()
args, kwargs = parse_argv_for(generate_audio)
print(args)
print(kwargs)
try: play(*args,**kwargs)
except KeyboardInterrupt: print('...ciao')
# get note by name
def get_note(name):
# (note)(octave) = (a4)*2**(octave-4)*2**(note-(a))
name = name.lower()
note = 'c d ef g a b'.find(name[0]) # sharps and flats parsed below
if note < 0: raise ValueError('unparsable note name', name)
sharp = 0 # semitones higher
if name[1] in '#s': sharp = +1
elif name[1] in 'bf': sharp = -1
octave = int(name[1+abs(sharp):])
a4 = 440 # 440Hz = concert pitch A4
a = 9 # 'c d ef g a b'.find('a')
return a4*2**((octave-4)+(note+sharp-a)/12)
# if running standalone
if __name__ == "__main__": play_from_command_line()