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++;
+ }
+ }
+}
|