Merge pull request #425 from dastels/synth

Add NeoTrellis M4 MIDI synth code and files
This commit is contained in:
Mike Barela 2018-12-07 12:49:33 -05:00 committed by GitHub
commit 6095703c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 959 additions and 0 deletions

View file

@ -0,0 +1,409 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
# Events as defined in http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html
# pylint: disable=unused-argument,no-self-use
class Event(object):
def __init__(self, delta_time):
self._delta_time = delta_time
@property
def time(self):
return self._delta_time
def execute(self, sequencer):
return False
class F0SysexEvent(Event):
def __init__(self, delta_time, data):
Event.__init__(self, delta_time)
self._data = data
class F7SysexEvent(Event):
def __init__(self, delta_time, data):
Event.__init__(self, delta_time)
self._data = data
class MetaEvent(Event):
def __init__(self, delta_time):
Event.__init__(self, delta_time)
class SequenceNumberMetaEvent(MetaEvent):
def __init__(self, delta_time, sequence_number):
MetaEvent.__init__(self, delta_time)
self._sequence_number = sequence_number
def __str__(self):
return '%d : Sequence Number : %d' % (self._delta_time, self._sequence_number)
class TextMetaEvent(MetaEvent):
def __init__(self, delta_time, text):
MetaEvent.__init__(self, delta_time)
self._text = text
def __str__(self):
return '%d : Text : %s' % (self._delta_time, self._text)
class CopyrightMetaEvent(MetaEvent):
def __init__(self, delta_time, copyright_notice):
MetaEvent.__init__(self, delta_time)
self._copyright_notice = copyright_notice
def __str__(self):
return '%d : Copyright : %s' % (self._delta_time, self._copyright_notice)
class TrackNameMetaEvent(MetaEvent):
def __init__(self, delta_time, track_name):
MetaEvent.__init__(self, delta_time)
self._track_name = track_name
def __str__(self):
return '%d : Track Name : %s' % (self._delta_time, self._track_name)
class InstrumentNameMetaEvent(MetaEvent):
def __init__(self, delta_time, instrument_name):
MetaEvent.__init__(self, delta_time)
self._instrument_name = instrument_name
def __str__(self):
return '%d : Instrument Name : %s' % (self._delta_time, self._instrument_name)
class LyricMetaEvent(MetaEvent):
def __init__(self, delta_time, lyric):
MetaEvent.__init__(self, delta_time)
self._lyric = lyric
def __str__(self):
return '%d : Lyric : %s' % (self._delta_time, self._lyric)
class MarkerMetaEvent(MetaEvent):
def __init__(self, delta_time, marker):
MetaEvent.__init__(self, delta_time)
self._marker = marker
def __str__(self):
return '%d : Marker : %s' % (self._delta_time, self._marker)
class CuePointMetaEvent(MetaEvent):
def __init__(self, delta_time, cue):
MetaEvent.__init__(self, delta_time)
self._cue = cue
def __str__(self):
return '%d : Cue : %s' % (self._delta_time, self._cue)
class ChannelPrefixMetaEvent(MetaEvent):
def __init__(self, delta_time, channel):
MetaEvent.__init__(self, delta_time)
self._channel = channel
def __str__(self):
return '%d: Channel Prefix : %d' % (self._delta_time, self._channel)
class EndOfTrackMetaEvent(MetaEvent):
def __init__(self, delta_time):
MetaEvent.__init__(self, delta_time)
def __str__(self):
return '%d: End Of Track' % (self._delta_time)
def execute(self, sequencer):
sequencer.end_track()
return True
class SetTempoMetaEvent(MetaEvent):
def __init__(self, delta_time, tempo):
MetaEvent.__init__(self, delta_time)
self._tempo = tempo
def __str__(self):
return '%d: Set Tempo : %d' % (self._delta_time, self._tempo)
def execute(self, sequencer):
sequencer.set_tempo(self._tempo)
return False
class SmpteOffsetMetaEvent(MetaEvent):
def __init__(self, delta_time, hour, minute, second, fr, rr):
MetaEvent.__init__(self, delta_time)
self._hour = hour
self._minute = minute
self._second = second
self._fr = fr
self._rr = rr
def __str__(self):
return '%d : SMPTE Offset : %02d:%02d:%02d %d %d' % (self._delta_time,
self._hour,
self._minute,
self._second,
self._fr,
self._rr)
class TimeSignatureMetaEvent(MetaEvent):
def __init__(self, delta_time, nn, dd, cc, bb):
MetaEvent.__init__(self, delta_time)
self._numerator = nn
self._denominator = dd
self._cc = cc
self._bb = bb
def __str__(self):
return '%d : Time Signature : %d %d %d %d' % (self._delta_time,
self._numerator,
self._denominator,
self._cc,
self._bb)
def execute(self, sequencer):
sequencer.set_time_signature(self._numerator, self._denominator, self._cc)
return False
class KeySignatureMetaEvent(MetaEvent):
def __init__(self, delta_time, sf, mi):
MetaEvent.__init__(self, delta_time)
self._sf = sf
self._mi = mi
def __str__(self):
return '%d : Key Signature : %d %d' % (self._delta_time, self._sf, self._mi)
class SequencerSpecificMetaEvent(MetaEvent):
def __init__(self, delta_time, data):
MetaEvent.__init__(self, delta_time)
self._data = data
class MidiEvent(Event):
def __init__(self, delta_time, channel):
Event.__init__(self, delta_time)
self._channel = channel
class NoteOffEvent(MidiEvent):
def __init__(self, delta_time, channel, key, velocity):
MidiEvent.__init__(self, delta_time, channel)
self._key = key
self._velocity = velocity
def __str__(self):
return '%d : Note Off : key %d, velocity %d' % (self._delta_time,
self._key,
self._velocity)
def execute(self, sequencer):
sequencer.note_off(self._key, self._velocity)
return False
class NoteOnEvent(MidiEvent):
def __init__(self, delta_time, channel, key, velocity):
MidiEvent.__init__(self, delta_time, channel)
self._key = key
self._velocity = velocity
def __str__(self):
return '%d : Note On : key %d, velocity %d' % (self._delta_time,
self._key,
self._velocity)
def execute(self, sequencer):
sequencer.note_on(self._key, self._velocity)
return False
class PolyphonicKeyPressureEvent(MidiEvent):
def __init__(self, delta_time, channel, key, pressure):
MidiEvent.__init__(self, delta_time, channel)
self._key = key
self._pressure = pressure
def __str__(self):
return '%d : Poly Key Pressure : key %d, velocity %d' % (self._delta_time,
self._key,
self._pressure)
class ControlChangeEvent(MidiEvent):
def __init__(self, delta_time, channel, controller, value):
MidiEvent.__init__(self, delta_time, channel)
self._controller = controller
self._value = value
def __str__(self):
return '%d : Control Change : controller %d, value %d' % (self._delta_time,
self._controller,
self._value)
class ProgramChangeEvent(MidiEvent):
def __init__(self, delta_time, channel, program_number):
MidiEvent.__init__(self, delta_time, channel)
self._program_number = program_number
def __str__(self):
return '%d : Program Change : program %d' % (self._delta_time,
self._program_number)
class ChannelPressureEvent(MidiEvent):
def __init__(self, delta_time, channel, pressure):
MidiEvent.__init__(self, delta_time, channel)
self._pressure = pressure
def __str__(self):
return '%d : Channel Pressure : %d' % (self._delta_time, self._channel)
class PitchWheelChangeEvent(MidiEvent):
def __init__(self, delta_time, channel, value):
MidiEvent.__init__(self, delta_time, channel)
self._value = value
def __str__(self):
return '%d : Pitch Wheel Change : %d' % (self._delta_time, self._value)
class SystemExclusiveEvent(MidiEvent):
def __init__(self, delta_time, channel, data):
MidiEvent.__init__(self, delta_time, channel)
self._data = data
class SongPositionPointerEvent(MidiEvent):
def __init__(self, delta_time, beats):
MidiEvent.__init__(self, delta_time, None)
self._beats = beats
def __str__(self):
return '%d: SongPositionPointerEvent(beats %d)' % (self._delta_time,
self._beats)
class SongSelectEvent(MidiEvent):
def __init__(self, delta_time, song):
MidiEvent.__init__(self, delta_time, None)
self._song = song
def __str__(self):
return '%d: SongSelectEvent(song %d)' % (self._delta_time,
self._song)
class TuneRequestEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Tune Request' % (self._delta_time)
class TimingClockEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Timing Clock' % (self._delta_time)
class StartEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Start' % (self._delta_time)
class ContinueEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Continue' % (self._delta_time)
class StopEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Stop' % (self._delta_time)
class ActiveSensingEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Active Sensing' % (self._delta_time)
class ResetEvent(MidiEvent):
def __init__(self, delta_time):
MidiEvent.__init__(self, delta_time, None)
def __str__(self):
return '%d : Reset' % (self._delta_time)

