X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=misc%2Ftools%2Fmidi2cfg-ng.pl;h=4fcf9760a22134d54dbbfbab75b48151bc961fc7;hb=611be7dbdb01ee7206e1e52dba0c54596c76a92f;hp=cf7dfb2fa60a333fb1ee0800c3996d59e44ffff2;hpb=6bf10fe484cb958c26361e85d485622a330f7a8e;p=xonotic%2Fxonotic.git diff --git a/misc/tools/midi2cfg-ng.pl b/misc/tools/midi2cfg-ng.pl index cf7dfb2f..4fcf9760 100755 --- a/misc/tools/midi2cfg-ng.pl +++ b/misc/tools/midi2cfg-ng.pl @@ -8,12 +8,28 @@ use MIDI; use MIDI::Opus; use Storable; +# workaround for possible refire time problems +use constant SYS_TICRATE => 0.033333; +#use constant SYS_TICRATE => 0; + use constant MIDI_FIRST_NONCHANNEL => 17; use constant MIDI_DRUMS_CHANNEL => 10; +use constant TEXT_EVENT_CHANNEL => -1; + +die "Usage: $0 filename.conf midifile1 transpose1 midifile2 transpose2 ..." + unless @ARGV > 1 and @ARGV % 2; -die "Usage: $0 filename.conf timeoffset_preinit timeoffset_postinit timeoffset_predone timeoffset_postdone timeoffset_preintermission timeoffset_postintermission midifile1 transpose1 midifile2 transpose2 ..." - unless @ARGV > 7 and @ARGV % 2; -my ($config, $timeoffset_preinit, $timeoffset_postinit, $timeoffset_predone, $timeoffset_postdone, $timeoffset_preintermission, $timeoffset_postintermission, @midilist) = @ARGV; +my $timeoffset_preinit = 2; +my $timeoffset_postinit = 2; +my $timeoffset_predone = 2; +my $timeoffset_postdone = 2; +my $timeoffset_preintermission = 2; +my $timeoffset_postintermission = 2; +my $time_forgetfulness = 1.5; +my %lists = (); +my %listindexes = (); + +my ($config, @midilist) = @ARGV; sub unsort(@) { @@ -75,7 +91,7 @@ sub botconfig_read($) while(<$fh>) { chomp; - s/\s*#.*//; + s/\s*\/\/.*//; next if /^$/; if(s/^\t\t//) { @@ -114,6 +130,10 @@ sub botconfig_read($) { $currentbot->{channels} = { map { $_ => 1 } split /\s+/, $1 }; } + elsif(/^programs (.*)/) + { + $currentbot->{programs} = { map { $_ => 1 } split /\s+/, $1 }; + } elsif(/^init$/) { $super = $currentbot->{init}; @@ -144,9 +164,14 @@ sub botconfig_read($) $super = $currentbot->{percussion}->{$1}; $currentbot->{percussion}->{$1} = $appendref = []; } + elsif(/^text (.*)$/) + { + $super = $currentbot->{text}->{$1}; + $currentbot->{text}->{$1} = $appendref = []; + } else { - print "unknown command: $_\n"; + print STDERR "unknown command: $_\n"; } } elsif(/^bot (.*)/) @@ -157,9 +182,42 @@ sub botconfig_read($) { $precommands .= "$1\n"; } + elsif(/^timeoffset_preinit (.*)/) + { + $timeoffset_preinit = $1; + } + elsif(/^timeoffset_postinit (.*)/) + { + $timeoffset_postinit = $1; + } + elsif(/^timeoffset_predone (.*)/) + { + $timeoffset_predone = $1; + } + elsif(/^timeoffset_postdone (.*)/) + { + $timeoffset_postdone = $1; + } + elsif(/^timeoffset_preintermission (.*)/) + { + $timeoffset_preintermission = $1; + } + elsif(/^timeoffset_postintermission (.*)/) + { + $timeoffset_postintermission = $1; + } + elsif(/^time_forgetfulness (.*)/) + { + $time_forgetfulness = $1; + } + elsif(/^list (.*?) (.*)/) + { + $lists{$1} = [split / /, $2]; + $listindexes{$1} = 0; + } else { - print "unknown command: $_\n"; + print STDERR "unknown command: $_\n"; } } @@ -177,24 +235,90 @@ sub botconfig_read($) my $busybots_orig = botconfig_read $config; -sub busybot_cmd_bot_test($$@) +# returns: ($mintime, $maxtime, $busytime) +sub busybot_cmd_bot_cmdinfo(@) { - my ($bot, $time, @commands) = @_; + my (@commands) = @_; + + my $mintime = undef; + my $maxtime = undef; + my $busytime = undef; + + for(@commands) + { + if($_->[0] eq 'time') + { + $mintime = $_->[1] + if not defined $mintime or $_->[1] < $mintime; + $maxtime = $_->[1] + SYS_TICRATE + if not defined $maxtime or $_->[1] + SYS_TICRATE > $maxtime; + } + elsif($_->[0] eq 'busy') + { + $busytime = $_->[1] + SYS_TICRATE; + } + } + + return ($mintime, $maxtime, $busytime); +} + +sub busybot_cmd_bot_matchtime($$$@) +{ + my ($bot, $targettime, $targetbusytime, @commands) = @_; + + # I want to execute @commands so that I am free on $targettime and $targetbusytime + # when do I execute it then? + + my ($mintime, $maxtime, $busytime) = busybot_cmd_bot_cmdinfo @commands; + + my $tstart_max = defined $maxtime ? $targettime - $maxtime : $targettime; + my $tstart_busy = defined $busytime ? $targetbusytime - $busytime : $targettime; + + return $tstart_max < $tstart_busy ? $tstart_max : $tstart_busy; +} + +# TODO function to find out whether, and when, to insert a command before another command to make it possible +# (note-off before note-on) + +sub busybot_cmd_bot_test($$$@) +{ + my ($bot, $time, $force, @commands) = @_; my $bottime = defined $bot->{timer} ? $bot->{timer} : -1; my $botbusytime = defined $bot->{busytimer} ? $bot->{busytimer} : -1; - return 0 - if $time < $botbusytime; - - my $mintime = (@commands && ($commands[0]->[0] eq 'time')) ? $commands[0]->[1] : 0; + my ($mintime, $maxtime, $busytime) = busybot_cmd_bot_cmdinfo @commands; - return 0 - if $time + $mintime < $bottime; + if($time < $botbusytime) + { + warn "FORCE: $time < $botbusytime" + if $force; + return $force; + } + + if(defined $mintime and $time + $mintime < $bottime) + { + warn "FORCE: $time + $mintime < $bottime" + if $force; + return $force; + } return 1; } +sub buildstring(@) +{ + return + join " ", + map + { + $_ =~ /^\@(.*)$/ + ? do { $lists{$1}[$listindexes{$1}++ % @{$lists{$1}}]; } + : $_ + } + @_; +} + sub busybot_cmd_bot_execute($$@) { my ($bot, $time, @commands) = @_; @@ -204,11 +328,15 @@ sub busybot_cmd_bot_execute($$@) if($_->[0] eq 'time') { $commands .= sprintf "sv_cmd bot_cmd %d wait_until %f\n", $bot->{id}, $time + $_->[1]; - $bot->{timer} = $time + $_->[1]; + if($bot->{timer} > $time + $_->[1] + SYS_TICRATE) + { + #use Carp; carp "Negative wait: $bot->{timer} <= @{[$time + $_->[1] + SYS_TICRATE]}"; + } + $bot->{timer} = $time + $_->[1] + SYS_TICRATE; } elsif($_->[0] eq 'busy') { - $bot->{busytimer} = $time + $_->[1]; + $bot->{busytimer} = $time + $_->[1] + SYS_TICRATE; } elsif($_->[0] eq 'buttons') { @@ -233,16 +361,25 @@ sub busybot_cmd_bot_execute($$@) } elsif($_->[0] eq 'cmd') { - $commands .= sprintf "sv_cmd bot_cmd %d %s\n", $bot->{id}, join " ", @{$_}[1..@$_-1]; + $commands .= sprintf "sv_cmd bot_cmd %d %s\n", $bot->{id}, buildstring @{$_}[1..@$_-1]; + } + elsif($_->[0] eq 'aim_random') + { + $commands .= sprintf "sv_cmd bot_cmd %d aim \"%f 0 %f\"\n", $bot->{id}, $_->[1] + rand($_->[2] - $_->[1]), $_->[3]; } elsif($_->[0] eq 'barrier') { $commands .= sprintf "sv_cmd bot_cmd %d barrier\n", $bot->{id}; $bot->{timer} = $bot->{busytimer} = 0; + undef $bot->{lastuse}; } elsif($_->[0] eq 'raw') { - $commands .= sprintf "%s\n", join " ", @{$_}[1..@$_-1]; + $commands .= sprintf "%s\n", buildstring @{$_}[1..@$_-1]; + } + else + { + warn "Invalid command: @$_"; } } @@ -270,34 +407,74 @@ sub busybot_note_off_bot($$$$) my ($bot, $time, $channel, $note) = @_; #print STDERR "note off $bot:$time:$channel:$note\n"; return 1 - if $channel == 10; - my $cmds = $bot->{notes_off}->{$note - ($bot->{transpose} || 0) - $transpose}; + if not $bot->{busy}; + my ($busychannel, $busynote, $cmds) = @{$bot->{busy}}; return 1 if not defined $cmds; # note off cannot fail - $bot->{busy} = 0; - #--$busy; - #print STDERR "BUSY: $busy bots (OFF)\n"; - busybot_cmd_bot_execute $bot, $time + $notetime, @$cmds; + die "Wrong note-off?!?" + if $busychannel != $channel || $busynote ne $note; + $bot->{busy} = undef; + + my $t = $time + $notetime; + my ($mintime, $maxtime, $busytime) = busybot_cmd_bot_cmdinfo @$cmds; + + # perform note-off "as soon as we can" + $t = $bot->{busytimer} + if $t < $bot->{busytimer}; + $t = $bot->{timer} - $mintime + if $t < $bot->{timer} - $mintime; + + busybot_cmd_bot_execute $bot, $t, @$cmds; return 1; } -sub busybot_note_on_bot($$$$$) +sub busybot_get_cmds_bot($$$) { - my ($bot, $time, $channel, $note, $init) = @_; - return -1 # I won't play on this channel - if defined $bot->{channels} and not $bot->{channels}->{$channel}; - my $cmds; - my $cmds_off; - if($channel == 10) + my ($bot, $channel, $note) = @_; + my ($k0, $k1, $cmds, $cmds_off) = (undef, undef, undef, undef); + if($channel == TEXT_EVENT_CHANNEL) { + # vocals + $note =~ /^([^:]*):(.*)$/; + my $name = $1; + my $data = $2; + $cmds = $bot->{text}->{$name}; + if(defined $cmds) + { + $cmds = [ map { [ map { $_ eq '%s' ? $data : $_ } @$_ ] } @$cmds ]; + } + $k0 = "text"; + $k1 = $name; + } + elsif($channel == 10) + { + # percussion $cmds = $bot->{percussion}->{$note}; - $cmds_off = undef; + $k0 = "percussion"; + $k1 = $note; } else { + # music $cmds = $bot->{notes_on}->{$note - ($bot->{transpose} || 0) - $transpose}; $cmds_off = $bot->{notes_off}->{$note - ($bot->{transpose} || 0) - $transpose}; + $k0 = "note"; + $k1 = $note - ($bot->{transpose} || 0) - $transpose; } + return ($cmds, $cmds_off, $k0, $k1); +} + +sub busybot_note_on_bot($$$$$$$) +{ + my ($bot, $time, $channel, $program, $note, $init, $force) = @_; + + return -1 # I won't play on this channel + if defined $bot->{channels} and not $bot->{channels}->{$channel}; + return -1 # I won't play this program + if defined $bot->{programs} and not $bot->{programs}->{$program}; + + my ($cmds, $cmds_off, $k0, $k1) = busybot_get_cmds_bot($bot, $channel, $note); + return -1 # I won't play this note if not defined $cmds; return 0 @@ -306,7 +483,7 @@ sub busybot_note_on_bot($$$$$) if($init) { return 0 - if not busybot_cmd_bot_test $bot, $time + $notetime, @$cmds; + if not busybot_cmd_bot_test $bot, $time + $notetime, $force, @$cmds; busybot_cmd_bot_execute $bot, 0, ['cmd', 'wait', $timeoffset_preinit]; busybot_cmd_bot_execute $bot, 0, ['barrier']; busybot_cmd_bot_execute $bot, 0, @{$bot->{init}} @@ -316,20 +493,35 @@ sub busybot_note_on_bot($$$$$) { busybot_intermission_bot $bot; } + # we always did a barrier, so we know this works busybot_cmd_bot_execute $bot, $time + $notetime, @$cmds; } else { return 0 - if not busybot_cmd_bot_test $bot, $time + $notetime, @$cmds; + if not busybot_cmd_bot_test $bot, $time + $notetime, $force, @$cmds; busybot_cmd_bot_execute $bot, $time + $notetime, @$cmds; } - if(defined $cmds and defined $cmds_off) + if(defined $cmds_off) { - $bot->{busy} = 1; - #++$busy; - #print STDERR "BUSY: $busy bots (ON)\n"; + $bot->{busy} = [$channel, $note, $cmds_off]; } + ++$bot->{seen}{$k0}{$k1}; + + if(($bot->{lastuse} // -666) >= $time - $time_forgetfulness && $channel == $bot->{lastchannel}) + { + $bot->{lastchannelsequence} += 1; + } + else + { + $bot->{lastchannelsequence} = 1; + } + $bot->{lastuse} = $time; + $bot->{lastchannel} = $channel; + +# print STDERR "$time $bot->{id} $channel:$note\n" +# if $channel == 11; + return 1; } @@ -346,10 +538,7 @@ sub busybot_note_off($$$) { my ($time, $channel, $note) = @_; - #print STDERR "note off $time:$channel:$note\n"; - - return 0 - if $channel == 10; +# print STDERR "note off $time:$channel:$note\n"; if(my $bot = $notechannelbots{$channel}{$note}) { @@ -361,55 +550,170 @@ sub busybot_note_off($$$) return 0; } -sub busybot_note_on($$$) +sub botsort($$$$@) { - my ($time, $channel, $note) = @_; + my ($time, $channel, $program, $note, @bots) = @_; + return + map + { + $_->[0] + } + sort + { + $b->[1] <=> $a->[1] + or + ($a->[0]->{lastuse} // -666) <=> ($b->[0]->{lastuse} // -666) + or + $a->[2] <=> $b->[2] + } + map + { + my $q = 0; + if($channel != 10) # percussion just should do round robin + { + if(($_->{lastuse} // -666) >= $time - $time_forgetfulness) + { + if($channel == $_->{lastchannel}) + { + $q += $_->{lastchannelsequence}; + } + else + { + # better leave this one alone + $q -= $_->{lastchannelsequence}; + } + } + } + [$_, $q, rand] + } + @bots; +} + +sub busybot_note_on($$$$) +{ + my ($time, $channel, $program, $note) = @_; if($notechannelbots{$channel}{$note}) { + print STDERR "THIS SHOULD NEVER HAPPEN\n"; busybot_note_off $time, $channel, $note; } - #print STDERR "note on $time:$channel:$note\n"; +# print STDERR "note on $time:$channel:$note\n"; my $overflow = 0; - for(unsort @busybots_allocated) + my @epicfailbots = (); + + for(botsort $time, $channel, $program, $note, @busybots_allocated) { - my $canplay = busybot_note_on_bot $_, $time, $channel, $note, 0; + my $canplay = busybot_note_on_bot $_, $time, $channel, $program, $note, 0, 0; if($canplay > 0) { $notechannelbots{$channel}{$note} = $_; return 1; } - $overflow = 1 + push @epicfailbots, $_ if $canplay == 0; # wrong } + my $needalloc = 0; + for(unsort keys %$busybots) { next if $busybots->{$_}->{count} <= 0; my $bot = Storable::dclone $busybots->{$_}; $bot->{id} = @busybots_allocated + 1; $bot->{classname} = $_; - my $canplay = busybot_note_on_bot $bot, $time, $channel, $note, 1; + my $canplay = busybot_note_on_bot $bot, $time, $channel, $program, $note, 1, 0; if($canplay > 0) { - die "noalloc\n" - if $noalloc; - --$busybots->{$_}->{count}; - $notechannelbots{$channel}{$note} = $bot; - push @busybots_allocated, $bot; - return 1; + if($noalloc) + { + $needalloc = 1; + } + else + { + --$busybots->{$_}->{count}; + $notechannelbots{$channel}{$note} = $bot; + push @busybots_allocated, $bot; + return 1; + } } die "Fresh bot cannot play stuff" if $canplay == 0; } - if($overflow) + if(@epicfailbots) + { + # we cannot add a new bot to play this + # we could try finding a bot that could play this, and force him to stop the note! + + my @candidates = (); # contains: [$bot, $score, $offtime] + + # put in all currently busy bots that COULD play this, if they did a note-off first + for my $bot(@epicfailbots) + { + next + if $busybots->{$bot->{classname}}->{count} != 0; + next + unless $bot->{busy}; + my ($busy_chan, $busy_note, $busy_cmds_off) = @{$bot->{busy}}; + next + unless $busy_cmds_off; + my ($cmds, $cmds_off, $k0, $k1) = busybot_get_cmds_bot $bot, $channel, $note; + next + unless $cmds; + my ($mintime, $maxtime, $busytime) = busybot_cmd_bot_cmdinfo @$cmds; + my ($mintime_off, $maxtime_off, $busytime_off) = busybot_cmd_bot_cmdinfo @$busy_cmds_off; + + my $noteofftime = busybot_cmd_bot_matchtime $bot, $time + $notetime + $mintime, $time + $notetime, @$busy_cmds_off; + next + if $noteofftime < $bot->{busytimer}; + next + if $noteofftime + $mintime_off < $bot->{timer}; + + my $score = 0; + # prefer turning off long notes + $score += 100 * ($noteofftime - $bot->{timer}); + # prefer turning off low notes + $score += 1 * (-$note); + # prefer turning off notes that already play on another channel + $score += 1000 * (grep { $_ != $busy_chan && $notechannelbots{$_}{$busy_note} && $notechannelbots{$_}{$busy_note}{busy} } keys %notechannelbots); + + push @candidates, [$bot, $score, $noteofftime]; + } + + # we found one? + + if(@candidates) + { + @candidates = sort { $a->[1] <=> $b->[1] } @candidates; + my ($bot, $score, $offtime) = @{(pop @candidates)}; + my $oldchan = $bot->{busy}->[0]; + my $oldnote = $bot->{busy}->[1]; + busybot_note_off $offtime - $notetime, $oldchan, $oldnote; + my $canplay = busybot_note_on_bot $bot, $time, $channel, $program, $note, 0, 1; + die "Canplay but not?" + if $canplay <= 0; + warn "Made $channel:$note play by stopping $oldchan:$oldnote"; + $notechannelbots{$channel}{$note} = $bot; + return 1; + } + } + + die "noalloc\n" + if $needalloc; + + if(@epicfailbots) { warn "Not enough bots to play this (when playing $channel:$note)"; +# for(@epicfailbots) +# { +# my $b = $_->{busy}; +# warn "$_->{classname} -> @{[$b ? qq{$b->[0]:$b->[1]} : 'none']} @{[$_->{timer} - $notetime]} ($time)\n"; +# } } else { @@ -495,54 +799,247 @@ sub ConvertMIDI($$) my ($command, $delta, @data) = @$_; $command = 'note_off' if $command eq 'note_on' and $data[2] == 0; $tick += $delta; + next + if $command eq 'text_event' && $data[0] !~ /:/; push @allmidievents, [$command, $tick, $sequence++, $track, @data]; } } + + if(open my $fh, "$filename.vocals") + { + my $scale = 1; + my $shift = 0; + for(<$fh>) + { + chomp; + my ($tick, $file) = split /\s+/, $_; + if($tick eq 'scale') + { + $scale = $file; + } + elsif($tick eq 'shift') + { + $shift = $file; + } + else + { + push @allmidievents, ['text_event', $tick * $scale + $shift, $sequence++, -1, "vocals:$file"]; + } + } + } + + # HACK for broken rosegarden export: put patch changes first by clearing their sequence number + for(@allmidievents) + { + if($_->[0] eq 'patch_change') + { + $_->[2] = -1; + } + } + + # sort events @allmidievents = sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } @allmidievents; + # find the first interesting event + my $shift = [grep { $_->[0] eq 'note_on' || $_->[0] eq 'text_event' } @allmidievents]->[0][1]; + die "No notes!" + unless defined $shift; + + # shift times by first event, no boring waiting + $_->[0] = ($_->[0] < $shift ? 0 : $_->[0] - $shift) for @tempi; + $_->[1] = ($_->[1] < $shift ? 0 : $_->[1] - $shift) for @allmidievents; + + # fix event list + my %midinotes = (); - my $note_min = undef; - my $note_max = undef; my $notes_stuck = 0; + my %notes_seen = (); + my %programs = (); my $t = 0; + my %sustain = (); + + my $note_on = sub + { + my ($ev) = @_; + my $chan = $ev->[4] + 1; + ++$notes_seen{$chan}{($programs{$chan} || 1)}{$ev->[5]}; + if($midinotes{$chan}{$ev->[5]}) + { + --$notes_stuck; + busybot_note_off($t - SYS_TICRATE - 0.001, $chan, $ev->[5]); + } + busybot_note_on($t, $chan, $programs{$chan} || 1, $ev->[5]); + ++$notes_stuck; + $midinotes{$chan}{$ev->[5]} = 1; + }; + + my $note_off = sub + { + my ($ev) = @_; + my $chan = $ev->[4] + 1; + if(exists $sustain{$chan}) + { + push @{$sustain{$chan}}, $ev; + return; + } + if($midinotes{$chan}{$ev->[5]}) + { + --$notes_stuck; + busybot_note_off($t - SYS_TICRATE - 0.001, $chan, $ev->[5]); + } + $midinotes{$chan}{$ev->[5]} = 0; + }; + + my $text_event = sub + { + my ($ev) = @_; + + my $chan = TEXT_EVENT_CHANNEL; + + busybot_note_on($t, TEXT_EVENT_CHANNEL, -1, $ev->[4]); + busybot_note_off($t, TEXT_EVENT_CHANNEL, $ev->[4]); + }; + + my $patch_change = sub + { + my ($ev) = @_; + my $chan = $ev->[4] + 1; + my $program = $ev->[5] + 1; + $programs{$chan} = $program; + }; + + my $sustain_change = sub + { + my ($ev) = @_; + my $chan = $ev->[4] + 1; + if($ev->[6] == 0) + { + # release all currently not pressed notes + my $s = $sustain{$chan}; + delete $sustain{$chan}; + for(@{($s || [])}) + { + $note_off->($_); + } + } + else + { + # no more note-off + $sustain{$chan} = []; + } + }; + for(@allmidievents) { $t = $tick2sec->($_->[1]); - my $track = $_->[3]; + # my $track = $_->[3]; if($_->[0] eq 'note_on') { - my $chan = $_->[4] + 1; - $note_min = $_->[5] - if not defined $note_min or $_->[5] < $note_min and $chan != 10; - $note_max = $_->[5] - if not defined $note_max or $_->[5] > $note_max and $chan != 10; - if($midinotes{$chan}{$_->[5]}) - { - --$notes_stuck; - busybot_note_off($t, $chan, $_->[5]); - } - busybot_note_on($t, $chan, $_->[5]); - ++$notes_stuck; - $midinotes{$chan}{$_->[5]} = 1; + $note_on->($_); } elsif($_->[0] eq 'note_off') { - my $chan = $_->[4] + 1; - if($midinotes{$chan}{$_->[5]}) - { - --$notes_stuck; - busybot_note_off($t, $chan, $_->[5]); - } - $midinotes{$chan}{$_->[5]} = 0; + $note_off->($_); } + elsif($_->[0] eq 'text_event') + { + $text_event->($_); + } + elsif($_->[0] eq 'patch_change') + { + $patch_change->($_); + } + elsif($_->[0] eq 'control_change' && $_->[5] == 64) # sustain pedal + { + $sustain_change->($_); + } + } + + # fake events for releasing pedal + for(keys %sustain) + { + $sustain_change->(['control_change', $t, undef, undef, $_ - 1, 64, 0]); } print STDERR "For file $filename:\n"; - print STDERR " Range of notes: $note_min .. $note_max\n"; - print STDERR " Safe transpose range: @{[$note_max - 19]} .. @{[$note_min + 13]}\n"; - print STDERR " Unsafe transpose range: @{[$note_max - 27]} .. @{[$note_min + 18]}\n"; print STDERR " Stuck notes: $notes_stuck\n"; + for my $testtranspose(-127..127) + { + my $toohigh = 0; + my $toolow = 0; + my $good = 0; + for my $channel(sort keys %notes_seen) + { + next if $channel == 10; + for my $program(sort keys %{$notes_seen{$channel}}) + { + for my $note(sort keys %{$notes_seen{$channel}{$program}}) + { + my $cnt = $notes_seen{$channel}{$program}{$note}; + my $votehigh = 0; + my $votelow = 0; + my $votegood = 0; + for(@busybots_allocated, grep { $_->{count} > 0 } values %$busybots) + { + next # I won't play on this channel + if defined $_->{channels} and not $_->{channels}->{$channel}; + next # I won't play this program + if defined $_->{programs} and not $_->{programs}->{$program}; + my $transposed = $note - ($_->{transpose} || 0) - $testtranspose; + if(exists $_->{notes_on}{$transposed}) + { + ++$votegood; + } + else + { + ++$votehigh if $transposed >= 0; + ++$votelow if $transposed < 0; + } + } + if($votegood) + { + $good += $cnt; + } + elsif($votelow >= $votehigh) + { + $toolow += $cnt; + } + else + { + $toohigh += $cnt; + } + } + } + } + next if !$toohigh != !$toolow; + print STDERR " Transpose $testtranspose: $toohigh too high, $toolow too low, $good good\n"; + } + + for my $program(sort keys %{$notes_seen{10}}) + { + for my $note(sort keys %{$notes_seen{10}{$program}}) + { + my $cnt = $notes_seen{10}{$program}{$note}; + my $votegood = 0; + for(@busybots_allocated) + { + next # I won't play on this channel + if defined $_->{channels} and not $_->{channels}->{10}; + next # I won't play this program + if defined $_->{programs} and not $_->{programs}->{$program}; + if(exists $_->{percussion}{$note}) + { + ++$votegood; + } + } + if(!$votegood) + { + print STDERR "Failed percussion $note ($cnt times)\n"; + } + } + } + while(my ($k1, $v1) = each %midinotes) { while(my ($k2, $v2) = each %$v1) @@ -561,9 +1058,31 @@ sub ConvertMIDI($$) sub Deallocate() { print STDERR "Bots allocated:\n"; + my %notehash; + my %counthash; for(@busybots_allocated) { print STDERR "$_->{id} is a $_->{classname}\n"; + ++$counthash{$_->{classname}}; + while(my ($type, $notehash) = each %{$_->{seen}}) + { + while(my ($k, $v) = each %$notehash) + { + $notehash{$_->{classname}}{$type}{$k} += $v; + } + } + } + for my $cn(sort keys %counthash) + { + print STDERR "$counthash{$cn} bots of $cn have played:\n"; + for my $type(sort keys %{$notehash{$cn}}) + { + for my $note(sort keys %{$notehash{$cn}{$type}}) + { + my $cnt = $notehash{$cn}{$type}{$note}; + print STDERR " $type $note ($cnt times)\n"; + } + } } for(@busybots_allocated) { @@ -582,6 +1101,7 @@ my @preallocate = (); $noalloc = 0; for(;;) { + %listindexes = (); $commands = ""; eval { @@ -597,6 +1117,7 @@ for(;;) my @preallocate_new = map { $_->{classname} } @busybots_allocated; if(@preallocate_new == @preallocate) { + print "sv_cmd bot_cmd setbots @{[scalar @preallocate_new]}\n"; print "$precommands$commands"; exit 0; }