#!perl -w &init; while (<>) { # unDOSify chomp; s/\s+$//; # skip blank lines /^\s*$/ and next; # set context /^\[Samples\]\s*$/ and ( $section='samples', $sampleName='', next ); /^\[Instruments\]\s*$/ and ( $section='instruments', $instrumentName='', next ); /^\[Presets\]\s*$/ and ( $section='presets', $presetName='', next ); /^\[Info\]\s*$/ and ( $section='info', next ); # nasty hacks /^\s*(GlobalZone)+\s*$/ and $_ = "Sample=.$1."; /^\s*(GlobalLayer)+\s*$/ and $_ = "Instrument=.$1."; # parse - rather, skip anything that's not keyword=value... /^\s*([^=]+)=([^=]+)\s*$/ or next; ($1 eq '') and next; ($2 eq '') and next; # process keywords # &{$section} calls a subroutine based on $section, passing the keyword and value # returns true if keyword/value pair accepted &{$section}($1, $2) and next; warn("$.: $_\n", "Unexpected keyword: section: $section; keyword: '$1'; value: '$2'\n"); }; # # We now have # %info{...keyword...}=...value... # %presets{...presetName...}{...keyword...}=...value... # %presets{...presetName...}{...GlobalLayer...}{...keyword...}=...value... # %presets{...presetName...}{...instrumentName...}{...keyword...}=...value... # %instruments{...instrumentName...}{...GlobalZone...}{...keyword...}=...value... # %instruments{...instrumentName...}{...sampleName...}{...keyword...}=...value... # %samples{...sampleName...}{...keyword...}=...value... # # Info stands on its own (and isn't really used in .sfz - I've included it in each preset) # # Each preset has to be a separate .sfz file - cut them up by hand afterwards # # Each preset contains layers ("Instruments"), plus a global layer (here an instrument called .GlobalLayer.) # # Each instrument, except the GlobalLayer, contains zones, plus a global zone (here called .GlobalZone.) # # Each zone, except the GlobalZone, points to a sample. # Each zone, except the GlobalZone, is a in .sfz format. The GlobalZone could be a but I've left it fully expanded. # foreach $preset (sort keys(%presets)) { print "\n//------ cut here -------\n\n"; &do_info(); &do_preset($preset); } sub do_info { print "////\n", '// ', (defined($info{'Name'}) and $info{'Name'}), (defined($info{'Product'}) and (' - ', $info{'Product'})), (defined($info{'Designer'}) and (' - ', $info{'Designer'})), "\n", '// ', (defined($info{'Date'}) and $info{'Date'}), (defined($info{'Copyright'}) and (' - ', $info{'Copyright'})), "\n", '// ', (defined($info{'Editor'}) and ($info{'Editor'}, ' - with ')), 'sf2tosfz.pl', "\n", (defined($info{'Comments'}) ? ('// ', $info{'Comments'}, "\n") : ''), "// - - -\n", '// ', (defined($info{'Engine'}) and $info{'Engine'}), (defined($info{'Version'}) and (', ', $info{'Version'})), "\n", (defined($info{'ROMName'}) ? ('// ROM: ', $info{'ROMName'}, (defined($info{'ROMVersion'}) and (' ', $info{'ROMVersion'})), "\n") : ''), "////\n", "\n"; } sub do_preset { print "//\n", "// Preset: $preset\n", "// ", sprintf('Bank: %03d; ', $presets{$preset}{'Bank'}), "\n", "// ", sprintf('Program: %03d; ', $presets{$preset}{'Program'}), "\n", "//\n"; foreach $layer (sort keys(%{$presets{$preset}})) { ($layer =~ /^(\.GlobalLayer\.|Bank|Program)$/) and next; &do_layer($layer, \%{$presets{$preset}{$layer}}, \%{$presets{$preset}{'.GlobalLayer.'}}); print "\n", "// ####\n"; } } sub do_layer { my($layer, $ref_thisLayer, $ref_globalLayer, @junk) = @_; print "\n", "//\n", "// Layer: ", $layer, "\n", "//\n"; foreach $zone (sort keys(%{$instruments{$layer}})) { ($zone eq '.GlobalZone.') and next; # Only process right-hand zone for linked-stereo defined($samples{$zone}{'Type'}) and ($samples{$zone}{'Type'} eq '4') and next; #Sample Definition # 'sample', print ''; &do_zone($zone, \%{$instruments{$layer}{$zone}}, \%{$instruments{$layer}{'.GlobalZone.'}}, $ref_thisLayer, $ref_globalLayer); print ' sample=', $zone, "\n"; } } # do_zone writes out sfz regions - no attempt to cleverly anything sub do_zone { # The soundfont spec has two EGs and LFOs: a pair for Vibrato, tied to pitch, and a pair for "modulation", which can be applied to # pitch, filter cutoff and amplitude. As the "mod" EG and LFO are reused, we parse them out up front. # // sf2: 'delayModEnv', 'attackModEnv', 'holdModEnv', 'decayModEnv', 'sustainModEnv', 'releaseModEnv', # // sf2: 'keynumToModEnvHold', 'keynumToModEnvDecay', &parse_modenv(@_); # // sf2: 'delayModLFO', 'freqModLFO', &parse_modlfo(@_); # sf2 modulators defined($_[1]{'Modulator'}) and &do_modulators($_[1]{'Modulator'}); #Input Controls # sfz: 'lochan', 'hichan', // - not supported in sf2 # sfz: 'lokey', 'hikey', 'key', // sf2: 'LowKey', 'HighKey', &sf2_to_sfz('LowKey' , 'lokey', 0, @_); &sf2_to_sfz('HighKey', 'hikey', 127, @_); # sfz: 'lovel', 'hivel', // sf2: 'LowVelocity', 'HighVelocity', &sf2_to_sfz('LowVelocity' , 'lovel', 0, @_); &sf2_to_sfz('HighVelocity', 'hivel', 127, @_); # sfz: 'loccN', 'hiccN', // - not supported in sf2 # sfz: 'lobend', 'hibend', // - not supported in sf2 # sfz: 'lochanaft', 'hichanaft', // - not supported in sf2 # sfz: 'lopolyaft', 'hipolyaft', // - not supported in sf2 # sfz: 'lorand', 'hirand', // - not supported in sf2 # sfz: 'lobpm', 'hibpm', // - not supported in sf2 # sfz: 'seq_length', 'seq_position', // - not supported in sf2 # sfz: 'sw_lokey', 'sw_hikey', 'sw_last', 'sw_down', 'sw_up', 'sw_previous', // - not supported in sf2 # sfz: 'sw_vel', // - not supported in sf2 # sfz: 'trigger', // - not supported in sf2 # sfz: 'group', 'off_by', // sf2: 'exclusiveClass', &exclusive(@_); # sfz: 'off_mode', // - not supported in sf2 # sfz: 'on_loccN', 'on_hiccN', // - not supported in sf2 #Performance Parameters # Sample Player # sfz: 'delay', 'delay_random', 'delay_ccN', // - not supported in sf2 # sfz: 'offset', 'end', // sf2: 'startAddrsOffset', 'startAddrsCoarseOffset', 'endAddrsOffset', 'endAddrsCoarseOffset', &sf2Offset('start', 'offset', 0, @_); &sf2Offset('end' , 'end' , 0, @_); # sfz: 'offset_random', 'offset_ccN', // - not supported in sf2 # sfz: 'count', // - not supported in sf2 # sfz: 'loop_mode', // sf2: 'sampleModes', &sf2_to_sfz('sampleModes', 'loop_mode', undef, @_); # sfz: 'loop_start', 'loop_end', // sf2: 'startloopAddrsOffset', 'startloopAddrsCoarseOffset', 'endloopAddrsOffset', 'endloopAddrsCoarseOffset', &sf2Offset('startloop', 'loop_start', 0, @_); &sf2Offset('endloop' , 'loop_end' , 0, @_); # sfz: 'sync_beats', 'sync_offset', // - not supported in sf2 # Pitch # sfz: 'transpose', 'tune', 'pitch_keytrack', 'pitch_keycenter', // sf2: 'coarseTune', 'FineTune', 'fineTune', 'scaleTuning', 'overridingRootKey', &sf2_to_sfz('coarseTune' , 'transpose' , 0, @_); &sf2_to_sfz('fineTune' , 'tune' , 0, @_); &sf2_to_sfz('scaleTuning' , 'pitch_keytrack' , 100, @_); &sf2_to_sfz('overridingRootKey', 'pitch_keycenter', -1, @_); # not in sfz? // sf2: 'Key', 'keynum', #?? &tuning(@_); # sfz: 'pitch_veltrack', 'pitch_random', // - not supported in sf2 # sfz: 'bend_up', 'bend_down', 'bend_step', // - not supported in sf2 # Pitch EG # sfz: 'pitcheg_delay', 'pitcheg_attack', 'pitcheg_hold', 'pitcheg_decay', # 'pitcheg_sustain', 'pitcheg_release', 'pitcheg_depth', // sf2: 'modEnvToPitch', &pitchEG(@_); # sfz: 'pitcheg_start', // - not supported in sf2 # sfz: 'pitcheg_vel2delay', 'pitcheg_vel2attack', 'pitcheg_vel2hold', 'pitcheg_vel2decay', // - not supported in sf2 # sfz: 'pitcheg_vel2sustain', 'pitcheg_vel2release', 'pitcheg_vel2depth', // - not supported in sf2 # Pitch LFO # sfz: 'pitchlfo_delay', 'pitchlfo_freq', 'pitchlfo_depth', // sf2: 'modLfoToPitch', 'delayVibLFO', 'freqVibLFO', 'vibLfoToPitch', &pitchLFO(@_); # sfz: 'pitchlfo_fade', // - not supported in sf2 # sfz: 'pitchlfo_freqccN', 'pitchlfo_freqchanaft', 'pitchlfo_freqpolyaft', // - not supported in sf2 # sfz: 'pitchlfo_depthccN', 'pitchlfo_depthchanaft', 'pitchlfo_depthpolyaft', // - not supported in sf2 # Filter # sfz: 'fil_type', // - not supported in sf2 # sfz: 'cutoff', // sf2: 'initialFilterFc', &sf2_to_sfz('initialFilterFc', 'cutoff', undef, @_); # sfz: 'resonance', // sf2: 'initialFilterQ', &sf2_to_sfz('initialFilterQ', 'resonance', 0, @_); # sfz: 'cutoff_ccN', 'cutoff_chanaft', 'cutoff_polyaft', // - not supported in sf2 # sfz: 'fil_keytrack', 'fil_keycenter', 'fil_veltrack', // - not supported in sf2 # sfz: 'fil_random', // - not supported in sf2 # Filter EG # sfz: 'fileg_delay', 'fileg_start', 'fileg_attack', 'fileg_hold', 'fileg_decay', # 'fileg_sustain', 'fileg_release', 'fileg_depth', // sf2: 'modEnvToFilterFc', &filterEG(@_); # sfz: 'fileg_vel2delay', 'fileg_vel2attack', 'fileg_vel2hold', 'fileg_vel2decay', // - not supported in sf2 # sfz: 'fileg_vel2sustain', 'fileg_vel2release', 'fileg_vel2depth', // - not supported in sf2 # Filter LFO # sfz: 'fillfo_delay', 'fillfo_freq', 'fillfo_depth', // sf2: 'modLfoToFilterFc', &filterLFO(@_); # sfz: 'fillfo_fade', // - not supported in sf2 # sfz: 'fillfo_freqccN', 'fillfo_freqchanaft', 'fillfo_freqpolyaft', // - not supported in sf2 # sfz: 'fillfo_depthccN', 'fillfo_depthchanaft', 'fillfo_depthpolyaft', // - not supported in sf2 # Amplifier # sfz: 'output', // - not supported in sf2 # sfz: 'pan', // sf2: 'pan', &sf2_to_sfz('pan', 'pan', 0, @_); # sfz: 'width', 'position', // - not supported in sf2 # sfz: doesn't support // sf2: 'velocity', # sfz: 'volume', // sf2: 'initialAttenuation', &sf2_to_sfz('initialAttenuation', 'volume', 0, @_); # sfz: 'gain_ccN', // - not supported in sf2 # sfz: 'amp_keytrack', 'amp_keycenter', // - not supported in sf2 # sfz: 'amp_veltrack', // - not supported in sf2 # not in sfz? // sf2: 'velocity', # sfz: 'amp_random', // - not supported in sf2 # sfz: 'amp_velcurve_1' .. 'amp_velcurve_127', // - not supported in sf2 # sfz: 'rt_decay', // - not supported in sf2 # sfz: 'xfin_lokey', 'xfin_hikey', 'xfout_lokey', 'xfout_hikey', 'xf_keycurve', // - not supported in sf2 # sfz: 'xfin_lovel', 'xfin_hivel', 'xfout_lovel', 'xfout_hivel', 'xf_velcurve', // - not supported in sf2 # sfz: 'xfin_loccN', 'xfin_hiccN', 'xfout_loccN', 'xfout_hiccN', 'xf_cccurve', // - not supported in sf2 # Amplifier EG # sfz: 'ampeg_delay', 'ampeg_attack', 'ampeg_hold', 'ampeg_decay', 'ampeg_sustain', 'ampeg_release', # // sf2: 'delayVolEnv', 'attackVolEnv', 'holdVolEnv', 'decayVolEnv', 'sustainVolEnv', 'releaseVolEnv', &EG(@_); # sfz: doesn't support // sf2: 'keynumToVolEnvHold', 'keynumToVolEnvDecay', # sfz: 'ampeg_start', // - not supported in sf2 # sfz: 'ampeg_vel2delay', 'ampeg_vel2attack', 'ampeg_vel2hold', 'ampeg_vel2decay', 'ampeg_vel2sustain', 'ampeg_vel2release', // - not supported in sf2 # sfz: 'ampeg_delayccN', 'ampeg_startccN', 'ampeg_attackccN', 'ampeg_holdccN', 'ampeg_decayccN', 'ampeg_sustainccN', 'ampeg_releaseccN', // - not supported in sf2 # Amplifier LFO # sfz: 'amplfo_fade', // - not supported in sf2 # sfz: 'amplfo_freq', 'amplfo_delay', 'amplfo_depth', // sf2: 'modLfoToVolume', &LFO(@_); # sfz: 'amplfo_freqccN', 'amplfo_freqchanaft', 'amplfo_freqpolyaft', // - not supported in sf2 # sfz: 'amplfo_depthccN', 'amplfo_depthchanaft', 'amplfo_depthpolyaft', // - not supported in sf2 # Equalizer # sfz: 'eq1_freq', 'eq2_freq', 'eq3_freq', 'eq1_freqccN', 'eq2_freqccN', 'eq3_freqccN', 'eq1_vel2freq', 'eq2_vel2freq', 'eq3_vel2freq', // - not supported in sf2 # sfz: 'eq1_bw', 'eq2_bw', 'eq3_bw', 'eq1_bwccN', 'eq2_bwccN', 'eq3_bwccN', // - not supported in sf2 # sfz: 'eq1_gain', 'eq2_gain', 'eq3_gain', 'eq1_gainccN', 'eq2_gainccN', 'eq3_gainccN', 'eq1_vel2gain', 'eq2_vel2gain', 'eq3_vel2gain', // - not supported in sf2 # Effects # sfz: 'effect1', // sf2: 'reverbEffectsSend', &sf2_to_sfz('reverbEffectsSend', 'effect1', 0, @_); # sfz: 'effect2', // sf2: 'chorusEffectsSend', &sf2_to_sfz('chorusEffectsSend', 'effect2', 0, @_); } sub parse_modenv { %EG = (); %EG = ( 'delay' => &want_sf2('delayModEnv' , @_), 'attack' => &want_sf2('attackModEnv' , @_), 'hold' => &want_sf2('holdModEnv' , @_), 'decay' => &want_sf2('decayModEnv' , @_), 'sustain' => &want_sf2('sustainModEnv', @_), 'release' => &want_sf2('releaseModEnv', @_), ); defined($EG{'delay' }) and ($EG{'delay' } eq 0) and delete($EG{'delay' }); defined($EG{'attack' }) and ($EG{'attack' } eq 0) and delete($EG{'attack' }); defined($EG{'hold' }) and ($EG{'hold' } eq 0) and delete($EG{'hold' }); defined($EG{'decay' }) and ($EG{'decay' } eq 0) and delete($EG{'decay' }); defined($EG{'sustain'}) and ($EG{'sustain'} eq 100) and delete($EG{'sustain'}); defined($EG{'release'}) and ($EG{'release'} eq 0) and delete($EG{'release'}); # sfz does not support // sf2: 'keynumToModEnvHold', 'keynumToModEnvDecay', } sub print_modenv { my($prefix, @junk) = @_; defined($EG{'delay' }) and print(" ${prefix}_delay=" , $EG{'delay' }, ', '), defined($EG{'attack' }) and print(" ${prefix}_attack=" , $EG{'attack' }, ', '), defined($EG{'hold' }) and print(" ${prefix}_hold=" , $EG{'hold' }, ', '), defined($EG{'decay' }) and print(" ${prefix}_decay=" , $EG{'decay' }, ', '), defined($EG{'sustain'}) and print(" ${prefix}_sustain=", $EG{'sustain'}, ', '), defined($EG{'release'}) and print(" ${prefix}_release=", $EG{'release'}, ', '); } sub parse_modlfo { %LFO = (); %LFO = ( 'delay' => &want_sf2('delayModLFO', @_), 'freq' => &want_sf2('freqModLFO' , @_), ); defined($LFO{'delay'}) and ($LFO{'delay'} eq 0) and delete($LFO{'delay'}); defined($LFO{'freq' }) and ($LFO{'freq' } eq 0) and delete($LFO{'freq' }); } sub do_modulators { my($ref, @junk) = @_; print "\n// In addition to default modulators, the following are specified:\n// mod: ", join("\n// mod: ", @{$ref}), "\n"; } sub exclusive { # sfz: 'group', 'off_by', // sf2: 'exclusiveClass', &sf2_to_sfz('exclusiveClass', 'group' , 0, @_); &sf2_to_sfz('exclusiveClass', 'off_by', 0, @_); } sub sf2Offset { my($sf2pref, $sfzkey, $def, @refs) = @_; my($CoarseOffset) = &want_sf2($sf2pref . 'AddrsCoarseOffset', @refs); my($Offset) = &want_sf2($sf2pref . 'AddrsOffset', @refs); !defined($CoarseOffset) and !defined($Offset) and return; my($value) = (defined($startAddrsOffset) ? $startAddrsOffset : 0) + ((defined($startAddrsCoarseOffset) ? $startAddrsCoarseOffset : 0) * 32768); ($value ne $def) and print " $sfzkey=$value"; } sub pitchEG { # sfz: 'pitcheg_delay', 'pitcheg_attack', 'pitcheg_hold', 'pitcheg_decay', # 'pitcheg_sustain', 'pitcheg_release', 'pitcheg_depth', // sf2: 'modEnvToPitch', # modEnvToPitch is number of cents by which a full-scale departure of the EG adjusts the pitch (+/-) my($modEnvToPitch) = &want_sf2('modEnvToPitch', @_); if (defined($modEnvToPitch) and ($modEnvToPitch ne 0)) { print(' pitcheg_depth=', $modEnvToPitch, ', '); &print_modenv('pitcheg'); } } sub pitchLFO { # sfz: 'pitchlfo_delay', 'pitchlfo_freq', 'pitchlfo_depth', // sf2: 'modLfoToPitch', 'delayVibLFO', 'freqVibLFO', 'vibLfoToPitch', # modLfoToPitch is number of cents by which a full-scale departure of the LFO adjusts the pitch (+/-) # vibLfoToPitch is number of cents by which a full-scale departure of the LFO adjusts the pitch (+/-) my($modLfoToPitch, $vibLfoToPitch) = (&want_sf2('modLfoToPitch', @_), &want_sf2('vibLfoToPitch', @_)); if (defined($vibLfoToPitch) and ($vibLfoToPitch ne 0)) { print "\n", "\n", "# Vibrato LFO used - mod LFO ignored, if present\n"; print(' pitchlfo_depth=', $vibLfoToPitch); &sf2_to_sfz('delayVibLFO', 'pitchlfo_delay', 0, @_); &sf2_to_sfz('freqVibLFO' , 'pitchlfo_freq' , 0, @_); } elsif (defined($modLfoToPitch) and ($modLfoToPitch ne 0)) { print "\n", "\n", "# mod LFO used (no Vibrato LFO effect)\n"; print(' pitchlfo_depth=', $modLfoToPitch); (defined($LFO{'delay'})) and print(' pitchlfo_delay=', $LFO{'delay'}); (defined($LFO{'freq' })) and print(' pitchlfo_freq=' , $LFO{'freq' }); } } sub filterEG { # sfz: 'fileg_delay', 'fileg_start', 'fileg_attack', 'fileg_hold', 'fileg_decay', # 'fileg_sustain', 'fileg_release', 'fileg_depth', // sf2: 'modEnvToFilterFc', my($modEnvToFilterFc) = &want_sf2('modEnvToFilterFc', @_); if (defined($modEnvToFilterFc) and ($modEnvToFilterFc ne 0)) { print(' fileg_delay=', $modEnvToFilterFc, ', '); &print_modenv('fileg'); } } sub filterLFO { # sfz: 'fillfo_delay', 'fillfo_freq', 'fillfo_depth', // sf2: 'modLfoToFilterFc', # modLfoToFilterFc is number of cents by which a full-scale departure of the LFO adjusts the filter cutoff (+/-) my($modLfoToFilterFc) = &want_sf2('modEnvToFilterFc', @_); if (defined($modLfoToFilterFc) and ($modLfoToFilterFc ne 0)) { print(' fillfo_depth=', $modLfoToFilterFc); (defined($LFO{'delay'})) and print(' fillfo_delay=', $LFO{'delay'}); (defined($LFO{'freq' })) and print(' fillfo_freq=' , $LFO{'freq' }); } } sub ampEG { # sfz: 'ampeg_delay', 'ampeg_attack', 'ampeg_hold', 'ampeg_decay', 'ampeg_sustain', 'ampeg_release', # // sf2: 'delayVolEnv', 'attackVolEnv', 'holdVolEnv', 'decayVolEnv', 'sustainVolEnv', 'releaseVolEnv', &sf2_to_sfz('delayVolEnv' , 'ampeg_delay' , 0, @_); &sf2_to_sfz('attackVolEnv' , 'ampeg_attack' , 0, @_); &sf2_to_sfz('holdVolEnv' , 'ampeg_hold' , 0, @_); &sf2_to_sfz('decayVolEnv' , 'ampeg_decay' , 0, @_); &sf2_to_sfz('sustainVolEnv', 'ampeg_sustain', 100, @_); &sf2_to_sfz('releaseVolEnv', 'ampeg_release', 0, @_); } sub ampLFO { # sfz: 'amplfo_freq', 'amplfo_delay', 'amplfo_depth', // sf2: 'modLfoToVolume', my($modLfoToVolume) = &want_sf2('modLfoToVolume', @_); if (defined($modLfoToVolume) and ($modLfoToVolume ne 0)) { print(' amplfo_depth=', $modLfoToVolume); (defined($LFO{'delay'})) and print(' amplfo_delay=', $LFO{'delay'}); (defined($LFO{'freq' })) and print(' amplfo_freq=' , $LFO{'freq' }); } } # Print the value for the sfz file sub sf2_to_sfz { my($sf2key, $sfzkey, $def, @refs) = @_; my($value) = &want_sf2($sf2key, @refs); defined($value) and (!defined($def) or ($value ne $def)) and print(' ', $sfzkey, '=', $value); } # Convert and return the value from the sf2 file sub want_sf2 { # for now, ignore anything in the sample defs # my($sf2Ssuff) = $sf2key; # different hashkeys! # ($sf2key eq 'fineTune') and $sf2Ssuff = 'FineTune'; # different hashkeys! # ($sf2key eq 'keynum') and $sf2Ssuff = 'Key'; # different hashkeys! # defined($sf2Ssuff) and defined(${$ref_samples}{$sf2Ssuff}) and return ${$ref_samples}{$sf2Ssuff}; # different hashkeys! my($sf2key, $zone, $ref_thisZone, $ref_globalZone, $ref_thisLayer, $ref_globalLayer, @junk) = @_; my($zoneVal ) = ${$ref_thisZone }{$sf2key}; my($globaZoneVal ) = ${$ref_globalZone }{$sf2key}; my($layerVal ) = ${$ref_thisLayer }{$sf2key}; my($globalLayerVal) = ${$ref_globalLayer}{$sf2key}; (!defined($zoneVal ) and defined($globalZoneVal )) and $zoneVal = $globalZoneVal ; (!defined($layerVal) and defined($globalLayerVal)) and $layerVal = $globalLayerVal; if (defined($generator{$sf2key})) { $zoneVal = &{$generator{$sf2key}}($zoneVal); $layerVal = &{$generator{$sf2key}}($layerVal); # "Generators at the Preset Level are instead considered “relative” and additive to all the default or instrument level generators within the Preset Zone." # No Preset modulators exist, though, in SF2.00 ... if (defined($layerVal)) { if (defined($zoneVal)) { $zoneVal += $layerVal; } else { $zoneVal = $layerVal; } } } return $zoneVal; } sub info { my($keyword, $value, @junk) = @_; $info{$keyword}=$value; return grep(/^${keyword}$/, ('Version', 'Engine', 'Name', 'ROMVersion', 'Date', 'Copyright', 'Editor', 'Comments', 'Designer', 'Product')); } sub presets { # In addition to the sf2 generator values, this section understands: 'PresetName', 'Bank', 'Program' # It also catches the 'Instrument' generator passed from sf2comp as the name of the instrument my($keyword, $value, @junk) = @_; if ($keyword eq 'PresetName') { $presetName = $value; $presets{$presetName}={}; $instrumentName=''; return -1; } elsif ($presetName eq '') { warn("$.: $_\n", "No current preset: current section: $section; \$keyword: $keyword; \$value: $value\n"); } elsif ($keyword =~ /^Bank|Program$/) { $presets{$presetName}{$keyword}=$value; return -1; } elsif ($keyword eq 'Instrument') { if (($value ne '.GlobalLayer.') and !defined($instruments{$value})) { warn("$.: $_\n", "Unknown instrument: $value\n", "Current preset: $presetName; current section: $section; \$keyword: $keyword; \$value: $value\n"); } $instrumentName = $value; $presets{$presetName}{$instrumentName}={}; return -1; } elsif ($instrumentName eq '') { warn("$.: $_\n", "No current instrument: Current preset: $presetName; current section: $section; \$keyword: $keyword; \$value: $value\n"); } else { $keyword =~ s/^G?[ZL]_//; $presets{$presetName}{$instrumentName}{$keyword}=$value; } return grep(/^${keyword}$/, keys %generator); } sub instruments { # In addition to the sf2 generator values, this section understands: 'InstrumentName', 'Sample' my($keyword, $value, @junk) = @_; if ($keyword eq 'InstrumentName') { $instrumentName = $value; $instruments{$instrumentName}={}; $sampleName = ''; return -1; } elsif ($instrumentName eq '') { warn("$.: $_\n", "No current instrument: section: $section; \$keyword: $keyword; \$value: $value\n"); } elsif ($keyword eq 'Sample') { if (($value ne '.GlobalZone.') and !defined($samples{$value})) { warn("$.: $_\n", "Unknown sample: $value\n", "Current instrument: $instrumentName; current section: $section; \$keyword: $keyword; \$value: $value\n"); } else { $sampleName = $value; $instruments{$instrumentName}{$sampleName}={}; } return -1; } elsif ($sampleName eq '') { warn("$.: $_\n", "No current zone: Current instrument: $instrumentName; current section: $section; \$keyword: $keyword; \$value: $value\n"); } else { $keyword =~ s/^G?Z_//; if ($keyword eq 'Modulator') { if (!defined($instruments{$instrumentName}{$sampleName}{'Modulator'})) { @{$instruments{$instrumentName}{$sampleName}{'Modulator'}} = (); } my($ref) = $instruments{$instrumentName}{$sampleName}{'Modulator'}; push(@{$ref}, $value); return -1; } else { $instruments{$instrumentName}{$sampleName}{$keyword}=$value; } } return grep(/^${keyword}$/, keys %generator); } sub samples { my($keyword, $value, @junk) = @_; if ($keyword eq 'SampleName') { $sampleName = $value; $samples{$sampleName}={}; return -1; } elsif ($sampleName eq '') { warn("$.: $_\n", "No current sample: section: $section; \$keyword: $keyword; \$value: $value\n"); } else { $samples{$sampleName}{$keyword}=$value; } return grep(/^${keyword}$/, ('SampleRate', 'Key', 'FineTune', 'Type', 'Link')); } sub init { $section=''; $sampleName=''; $instrumentName=''; $presetName=''; %generator = ( # Generator Enumerators 'startAddrsOffset' => \&bit16, 'endAddrsOffset' => \&bit16, 'startloopAddrsOffset' => \&bit16, 'endloopAddrsOffset' => \&bit16, 'startAddrsCoarseOffset' => \&bit16, 'modLfoToPitch' => \&bit16, 'vibLfoToPitch' => \&bit16, 'modEnvToPitch' => \&bit16, 'initialFilterFc' => \&freqcent, 'initialFilterQ' => \&div10, 'modLfoToFilterFc' => \&bit16, 'modEnvToFilterFc' => \&bit16, 'endAddrsCoarseOffset' => \&bit16, 'modLfoToVolume' => \&div10, 'unused1' => undef, 'chorusEffectsSend' => \&div10, 'reverbEffectsSend' => \&div10, 'pan' => \&pan, 'unused2' => undef, 'unused3' => undef, 'unused4' => undef, 'delayModLFO' => \¢s, 'freqModLFO' => \&freqcent, 'delayVibLFO' => \¢s, 'freqVibLFO' => \&freqcent, 'delayModEnv' => \¢s, 'attackModEnv' => \¢s, 'holdModEnv' => \¢s, 'decayModEnv' => \¢s, 'sustainModEnv' => \&div10, 'releaseModEnv' => \¢s, 'keynumToModEnvHold' => undef, 'keynumToModEnvDecay' => undef, 'delayVolEnv' => \¢s, 'attackVolEnv' => \¢s, 'holdVolEnv' => \¢s, 'decayVolEnv' => \¢s, 'sustainVolEnv' => \&div10, 'releaseVolEnv' => \¢s, 'keynumToVolEnvHold' => undef, 'keynumToVolEnvDecay' => undef, 'Instrument' => undef, 'reserved1' => undef, # keyrange as this pair: 'LowKey', => undef, 'HighKey', => undef, # velrange as this pair: 'LowVelocity', => undef, 'HighVelocity', => undef, 'startloopAddrsCoarseOffset' => \&bit16, 'keynum' => undef, 'velocity' => undef, 'initialAttenuation' => \&divm10, 'reserved2' => undef, 'endloopAddrsCoarseOffset' => \&bit16, 'coarseTune' => \&bit16, 'fineTune' => \&bit16, # sampleID -> "Sample" 'sampleModes' => \&sampleModes, 'scaleTuning' => \&bit16, 'exclusiveClass' => undef, 'overridingRootKey' => \&bit16, 'unused5', => undef, # endOper ); } sub bit16 { my($value, @junk) = @_ ; return !defined($value) ? undef : (($value < 32768) ? $value : $value - 65536); } sub pan { my($value) = &bit16(@_); return !defined($value) ? undef : sprintf("%0.3f", ($value - 500) / 10.0); } # dirty hack to handle "right channel only" sub div10 { my($value) = &bit16(@_); return !defined($value) ? undef : $value / 10.0; } sub divm10 { my($value) = &bit16(@_); return !defined($value) ? undef : $value / -10.0; } sub cents { my($value) = &bit16(@_); return !defined($value) ? undef : sprintf("%0.3f", ($value eq -32768) ? 0 : 2.0 ** ($value / 1200.0)); } sub freqcent { my($value) = &bit16(@_); return !defined($value) ? undef : sprintf("%0.3f", ($value eq -32768) ? 0 : (2.0 ** ($value / 1200.0)) * 8.176); } sub cB_to_pc { my($value) = &bit16(@_); return !defined($value) ? undef : sprintf("%0.3f", ($value le 1000) ? 100 : (2.0 ** (-$value / 60.0)) / 100.0); } sub sampleModes { my($value, @junk) = @_; !defined($value) and return undef; $value &= 3; ($value eq 0) and return 'no_loop'; ($value eq 1) and return 'loop_continuous'; ($value eq 2) and return 'no_loop'; ($value eq 3) and return 'loop_sustain'; }