View file

@ -0,0 +1,43 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
class MidiHeader(object):
def __init__(self,
midi_format,
number_of_tracks,
ticks_per_frame,
negative_SMPTE_format,
ticks_per_quarternote):
self._format = midi_format
self._number_of_tracks = number_of_tracks
self._ticks_per_frame = ticks_per_frame
self._negative_SMPTE_format = negative_SMPTE_format
self._ticks_per_quarternote = ticks_per_quarternote
@property
def number_of_tracks(self):
return self._number_of_tracks
def __str__(self):
format_string = ('Header - format: {0}, '
'track count: {1}, '
'ticks per frame: {2}, '
'SMPTE: {3}, '
'ticks per quarternote: {4}')
return format_string.format(self._format,
self._number_of_tracks,
self._ticks_per_frame,
self._negative_SMPTE_format,
self._ticks_per_quarternote)

View file

@ -0,0 +1,65 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import os
import parser
import sequencer
import synth
import adafruit_trellism4
trellis = adafruit_trellism4.TrellisM4Express(rotation=0)
trellis.pixels.brightness = 0.1
trellis.pixels.fill(0)
syn = synth.Synth()
seq = sequencer.Sequencer(syn)
p = parser.MidiParser()
voices = sorted([f.split('.')[0] for f in os.listdir('/samples') if f.endswith('.txt')])
tunes = sorted([f for f in os.listdir('/midi') if f.endswith('.mid')])
selected_voice = None
def reset_voice_buttons():
for i in range(len(voices)):
trellis.pixels[(i, 0)] = 0x0000FF
def reset_tune_buttons():
for i in range(len(tunes)):
trellis.pixels[(i % 8, (i // 8) + 1)] = 0x00FF00
current_press = set()
reset_voice_buttons()
reset_tune_buttons()
while True:
pressed = set(trellis.pressed_keys)
just_pressed = pressed - current_press
for down in just_pressed:
if down[1] == 0:
if down[0] < len(voices): # a voice selection
selected_voice = down[0]
reset_voice_buttons()
trellis.pixels[down] = 0xFFFFFF
syn.voice = voices[selected_voice]
else:
tune_index = (down[1] - 1) * 8 + down[0]
if tune_index < len(tunes) and selected_voice is not None:
trellis.pixels[down] = 0xFFFFFF
header, tracks = p.parse('/midi/' + tunes[tune_index])
for track in tracks:
seq.play(track)
reset_tune_buttons()
current_press = pressed

View file

@ -0,0 +1,257 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
# pylint: disable=no-self-use,too-many-return-statements,too-many-branches,inconsistent-return-statements
import header
import events
def log(txt):
print(txt)
#pass
class MidiParser(object):
def __init__(self):
pass
def _as_8(self, d):
return d[0]
def _as_16(self, d):
return (d[0] << 8) | d[1]
def _as_24(self, d):
return (d[0] << 16) | (d[1] << 8) | d[2]
def _as_32(self, d):
return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]
def _as_str(self, d):
return str(d, encoding='utf8')
def _read_bytes(self, f, count):
val = f.read(count)
return val
def _read_1_byte(self, f):
return self._read_bytes(f, 1)
def _read_2_bytes(self, f):
return self._read_bytes(f, 2)
def _read_3_bytes(self, f):
return self._read_bytes(f, 3)
def _read_4_bytes(self, f):
return self._read_bytes(f, 4)
def _read_8(self, f):
return self._as_8(self._read_bytes(f, 1))
def _read_16(self, f):
return self._as_16(self._read_bytes(f, 2))
def _read_24(self, f):
return self._as_24(self._read_bytes(f, 3))
def _read_32(self, f):
return self._as_32(self._read_bytes(f, 4))
def _parse_header(self, f):
if self._read_4_bytes(f) != b'MThd':
return None
if self._read_32(f) != 6:
return None
midi_format = self._read_16(f)
midi_number_of_tracks = self._read_16(f)
d = self._read_2_bytes(f)
if d[0] & 0x80:
ticks_per_frame = d[1]
negative_SMPTE_format = d[0] & 0x7F
ticks_per_quarternote = None
else:
ticks_per_frame = None
negative_SMPTE_format = None
ticks_per_quarternote = (d[0] << 8) | d[1]
return header.MidiHeader(midi_format,
midi_number_of_tracks,
ticks_per_frame,
negative_SMPTE_format,
ticks_per_quarternote)
def _parse_variable_length_number(self, f):
value = self._read_8(f)
if not value & 0x80:
return value
value &= 0x7F
b = self._read_8(f)
while b & 0x80:
value = (value << 7) | (b & 0x7F)
b = self._read_8(f)
return (value << 7) | b
def _parse_F0_sysex_event(self, delta_time, f):
length = self._parse_variable_length_number(f)
data = self._read_bytes(f, length)
return events.F0SysexEvent(delta_time, data)
def _parse_F7_sysex_event(self, f, delta_time):
length = self._parse_variable_length_number(f)
data = self._read_bytes(f, length)
return events.F7SysexEvent(delta_time, data)
def _parse_meta_event(self, f, delta_time):
meta_event_type = self._read_8(f)
length = self._parse_variable_length_number(f)
data = self._read_bytes(f, length)
if meta_event_type == 0x00:
return events.SequenceNumberMetaEvent(delta_time, self._as_16(data))
elif meta_event_type == 0x01:
return events.TextMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x02:
return events.CopyrightMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x03:
return events.TrackNameMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x04:
return events.InstrumentNameMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x05:
return events.LyricMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x06:
return events.MarkerMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x07:
return events.CuePointMetaEvent(delta_time, self._as_str(data))
elif meta_event_type == 0x20:
if length != 0x01:
return None
track = self._as_8(data)
if track > 15:
return None
return events.ChannelPrefixMetaEvent(delta_time, track)
elif meta_event_type == 0x2F:
if length != 0:
return None
return events.EndOfTrackMetaEvent(delta_time)
elif meta_event_type == 0x51:
if length != 3:
return None
return events.SetTempoMetaEvent(delta_time, self._as_24(data))
elif meta_event_type == 0x54:
if length != 5:
return None
return events.SmpteOffsetMetaEvent(delta_time, data[0], data[1],
data[2], data[3], data[4])
elif meta_event_type == 0x58:
if length != 4:
return None
return events.TimeSignatureMetaEvent(delta_time, data[0], data[1],
data[2], data[3])
elif meta_event_type == 0x59:
if length != 2:
return None
return events.KeySignatureMetaEvent(delta_time, data[0], data[1])
elif meta_event_type == 0x7F:
return events.SequencerSpecificMetaEvent(delta_time, data)
def _parse_midi_event(self, f, delta_time, status):
if status & 0xF0 != 0xF0:
command = (status & 0xF0) >> 4
channel = status & 0x0F
data_1 = self._read_8(f) & 0x7F
data_2 = 0
if command in [8, 9, 10, 11, 14]:
data_2 = self._read_8(f) & 0x7F
if command == 8:
return events.NoteOffEvent(delta_time, channel, data_1, data_2)
elif command == 9:
if data_2 == 0:
return events.NoteOffEvent(delta_time, channel, data_1, data_2)
return events.NoteOnEvent(delta_time, channel, data_1, data_2)
elif command == 10:
return events.PolyphonicKeyPressureEvent(delta_time, channel, data_1, data_2)
elif command == 11:
return events.ControlChangeEvent(delta_time, channel, data_1, data_2)
elif command == 12:
return events.ProgramChangeEvent(delta_time, channel, data_1)
elif command == 13:
return events.ChannelPressureEvent(delta_time, channel, data_1)
elif command == 14:
return events.PitchWheelChangeEvent(delta_time, channel, (data_2 << 7) | data_1)
return None
message_id = status & 0x0F
if message_id == 0:
manufacturer_id = self._read_8(f)
data = []
d = self._read_8(f)
while d != 0xF7:
data.append(d)
d = self._read_8(f)
return events.SystemExclusiveEvent(delta_time, manufacturer_id, data)
elif message_id == 2:
lo7 = self._read_8(f)
hi7 = self._read_8(f)
return events.SongPositionPointerEvent(delta_time, (hi7 << 7) | lo7)
elif message_id == 3:
return events.SongSelectEvent(delta_time, self._read_8(f))
elif message_id == 6:
return events.TuneRequestEvent(delta_time)
elif message_id == 8:
return events.TimingClockEvent(delta_time)
elif message_id == 10:
return events.StartEvent(delta_time)
elif message_id == 11:
return events.ContinueEvent(delta_time)
elif message_id == 12:
return events.StopEvent(delta_time)
elif message_id == 14:
return events.ActiveSensingEvent(delta_time)
elif message_id == 15:
return events.ResetEvent(delta_time)
return None
def parse_mtrk_event(self, f):
delta_time = self._parse_variable_length_number(f)
event_type = self._read_8(f)
if event_type == 0xF0: #sysex event
event = self._parse_F0_sysex_event(f, delta_time)
elif event_type == 0xF7: #sysex event
event = self._parse_F7_sysex_event(f, delta_time)
elif event_type == 0xFF: #meta event
event = self._parse_meta_event(f, delta_time)
else: #regular midi event
event = self._parse_midi_event(f, delta_time, event_type)
log(event)
return event
def _parse_track(self, f):
if self._read_4_bytes(f) != b'MTrk':
return None
track_length = self._read_32(f)
track_data = []
for _ in range(track_length):
event = self.parse_mtrk_event(f)
if event is None:
log('Error')
track_data.append(event)
if isinstance(event, events.EndOfTrackMetaEvent):
return track_data
return track_data
def parse(self, filename):
with open(filename, 'rb') as f:
tracks = []
h = self._parse_header(f)
for _ in range(h.number_of_tracks):
tracks.append(self._parse_track(f))
return (h, tracks)

View file

@ -0,0 +1,62 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import time
class Sequencer(object):
def __init__(self, synth):
self._synth = synth
self.set_tempo(120)
self._numerator = 4
self._denominator = 2
self._clocks_per_metronome_click = 24
self.set_tempo(500000)
self.set_time_signature(4, 2, 24)
def _tick(self):
time.sleep(self._tick_size)
def play(self, track):
for event in track:
delta_time = 0
while event.time > delta_time:
delta_time += 1
self._tick()
print('Executing %s' % str(event))
if event.execute(self):
return
def set_tempo(self, tempo):
print('Setting tempo %d' % tempo)
self._tempo = tempo
self._tick_size = tempo / 250000000.0
def set_time_signature(self, numerator, denominator, clocks_per_metronome_click):
print('Setting time signature')
self._numerator = numerator
self._denominator = denominator
self._clocks_per_metronome_click = clocks_per_metronome_click
def note_on(self, key, velocity):
# print('Note on')
self._synth.note_on(key, velocity)
def note_off(self, key, velocity):
# print('Note off')
self._synth.note_off(key, velocity)
def end_track(self):
pass
# print('End track')

Binary file not shown.

View file

@ -0,0 +1,123 @@
"""
NeoTrellis M4 Express MIDI synth
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
# pylint: disable=unused-argument
import time
import board
import audioio
SAMPLE_FOLDER = '/samples/' # the name of the folder containing the samples
VOICE_COUNT = 8
def capitalize(s):
if not s:
return ''
return s[0].upper() + ''.join([x.lower() for x in s[1:]])
class Synth(object):
def __init__(self):
self._voice_name = None
self._voice_file = None
self._samples = [None] * 128
self._channel_count = None
self._bits_per_sample = None
self._sample_rate = None
self._audio = None
self._mixer = None
self._currently_playing = [{'key': None, 'voice' : x} for x in range(VOICE_COUNT)]
self._voices_used = 0
def _initialize_audio(self):
if self._audio is None:
self._audio = audioio.AudioOut(board.A1)
self._mixer = audioio.Mixer(voice_count=VOICE_COUNT,
sample_rate=16000,
channel_count=1,
bits_per_sample=16,
samples_signed=True)
self._audio.play(self._mixer)
def reset(self):
for i in range(len(self._samples)):
self._samples[i] = None
for p in self._currently_playing:
p['key'] = None
@property
def voice(self):
return self._voice_name
@voice.setter
def voice(self, v):
self._initialize_audio()
self._voice_name = capitalize(v)
self._voice_file = '/samples/%s.txt' % v.lower()
first_note = None
with open(self._voice_file, "r") as f:
for line in f:
cleaned = line.strip()
if len(cleaned) > 0 and cleaned[0] != '#':
key, filename = cleaned.split(',', 1)
self._samples[int(key)] = filename.strip()
if first_note is None:
first_note = filename.strip()
sound_file = open(SAMPLE_FOLDER+first_note, 'rb')
wav = audioio.WaveFile(sound_file)
self._mixer.play(wav, voice=0, loop=False)
time.sleep(0.5)
self._mixer.stop_voice(0)
def _find_usable_voice_for(self, key):
if self._voices_used == VOICE_COUNT:
return None
available = None
for voice in self._currently_playing:
if voice['key'] == key:
return None
if voice['key'] is None:
available = voice
if available is not None:
self._voices_used += 1
return available
return None
def _find_voice_for(self, key):
for voice in self._currently_playing:
if voice['key'] == key:
return voice
return None
def note_on(self, key, velocity):
fname = self._samples[key]
if fname is not None:
f = open(SAMPLE_FOLDER+fname, 'rb')
wav = audioio.WaveFile(f)
voice = self._find_usable_voice_for(key)
if voice is not None:
voice['key'] = key
voice['file'] = f
self._mixer.play(wav, voice=voice['voice'], loop=False)
def note_off(self, key, velocity):
if self._voices_used > 0:
voice = self._find_voice_for(key)
if voice is not None:
self._voices_used -= 1
self._mixer.stop_voice(voice['voice'])
voice['file'].close()
voice['file'] = None
voice['key'] = None