Hi Darren! :-)
Note that Garritan/Plogue are using a later version of the SFZ format with additional and sometimes custom opcodes in their ARIA player. (See e.g.
http://www.northernsounds.com/forum/showthread.php?t=60929 )
Here's a simple tokenizer for SFZ I wrote once upon a time in Python. Not sure if I got it completely right.
[BEGIN PYTHON]
import sys
def find_delimiter(string, delimiter):
"""Find all occurences of delimiter."""
start, i, ndx = 0, 0, []
while True:
i = string.find(delimiter, start)
if i != -1:
start = i + 1
ndx.append((i, delimiter))
else:
break
return ndx
def find_all_delimiters(string, delimiters):
"""Find all delimiters."""
ndx = []
for delimiter in delimiters:
ndx.extend(find_delimiter(string, delimiter))
return ndx
def tokenize_sfz(string):
"""Tokenize SFZ string."""
delimiters = [
# comments
'\n',
'//',
# headers
'<region>',
'<group>',
# sample definition
'sample=',
# input controls
'lochan=', 'hichan=',
'lokey=', 'hikey=', 'key=',
'lovel=', 'hivel=',
'lobend=', 'hibend=',
'locahnaft=', 'hichanaft=',
'lopolyaft=', 'hiplyaft=',
'lorand=', 'hirand=',
'lobpm=', 'hibmp=',
'seq_length=', 'seq_position=',
'sw_lokey=', 'sw_hikey=', 'sw_last=', 'sw_down=', 'sw_up=', 'sw_previous=', 'sw_vel=',
'trigger=',
'group=',
'off_by=', 'off_mode=',
] + [
'on_locc%d=' % cc for cc in range(128)
] + [
'on_locc%d=' % cc for cc in range(128)
] + [
# performance parameters
'delay=', 'delay_random=',
] + [
'delay_cc%d=' % cc for cc in range(128)
] + [
'offset=', 'offset_random=',
] + [
'offset_cc%d=' % cc for cc in range(128)
] + [
'end=',
'count=',
'loop_mode=', 'loop_start=', 'loop_end=',
'sync_beats=', 'sync_offset=',
# pitch
'transpose=',
'tune=',
'pitch_keycenter=', 'pitch_keytrack=', 'pitch_veltrack=', 'pitch_random=',
'bend_up=', 'bend_down=', 'bend_step=',
# pitch eg
'pitcheg_delay=',
'pitcheg_start=',
'pitcheg_attack=',
'pitcheg_hold=',
'pitcheg_decay=',
'pitcheg_sustain=',
'pitcheg_release=',
'pitcheg_depth=',
'pitcheg_vel2delay=',
'pitcheg_vel2attack=',
'pitcheg_vel2hold=',
'pitcheg_vel2decay=',
'pitcheg_vel2sustain=',
'pitcheg_vel2realease=',
'pitcheg_vel2depth=',
# pitch lfo
'pitchlfo_delay=',
'pitchlfo_fade=',
'pitchlfo_freq=',
'pitchlfo_depth=',
] + [
'pitchlfo_depthcc%d=' % cc for cc in range(128)
] + [
'pitchlfo_depthchanaft=',
'pitchlfo_depthpolyaft=',
] + [
'pitchlfo_freqcc%d=' % cc for cc in range(128)
] + [
'pitchlfo_freqchanaft=',
'pitchlfo_freqpolyaft=',
# filter
'fil_type=',
'cutoff=',
] + [
'cutoff_cc%d=' % cc for cc in range(128)
] + [
'cutoff_chanaft=', 'cutoff_polyaft=',
'resonance=',
'fil_keytrack=', 'fil_keycenter=', 'fil_veltrack=', 'fil_random=',
# filter eg
'fileg_delay=',
'fileg_start=',
'fileg_attack=',
'fileg_hold=',
'fileg_decay=',
'fileg_sustain=',
'fileg_release=',
'fileg_depth=',
'fileg_vel2delay=',
'fileg_vel2attack=',
'fileg_vel2hold=',
'fileg_vel2decay=',
'fileg_vel2sustain=',
'fileg_vel2realease=',
'fileg_vel2depth=',
# filter lfo
'fillfo_delay=',
'fillfo_fade=',
'fillfo_freq=',
'fillfo_depth=',
] + [
'fillfo_depthcc%d=' % cc for cc in range(128)
] + [
'fillfo_depthchanaft=',
'fillfo_depthpolyaft=',
] + [
'fillfo_freqcc%d=' % cc for cc in range(128)
] + [
'fillfo_freqchanaft=',
'fillfo_freqpolyaft=',
# amplifier
'volume=',
'pan=',
'width=',
'position=',
'amp_keytrack=', 'amp_keycenter=', 'amp_veltrack=',
] + [
'amp_velcurve_%d=' % c for c in range(128)
] + [
'amp_random=',
'rt_decay=',
'output=',
] + [
'gain_cc%d=' % cc for cc in range(128)
] + [
'xfin_lokey=', 'xfin_hikey=',
'xfout_lokey=', 'xfout_hikey=',
'xf_keycurve=',
'xfin_lovel=', 'xfin_hilev=',
'xfout_lovel=', 'xfout_hilev=',
'xf_velcurve=',
] + [
'xfin_locc%d=' % cc for cc in range(128)
] + [
'xfin_hicc%d=' % cc for cc in range(128)
] + [
'xfout_locc%d=' % cc for cc in range(128)
] + [
'xfout_hicc%d=' % cc for cc in range(128)
] + [
'xf_cccurve=',
# amplifier eg
'ampeg_delay=',
'ampeg_start=',
'ampeg_attack=',
'ampeg_hold=',
'ampeg_decay=',
'ampeg_sustain=',
'ampeg_release=',
'ampeg_vel2delay=',
'ampeg_vel2attack=',
'ampeg_vel2hold=',
'ampeg_vel2decay=',
'ampeg_vel2sustain=',
'ampeg_vel2release=',
] + [
'ampeg_delaycc%d=' % cc for cc in range(128)
] + [
'ampeg_startcc%d=' % cc for cc in range(128)
] + [
'ampeg_attackcc%d=' % cc for cc in range(128)
] + [
'ampeg_holdcc%d=' % cc for cc in range(128)
] + [
'ampeg_decaycc%d=' % cc for cc in range(128)
] + [
'ampeg_sustaincc%d=' % cc for cc in range(128)
] + [
'ampeg_releasecc%d=' % cc for cc in range(128)
] + [
# amplifier lfo
'amplfo_delay=',
'amplfo_fade=',
'amplfo_freq=',
'amplfo_depth=',
] + [
'amplfo_depthcc%d=' % cc for cc in range(128)
] + [
'amplfo_depthchanaft=',
'amplfo_depthpolyaft=',
] + [
'amplfo_freqcc%d=' % cc for cc in range(128)
] + [
'amplfo_freqchanaft=',
'amplfo_freqpolyaft=',
# equalizer
'eq1_freq=', 'eq2_freq=', 'eq3_freq=',
] + [
'eq1_freqcc%d=' % cc for cc in range(128)
] + [
'eq2_freqcc%d=' % cc for cc in range(128)
] + [
'eq3_freqcc%d=' % cc for cc in range(128)
] + [
'eq1_vel2freq', 'eq2_vel2freq', 'eq3_vel2freq',
'eq1_bw', 'eq2_bw', 'eq3_bw',
] + [
'eq1_bwcc%d=' % cc for cc in range(128)
] + [
'eq2_bwcc%d=' % cc for cc in range(128)
] + [
'eq3_bwcc%d=' % cc for cc in range(128)
] + [
'eq1_gain=', 'eq2_gain=', 'eq3_gain=',
] + [
'eq1_gaincc%d=' % cc for cc in range(128)
] + [
'eq2_gaincc%d=' % cc for cc in range(128)
] + [
'eq3_gaincc%d=' % cc for cc in range(128)
] + [
'eq1_vel2gain=', 'eq2_vel2gain=', 'eq3_vel2gain=',
# effects
'effect1=', 'effect2=',
]
stack = find_all_delimiters(string, delimiters)
stack.sort()
stack.reverse()
comment = False
tokens = []
while True:
try:
start = stack.pop()
end = stack.pop()
stack.append(end)
except IndexError:
break
if start[1] == '//':
comment = True
elif start[1] == '\n':
comment = False
continue
if not comment:
tokens.append(sfz[start[0]:end[0]].rstrip().split('=', 1))
return tokens
sfzf = open(sys.argv[1], 'r')
sfz = sfzf.read()
sfzf.close()
print tokenize_sfz(sfz)
[END PYTHON]