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;
my $timeoffset_preintermission = 2;
my $timeoffset_postintermission = 2;
my $time_forgetfulness = 1.5;
+my %lists = ();
+my %listindexes = ();
my ($config, @midilist) = @ARGV;
while(<$fh>)
{
chomp;
- s/\s*#.*//;
+ s/\s*\/\/.*//;
next if /^$/;
if(s/^\t\t//)
{
$super = $currentbot->{percussion}->{$1};
$currentbot->{percussion}->{$1} = $appendref = [];
}
- elsif(/^vocals$/)
+ elsif(/^text (.*)$/)
{
- $super = $currentbot->{vocals};
- $currentbot->{vocals} = $appendref = [];
+ $super = $currentbot->{text}->{$1};
+ $currentbot->{text}->{$1} = $appendref = [];
}
else
{
- print "unknown command: $_\n";
+ print STDERR "unknown command: $_\n";
}
}
elsif(/^bot (.*)/)
{
$time_forgetfulness = $1;
}
+ elsif(/^list (.*?) (.*)/)
+ {
+ $lists{$1} = [split / /, $2];
+ $listindexes{$1} = 0;
+ }
else
{
- print "unknown command: $_\n";
+ print STDERR "unknown command: $_\n";
}
}
return 1;
}
+sub buildstring(@)
+{
+ return
+ join " ",
+ map
+ {
+ $_ =~ /^\@(.*)$/
+ ? do { $lists{$1}[$listindexes{$1}++ % @{$lists{$1}}]; }
+ : $_
+ }
+ @_;
+}
+
sub busybot_cmd_bot_execute($$@)
{
my ($bot, $time, @commands) = @_;
}
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')
{
}
elsif($_->[0] eq 'raw')
{
- $commands .= sprintf "%s\n", join " ", @{$_}[1..@$_-1];
+ $commands .= sprintf "%s\n", buildstring @{$_}[1..@$_-1];
+ }
+ else
+ {
+ warn "Invalid command: @$_";
}
}
{
my ($bot, $time, $channel, $note) = @_;
#print STDERR "note off $bot:$time:$channel:$note\n";
+ return 1
+ if not $bot->{busy};
my ($busychannel, $busynote, $cmds) = @{$bot->{busy}};
return 1
if not defined $cmds; # note off cannot fail
{
my ($bot, $channel, $note) = @_;
my ($k0, $k1, $cmds, $cmds_off) = (undef, undef, undef, undef);
- if($channel <= 0)
+ if($channel == TEXT_EVENT_CHANNEL)
{
# vocals
- $cmds = $bot->{vocals};
+ $note =~ /^([^:]*):(.*)$/;
+ my $name = $1;
+ my $data = $2;
+ $cmds = $bot->{text}->{$name};
if(defined $cmds)
{
- $cmds = [ map { [ map { $_ eq '%s' ? $note : $_ } @$_ ] } @$cmds ];
+ $cmds = [ map { [ map { $_ eq '%s' ? $data : $_ } @$_ ] } @$cmds ];
}
- $k0 = "vocals";
- $k1 = $channel;
+ $k0 = "text";
+ $k1 = $name;
}
elsif($channel == 10)
{
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 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} = [$channel, $note, $cmds_off];
}
# print STDERR "note off $time:$channel:$note\n";
- return 0
- if $channel <= 0;
- return 0
- if $channel == 10;
-
if(my $bot = $notechannelbots{$channel}{$note})
{
busybot_note_off_bot $bot, $time, $channel, $note;
map
{
my $q = 0;
- if(($_->{lastuse} // -666) >= $time - $time_forgetfulness)
+ if($channel != 10) # percussion just should do round robin
{
- if($channel == $_->{lastchannel})
+ if(($_->{lastuse} // -666) >= $time - $time_forgetfulness)
{
- $q += $_->{lastchannelsequence};
- }
- else
- {
- # better leave this one alone
- $q -= $_->{lastchannelsequence};
+ if($channel == $_->{lastchannel})
+ {
+ $q += $_->{lastchannelsequence};
+ }
+ else
+ {
+ # better leave this one alone
+ $q -= $_->{lastchannelsequence};
+ }
}
}
[$_, $q, rand]
if($notechannelbots{$channel}{$note})
{
+ print STDERR "THIS SHOULD NEVER HAPPEN\n";
busybot_note_off $time, $channel, $note;
}
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];
}
}
}
else
{
- push @allmidievents, ['note_on', $tick * $scale + $shift, $sequence++, -1, -1, $file];
- push @allmidievents, ['note_off', $tick * $scale + $shift, $sequence++, -1, -1, $file];
+ 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 $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;
- ++$notes_seen{$chan}{($programs{$chan} || 1)}{$_->[5]};
- if($midinotes{$chan}{$_->[5]})
- {
- --$notes_stuck;
- busybot_note_off($t - SYS_TICRATE - 0.001, $chan, $_->[5]);
- }
- busybot_note_on($t, $chan, $programs{$chan} || 1, $_->[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 - SYS_TICRATE - 0.001, $chan, $_->[5]);
- }
- $midinotes{$chan}{$_->[5]} = 0;
+ $note_off->($_);
+ }
+ elsif($_->[0] eq 'text_event')
+ {
+ $text_event->($_);
}
elsif($_->[0] eq 'patch_change')
{
- my $chan = $_->[4] + 1;
- my $program = $_->[5] + 1;
- $programs{$chan} = $program;
+ $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";
my $good = 0;
for my $channel(sort keys %notes_seen)
{
- next if $channel == 10 or $channel < 0;
+ next if $channel == 10;
for my $program(sort keys %{$notes_seen{$channel}})
{
for my $note(sort keys %{$notes_seen{$channel}{$program}})
my $votehigh = 0;
my $votelow = 0;
my $votegood = 0;
- for(@busybots_allocated)
+ for(@busybots_allocated, grep { $_->{count} > 0 } values %$busybots)
{
next # I won't play on this channel
if defined $_->{channels} and not $_->{channels}->{$channel};
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}})
+ for my $note(sort keys %{$notehash{$cn}{$type}})
{
my $cnt = $notehash{$cn}{$type}{$note};
print STDERR " $type $note ($cnt times)\n";
$noalloc = 0;
for(;;)
{
+ %listindexes = ();
$commands = "";
eval
{
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;
}