Skip to content
Permalink
Browse files

Improvements to soundfont event handling

  • Loading branch information...
hecanjog committed Oct 5, 2019
1 parent 6294fc6 commit d5ccf4a96293df9de846e4d43bae23942b66bc46
Showing with 3,284 additions and 1,723 deletions.
  1. +3,099 −1,610 pippi/soundfont.c
  2. +10 −2 pippi/soundfont.pxd
  3. +74 −45 pippi/soundfont.pyx
  4. +86 −58 pippi/tukey.c
  5. +5 −5 tests/test_oscs.py
  6. +10 −3 tests/test_soundfont.py

Large diffs are not rendered by default.

@@ -14,9 +14,17 @@ cdef extern from "tsf.h":
tsf* tsf_load_filename(const char* filename)
void tsf_set_output(tsf* f, TSFOutputMode outputmode, int samplerate, float global_gain_db)
void tsf_note_on(tsf* f, int preset_index, int key, float vel)
void tsf_note_off(tsf* f, int preset_index, int key)
void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing)
void tsf_reset(tsf* f)

cdef double[:,:] render(str font, object events, int voice, int channels, int samplerate)
void tsf_channel_note_on(tsf* f, int channel, int key, float vel)
void tsf_channel_note_off(tsf* f, int channel, int key)
void tsf_channel_note_off_all(tsf* f, int channel)
void tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel)
void tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value)
int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums)
int tsf_active_voice_count(tsf* f)

cdef double[:,:] render(str font, list events, int voice, int channels, int samplerate)
cpdef SoundBuffer play(str font, double length=*, double freq=*, double amp=*, int voice=*, int channels=*, int samplerate=*)
cpdef SoundBuffer playall(str font, object events, int voice=*, int channels=*, int samplerate=*)
@@ -1,5 +1,7 @@
#cython: language_level=3

import uuid

from libc.stdlib cimport malloc, free

import numpy as np
@@ -13,18 +15,35 @@ np.import_array()
SAMPLERATE = 44100
CHANNELS = 2
BLOCKSIZE = 64
NOTE_ON = 0
NOTE_OFF = 1


cdef list parsemessages(list events, int samplerate):
cdef list messages = []
cdef dict e, m
cdef str _id
cdef int note
cdef size_t start
cdef size_t end

for e in events:
note = <int>ftom(e['freq'])
start = <int>(e['start'] * samplerate)
end = <int>(e['length'] * samplerate) + start
_id = str(uuid.uuid4())

messages += [dict(id=_id, type=NOTE_ON, note=note, pos=start, amp=e['amp'], instrument=e['voice'])]
messages += [dict(id=_id, type=NOTE_OFF, note=note, pos=end, amp=e['amp'], instrument=e['voice'])]

return sorted(messages, key=lambda x: x['pos'])

cdef double[:,:] render(str font, object events, int voice, int channels, int samplerate):
if not isinstance(events, list) and not isinstance(events, tuple):
raise ValueError('Invalid event list of type %s' % type(events))

cdef double[:,:] render(str font, list events, int voice, int channels, int samplerate):
cdef tsf* TSF = tsf_load_filename(font.encode('UTF-8'))

# Total length is last event onset time + length + 3 seconds of slop for the tail
cdef double length = events[-1][0] + events[-1][1] + 3
cdef int _length = <int>(length * samplerate)

cdef int _voice = max(0, voice-1)
cdef int length = <int>(events[-1]['start'] + events[-1]['length'] + 3) * samplerate

# TSF only supports stereo or mono
if channels == 1:
@@ -33,66 +52,76 @@ cdef double[:,:] render(str font, object events, int voice, int channels, int sa
channels = 2
tsf_set_output(TSF, TSF_STEREO_UNWEAVED, samplerate, 0)

cdef double[:,:] out = np.zeros((_length, channels), dtype='d')
cdef double[:,:] out = np.zeros((length, channels), dtype='d')
cdef float* block = <float*>malloc(sizeof(float) * BLOCKSIZE * 2)

cdef int elapsed = 0
cdef size_t c = 0
cdef size_t c=0, i=0
cdef size_t offset = 0
cdef int channel = 0

cdef list playing = []
cdef list stopped = []
cdef list _events = []
cdef list messages = parsemessages(events, samplerate)
cdef dict channel_map = { c+1: [] for c in range(16) }
cdef dict event_map = {}

cdef int _onset, _end, _note
# TODO: handle microtones with pitch bend

for e in events:
onset = <int>(e[0] * samplerate)
end = <int>(e[1] * samplerate) + onset
note = <int>ftom(e[2])

if len(e) == 5:
_events += [(onset, end, note, e[3], e[4])]
else:
_events += [(onset, end, note, e[3])]

while elapsed < _length:
offset = 0
for ei, e in enumerate(_events):
if e[0] <= elapsed and ei not in playing:
_play_note(TSF, _voice, e)
playing += [ ei ]

if e[1] >= elapsed and ei in playing and ei not in stopped:
_stop_note(TSF, _voice, e)
stopped += [ ei ]
while True:
for msg in messages:
if msg['pos'] > elapsed:
break

if msg['pos'] <= elapsed:
tsf_channel_set_presetnumber(TSF, channel, msg['instrument'], False)

if msg['type'] == NOTE_ON:
channel = -1

# look for a free channel not already playing this note
for c in channel_map.keys():
if msg['note'] not in channel_map[c]:
# Populate the maps for noteoff lookups
channel = c
channel_map[c] += [ msg['note'] ]
event_map[msg['id']] = channel
break

if channel < 0:
# All channels are in use, so ignore the note.
# Whaddya gonna do...?
continue

tsf_channel_note_on(TSF, channel, msg['note'], msg['amp'])

elif msg['type'] == NOTE_OFF:
if msg['id'] in event_map:
# If this event is playing, find the channel and cleanup
channel = event_map[msg['id']]
channel_map[channel].pop(channel_map[channel].index(msg['note']))
del event_map[msg['id']]
tsf_channel_note_off(TSF, channel, msg['note'])

messages.pop(messages.index(msg))

tsf_render_float(TSF, block, BLOCKSIZE, 0)

for c in range(channels):
offset = c * BLOCKSIZE
for i in range(BLOCKSIZE):
if i+elapsed < _length:
if i+elapsed < length:
out[i+elapsed,c] = block[i+offset]

elapsed += BLOCKSIZE

if elapsed > length:
break

free(block)

return out

cdef _stop_note(tsf* TSF, int _voice, tuple event):
if len(event) == 5:
_voice = event[4]
tsf_note_on(TSF, _voice, event[2], 0)

cdef _play_note(tsf* TSF, int _voice, tuple event):
if len(event) == 5:
_voice = event[4]
tsf_note_on(TSF, _voice, event[2], event[3])

cpdef SoundBuffer play(str font, double length=1, double freq=440, double amp=1, int voice=1, int channels=CHANNELS, int samplerate=SAMPLERATE):
cdef list events = [(0, length, freq, amp)]
cdef list events = [dict(start=0, length=length, freq=freq, amp=amp, voice=voice)]
return SoundBuffer(render(font, events, voice, channels, samplerate), channels=channels, samplerate=samplerate)

cpdef SoundBuffer playall(str font, object events, int voice=1, int channels=CHANNELS, int samplerate=SAMPLERATE):

0 comments on commit d5ccf4a

Please sign in to comment.
You can’t perform that action at this time.