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 my ($filename, $transpose, $walktime, @coords) = @ARGV;
13 my @coords_percussion = ();
15 my $l = \@coords_tuba;
20 $l = \@coords_percussion;
24 push @$l, [split /\s+/, $_];
28 my $opus = MIDI::Opus->new({from_file => $filename});
29 my $ticksperquarter = $opus->ticks();
30 my $tracks = $opus->tracks_r();
31 my @tempi = (); # list of start tick, time per tick pairs (calculated as seconds per quarter / ticks per quarter)
35 for($tracks->[0]->events())
38 if($_->[0] eq 'set_tempo')
40 push @tempi, [$_->[1], $_->[2] * 0.000001 / $ticksperquarter];
47 my $curtempo = [0, 0.5 / $ticksperquarter];
52 # this event is in the past
53 # we add the full time since the last one then
54 $sec += ($_->[0] - $curtempo->[0]) * $curtempo->[1];
58 # if this event is in the future, we break
63 $sec += ($tick - $curtempo->[0]) * $curtempo->[1];
67 # merge all to a single track
68 my @allmidievents = ();
70 for my $track(0..@$tracks-1)
73 for($tracks->[$track]->events())
75 my ($command, $delta, @data) = @$_;
77 push @allmidievents, [$command, $tick, $sequence++, $track, @data];
80 @allmidievents = sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } @allmidievents;
86 my @busybots_percussion = map { undef } @coords_percussion;
87 my @busybots_tuba = map { undef } @coords_tuba;
90 sub busybot_findfree($$$)
92 my ($time, $vchannel, $note) = @_;
93 my $l = ($vchannel < 16) ? \@busybots_tuba : \@busybots_percussion;
98 my $bot = {id => $_ + 1, busy => 0, busytime => 0, channel => $vchannel, curtime => -$walktime, curbuttons => 0, noteoffset => 0};
102 $l->[$_]{channel} == $vchannel
106 $time > $l->[$_]{busytime};
108 die "No free channel found ($notes notes active)\n";
113 my ($vchannel, $note) = @_;
114 my $l = ($vchannel < 16) ? \@busybots_tuba : \@busybots_percussion;
122 $l->[$_]{channel} == $vchannel
124 defined $l->[$_]{note}
126 $l->[$_]{note} == $note;
131 sub busybot_advance($$)
134 my $t0 = $bot->{curtime};
137 #print "sv_cmd bot_cmd $bot->{id} wait @{[$t - $t0]}\n";
138 print "sv_cmd bot_cmd $bot->{id} wait_until $t\n";
140 $bot->{curtime} = $t;
143 sub busybot_setbuttons($$)
146 my $b0 = $bot->{curbuttons};
147 my $press = $b & ~$b0;
148 my $release = $b0 & ~$b;
149 print "sv_cmd bot_cmd $bot->{id} releasekey forward\n" if $release & 1;
150 print "sv_cmd bot_cmd $bot->{id} releasekey backward\n" if $release & 2;
151 print "sv_cmd bot_cmd $bot->{id} releasekey left\n" if $release & 4;
152 print "sv_cmd bot_cmd $bot->{id} releasekey right\n" if $release & 8;
153 print "sv_cmd bot_cmd $bot->{id} releasekey crouch\n" if $release & 16;
154 print "sv_cmd bot_cmd $bot->{id} releasekey attack1\n" if $release & 32;
155 print "sv_cmd bot_cmd $bot->{id} releasekey attack2\n" if $release & 64;
156 print "sv_cmd bot_cmd $bot->{id} presskey forward\n" if $press & 1;
157 print "sv_cmd bot_cmd $bot->{id} presskey backward\n" if $press & 2;
158 print "sv_cmd bot_cmd $bot->{id} presskey left\n" if $press & 4;
159 print "sv_cmd bot_cmd $bot->{id} presskey right\n" if $press & 8;
160 print "sv_cmd bot_cmd $bot->{id} presskey crouch\n" if $press & 16;
161 print "sv_cmd bot_cmd $bot->{id} presskey attack1\n" if $press & 32;
162 print "sv_cmd bot_cmd $bot->{id} presskey attack2\n" if $press & 64;
163 $bot->{curbuttons} = $b;
201 my ($bot, $note) = @_;
202 $note_max = $note if $note_max < $note;
203 $note_min = $note if $note_min > $note;
205 $note -= $bot->{noteoffset};
206 my $s = $notes{$note};
210 sub busybot_playnote($$)
212 my ($bot, $note) = @_;
213 my $s = getnote $bot => $note;
214 return (warn("note $note not found"), 0)
217 $buttons |= 1 if $s =~ /f/;
218 $buttons |= 2 if $s =~ /b/;
219 $buttons |= 4 if $s =~ /l/;
220 $buttons |= 8 if $s =~ /r/;
221 $buttons |= 16 if $s =~ /c/;
222 $buttons |= 32 if $s =~ /1/;
223 $buttons |= 64 if $s =~ /2/;
224 busybot_setbuttons $bot => $buttons;
228 sub busybot_stopnote($$)
230 my ($bot, $note) = @_;
231 my $s = getnote $bot => $note;
234 my $buttons = $bot->{curbuttons};
235 $buttons &= ~(32 | 64);
237 busybot_setbuttons $bot => $buttons;
243 my ($t, $channel, $note) = @_;
244 if(busybot_find($channel, $note))
246 note_off($t, $channel, $note); # MIDI allows redoing a note-on for the same note
251 $channel = 16 + $note; # percussion
253 my $bot = busybot_findfree($t, $channel, $note);
256 busybot_advance $bot => $t if getnote $bot => $note;
257 if(busybot_playnote $bot => $note)
260 $bot->{note} = $note;
261 $bot->{busytime} = $t + 0.25;
262 busybot_stopnote $bot => $note;
267 print "sv_cmd bot_cmd $bot->{id} presskey attack1\n";
268 print "sv_cmd bot_cmd $bot->{id} releasekey attack1\n";
270 $bot->{note} = $note;
271 $bot->{busytime} = $t + 1.5;
277 my ($t, $channel, $note) = @_;
281 $channel = 16 + $note; # percussion
283 my $bot = busybot_find($channel, $note)
288 busybot_advance $bot => $t;
289 busybot_stopnote $bot => $note;
290 $bot->{busytime} = $t + 0.25;
296 my $t = tick2sec $_->[1];
298 if($_->[0] eq 'note_on')
301 note_on($t, $chan, $_->[5]);
303 elsif($_->[0] eq 'note_off')
306 note_off($t, $chan, $_->[5]);
310 print STDERR "Range of notes: $note_min .. $note_max\n";
311 print STDERR "Safe transpose range: @{[$note_max - 7]} .. @{[$note_min + 13]}\n";
312 print STDERR "Unsafe transpose range: @{[$note_max - 12]} .. @{[$note_min + 18]}\n";
313 printf STDERR "%d bots allocated for tuba, %d for percussion\n", scalar grep { defined $_ } @busybots_tuba, scalar grep { defined $_ } @busybots_percussion;
316 for(@busybots_percussion, @busybots_tuba)
318 ++$n if $_ && $_->{busy};
322 die "$n channels blocked ($notes MIDI notes)";