@@ -507,6 +507,7 @@ use POSIX ":sys_wait_h"; # for nonblocking read
507
507
use DateTime;
508
508
use Time::HiRes; # overrides (qw) do not seem to work
509
509
use Expect::Simple;
510
+ use File::Format::RIFF;
510
511
511
512
use Exporter;
512
513
require Exporter;
@@ -554,6 +555,7 @@ use vars qw( %expected_latency );
554
555
555
556
use vars qw( $wood_note ) ;
556
557
use vars qw( %instrument_name_to_channel_number ) ;
558
+ use vars qw( %instrument_name_to_mf_velocity ) ;
557
559
use vars qw( $fluidR3 ) ;
558
560
$fluidR3 = ' /usr/share/sounds/sf2/FluidR3_GM.sf2' ;
559
561
@@ -611,6 +613,7 @@ use vars qw( %alias_to_canonical_instrument );
611
613
%alias_to_canonical_instrument = (
612
614
' Keyboard' => ' Piano' ,
613
615
' Carillon' => ' Bell' ,
616
+ ' Chimes' => ' Church Bells' ,
614
617
);
615
618
616
619
use vars qw( %laisser_vibrer_p ) ;
@@ -1788,10 +1791,10 @@ sub define_chimes () {
1788
1791
x = { \t empo 4 = 96 \t ime 3/4 }
1789
1792
y = { \s et Staff.instrumentName = #"Organ" }
1790
1793
melodyonly = { <>-\f ff }
1791
- melodywaccomp = { <>-\f ff \s et Staff.instrumentName = #"Piano " }
1792
- quarteraccomp = { <>-\p \y }
1793
- halfaccomp = { <>-\m p \y }
1794
- houraccomp = { <>-\m p \y }
1794
+ melodywaccomp = { <>-\f f \s et Staff.instrumentName = #"Carillon " }
1795
+ quarteraccomp = { <>-\m p \y }
1796
+ halfaccomp = { <>-\m f \y }
1797
+ houraccomp = { <>-\f \y }
1795
1798
z = { <>-\m f }
1796
1799
A = \t ranspose c b { c'4 g' f' e' }
1797
1800
B = \t ranspose c b { c'4 g' f' e' g' d' }
@@ -2956,15 +2959,34 @@ sub send_midi_command ($) {
2956
2959
}
2957
2960
}
2958
2961
2959
- sub probe_synth_instruments () {
2960
- send_midi_command('inst 1');
2961
- my @instruments = $synth ->before;
2962
- my %instruments = map { /^(\d +)-(\d +)\s +(.*)$/s? ($3 => [$1 + 0, $2 + 0], "$1 -$2 " => $3 ): () } split(/\r ?\n /, $synth ->before);
2962
+ sub probe_synth_instruments ($) {
2963
+ my($soundfonts ) = @_ ;
2964
+ my %instruments ;
2965
+ for (my $i = 0; $i < @$soundfonts ; $i += 1) {
2966
+ my $font = $i + 1;
2967
+ send_midi_command(sprintf('inst %d ', $font ));
2968
+ for my $s (split(/\r ?\n /, $synth ->before)) {
2969
+ if ($s =~ /^(\d +)-(\d +)\s +(.*?)\r ?$/s) {
2970
+ if (!defined $instruments {$3 }) {
2971
+ $instruments {$3 } = [$font , $1 + 0, $2 + 0, $soundfonts ->[$i ]->{'zero'}];
2972
+ log_debug sprintf "instrument %s defined as %s ", $3 , Debug::cvs($instruments {$3 }) if $debug > 1;
2973
+ } else {
2974
+ log_debug sprintf "instrument %s from soundfont %d ignored", $3 , $font if $debug > 1;
2975
+ }
2976
+ if (!defined $instruments {"$1 -$2 "}) {
2977
+ $instruments {"$1 -$2 "} = $3 ;
2978
+ log_debug sprintf "bank %s -%s from soundfont %s defined as alias for instrument %s ", $1 , $2 , $font , $3 if $debug > 1;
2979
+ } else {
2980
+ log_debug sprintf "alias %s -%s from soundfont %s ignored", $1 , $2 , $font if $debug > 1;
2981
+ }
2982
+ }
2983
+ }
2984
+ }
2963
2985
return wantarray? %instruments : \% instruments;
2964
2986
}
2965
2987
2966
- sub find_synth_instrument ($@ ) {
2967
- my($instruments , @preferences ) = @_ ;
2988
+ sub find_synth_instrument ($$ @) {
2989
+ my($instruments , $soundfonts , @preferences ) = @_ ;
2968
2990
my $it ;
2969
2991
for my $candidate (@preferences ) {
2970
2992
$it = $instruments ->{$candidate } if defined $instruments ->{$candidate };
@@ -3014,7 +3036,7 @@ sub probe_score_instruments () {
3014
3036
sub connect_synth ($) {
3015
3037
my($id ) = @_ ;
3016
3038
my $output = get_cache_pathname_for_event($id );
3017
- my($sf_path , $gain );
3039
+ my($sf_path , $master_gain );
3018
3040
my $immediate_p = $id eq 'test';
3019
3041
if (!defined $synth && ($immediate_p || defined $output )) { # if we're connected don't try to create a second instance, it was disastrous
3020
3042
my %need_instrument = probe_score_instruments;
@@ -3027,31 +3049,66 @@ sub connect_synth ($) {
3027
3049
log_debug sprintf "needed instruments adjusted to %s ", Debug::cvs [keys %need_instrument ] if $debug ;
3028
3050
}
3029
3051
3030
- # Figure out where our soundfont is
3031
- for my $key ('Timbres Of Heaven', 'FluidR3') {
3032
- if (defined $config ->{'/'}->{$key }) {
3033
- foreach my $candidate (@{$config ->{'/'}->{$key }}) {
3034
- $sf_path = $candidate if -f $candidate ;
3035
- last if defined $sf_path ;
3052
+ # Figure out where our soundfont(s) is (are)
3053
+ my $soundfonts ;
3054
+ if (defined $config ->{'/'}->{'soundfont'}) {
3055
+ my $prio = 0;
3056
+ for my $input (@{$config ->{'/'}->{'soundfont'}}) {
3057
+ if (open(INPUT, '<', $input )) {
3058
+ my $sf = new File::Format::RIFF;
3059
+ $sf ->read(*INPUT);
3060
+ if ($sf ->type eq 'sfbk') {
3061
+ my $chunk = $sf ->at(0); # XXX assume first chunk is INFO
3062
+ if ($chunk ->type eq 'INFO') {
3063
+ my($inam , $iprd );
3064
+ for (my $j = 0; $j < $chunk ->numChunks; $j += 1) {
3065
+ my $subchunk = $chunk ->at($j );
3066
+ $inam = $subchunk ->data if $subchunk ->id eq 'INAM';
3067
+ $iprd = $subchunk ->data if $subchunk ->id eq 'IPRD';
3068
+ }
3069
+ my($type , $mf ); # MIDI velocity that measures ~0 dB in japa when fluidsynth gain = 0.48 (MIDI velocity 64 supposedly = mf)
3070
+ if ($iprd =~ /^Aegean Symphonic Orchestra\b /) { # INAM identifies as Musescore_General
3071
+ ($type , $mf ) = ('Aegean Symphonic Orchestra', 64);
3072
+ } elsif ($inam =~ /^Sonatina Symphonic Orchestra\b /) {
3073
+ ($type , $mf ) = ('Sonatina Symphonic Orchestra', 48);
3074
+ } elsif ($inam =~ /^Timbres Of Heaven\b /) {
3075
+ ($type , $mf ) = ('Timbres Of Heaven', 32);
3076
+ } elsif ($inam =~ /^Musescore_General\b /) {
3077
+ ($type , $mf ) = ('Musescore_General', 64);
3078
+ } elsif ($inam =~ /^Fluid R\d +\b /) {
3079
+ ($type , $mf ) = ('Fluid', 64);
3080
+ } else {
3081
+ $mf = 64;
3082
+ log_warning "$input : Unknown sf2 soundfont, assuming mf=$mf ";
3083
+ }
3084
+ my $descriptor = {'path' => $input , 'priority' => $prio , 'mf' => $mf };
3085
+ $descriptor ->{'type'} = $type if defined $type ;
3086
+ push @$soundfonts , $descriptor ;
3087
+ } else {
3088
+ log_warning "$input : INFO chunk not found at beginning of file";
3089
+ }
3090
+ } else {
3091
+ log_warning "$input : Not an sf2 soundfont";
3092
+ }
3093
+ close INPUT;
3094
+ } else {
3095
+ log_warning "$input : $! ";
3036
3096
}
3097
+ $prio += 1;
3037
3098
}
3038
- if (defined $sf_path && $key eq 'Timbres Of Heaven') {
3039
- $gain = 0.2; # default gain
3040
- }
3041
- last if defined $sf_path ;
3042
3099
}
3043
- $sf_path = $fluidR3 if ! defined $sf_path ; # hmm hope this works
3044
- $gain = 1 if !defined $gain ;
3100
+ die "No usable soundfont found \n " unless defined $soundfonts ;
3101
+ $master_gain = 0.48; # measured ~0 dB with Aegean in japa
3045
3102
3046
3103
log_comment sprintf('Connecting synth %s ', $immediate_p ? (@sinks ? "to $sinks [0]": ''): "for write to $output ") if $debug || $verbose_p ;
3047
3104
set_sink $sinks [0] if $immediate_p && @sinks ;
3048
- my @cmd = ('fluidsynth', '-g', $gain ,
3105
+ my @cmd = ('fluidsynth', '-g', $master_gain ,
3049
3106
'--midi-channels', scalar(keys %need_instrument ),
3050
3107
($immediate_p ?
3051
3108
($use_jack_p ? '-j': ('-a', 'pulseaudio')):
3052
3109
('-a', 'file',
3053
3110
'-o', "audio.file.name=$output ")),
3054
- $sf_path );
3111
+ map { $_ ->{'path'} } @$soundfonts );
3055
3112
3056
3113
log_command @cmd if $debug ;
3057
3114
$synth = new Expect::Simple({
@@ -3061,55 +3118,70 @@ sub connect_synth ($) {
3061
3118
});
3062
3119
3063
3120
# Figure out what instruments to use
3064
- my $instruments = probe_synth_instruments;
3121
+ my $instruments = probe_synth_instruments $soundfonts ;
3122
+ %instrument_name_to_channel_number = (); # need to reset this for each instance of fluidsynth
3123
+ $wood_note = undef; # and this
3065
3124
for my $instrument ('Gong', 'Bell', 'Woodblock', 'drum kit', sort keys %need_instrument ) { # handle hard-coded values 0-3 first
3066
3125
if (defined $need_instrument {$instrument } && !defined $instrument_name_to_channel_number {$instrument }) {
3067
3126
my $midi_instrument_id = scalar keys %instrument_name_to_channel_number ;
3068
3127
my $spec ;
3069
3128
if ($instrument eq 'Gong') {
3070
- $spec = find_synth_instrument($instruments , 'Gong!!!', 'Tubular Bells');
3129
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Gong!!!', 'Tubular Bells');
3071
3130
} elsif ($instrument eq 'Bell') {
3072
- # XXX Chimes from Sonatina seems to sound nicer than Carillon, but Sonatina doesn't have standard percussion so we can't really use it
3073
- $spec = find_synth_instrument($instruments , 'Chimes', 'Carillon', 'Tubular Bells');
3131
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Carillon', 'Tubular Bells (damp)', 'Tubular Bells');
3074
3132
} elsif ($instrument eq 'Church Bells') {
3075
- $spec = find_synth_instrument($instruments , 'Church Bell', 'Church Bells', 'Chimes');
3133
+ # NOTE: instruments in Sonatina only have a range of [48, 84], Chimes also doesn't sound nicer (though it's a single strike)
3134
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Church Bell', 'Church Bells', 'Chimes');
3076
3135
} elsif ($instrument eq 'Woodblock') {
3077
- $spec = find_synth_instrument($instruments , 'Woodblock'); # 000-115
3136
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Woodblock'); # 000-115
3078
3137
} elsif ($instrument eq 'drum kit') {
3079
- $spec = find_synth_instrument($instruments , 'Jazz Drum Kit', 'Jazz', 'Orchestra Kit');
3138
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Jazz Drum Kit', 'Jazz', 'Orchestra Kit');
3080
3139
} elsif ($instrument eq 'Sine Wave') {
3081
- $spec = find_synth_instrument($instruments , 'Sine Wave', '1 kHz Test', '000-008'); # fallback to any celesta
3140
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Sine Wave', '1 kHz Test', '000-008'); # fallback to any celesta
3082
3141
} elsif ($instrument eq 'Music Box') {
3083
- $spec = find_synth_instrument($instruments , 'Music Box', '000-010'); # XXX this doesn't sound right
3142
+ # Glockenspiel from Sonatina sounds the most realistic
3143
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Glockenspiel', 'Music Box', '000-010');
3084
3144
} elsif ($instrument eq 'Organ') {
3085
- $spec = find_synth_instrument($instruments , '000-019'); # church organ
3145
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-019'); # church organ
3086
3146
} elsif ($instrument eq 'Piano') {
3087
- $spec = find_synth_instrument($instruments , '000-000'); # piano
3147
+ # NOTE: These are all 000-000 but they sound very different so there's a preferred order
3148
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Grand Piano C5', 'Concert Grand', 'Grand Piano', '000-000');
3149
+ } elsif ($instrument eq 'Glockenspiel') {
3150
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-009'); # glockenspiel
3088
3151
} elsif ($instrument eq 'Flute') {
3089
- $spec = find_synth_instrument($instruments , '000-073'); # flute
3152
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-073'); # flute
3090
3153
} elsif ($instrument eq 'Recorder') {
3091
- $spec = find_synth_instrument($instruments , '000-074'); # recorder
3154
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-074'); # recorder
3092
3155
} elsif ($instrument eq 'Violin') {
3093
- $spec = find_synth_instrument($instruments , '000-040'); # violin
3156
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-040'); # violin
3094
3157
} elsif ($instrument eq 'Viola') {
3095
- $spec = find_synth_instrument($instruments , '000-041'); # viola
3158
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-041'); # viola
3096
3159
} elsif ($instrument eq 'Cello') {
3097
- $spec = find_synth_instrument($instruments , '000-042'); # cello
3160
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-042'); # cello
3098
3161
} elsif ($instrument eq 'Guitar') {
3099
- $spec = find_synth_instrument($instruments , '000-026'); # jazz guitar
3162
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-026'); # jazz guitar
3100
3163
} elsif ($instrument eq 'Bass Guitar') {
3101
- $spec = find_synth_instrument($instruments , '000-032'); # bass guitar
3164
+ $spec = find_synth_instrument($instruments , $soundfonts , '000-032'); # bass guitar
3165
+ } elsif ($instrument eq 'Harp') {
3166
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Concert Harp', 'Harp (sustain)', 'Clavinova Harp', 'Harp');
3167
+ } elsif ($instrument eq 'Harpsichord') {
3168
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Harpsichord', "Don's Harpsichord");
3102
3169
} elsif ($instrument eq 'Guzheng') {
3103
- $spec = find_synth_instrument($instruments , 'Guzheng Harp'); # this is unrealistic; this is in reality a really quiet instrument
3170
+ $spec = find_synth_instrument($instruments , $soundfonts , 'Guzheng Harp'); # this is unrealistic; this is in reality a really quiet instrument
3104
3171
} else {
3105
3172
die "$instrument : Unhandled instrument\n ";
3106
3173
}
3107
- my($bank , $inst ) = @$spec ;
3174
+ die "$instrument : Instrument not found\n " unless defined $spec ;
3175
+ # XXX You can't compensate for gain. First, there is no formula. Second, even if you can find one,
3176
+ # XXX a loud note with low gain sounds different from a soft note with high gain,
3177
+ # XXX probably because loud and soft notes use different samples
3178
+ my($font , $bank , $inst , $zero ) = @$spec ;
3108
3179
if ($instrument eq 'Woodblock') {
3109
3180
$wood_note = (synth_instrument_name($instruments , $spec ) =~ / Kit\b / || $bank == 128)? 31: $note_to_midi_number {'d#5'};
3110
3181
}
3111
- send_midi_command "select $midi_instrument_id 1 $bank $inst ";
3182
+ send_midi_command "select $midi_instrument_id $font $bank $inst ";
3112
3183
$instrument_name_to_channel_number {$instrument } = $midi_instrument_id ;
3184
+ $instrument_name_to_mf_velocity {$instrument } = $zero ;
3113
3185
log_debug "instrument $instrument mapped to $midi_instrument_id " if $debug ;
3114
3186
}
3115
3187
}
@@ -3345,28 +3417,30 @@ sub strike_tune_real ($@) {
3345
3417
my $semitones = 8;
3346
3418
my @midi_commands ;
3347
3419
my $percussion_channel = $instrument_name_to_channel_number {'drum kit'};
3420
+ my $percussion_mf = $instrument_name_to_mf_velocity {'drum kit'};
3348
3421
for my $voice (@tune ) {
3349
3422
my $elapsed = 0;
3350
3423
my $t0 = current_time;
3351
3424
for my $spec (@$voice ) {
3352
3425
my($note , $time , $velocity , $instrument , $flags ) = @$spec ;
3353
3426
$velocity = 127 unless defined $velocity ; # NOTE backward compatibility
3354
3427
$instrument = canonical_instrument $instrument ;
3428
+ my $velocity_adjustment = defined $instrument_name_to_mf_velocity {$instrument }? $instrument_name_to_mf_velocity {$instrument }/64: 1;
3355
3429
my $t_i = current_time;
3356
3430
my $actual_time = $flags ->{'actual-duration'};
3357
3431
my $note_length = $time * $beat_length ;
3358
3432
my $effective_note_length = defined $actual_time ? $actual_time * $beat_length : $note_length ;
3359
3433
if (defined $flags && defined $flags ->{'directive'}) {
3360
- push @midi_commands , [$elapsed , $flags ->{'directive'}, $velocity ];
3434
+ push @midi_commands , [$elapsed , $flags ->{'directive'}, $velocity * $velocity_adjustment ];
3361
3435
} elsif (defined $percussion_note_to_midi_number {$note }) {
3362
- push @midi_commands , [$elapsed , sprintf 'noteon %d %d %d ', $percussion_channel , $percussion_note_to_midi_number {$note }, $velocity ];
3436
+ push @midi_commands , [$elapsed , sprintf 'noteon %d %d %d ', $percussion_channel , $percussion_note_to_midi_number {$note }, $velocity * $velocity_adjustment ];
3363
3437
} elsif (exists $percussion_note_to_midi_number {$note }) { # valid percussion note but no MIDI equivalent
3364
3438
log_debug "percussion note $note has no MIDI equivalent"; # XXX
3365
3439
} elsif (defined $note_to_midi_number {$note }) {
3366
3440
my $channel = (defined $instrument_name_to_channel_number {$instrument })? $instrument_name_to_channel_number {$instrument }: 1;
3367
3441
my $note_number = $note_to_midi_number {$note };
3368
3442
my $laisser_vibrer_p = 1 if (defined $flags && defined $flags ->{'laisser-vibrer'}) || (defined $laisser_vibrer_p {$instrument } && !defined $actual_time );
3369
- push @midi_commands , [$elapsed , sprintf 'noteon %d %d %d ', $channel , $note_number , $velocity ];
3443
+ push @midi_commands , [$elapsed , sprintf 'noteon %d %d %d ', $channel , $note_number , $velocity * $velocity_adjustment ];
3370
3444
push @midi_commands , [$elapsed + $effective_note_length , sprintf 'noteoff %d %d ', $channel , $note_number ] unless $laisser_vibrer_p ;
3371
3445
} elsif ($note ne 'r') {
3372
3446
log_debug "strike_tune: $note : \" else\" case reached";
0 commit comments