# program1_echo.py - creates an echo effect using pyalsamidi # Copyright (C) 2004 David Baird (dbaird@nmt.edu) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # I would recommend running this program on at least a 500MHz computer. # ...I haven't tuned for performance yet, but you can be sure I am # interested in tuning for better performance. # - David import sys sys.path.append('./lib/python2.2/site-packages') import select import os from pyalsamidi import pyalsamidi as amidi from time import sleep # Module (global) variables: incompleteNotes = {} def main_callback(ev): global incompleteNotes Ev = amidi.MidiEvent if ev.data.__class__ is Ev.NoteOn: # Record note as incomplete, indexed by port, channel, and # note number. key = (0, ev.data.channel, ev.data.note) incompleteNotes[key] = [] # Call a callback to process note on events, given it a list # that it can append note completion callbacks to. The form # of items inserted into the completion callback list is # (function, [args]) noteon_callback(ev, incompleteNotes[key]) elif ev.data.__class__ is Ev.NoteOff: key = (0, ev.data.channel, ev.data.note) if incompleteNotes.has_key(key): for callback in incompleteNotes[key]: # Callbacks in the form: # def some_callback(event, extra params...) apply(callback[0], [ev] + callback[1]) del incompleteNotes[key] else: print 'Got note off without note on:', ev.as_list() elif ev.data.__class__ is Ev.NoteComplete: print "Oops, wasn't expecting a complete note:", ev.as_list() else: print 'Dropping:', ev.as_list() def noteon_callback(on_ev, completionList): global outp global ppq # The velocities that each of the echoed notes will be played # at (normalized to the first note's velocity)... it could # be freaky the specify values greater than 1 unless you # manually limit the maximum velocity to 127: vels = [0, .25, .50, .75] # This creates a nice echo effect # vels = [.75, .50, .25, 0] # vels = [0, .75, .50, .25] N = len(vels) # Base all the echos on a copy of the original note, on_ev: echos = [e.copy() for e in [on_ev]*N] for n in range(N): echos[n].time.tick += ppq * n / N echos[n].data.velocity *= vels[n] outp.send_event(echos[n]) completionList.append((notecompletion_callback, [on_ev, echos])) return def notecompletion_callback(off_ev, on_ev, echos): global outp global ppq T = off_ev.time.tick - on_ev.time.tick for echo in echos: c, n = echo.data.channel, echo.data.note echo.data = amidi.MidiEvent.NoteOff(c, n, 0) echo.time.tick += T # This type value should be determined from the class type # of the data (NoteOff in this case) but it was not at the time # I wrote this program: echo.type = amidi.MidiEvent.NOTEOFF outp.send_event(echo) print 'Complete note:' print ' %s' % on_ev.as_list() print ' %s' % off_ev.as_list() return seq = amidi.Sequencer('Test Sequencer') inp = seq.create_midiin('Test Midi In') outp = seq.create_midiout('Test Midi Out') queue = seq.create_queue() ppq = 120 queue.set_bpm(120, ppq) queue.start() # If you do not have a musical keyboard connected to 64:0, you might # need to modify this line: os.system('aconnect -t %d 64:0 %d:%d' % (queue.id, seq.id, inp.id)) os.system('aconnect -t %d %d:%d 64:0' % (queue.id, seq.id, outp.id)) print seq.as_dict() while 1: a, _, _ = select.select([inp], [], [], 1) while (inp in a) or inp.events_pending(): a = [] ev = inp.read_event() l = ev.as_list() #print "Got:", l assert(l == amidi.create_MidiEvent(l).as_list()) main_callback(ev)