#! /usr/bin/perl ######################################################################### # This Perl script is Copyright (c) 2011, Peter J Billam # # www.pjb.com.au # # # # This script is free software; you can redistribute it and/or # # modify it under the same terms as Perl itself. # ######################################################################### my $Version = '1.2'; my $VersionDate = '03nov2011'; use open ':locale'; my $InputPort = ''; while ($ARGV[$[] =~ /^-([a-z])/) { if ($1 eq 'v') { shift; my $n = $0; $n =~ s{^.*/([^/]+)$}{$1}; print "$n version $Version $VersionDate\n"; exit 0; } elsif ($1 eq 'p' or $1 eq 'i') { shift; $InputPort = shift; } else { print "usage:\n"; my $synopsis = 0; while (<DATA>) { if (/^=head1 SYNOPSIS/) { $synopsis = 1; next; } if ($synopsis && /^=head1/) { last; } if ($synopsis && /\S/) { s/^\s*/ /; print $_; next; } } exit 0; } } if (!$InputPort) { $InputPort = $ENV{'ALSA_INPUT_PORTS'}; } if (!$InputPort) { die "-p not specified and ALSA_INPUT_PORTS not set\n"; } use Data::Dumper; $Data::Dumper::Indent = 0; $Data::Dumper::Sortkeys = 1; eval 'require MIDI'; if ($@) { die "you'll need to install the MIDI-Perl module from www.cpan.org\n"; } eval 'require MIDI::ALSA'; if ($@) { die "you'll need to install the MIDI::ALSA module from www.cpan.org\n"; } MIDI::ALSA::client("$0 MIDI::ALSA client", 1, 0, 1) or die "client failed"; foreach my $cl_po (split /,/, $InputPort) { if (! MIDI::ALSA::connectfrom( 0, $cl_po )) { die "can't connect from ALSA client $cl_po\n"; } } MIDI::ALSA::start() or die "start failed"; my @score = (1000, [['set_tempo',0,1000000],]); sub discon { warn " Writing to file $ARGV[$[]\n"; score2file($ARGV[$[], @score); exit 0; }; $SIG{INT} = \&discon; $SIG{QUIT} = \&discon; while (1) { # must exit the loop on SIGINT ... @alsaevent = MIDI::ALSA::input(); if (!@alsaevent) { warn "interrupted\n"; last; } if ($alsaevent[0]==MIDI::ALSA::SND_SEQ_EVENT_PORT_UNSUBSCRIBED()) { warn "unsubscribed\n"; last; } my @scoreevent = MIDI::ALSA::alsa2scoreevent(@alsaevent); if (@scoreevent) { push @{$score[1]}, \@scoreevent; } } warn " Writing to file $ARGV[$[]\n"; score2file($ARGV[$[], @score); exit 0; #--------------------- Encoding stuff from midisox_pl ------------------- sub opus2file { my ($filename, @opus) = @_; # print "opus2file: filename=$filename opus = ", Dumper(@opus); my $format = 1; if (2 == @opus) { $format = 0; } my $cpan_opus = MIDI::Opus->new( {'format'=>$format, 'ticks' => 1000, 'tracks' => []}); # my $tracks_r = $cpan_opus->tracks_r(); my @list_of_tracks = (); my $itrack = $[+1; while ($itrack <= $#opus) { push @list_of_tracks, MIDI::Track->new({ 'type' => 'MTrk', 'events' => $opus[$itrack]}); $itrack += 1; } # print "opus2file: list_of_tracks = ", Dumper(@list_of_tracks); $cpan_opus->tracks(@list_of_tracks); # $cpan_opus->dump({'dump_tracks'=>1}); if ($filename eq '-') { $cpan_opus->write_to_file( '>-' ); # $cpan_opus->write_to_handle({'to_handle' => *STDOUT{IO}}); } elsif ($filename eq '-d') { $PID = fork; if (! $PID) { if (!open(P, '| aplaymidi -')) { die "can't run aplaymidi: $!\n"; } $cpan_opus->write_to_handle( *P{IO}, {} ); close P; exit 0; } } else { $cpan_opus->write_to_file($filename); } } sub score2opus { if (2 > @_) { return (1000, []); } my ($ticks, @tracks) = @_; # print "score2opus: tracks is ", Dumper(@tracks); my @opus = ($ticks,); my $itrack = $[; while ($itrack <= $#tracks) { # MIDI::Score::dump_score( $_[$itrack] ); # push @opus, MIDI::Score::score_r_to_events_r($_[$itrack]); my %time2events = (); foreach my $scoreevent_ref (@{$tracks[$itrack]}) { my @scoreevent = @{$scoreevent_ref}; # print "score2opus: scoreevent = @scoreevent\n"; if ($scoreevent[0] eq 'note') { my @note_on_event = ('note_on',$scoreevent[1], $scoreevent[3],$scoreevent[4],$scoreevent[5]); my @note_off_event = ('note_off',$scoreevent[1]+$scoreevent[2], $scoreevent[3],$scoreevent[4],$scoreevent[5]); if ($time2events{$note_on_event[1]}) { push @{$time2events{$note_on_event[1]}}, \@note_on_event; } else { @{$time2events{$note_on_event[1]}} = (\@note_on_event,); } if ($time2events{$note_off_event[1]}) { push @{$time2events{$note_off_event[1]}}, \@note_off_event; } else { @{$time2events{$note_off_event[1]}} = (\@note_off_event,); } } elsif ($time2events{$scoreevent[1]}) { push @{$time2events{$scoreevent[1]}}, \@scoreevent; } else { @{$time2events{$scoreevent[1]}} = (\@scoreevent,); } } my @sorted_events = (); # list of event_refs sorted by time for my $time (sort {$a <=> $b} keys %time2events) { push @sorted_events, @{$time2events{$time}}; } my $abs_time = 0; for my $event_ref (@sorted_events) { # convert abs times => delta times my $delta_time = ${$event_ref}[1] - $abs_time; $abs_time = ${$event_ref}[1]; ${$event_ref}[1] = $delta_time; } push @opus, \@sorted_events; $itrack += 1; } return (@opus); } sub score2file { my ($filename, @score) = @_; my @opus = score2opus(@score); return opus2file($filename, @opus); } __END__ =pod =head1 NAME armid - rough arecordmidi work-alike, to demonstrate MIDI::ALSA =head1 SYNOPSIS armid -p 28 out.mid =head1 DESCRIPTION This script is a rough arecordmidi work-alike, to demonstrate MIDI::ALSA =head1 OPTIONS =over 3 =item I<-p 28:0,32:0> Records from the ALSA clients 28 and 32; the default is the envronment variable ALSA_INPUT_PORTS =item I<-v> Prints version number. =back =head1 CHANGES 20111103 1.2 use the new MIDI-ALSA 1.11 to handle portnames 20111031 1.1 connects from multiple (comma-separated) ports 20110310 1.0 first working version =head1 AUTHOR Peter J Billam http://www.pjb.com.au/comp/contact.html =head1 CREDITS Based on =head1 SEE ALSO http://www.pjb.com.au/ perl(1). =cut