diff --git a/scripts/midi_to_matrix.pl b/scripts/midi_to_matrix.pl new file mode 100755 index 0000000000..e67f344c67 --- /dev/null +++ b/scripts/midi_to_matrix.pl @@ -0,0 +1,136 @@ +#!/opt/local/bin/perl + +use strict; +use warnings; + +use Data::Dumper; +use IO::Async::Loop; +use Net::Async::Matrix; +use Net::Pcap; +use Data::HexDump; +use JSON; +use Music::Chord::Namer qw/chordname/; + +$| = 1; + +our $notes = {}; +our $notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; + +our $room; + +my $loop = IO::Async::Loop->new; +my $matrix = Net::Async::Matrix->new( + server => "echo-matrix:8008", + on_log => sub { warn "log: @_\n" }, + on_room_new => sub { + my ($matrix, $new_room) = @_; + warn "[Matrix] have a room ID: " . $new_room->room_id . "\n"; + $room = $new_room if ($new_room->room_id eq '!GaUcuyvZyXfoqmQTNR:echo-matrix'); + }, + on_error => sub { + print STDERR "Matrix failure: @_\n"; + }, +); + +$loop->add( $matrix ); +$matrix->login( + # XXX: password is broke + user_id => 'matthew', + access_token => 'QG1hdHRoZXc6ZWNoby1tYXRyaXg..ZVZbCQuOmnhwakNnOt', +)->get; + +$matrix->join_room( '#midi:echo-matrix' )->get; +# ->on_done(sub { +# print Dumper([@_]); +# ($room) = @_; +# warn "joined $room"; +# } )->get; + +$matrix->start(); + +my $err = ''; +my $dev = "en1"; + +my $pcap = pcap_open_live($dev, 1024, 0, 100, \$err); +die $err if $err; + +my ($net, $mask); +pcap_lookupnet($dev, \$net, \$mask, \$err); +die $err if $err; + +my $filter_str = "src host 10.12.76.65 and udp and port 5005"; +my $filter; +if (pcap_compile($pcap, \$filter, $filter_str, 1, $net) == -1) { + die "Unable to compile filter string '$filter_str'\n"; +} + +pcap_setfilter($pcap, $filter); + +while (1) { + pcap_dispatch($pcap, -1, \&process_packet, ""); + #print ".\n"; +} + +pcap_close($pcap); + +sub handle_event { + my ($event) = @_; + print to_json($event, { pretty => 1 }); + + if ($event->{state} eq 'on') { + $notes->{$event->{note}} = 1; + } + else { + delete $notes->{$event->{note}}; + } + + if (scalar keys %$notes >= 3) { + my $chord = (chordname(map { $notenames->[$_ % 12] } sort keys %$notes))[0]; + print "$chord\n"; + $room->send_message( $chord )->get; + } + + # HACK HACK HACK HACK + $room->_do_POST_json( "/send/org.matrix.midi", $event )->get; +} + +sub process_packet { + my ($user_data, $header, $packet) = @_; + my ($ether, $ip, $udp, $rtp_byte, $payload, $seqnum, $ts, $ssrc, @midi) + = unpack("a14a20a8CCSNNC*", $packet); + + return if ($rtp_byte == 0xff); + #print HexDump $packet; + + my $midilen; + if ($midi[0] & 0x80) { # long header + $midilen = (($midi[0] & 0x0f) << 8) | $midi[1]; + shift @midi; + shift @midi; + } + else { # short header + $midilen = ($midi[0] & 0x0f); + shift @midi; + } + + my $midiparsed = 0; + my $state = ($midi[0] >> 4 == 0x9 ? "on" : "off"); + my $channel = ($midi[0] & 0x0f) + 1; + shift (@midi); $midiparsed++; + + while ($midiparsed < $midilen) { + my ($event) = { + midi_ts => $ts, + note => $midi[0], + channel => $channel, + state => ($midi[1] == 0 ? "off" : $state), + velocity => $midi[1], + }; + handle_event($event); + shift (@midi); $midiparsed++; + shift (@midi); $midiparsed++; + if (scalar @midi) { + $ts += shift @midi; $midiparsed++; + } + } +}