\\ \\ ====== Convert SFZ (sound-bank-definition) to H2 (hydrogen-xml) format (python script) ====== \\ \\ \\ Unfortunately this script is NOT flexible and requires a certain SFZ custom format (as in matchstr object below). \\ \\ create an SFZ file in the same directory, with name \\ "drumkit.sfz" \\ \\ run the script: \\ python sfz_to_h2.py \\ \\ The SFZ file content format must be very close to this: \\ \\ key=34 offset=0 lovel=0 hivel=127 sample=ogg/beats_01-34.oga key=35 offset=0 lovel=0 hivel=127 sample=ogg/beats_06-50.oga // c3 - bass drum 1 key=36 offset=0 lovel=0 hivel=127 sample=ogg/beats_06-38.oga // c#3 - stick key=37 offset=0 lovel=0 hivel=127 sample=ogg/beats_09-12.oga // d3 - snare 1 key=38 offset=0 lovel=0 hivel=127 sample=ogg/beats_01-21.oga \\ \\ Note: hydrogen ignores midi notes (key=36) below NR. 36 (=bass drum) ! \\ \\ \\ Python Script: \\ -- \\ #!/usr/bin/python # -*- coding: utf-8 -*- ############################################################################# # # Info Section # ############################################################################# ''' python script converts _simple_ SFZ sound-bank-definition files to Hydrogen2-drum-machine format. adjust the input-file name below ! then run the script like: python sfz_to_h2.py > drumkit.xml edit drumkit.xml (h2 does not support sub-directories yet, remove them. copy oga/flac files to same directory) drumkit.sfz ===> drumkit.h2.xml Note: midi note-nr _36_ (c3) will be hydrogen-instrument-id _0_ midi notes below 36 will be appended to the end ! ********************************************************************** * License Info ********************************************************************** Copyright (C) Emanuel Rumpf Contact: em-rumpf (at) gmx (.) de 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, version 3 of the License. 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, see ''' #!/usr/bin/python # -*- coding: utf-8 -*- #import sys import re import os, os.path # ADJUST INPUT FILE NAME (will not be modified) # # filename = 'drumkit.sfz' output_file = 'drumkit.h2.xml' # ############################################################################# ############################################################################# # # Start template definitions # ############################################################################# # key=37 offset=0 lovel=0 hivel=127 sample=ogg/beats_09-12.oga # hydrogen 2 xml-file template h2tem_head = ''' Drumkit Name Author Name License Info + License URL Information ''' h2tem = ''' %s %s %s 0.900000 false 1.000000 1.000000 ''' h2tem_foot = ''' ''' # test string (part of an SFZ file) matchstr = """ key=34 offset=0 lovel=0 hivel=127 sample=ogg/beats_01-34.oga key=35 offset=0 lovel=0 hivel=127 sample=ogg/beats_06-50.oga // c3 - bass drum 1 key=36 offset=0 lovel=0 hivel=127 sample=ogg/beats_06-38.oga // c#3 - stick key=37 offset=0 lovel=0 hivel=127 sample=ogg/beats_09-12.oga // d3 - snare 1 key=38 offset=0 lovel=0 hivel=127 sample=ogg/beats_01-21.oga """ ############################################################################# # # End template definitions # ############################################################################# fcont = '' def read_file(): global fcont # try: f = open( filename, 'rb' ) fcont = str( f.read() ) f.close() def create_match_dic( mat ): #print "match : ", mat if type( mat ) is tuple: m1 = mat[0] m2 = mat[1] elif ( type( mat ) is str ): # is string m1 = '' # comment not given m2 = mat else: print( "Error in create_match_dic() - mat is neither tuple nor string ") return dd = {} s2 = m2.split(' ') for k in s2: ss = k.strip() if ss != '': s3 = ss.split('=') if len(s3) > 1: dd[ s3[0].strip() ] = s3[1].strip() #end #end # note: m1 is set, if there is a comment # before the word in the sfz file dd['comment'] = m1 #print " Dic : ", dd return dd def process_file(): ins_id = '0' name = 'bass drum 1' filename = 'flac/beats_08-19.flac' ret = '' # common variables # match with comment #rawstr_c = r"""\/\/\s?(.*)\s?\n\(.*)""" rawstr_c = r"""\/\/\s?(.*)\s?\n\([^\<]*)""" # match without comment #rawstr_n = r"""\(.*)""" rawstr_n = r"""\([^\<]*)""" # embedded_rawstr = r"""(?mx)\(.*)""" global fcont matchstr = str( fcont ) # use a compile object re_comp_n = re.compile( rawstr_n, re.MULTILINE| re.VERBOSE |re.DOTALL ) re_comp_c = re.compile( rawstr_c, re.MULTILINE| re.VERBOSE |re.DOTALL ) # first process matches _with_ comment ##################################### # search pattern #match_obj = re_comp_c.search(matchstr) matches = re_comp_c.findall( matchstr ) #print matches #dir( matches ) # Retrieve group(s) from match_obj #all_groups = match_obj.groups() # a dic with instrument-id as key and another dic as value, # containing name-value mappings for a region all_mat = {} for mat in matches: # matches with comment # mDic has name-value mappings for a single region mDic = create_match_dic( mat ) ins_id = mDic['key'] # 0 all_mat[ ins_id ] = mDic # end for(mat) # now process matches _without_ comment ##################################### # search pattern #match_obj = re_comp_n.search(matchstr) matches = re_comp_n.findall(matchstr) #print matches #dir( matches ) # Retrieve group(s) from match_obj #all_groups = match_obj.groups() for mat in matches: # matches without comment mDic = create_match_dic( mat ) ins_id = mDic['key'] # 0 if mDic['comment'] == '': mDic['comment'] = 'Sample: ' + mDic['sample'] if ins_id in all_mat: pass # region was processed in previous match round (with comments) else: all_mat[ ins_id ] = mDic # end for(mat) keys = list(all_mat.keys()) # sort regions (found matches) by instrument id keys.sort() sorted_out_dic = {} last_id = 0 for k in keys: mDic = all_mat[ k ] ins_id = mDic['key'] # 0 ins_id_nr = int(ins_id) name = mDic['comment'] # bass drum 1 filename = mDic['sample'] # flac/beats_08-19.flac if int(ins_id) < 36: # # sfz note 36 is equal hydrogen instrument 0 # thus we sort out values below 36 # and append those later # sorted_out_dic[ ins_id ] = mDic continue #last_id = ins_id_nr temx = h2tem % ( last_id, name, filename ) ret += temx last_id += 1 keys = list(sorted_out_dic.keys()) # sort regions (found matches) by instrument id keys.sort() for k in keys: mDic = sorted_out_dic[ k ] last_id += 1 ins_id = mDic['key'] # 0 ins_id_nr = last_id # int( ins_id) + last_id name = mDic['comment'] # bass drum 1 filename = mDic['sample'] # flac/beats_08-19.flac temx = h2tem % ( str(ins_id_nr), name, filename ) ret += temx # FINAL OUTPUT # output_text = h2tem_head + ret + h2tem_foot # print( out ) global output_file # output_file = 'drumkit.h2.xml' if os.path.exists( output_file ): print( 'NOTE: File NOT written. File %s already exists. ' % output_file ) else: f = open( output_file, 'w+b' ) # f.write( bytes( output_text, 'UTF-8' )) f.write( output_text.encode('UTF-8') ) f.close() # print( 'Output file written: ' + output_file ) ############################################################################# # # Start main function # ############################################################################# def main(): read_file() process_file() if __name__ == '__main__': main() k = """ // 35 Acoustic Bass Drum 36 Bass Drum 1 = h2-instrument 0 37 Side Stick = h2-instrument 1 38 Acoustic Snare 39 Hand Clap 40 Electric Snare 41 Low Floor Tom 42 Closed Hi Hat 43 High Floor Tom 44 Pedal Hi-Hat 45 Low Tom 46 Open Hi-Hat 47 Low-Mid Tom 48 Hi-Mid Tom 49 Crash Cymbal 1 50 High Tom 51 Ride Cymbal 1 52 Chinese Cymbal """