X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fxonotic.git;a=blobdiff_plain;f=misc%2Ftools%2Fmidi2cfg-ng.pl;h=4fcf9760a22134d54dbbfbab75b48151bc961fc7;hp=1111f468ee3f951d1f63eb806dfbe389abbf8dbc;hb=c6297696eda1a5a66f2d25b1358e2b21898ab3ea;hpb=08fc9267bbef1bd1cae69b8e740e2c0d0cf4b7be diff --git a/misc/tools/midi2cfg-ng.pl b/misc/tools/midi2cfg-ng.pl index 1111f468..4fcf9760 100755 --- a/misc/tools/midi2cfg-ng.pl +++ b/misc/tools/midi2cfg-ng.pl @@ -14,6 +14,7 @@ use constant SYS_TICRATE => 0.033333; 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; @@ -25,6 +26,8 @@ my $timeoffset_postdone = 2; my $timeoffset_preintermission = 2; my $timeoffset_postintermission = 2; my $time_forgetfulness = 1.5; +my %lists = (); +my %listindexes = (); my ($config, @midilist) = @ARGV; @@ -88,7 +91,7 @@ sub botconfig_read($) while(<$fh>) { chomp; - s/\s*#.*//; + s/\s*\/\/.*//; next if /^$/; if(s/^\t\t//) { @@ -161,14 +164,14 @@ sub botconfig_read($) $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 (.*)/) @@ -207,9 +210,14 @@ sub botconfig_read($) { $time_forgetfulness = $1; } + elsif(/^list (.*?) (.*)/) + { + $lists{$1} = [split / /, $2]; + $listindexes{$1} = 0; + } else { - print "unknown command: $_\n"; + print STDERR "unknown command: $_\n"; } } @@ -298,6 +306,19 @@ sub busybot_cmd_bot_test($$$@) return 1; } +sub buildstring(@) +{ + return + join " ", + map + { + $_ =~ /^\@(.*)$/ + ? do { $lists{$1}[$listindexes{$1}++ % @{$lists{$1}}]; } + : $_ + } + @_; +} + sub busybot_cmd_bot_execute($$@) { my ($bot, $time, @commands) = @_; @@ -340,7 +361,11 @@ sub busybot_cmd_bot_execute($$@) } 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') { @@ -350,7 +375,11 @@ sub busybot_cmd_bot_execute($$@) } elsif($_->[0] eq 'raw') { - $commands .= sprintf "%s\n", join " ", @{$_}[1..@$_-1]; + $commands .= sprintf "%s\n", buildstring @{$_}[1..@$_-1]; + } + else + { + warn "Invalid command: @$_"; } } @@ -377,6 +406,8 @@ sub busybot_note_off_bot($$$$) { 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 @@ -401,16 +432,19 @@ sub busybot_get_cmds_bot($$$) { 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) { @@ -433,6 +467,7 @@ sub busybot_get_cmds_bot($$$) 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 @@ -467,16 +502,25 @@ sub busybot_note_on_bot($$$$$$$) 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]; } ++$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; - $bot->{lastprogram} = $program; - $bot->{lastnote} = $note; + +# print STDERR "$time $bot->{id} $channel:$note\n" +# if $channel == 11; return 1; } @@ -496,11 +540,6 @@ sub busybot_note_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; @@ -530,24 +569,19 @@ sub botsort($$$$@) 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; - if($program == $_->{lastprogram}) + if($channel == $_->{lastchannel}) { - ++$q; - if($note == $_->{lastnote}) - { - ++$q; - } + $q += $_->{lastchannelsequence}; + } + else + { + # better leave this one alone + $q -= $_->{lastchannelsequence}; } - } - else - { - # better leave this one alone - --$q; } } [$_, $q, rand] @@ -561,6 +595,7 @@ sub busybot_note_on($$$$) if($notechannelbots{$channel}{$note}) { + print STDERR "THIS SHOULD NEVER HAPPEN\n"; busybot_note_off $time, $channel, $note; } @@ -764,6 +799,8 @@ sub ConvertMIDI($$) 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]; } } @@ -786,52 +823,142 @@ sub ConvertMIDI($$) } 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"; @@ -844,7 +971,7 @@ sub ConvertMIDI($$) 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}}) @@ -853,7 +980,7 @@ sub ConvertMIDI($$) 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}; @@ -950,7 +1077,7 @@ sub Deallocate() 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"; @@ -974,6 +1101,7 @@ my @preallocate = (); $noalloc = 0; for(;;) { + %listindexes = (); $commands = ""; eval { @@ -989,6 +1117,7 @@ for(;;) 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; }