diff --git a/Makefile.am b/Makefile.am index 60d7d17f2fb4745e230be9e80f04b4bae6bcac76..531f471e2b1e6aa7c741fc62a4b15c8b05163468 100644 --- a/Makefile.am +++ b/Makefile.am @@ -13,10 +13,10 @@ libengine_la_LIBADD = $(SNDFILE_LIBS) $(SAMPLERATE_LIBS) noinst_PROGRAMS = load-drumkit verify-voice -load_drumkit_SOURCES = engine/load-drumkit.c +load_drumkit_SOURCES = aux/load-drumkit.c load_drumkit_LDADD = libengine.la -verify_voice_SOURCES = engine/verify-voice.c +verify_voice_SOURCES = aux/verify-voice.c verify_voice_LDADD = libengine.la if LV2 diff --git a/engine/load-drumkit.c b/aux/load-drumkit.c similarity index 100% rename from engine/load-drumkit.c rename to aux/load-drumkit.c diff --git a/aux/sfz2hy.py b/aux/sfz2hy.py new file mode 100755 index 0000000000000000000000000000000000000000..9f688645ad7622f92bf5175b701e96ee8e8e7c67 --- /dev/null +++ b/aux/sfz2hy.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# +# Convert (some) SFZ metadata files to Hydrogen drumkit files. +# + +import re +import os +import sys + +_keys = ['a', 'a#', 'b', 'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#'] +_keys_to_note = dict((v, k) for k, v in enumerate(_keys)) +_key_rx = re.compile(r'^([abcdefg]#?)(\d+)$') + + +def key_to_note(key): + m = _key_rx.match(key) + base_note = _keys_to_note[m.group(1)] + octave = int(m.group(2)) + return 21 + octave * 12 + base_note + + +def get_key(region): + if 'pitch_keycenter' in region: + return region['pitch_keycenter'] + return region['lokey'] + + +def common_root(names): + root = names[0] + for n in names[1:]: + for i in xrange(min(len(root), len(n))): + if root[i] != n[i]: + root = root[:i] + break + return root.rstrip('-_+=(). ') + + +def read_sfz(filename): + regions = [] + with open(filename) as fd: + in_region = False + region = {} + for line in fd: + line = line.strip() + if not line: + continue + if line.startswith('<region>'): + if region: + regions.append(region) + region = {'lovel': 0, 'hivel': 127} + in_region = True + elif line.startswith('<'): + in_region = False + elif in_region: + key, value = line.split('=', 1) + try: + value = int(value) + except ValueError: + pass + region[key] = value + return regions + + +def to_xml(instruments): + print '''<?xml version="1.0" encoding="UTF-8"?> +<drumkit_info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.hydrogen-music.org/drumkit"> + <name>Drumkit</name> + <componentList> + <drumkitComponent> + <id>0</id> + <name>Main</name> + <volume>1</volume> + </drumkitComponent> + </componentList> + <instrumentList>''' + for id, ins in enumerate(instruments): + print ''' <instrument> + <id>%(id)d</id> + <name>%(name)s</name> + <isMuted>false</isMuted> + <pan_L>1</pan_L> + <pan_R>1</pan_R> + <randomPitchFactor>0</randomPitchFactor> + <gain>1</gain> + <volume>1</volume> + <applyVelocity>true</applyVelocity> + <filterActive>false</filterActive> + <filterCutoff>1</filterCutoff> + <filterResonance>0</filterResonance> + <Attack>0</Attack> + <Decay>0</Decay> + <Sustain>1</Sustain> + <Release>1000</Release> + <muteGroup>%(mute_group)d</muteGroup> + <midiOutChannel>-1</midiOutChannel> + <midiOutNote>%(note)d</midiOutNote> + <isStopNote>false</isStopNote> + <sampleSelectionAlgo>VELOCITY</sampleSelectionAlgo> + <isHihat>-1</isHihat> + <lower_cc>0</lower_cc> + <higher_cc>127</higher_cc> + <FX1Level>0</FX1Level> + <FX2Level>0</FX2Level> + <FX3Level>0</FX3Level> + <FX4Level>0</FX4Level> + <instrumentComponent> + <componentId>0</componentId> + <gain>1</gain>''' % {'id': id, 'name': ins['name'], 'note': ins['note'], 'mute_group': ins['mute_group']} + for l in ins['layers']: + print ''' <layer> + <filename>%s</filename> + <min>%g</min> + <max>%g</max> + <gain>1</gain> + <pitch>0</pitch> + </layer>''' % (l['sample'], l.get('lovel', 0) / 127.0, l.get('hivel', 127) / 127.0) + print ''' </instrumentComponent> + </instrument>''' + print ''' </instrumentList> +</drumkit_info>''' + + +def convert(filename): + dir = os.path.dirname(filename) + regions = read_sfz(filename) + # Regroup by pitch_keycenter + by_pitch = {} + for r in regions: + by_pitch.setdefault(get_key(r), []).append(r) + instruments = [] + for key, rlist in by_pitch.iteritems(): + note = key_to_note(key) + rlist.sort(key=lambda x: x['lovel']) + instrument_name = common_root([ + os.path.splitext(x['sample'])[0] for x in rlist]) + if not instrument_name: + instrument_name = 'instrument_%s' + key + instruments.append({ + 'note': note, + 'name': instrument_name, + 'layers': rlist, + 'mute_group': rlist[0].get('group', -1), + }) + print >>sys.stderr, 'note %d: %s' % (note, instrument_name) + for r in rlist: + print >>sys.stderr, ' + [%d, %d] %s' % ( + int(r['lovel']), int(r['hivel']), + os.path.join(dir, r['sample'])) + to_xml(instruments) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + sys.exit(2) + convert(sys.argv[1]) diff --git a/engine/verify-voice.c b/aux/verify-voice.c similarity index 100% rename from engine/verify-voice.c rename to aux/verify-voice.c