3 # converter from Type 1 MIDI files to CFG files that control bots with the Tuba and other weapons for percussion (requires g_weaponarena all)
5 # perl midi2cfg.pl filename.mid basenote walktime "x y z" "x y z" "x y z" ... "/" "x y z" "x y z" ... > filename.cfg
12 use constant MIDI_FIRST_NONCHANNEL => 17;
13 use constant MIDI_DRUMS_CHANNEL => 10;
15 my ($filename, $transpose, $walktime, $staccato, @coords) = @ARGV;
16 my @coords_percussion = ();
18 my $l = \@coords_tuba;
23 $l = \@coords_percussion;
27 push @$l, [split /\s+/, $_];
31 my $opus = MIDI::Opus->new({from_file => $filename});
32 #$opus->write_to_file("/tmp/y.mid");
33 my $ticksperquarter = $opus->ticks();
34 my $tracks = $opus->tracks_r();
35 my @tempi = (); # list of start tick, time per tick pairs (calculated as seconds per quarter / ticks per quarter)
39 for($tracks->[0]->events())
42 if($_->[0] eq 'set_tempo')
44 push @tempi, [$_->[1], $_->[2] * 0.000001 / $ticksperquarter];
51 my $curtempo = [0, 0.5 / $ticksperquarter];
56 # this event is in the past
57 # we add the full time since the last one then
58 $sec += ($_->[0] - $curtempo->[0]) * $curtempo->[1];
62 # if this event is in the future, we break
67 $sec += ($tick - $curtempo->[0]) * $curtempo->[1];
71 # merge all to a single track
72 my @allmidievents = ();
74 for my $track(0..@$tracks-1)
77 for($tracks->[$track]->events())
79 my ($command, $delta, @data) = @$_;
81 push @allmidievents, [$command, $tick, $sequence++, $track, @data];
84 @allmidievents = sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } @allmidievents;
90 my @busybots_percussion = map { undef } @coords_percussion;
91 my @busybots_tuba = map { undef } @coords_tuba;
94 sub busybot_findfree($$$)
96 my ($time, $vchannel, $note) = @_;
97 my $l = ($vchannel < MIDI_FIRST_NONCHANNEL) ? \@busybots_tuba : \@busybots_percussion;
98 my $c = ($vchannel < MIDI_FIRST_NONCHANNEL) ? \@coords_tuba : \@coords_percussion;
103 my $bot = {id => $_ + 1, busy => 0, busytime => 0, channel => $vchannel, curtime => -$walktime, curbuttons => 0, noteoffset => 0};
106 # let the bot walk to his place
107 printf "m $_ $c->[$_]->[0] $c->[$_]->[1] $c->[$_]->[2]\n";
112 (($vchannel < MIDI_FIRST_NONCHANNEL) || ($l->[$_]{channel} == $vchannel))
116 $time > $l->[$_]{busytime};
118 die "No free channel found ($notes notes active)\n";
123 my ($vchannel, $note) = @_;
124 my $l = ($vchannel < MIDI_FIRST_NONCHANNEL) ? \@busybots_tuba : \@busybots_percussion;
132 $l->[$_]{channel} == $vchannel
134 defined $l->[$_]{note}
136 $l->[$_]{note} == $note;
141 sub busybot_advance($$)
144 my $t0 = $bot->{curtime};
147 #print "sv_cmd bot_cmd $bot->{id} wait @{[$t - $t0]}\n";
148 print "w $bot->{id} $t\n";
150 $bot->{curtime} = $t;
153 sub busybot_setbuttonsandadvance($$$)
155 my ($bot, $t, $b) = @_;
156 my $b0 = $bot->{curbuttons};
157 my $press = $b & ~$b0;
158 my $release = $b0 & ~$b;
159 busybot_advance $bot => $t - 0.10
160 if $release & (32 | 64);
161 print "r $bot->{id} attack1\n" if $release & 32;
162 print "r $bot->{id} attack2\n" if $release & 64;
163 busybot_advance $bot => $t - 0.05
164 if ($release | $press) & (1 | 2 | 4 | 8 | 16 | 128);
165 print "r $bot->{id} forward\n" if $release & 1;
166 print "r $bot->{id} backward\n" if $release & 2;
167 print "r $bot->{id} left\n" if $release & 4;
168 print "r $bot->{id} right\n" if $release & 8;
169 print "r $bot->{id} crouch\n" if $release & 16;
170 print "r $bot->{id} jump\n" if $release & 128;
171 print "p $bot->{id} forward\n" if $press & 1;
172 print "p $bot->{id} backward\n" if $press & 2;
173 print "p $bot->{id} left\n" if $press & 4;
174 print "p $bot->{id} right\n" if $press & 8;
175 print "p $bot->{id} crouch\n" if $press & 16;
176 print "p $bot->{id} jump\n" if $press & 128;
177 busybot_advance $bot => $t
178 if $press & (32 | 64);
179 print "p $bot->{id} attack1\n" if $press & 32;
180 print "p $bot->{id} attack2\n" if $press & 64;
181 $bot->{curbuttons} = $b;
231 my ($bot, $note) = @_;
232 $note_max = $note if $note_max < $note;
233 $note_min = $note if $note_min > $note;
235 $note -= $bot->{noteoffset};
236 my $s = $notes{$note};
240 sub busybot_playnoteandadvance($$$)
242 my ($bot, $t, $note) = @_;
243 my $s = getnote $bot => $note;
244 return (warn("note $note not found"), 0)
247 $buttons |= 1 if $s =~ /f/;
248 $buttons |= 2 if $s =~ /b/;
249 $buttons |= 4 if $s =~ /l/;
250 $buttons |= 8 if $s =~ /r/;
251 $buttons |= 16 if $s =~ /c/;
252 $buttons |= 32 if $s =~ /1/;
253 $buttons |= 64 if $s =~ /2/;
254 $buttons |= 128 if $s =~ /j/;
255 busybot_setbuttonsandadvance $bot => $t, $buttons;
259 sub busybot_stopnoteandadvance($$$)
261 my ($bot, $t, $note) = @_;
262 my $s = getnote $bot => $note;
265 my $buttons = $bot->{curbuttons};
266 #$buttons &= ~(32 | 64);
268 busybot_setbuttonsandadvance $bot => $t, $buttons;
274 my ($t, $channel, $note) = @_;
276 if($channel == MIDI_DRUMS_CHANNEL)
278 $channel = MIDI_FIRST_NONCHANNEL + $note; # percussion
279 return if !@coords_percussion;
281 my $bot = busybot_findfree($t, $channel, $note);
282 if($channel < MIDI_FIRST_NONCHANNEL)
284 if(busybot_playnoteandadvance $bot => $t, $note)
287 $bot->{note} = $note;
288 $bot->{busytime} = $t + 0.25;
291 busybot_stopnoteandadvance $bot => $t + 0.15, $note;
296 if($channel >= MIDI_FIRST_NONCHANNEL)
298 busybot_advance $bot => $t;
299 print "p $bot->{id} attack1\n";
300 print "r $bot->{id} attack1\n";
302 $bot->{note} = $note;
303 $bot->{busytime} = $t + 1.5;
309 my ($t, $channel, $note) = @_;
311 if($channel == MIDI_DRUMS_CHANNEL)
313 $channel = MIDI_FIRST_NONCHANNEL + $note; # percussion
315 my $bot = busybot_find($channel, $note)
318 if($channel < MIDI_FIRST_NONCHANNEL)
320 busybot_stopnoteandadvance $bot => $t, $note;
321 $bot->{busytime} = $t + 0.25;
325 print 'alias p "sv_cmd bot_cmd $1 presskey $2"' . "\n";
326 print 'alias r "sv_cmd bot_cmd $1 releasekey $2"' . "\n";
327 print 'alias w "sv_cmd bot_cmd $1 wait_until $2"' . "\n";
328 print 'alias m "sv_cmd bot_cmd $1 moveto \"$2 $3 $4\""' . "\n";
333 my $t = tick2sec $_->[1];
335 if($_->[0] eq 'note_on')
337 my $chan = $_->[4] + 1;
338 if($midinotes{$chan}{$_->[5]})
340 note_off($t, $chan, $_->[5]);
342 note_on($t, $chan, $_->[5]);
343 $midinotes{$chan}{$_->[5]} = 1;
345 elsif($_->[0] eq 'note_off')
347 my $chan = $_->[4] + 1;
348 if($midinotes{$chan}{$_->[5]})
350 note_off($t, $chan, $_->[5]);
352 $midinotes{$chan}{$_->[5]} = 0;
356 print STDERR "Range of notes: $note_min .. $note_max\n";
357 print STDERR "Safe transpose range: @{[$note_max - 19]} .. @{[$note_min + 13]}\n";
358 print STDERR "Unsafe transpose range: @{[$note_max - 24]} .. @{[$note_min + 18]}\n";
359 printf STDERR "%d bots allocated for tuba, %d for percussion\n", int scalar grep { defined $_ } @busybots_tuba, int scalar grep { defined $_ } @busybots_percussion;
362 for(@busybots_percussion, @busybots_tuba)
364 ++$n if $_ && $_->{busy};
369 print STDERR Dumper \%midinotes;
370 die "$n channels blocked ($notes MIDI notes)";