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;
-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;
+die "Usage: $0 filename.conf midifile1 transpose1 midifile2 transpose2 ..."
+ unless @ARGV > 1 and @ARGV % 2;
+
+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 ($config, @midilist) = @ARGV;
sub unsort(@)
{
}
elsif(/^transpose (\d+)/)
{
+ $currentbot->{transpose} ||= 0;
$currentbot->{transpose} += $1;
}
elsif(/^channels (.*)/)
{
$currentbot->{channels} = { map { $_ => 1 } split /\s+/, $1 };
}
+ elsif(/^programs (.*)/)
+ {
+ $currentbot->{programs} = { map { $_ => 1 } split /\s+/, $1 };
+ }
elsif(/^init$/)
{
$super = $currentbot->{init};
$super = $currentbot->{percussion}->{$1};
$currentbot->{percussion}->{$1} = $appendref = [];
}
+ elsif(/^vocals$/)
+ {
+ $super = $currentbot->{vocals};
+ $currentbot->{vocals} = $appendref = [];
+ }
else
{
print "unknown command: $_\n";
}
elsif(/^bot (.*)/)
{
- $currentbot = ($bots{$1} ||= {count => 0, transpose => 0});
+ $currentbot = ($bots{$1} ||= {count => 0});
}
elsif(/^raw (.*)/)
{
$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;
+ }
else
{
print "unknown command: $_\n";
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;
}
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')
{
{
$commands .= sprintf "sv_cmd bot_cmd %d %s\n", $bot->{id}, join " ", @{$_}[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')
{
{
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} - $transpose};
+ 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 <= 0)
+ {
+ # vocals
+ $cmds = $bot->{vocals};
+ if(defined $cmds)
+ {
+ $cmds = [ map { [ map { $_ eq '%s' ? $note : $_ } @$_ ] } @$cmds ];
+ }
+ $k0 = "vocals";
+ $k1 = $channel;
+ }
+ elsif($channel == 10)
{
+ # percussion
$cmds = $bot->{percussion}->{$note};
- $cmds_off = undef;
+ $k0 = "percussion";
+ $k1 = $note;
}
else
{
- $cmds = $bot->{notes_on}->{$note - $bot->{transpose} - $transpose};
- $cmds_off = $bot->{notes_off}->{$note - $bot->{transpose} - $transpose};
+ # 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
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}}
{
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)
{
- $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;
}
{
my ($time, $channel, $note) = @_;
- #print STDERR "note off $time:$channel:$note\n";
+# print STDERR "note off $time:$channel:$note\n";
+ return 0
+ if $channel <= 0;
return 0
if $channel == 10;
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})
{
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
{
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, ['note_on', $tick * $scale + $shift, $sequence++, -1, -1, $file];
+ push @allmidievents, ['note_off', $tick * $scale + $shift, $sequence++, -1, -1, $file];
+ }
+ }
+ }
+
@allmidievents = sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } @allmidievents;
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 $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 '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 or $channel < 0;
+ 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)
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 { $a <=> $b } keys %{$notehash{$cn}{$type}})
+ {
+ my $cnt = $notehash{$cn}{$type}{$note};
+ print STDERR " $type $note ($cnt times)\n";
+ }
+ }
}
for(@busybots_allocated)
{
my @preallocate_new = map { $_->{classname} } @busybots_allocated;
if(@preallocate_new == @preallocate)
{
+ print "sv_cmd bot_cmd reset\n";
+ print "sv_cmd bot_cmd setbots @{[scalar @preallocate_new]}\n";
print "$precommands$commands";
exit 0;
}