From a5a2d476f802bd571f15ecaa42f1642ca3af7a7e Mon Sep 17 00:00:00 2001 From: div0 Date: Sun, 14 Jun 2009 10:33:44 +0000 Subject: [PATCH] midi2cfg-ng: new command syntax, support multiple MIDIs (yet to be tested) git-svn-id: svn://svn.icculus.org/nexuiz/trunk@7036 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- misc/tools/midi2cfg-ng.pl | 348 +++++++++++++++++++++++--------------- 1 file changed, 212 insertions(+), 136 deletions(-) diff --git a/misc/tools/midi2cfg-ng.pl b/misc/tools/midi2cfg-ng.pl index 23bcfb8d9..453996063 100755 --- a/misc/tools/midi2cfg-ng.pl +++ b/misc/tools/midi2cfg-ng.pl @@ -1,8 +1,6 @@ #!/usr/bin/perl # converter from Type 1 MIDI files to CFG files that control bots with the Tuba and other weapons for percussion (requires g_weaponarena all) -# usage: -# perl midi2cfg.pl filename.mid basenote walktime "x y z" "x y z" "x y z" ... "/" "x y z" "x y z" ... > filename.cfg use strict; use warnings; @@ -13,77 +11,23 @@ use Storable; use constant MIDI_FIRST_NONCHANNEL => 17; use constant MIDI_DRUMS_CHANNEL => 10; -die "Usage: $0 filename.conf filename.mid transpose timeoffset timeoffset2 timeoffset3 timeoffset4 preallocatedbots..." - unless @ARGV >= 7; -my ($config, $filename, $transpose, $timeoffset, $timeoffset2, $timeoffset3, $timeoffset4, @preallocate) = @ARGV; - -my $opus = MIDI::Opus->new({from_file => $filename}); -#$opus->write_to_file("/tmp/y.mid"); -my $ticksperquarter = $opus->ticks(); -my $tracks = $opus->tracks_r(); -my @tempi = (); # list of start tick, time per tick pairs (calculated as seconds per quarter / ticks per quarter) -my $tick; - -$tick = 0; -for($tracks->[0]->events()) -{ - $tick += $_->[1]; - if($_->[0] eq 'set_tempo') - { - push @tempi, [$tick, $_->[2] * 0.000001 / $ticksperquarter]; - } -} -sub tick2sec($) -{ - my ($tick) = @_; - my $sec = 0; - my $curtempo = [0, 0.5 / $ticksperquarter]; - for(@tempi) - { - if($_->[0] < $tick) - { - # this event is in the past - # we add the full time since the last one then - $sec += ($_->[0] - $curtempo->[0]) * $curtempo->[1]; - } - else - { - # if this event is in the future, we break - last; - } - $curtempo = $_; - } - $sec += ($tick - $curtempo->[0]) * $curtempo->[1]; - return $sec; -} - -# merge all to a single track -my @allmidievents = (); -my $sequence = 0; -for my $track(0..@$tracks-1) -{ - $tick = 0; - for($tracks->[$track]->events()) - { - my ($command, $delta, @data) = @$_; - $command = 'note_off' if $command eq 'note_on' and $data[2] == 0; - $tick += $delta; - push @allmidievents, [$command, $tick, $sequence++, $track, @data]; - } -} -@allmidievents = sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] } @allmidievents; - +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; sub unsort(@) { return map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, rand] } @_; } - - - - +my $precommands = ""; +my $commands = ""; +my $busybots; +my @busybots_allocated; +my %notechannelbots; +my $transpose = 0; my $notetime = undef; +my $lowestnotestart = undef; sub botconfig_read($) { my ($fn) = @_; @@ -182,7 +126,7 @@ sub botconfig_read($) } elsif(/^raw (.*)/) { - printf "$1\n"; + $precommands .= "$1\n"; } else { @@ -190,7 +134,6 @@ sub botconfig_read($) } } - my $lowestnotestart = undef; for(values %bots) { for(values %{$_->{notes_on}}, values %{$_->{percussion}}) @@ -200,10 +143,10 @@ sub botconfig_read($) } } - $notetime = $timeoffset2 - $lowestnotestart; - return \%bots; } +my $busybots_orig = botconfig_read $config; + sub busybot_cmd_bot_test($$@) { @@ -231,7 +174,7 @@ sub busybot_cmd_bot_execute($$@) { if($_->[0] eq 'time') { - printf "sv_cmd bot_cmd %d wait_until %f\n", $bot->{id}, $time + $_->[1]; + $commands .= sprintf "sv_cmd bot_cmd %d wait_until %f\n", $bot->{id}, $time + $_->[1]; $bot->{timer} = $time + $_->[1]; } elsif($_->[0] eq 'busy') @@ -248,33 +191,55 @@ sub busybot_cmd_bot_execute($$@) } for(keys %buttons_release) { - printf "sv_cmd bot_cmd %d releasekey %s\n", $bot->{id}, $_; + $commands .= sprintf "sv_cmd bot_cmd %d releasekey %s\n", $bot->{id}, $_; delete $bot->{buttons}->{$_}; } for(@{$_}[1..@$_-1]) { /(.*)(\?)?/ or next; defined $2 and next; - printf "sv_cmd bot_cmd %d presskey %s\n", $bot->{id}, $_; + $commands .= sprintf "sv_cmd bot_cmd %d presskey %s\n", $bot->{id}, $_; $bot->{buttons}->{$_} = 1; } } elsif($_->[0] eq 'cmd') { - printf "sv_cmd bot_cmd %d %s\n", $bot->{id}, join " ", @{$_}[1..@$_-1]; + $commands .= sprintf "sv_cmd bot_cmd %d %s\n", $bot->{id}, join " ", @{$_}[1..@$_-1]; + } + elsif($_->[0] eq 'barrier') + { + $commands .= sprintf "sv_cmd bot_cmd %d barrier\n", $bot->{id}; + $bot->{timer} = $bot->{busytimer} = 0; } elsif($_->[0] eq 'raw') { - printf "%s\n", join " ", @{$_}[1..@$_-1]; + $commands .= sprintf "%s\n", join " ", @{$_}[1..@$_-1]; } } return 1; } +my $intermissions = 0; + +sub busybot_intermission_bot($) +{ + my ($bot) = @_; + busybot_cmd_bot_execute $bot, 0, ['cmd', 'wait', $timeoffset_preintermission]; + busybot_cmd_bot_execute $bot, 0, ['barrier']; + if($bot->{intermission}) + { + busybot_cmd_bot_execute $bot, 0, @{$bot->{intermission}}; + } + busybot_cmd_bot_execute $bot, 0, ['barrier']; + $notetime = $timeoffset_postintermission - $lowestnotestart; +} + sub busybot_note_off_bot($$$$) { my ($bot, $time, $channel, $note) = @_; + return 1 + if $channel == 10; my $cmds = $bot->{notes_off}->{$note - $bot->{transpose} - $transpose}; return 1 if not defined $cmds; # note off cannot fail @@ -310,12 +275,15 @@ sub busybot_note_on_bot($$$$$) { return 0 if not busybot_cmd_bot_test $bot, $time + $notetime, @$cmds; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'wait', $timeoffset]; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'barrier']; + 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}} if @{$bot->{init}}; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'barrier']; - $bot->{timer} = $bot->{busytimer} = 0; + busybot_cmd_bot_execute $bot, 0, ['barrier']; + for(1..$intermissions) + { + busybot_intermission_bot $bot; + } busybot_cmd_bot_execute $bot, $time + $notetime, @$cmds; } else @@ -327,9 +295,14 @@ sub busybot_note_on_bot($$$$$) return 1; } -my $busybots = botconfig_read $config; -my @busybots_allocated; -my %notechannelbots; +sub busybots_reset() +{ + $busybots = Storable::dclone $busybots_orig; + @busybots_allocated = (); + %notechannelbots = (); + $transpose = 0; + $notetime = $timeoffset_postinit - $lowestnotestart; +} sub busybot_note_off($$$) { @@ -402,78 +375,181 @@ sub busybot_note_on($$$) return 0; } -for(@preallocate) +sub Preallocate(@) { - die "Cannot preallocate any more $_ bots" - if $busybots->{$_}->{count} <= 0; - my $bot = Storable::dclone $busybots->{$_}; - $bot->{id} = @busybots_allocated + 1; - $bot->{classname} = $_; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'wait', $timeoffset]; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'barrier']; - busybot_cmd_bot_execute $bot, 0, @{$bot->{init}} - if @{$bot->{init}}; - busybot_cmd_bot_execute $bot, 0, ['cmd', 'barrier']; - $bot->{timer} = $bot->{busytimer} = 0; - --$busybots->{$_}->{count}; - push @busybots_allocated, $bot; + my (@preallocate) = @_; + busybots_reset(); + for(@preallocate) + { + die "Cannot preallocate any more $_ bots" + if $busybots->{$_}->{count} <= 0; + my $bot = Storable::dclone $busybots->{$_}; + $bot->{id} = @busybots_allocated + 1; + $bot->{classname} = $_; + 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}} + if @{$bot->{init}}; + busybot_cmd_bot_execute $bot, 0, ['barrier']; + --$busybots->{$_}->{count}; + push @busybots_allocated, $bot; + } } -my %midinotes = (); -my $note_min = undef; -my $note_max = undef; -my $notes_stuck = 0; -my $t = 0; -for(@allmidievents) +sub ConvertMIDI($$) { - $t = tick2sec $_->[1]; - my $track = $_->[3]; - if($_->[0] eq 'note_on') + my ($filename, $trans) = @_; + $transpose = $trans; + + my $opus = MIDI::Opus->new({from_file => $filename}); + my $ticksperquarter = $opus->ticks(); + my $tracks = $opus->tracks_r(); + my @tempi = (); # list of start tick, time per tick pairs (calculated as seconds per quarter / ticks per quarter) + my $tick; + + $tick = 0; + for($tracks->[0]->events()) + { + $tick += $_->[1]; + if($_->[0] eq 'set_tempo') + { + push @tempi, [$tick, $_->[2] * 0.000001 / $ticksperquarter]; + } + } + my $tick2sec = sub { - 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]}) + my ($tick) = @_; + my $sec = 0; + my $curtempo = [0, 0.5 / $ticksperquarter]; + for(@tempi) { - --$notes_stuck; - busybot_note_off($t, $chan, $_->[5]); + if($_->[0] < $tick) + { + # this event is in the past + # we add the full time since the last one then + $sec += ($_->[0] - $curtempo->[0]) * $curtempo->[1]; + } + else + { + # if this event is in the future, we break + last; + } + $curtempo = $_; + } + $sec += ($tick - $curtempo->[0]) * $curtempo->[1]; + return $sec; + }; + + # merge all to a single track + my @allmidievents = (); + my $sequence = 0; + for my $track(0..@$tracks-1) + { + $tick = 0; + for($tracks->[$track]->events()) + { + my ($command, $delta, @data) = @$_; + $command = 'note_off' if $command eq 'note_on' and $data[2] == 0; + $tick += $delta; + push @allmidievents, [$command, $tick, $sequence++, $track, @data]; } - busybot_note_on($t, $chan, $_->[5]); - ++$notes_stuck; - $midinotes{$chan}{$_->[5]} = 1; } - elsif($_->[0] eq 'note_off') + @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 $t = 0; + for(@allmidievents) { - my $chan = $_->[4] + 1; - if($midinotes{$chan}{$_->[5]}) + $t = $tick2sec->($_->[1]); + my $track = $_->[3]; + if($_->[0] eq 'note_on') { - --$notes_stuck; - busybot_note_off($t, $chan, $_->[5]); + 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; } - $midinotes{$chan}{$_->[5]} = 0; + 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; + } + } + + 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"; + + while(my ($k1, $v1) = each %midinotes) + { + while(my ($k2, $v2) = each %$v1) + { + busybot_note_off($t, $k1, $k2); + } + } + + for(@busybots_allocated) + { + busybot_intermission_bot $_; } + ++$intermissions; } -for(@busybots_allocated) +sub Deallocate() { - busybot_cmd_bot_execute $_, 0, ['cmd', 'wait', $timeoffset3]; - busybot_cmd_bot_execute $_, 0, ['cmd', 'barrier']; - if($_->{done}) + print STDERR "Bots allocated:\n"; + for(@busybots_allocated) { - busybot_cmd_bot_execute $_, 0, @{$_->{done}}; + print STDERR "$_->{id} is a $_->{classname}\n"; + } + for(@busybots_allocated) + { + busybot_cmd_bot_execute $_, 0, ['cmd', 'wait', $timeoffset_predone]; + busybot_cmd_bot_execute $_, 0, ['barrier']; + if($_->{done}) + { + busybot_cmd_bot_execute $_, 0, @{$_->{done}}; + } + busybot_cmd_bot_execute $_, 0, ['cmd', 'wait', $timeoffset_postdone]; + busybot_cmd_bot_execute $_, 0, ['barrier']; } - busybot_cmd_bot_execute $_, 0, ['cmd', 'barrier']; - busybot_cmd_bot_execute $_, 0, ['cmd', 'wait', $timeoffset4]; } -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"; -print STDERR "Bots allocated:\n"; -for(@busybots_allocated) +my @preallocate = (); +for(;;) { - print STDERR "$_->{id} is a $_->{classname}\n"; + $commands = ""; + Preallocate(@preallocate); + my @l = @midilist; + while(@l) + { + my $filename = shift @l; + my $transpose = shift @l; + ConvertMIDI($filename, $transpose); + } + Deallocate(); + my @preallocate_new = map { $_->{classname} } @busybots_allocated; + last if @preallocate_new == @preallocate; + @preallocate = @preallocate_new; } + +print "$precommands$commands"; -- 2.39.2