1#! /usr/bin/perl 2######################################################################### 3# This Perl script is Copyright (c) 2003, Peter J Billam # 4# c/o P J B Computing, www.pjb.com.au # 5# # 6# This script is free software; you can redistribute it and/or # 7# modify it under the same terms as Perl itself. # 8######################################################################### 9 10# SCSI: cdda2wav, cdrecord, cdparanoia, idprio, avconv, mplayer 11# $ENV{CDDA_DEVICE} cdrecord -scanbus 12# ATAPI: cdcontrol -f /dev/cd0c info, burncd, mkisofs 13# $ENV{CDROM} /usr/sbin/pciconf -lv, atacontrol list 14# MIDI: timidity, lame or toolame or twolame 15# 16# timidity -Ow -o sample.wav sample.mid 17# normalize-audio -m *.wav 18# scp *.wav theflame:/data/cd/ 19# ssh theflame 20# su - 21# cd /data/cd/ 22# cdrecord dev=0,0 -v -dao -pad -speed=12 -copy *.wav 23 24# .mid to .wav to .mp3 ... 25# timidity -Ow -o sample.wav sample.mid 26# normalize-audio -m *.wav 27# lame -h sample.wav sample.mp3 28 29# .wav files can be played with 30# sndfile-play whatever.wav or with mplayer or with play (comes with sox) 31# .mpg files can be played with 32# sndfile-play whatever.mpg or with mplayer or mpg123 33 34my $BigTmp = $ENV{'BIGTMP'} || '/tmp'; 35 36use Cwd qw(chdir); 37use Term::Clui; 38use Term::Clui::FileSelect; 39my @PATH = split (":",$ENV{PATH}); 40my $MidiOutPort = (); 41 42my $aconnect = which('aconnect'); 43my $alsamixer = which('alsamixer'); 44my $aplaymidi = which('aplaymidi'); 45my $arecordmidi = which('arecordmidi'); 46my $cdda2wav = which('icedax') || which('cdda2wav'); 47my $cdrecord = which('cdrecord') || which('wodim'); 48my $dvdbackup = which('dvdbackup'); 49my $eject = which('eject'); 50my $festival = which('festival'); 51my $growisofs = which('growisofs'); 52my $lame = which('lame'); 53my $man = which('man'); 54my $mkisofs = which('mkisofs') || which ('genisoimage'); 55my $mediainfo = which('dvd+rw-mediainfo'); 56my $mpg123 = which('mpg123'); 57my $mplayer = which('mplayer'); 58my $normalize = which('normalize') || which ('normalize-audio'); 59my $play = which('play'); # comes with sox 60my $rec = which('rec'); # comes with sox 61my $sox = which('sox'); 62my $sndfile_play = which('sndfile-play'); 63my $startBristol = which('startBristol'); 64my $su = which('su'); 65my $timidity = which('timidity'); 66my $toolame = which('toolame') || which('twolame'); 67my $mp3_player = $mpg123 || $mplayer || $sndfile_play; 68my $wav_player = $play || $sndfile_play || $mplayer; 69 70while (1) { 71 my $task = choose('Do what ?', tasks()); 72 exit unless $task; 73 if ($task eq 'Extract and Burn') { 74 warn "You'll need to be superuser ...\n"; 75 system "$su root -c $0"; exit 0; 76 } elsif ($task eq 'burn WAV->AudioCD') { burn_wav(); 77 } elsif ($task eq 'burn files->DataCD') { burn_files(); 78 } elsif ($task eq 'change Directory') { changedir(); 79 } elsif ($task eq 'configure Timidity') { configure_timidity(); 80 } elsif ($task eq 'connect MIDIports') { connect_midi_ports(); 81 } elsif ($task eq 'consult Manual') { man(); 82 } elsif ($task eq 'convert MIDI->WAV') { mid2wav(); 83 } elsif ($task eq 'convert MIDI->MP3') { mid2mp3(); 84 } elsif ($task eq 'copy audio CD') { copy_cd(); 85 } elsif ($task eq 'copy video DVD') { copy_dvd(); 86 } elsif ($task eq 'decode MP3->WAV') { mp32wav(); 87 } elsif ($task eq 'edit Makefile') { edit('Makefile'); 88 } elsif ($task eq 'encode WAV->MP2') { wav2mp2(); 89 } elsif ($task eq 'encode WAV->MP3') { wav2mp3(); 90 } elsif ($task eq 'list Soundfont') { list_soundfont(); 91 } elsif ($task eq 'play AudioCD') { play_cd(); 92 } elsif ($task eq 'play MIDI,WAV,MP3') { play(); 93 } elsif ($task eq 'record AudioIn->WAV') { audio2wav(); 94 } elsif ($task eq 'record Keyboard->MIDI') { kbd2mid(); 95 } elsif ($task eq 'rip AudioCD->WAV') { rip_wav(); 96 } elsif ($task eq 'rip MP3CD->MP3') { rip_mp3(); 97 } elsif ($task eq 'run a Bristol synth'){ bristol(); 98 } elsif ($task eq 'run alsamixer') { alsamixer(); 99 } elsif ($task eq 'run Make') { system 'make'; 100 } 101} 102exit 0; 103 104#----------------------- functionality ------------------------ 105sub alsamixer { 106 if (! $alsamixer) { sorry("you need to install alsamixer."); return; } 107 system $alsamixer; 108} 109sub bristol { 110 if (! $startBristol) { sorry("you need to install Bristol."); return; } 111 if (! open(P, "$startBristol -v -h |")) { 112 sorry("can't run $startBristol -v -h: $!"); return; 113 } 114 my $is_in_emulations = 0; 115 my %long2short = (); 116 while (<P>) { 117 if (/Emulation:/) { $is_in_emulations = 1; next; } 118 if (!$is_in_emulations) { next; } 119 if ($is_in_emulations and /Synthesiser:/) { last; } 120 if (/^\s+-(\w+)\s+-\s(\w.*)$/) { $long2short{$2} = $1; } 121 } 122 close P; 123 my $long = choose("which synth emulation ?", sort keys %long2short); 124 return unless $long; 125 my $out_file = ask("save output to wav file (return = don't save) ?"); 126 if (! $out_file) { 127 system "$startBristol -alsa -$long2short{$long}"; 128 } else { 129 $out_file =~ s/\.WAV$//i; 130 system "$startBristol -alsa -$long2short{$long} -o $out_file.raw"; 131 if (!$sox) { 132 sorry("you need to install sox to convert raw to wav."); return; 133 } 134 system "$sox -c 2 -s -r 44100 -2 $out_file.raw $out_file.wav"; 135 } 136} 137sub burn_wav { 138 if (!$cdrecord) {sorry("you need to install cdrecord or wodim."); return;} 139 set_cdda_device() || return; 140 my @files = select_file(-FPat=>'{*.wav,*.WAV}',-Path=>$ENV{PWD},-Chdir=>0); 141 return unless @files; 142 my $files = join "' '", @files; 143 ask("insert the C D into the drive, and press Return"); 144 system "$cdrecord dev=0,0 -v -dao -pad -speed=12 -copy '$files'"; 145 inform("finished burning the C D"); 146 if ($eject) { system $eject; } 147} 148sub burn_files { 149 my $ok = 1; 150 if (! $mkisofs) { 151 sorry("you need to install mkisofs or genisoimage."); return; 152 } 153 if (!$cdrecord) {sorry("you need to install cdrecord or wodim."); return;} 154 if (! -e '/dev/cdrom') { sorry("can't find /dev/cdrom"); $ok = 0; 155 } elsif (! -w '/dev/cdrom') { sorry("can't write to /dev/cdrom"); $ok=0; 156 } 157 my $tmpfile = "$BigTmp/cd_$$"; 158 my $tmp_dir = "$BigTmp/mnt_$$"; 159 if (! mkdir $tmp_dir) { sorry("can't mkdir $tmp_dir: $!"); $ok=0; } 160 return unless $ok; 161 # must choose_files repeatedly to within size limit 162 my $max_mb = 700; 163 while (1) { 164 my $mb_so_far = `du -ms $tmp_dir`; $mb_so_far =~ s/\s.*$//; 165 my $remaining = $max_mb - $mb_so_far; 166 if ($remaining > 1) { 167 warn "$remaining Mb remaining:\n"; 168 my $f = select_file(SelDir=>1,-Title=>"looking"); 169 if (! $f) { last; 170 } if (-d $f) { system("cp","-R",$f,"$tmp_dir/"); 171 } else { system("cp",$f,"$tmp_dir/"); 172 } 173 } elsif ($remaining < 0) { 174 my $f = select_file(-TopDir=>$tmp_dir -SelDir=>1, 175 -Title=>"$remaining Mb remaining: Delete which file "); 176 if (! $f) { last; 177 } else { system("rm","-rf","$tmp_dir/$f"); 178 } 179 } 180 } 181 182 system "ls -lR $tmp_dir/*"; 183 system "$mkisofs -gui -r -J -T -allow-limited-size" 184 ." -V DataCD -o $tmpfile $tmp_dir 2>&1 | perl -pe 's/\$/\\e[K\\e[A/'"; 185 print "\n"; 186 system("rm","-rf",$tmp_dir); 187 # could mount -o loop $tmpfile and check it's OK ... 188 if ($eject) { system $eject; } 189 while (1) { 190 ask("insert blank CD into drive and press Return..."); 191 # suppress line-feeds in the progress-bar (on stderr) ... 192 # should sleep, try, sleep, retry up to about 15 sec ... 193 system "$cdrecord dev=/dev/cdrom -v -dao $tmpfile"; 194 if ($eject) { system $eject; } 195 last unless confirm("do you want to write that to another CD ?"); 196 } 197 if (!unlink $tmpfile) { warn "can't unlink $tmpfile: $!\n"; } 198} 199sub copy_cd { 200 my $ok = 1; 201 if (!$cdda2wav) {sorry("you need to install cdda2wav or icedax."); $ok=0;} 202 if (!$cdrecord) {sorry("you need to install cdrecord or wodim."); $ok=0;} 203 if (! -e '/dev/cdrom') { sorry("can't find /dev/cdrom"); $ok = 0; 204 } elsif (! -w '/dev/cdrom') { sorry("can't write to /dev/cdrom"); $ok=0; 205 } 206 my $tmpdir = "$BigTmp/audio_stuff_$$"; 207 if (! mkdir $tmpdir) { sorry("can't mkdir $tmpdir: $!"); $ok=0; } 208 my $olddir = 'pwd'; 209 if (! chdir $tmpdir) { sorry("can't chdir $tmpdir: $!"); $ok=0; } 210 return unless $ok; 211 ask("insert the C D into the drive, and press Return"); 212 system "$cdda2wav dev=/dev/cdrom -vall cddb=0 -B -Owav"; 213 if ($eject) { system $eject; } 214 while (1) { 215 ask("insert blank CD into drive and press Return..."); 216 if (($> == 0) and (! -e '/dev/cdrom')) { # CURSE icedax! 217 symlink '/dev/sr0', '/dev/cdrom'; 218 } 219 system "$cdrecord dev=/dev/cdrom -v -dao -useinfo -text *.wav"; 220 if ($eject) { system $eject; } 221 last unless confirm "do you want to write that to another CD ?"; 222 } 223 chdir "$oldir"; 224 system "rm -rf $tmpdir"; 225} 226sub copy_dvd { 227 my $ok = 1; 228 if (!$dvdbackup) {sorry("you need to install dvdbackup."); $ok=0;} 229 if (!$mkisofs){sorry("you need to install mkisofs or genisoimage.");$ok=0;} 230 if (!$growisofs) {sorry("you need to install growisofs."); $ok=0;} 231 if (! -e '/dev/cdrom') { sorry("can't find /dev/cdrom"); $ok = 0; 232 } elsif (! -w '/dev/cdrom') { sorry("can't write to /dev/cdrom"); $ok=0; 233 } 234 my $tmpfile = "$BigTmp/dvd_$$.iso"; 235 my $tmp_mnt = "$BigTmp/mnt_$$"; 236 if (! mkdir $tmp_mnt) { sorry("can't mkdir $tmp_mnt: $!"); $ok=0; } 237 return unless $ok; 238 ask("insert the DVD into drive, and press Return..."); 239 240 ## The old non-dvdcss-capable method using mount ... 241 # system "mount -t iso9660 -o ro,map=off /dev/cdrom $tmp_mnt"; 242 #my $return_code; 243 #foreach (1..5) { # sleep, try, sleep, retry up to about 15 sec ... 244 # sleep 2; 245 # $return_code = system "mount -t udf -o ro /dev/cdrom $tmp_mnt"; 246 # last unless $return_code; 247 # sleep 2; 248 #} 249 #if ($return_code) { sorry("couldn't mount the DVD"); return 0; } 250 #if (! -d "$tmp_mnt/VIDEO_TS" and ! -d "$tmp_mnt/video_ts") { 251 # sorry("not a video DVD; can't see a /VIDEO_TS directory"); 252 # system "ls -lR $tmp_mnt ; umount $tmp_mnt"; 253 # if (! rmdir $tmp_mnt) { warn "can't rmdir $tmp_mnt: $!\n"; } 254 # return 0; 255 #} 256 257 warn "$dvdbackup -v -M -o $tmp_mnt -i /dev/cdrom\n"; 258 system "$dvdbackup -v -M -o $tmp_mnt -i /dev/cdrom"; # uses libdvdcss! 259 # discover the DVD's title 260 my $dh; opendir($dh, $tmp_mnt) or die "can't opendir $tmp_mnt: $!"; 261 my @ds = grep { !/^\./ && -d "$tmp_mnt/$_" } readdir($dh); 262 closedir $dh; 263 if (! @ds) { die "no directories found in $tmp_mnt/\n"; } 264 my $title = $ds[0]; 265 if (1 != scalar @ds) { 266 warn "directories @ds found in $tmp_mnt/ , using $title\n"; 267 } 268 if (! mkdir "$tmp_mnt/$title/AUDIO_TS") { 269 warn " can't mkdir $tmp_mnt/$title/AUDIO_TS\n"; 270 } 271 272 # mkisofs -dvd-video -o i1.img d1/NAQOYQATSI/ 273 # growisofs -dvd-compat -Z /dev/sr0=i1.img 274 # suppress line-feeds in the progress-bar (on stderr) ... 275 system "$mkisofs -gui -r -J -T -dvd-video -allow-limited-size" 276 ." -V Video_DVD -o $tmpfile '$tmp_mnt/$title'" 277 ." 2>&1 | perl -pe 's/\$/\\e[K\\e[A/'"; 278 # quotes 20130611, but should use the list form of system 279 #system "umount $tmp_mnt"; 280 print "\n"; 281 if ($eject) { system $eject; } 282 # if (! rmdir $tmp_mnt) { warn "can't rmdir $tmp_mnt: $!\n"; } 283 use File::Path; File::Path::remove_tree($tmp_mnt); 284 unlink $tmp_mnt; 285 system "ls -l $tmpfile"; 286 # to be fussy, could mount -o loop $tmpfile and check it's OK ... 287 if (! -s $tmpfile) { warn " the iso fs was empty :-(\n"; return; } 288 while (1) { 289 ask("insert blank DVD into drive, wait for light to go out, then press Return..."); 290 # suppress line-feeds in the progress-bar (on stderr) ... 291 # should sleep, try, sleep, retry up to about 15 sec ... 292 system "growisofs -dvd-compat -Z /dev/cdrom=$tmpfile" 293 . " 2>&1 | perl -pe 's/\$/\\e[K\\e[A/'"; 294 warn "\n"; 295 if ($eject) { system $eject; } 296 last unless confirm "do you want to write that to another DVD ?"; 297 } 298 if (!unlink $tmpfile) { warn "can't unlink $tmpfile: $!\n"; } 299} 300 301sub dvd_size { 302 if (!$mediainfo) { 303 # could try some other program ? 304 sorry('you should install dvd+rw-mediainfo'); return undef; 305 } 306 my $dev = 'dev/cdrom'; 307 my $size = undef; 308 foreach (1..5) { 309 sleep 2; 310 if (! open(P, "$mediainfo $dev 2>&1 |")) { 311 sorry("can't run $mediainfo $dev"); return undef; 312 } 313 while (<P>) { if (/Legacy lead-out.+=(\d+)$/) { $size = 0+$1; } } 314 close P; 315 if ($size) { return $size; } 316 sleep 2; 317 } 318 sorry("no dvd media present in $dev"); 319 return undef; 320} 321 322sub speak { 323 if (!$festival) { return; } 324 if (!@_) { return; } 325 if (! open(P, "|$festival --tts")) { 326 sorry("can't run $festival"); return; 327 } 328 print P $_[0]; 329 close P; 330} 331 332sub which_track { my $do_what = $_[0]; 333 # cdda2wav produces its output on stderr ARRGghhh :-( 334 if (! open (P, "$cdda2wav -Q -H -g -v toc -J 2>&1 |")) { 335 die "can't run $cdda2wav: $!\n"; 336 } 337 my @toc = <P>; 338 close P; 339 my @tracks, @header; 340 foreach (@toc) { 341 next if /^\s*#/; 342 next if /not detected/; 343 next if /not supported/; 344 next if /Album title: '' from ''/; 345 chop; 346 s/^\s+//; 347 if (/^T\d/) { s/ title '' from ''//; push @tracks, $_; 348 } else { push @header, $_; 349 } 350 } 351 print join("\n", @header), "\n"; 352 $track = choose("$do_what which track ?", @tracks); 353 $track =~ s/^\s*T0?//; 354 $track =~ s/:?\s+.*$//; 355 if ($track =~ /^\d$/) { $track = "0$track"; } 356 return $track; 357} 358 359sub play_cd { 360 if (!$cdda2wav) {sorry("you need to install cdda2wav or icedax."); return;} 361 set_cdda_device() || return; 362 my $task = choose('Play', 'All tracks', 'Just one track'); 363 return unless $task; 364 if ($task eq 'All tracks') { 365 system "$cdda2wav cddb=0 -H -B -e -N"; return; 366 } 367 my $track = which_track('Play'); 368 if ($track) { system "$cdda2wav -H -Q -x -e -N -t $track+$track"; } 369} 370 371sub rip_wav { 372 if (!$cdda2wav) {sorry("you need to install cdda2wav or icedax."); return;} 373 set_cdda_device() || return; 374 my $task = choose('Extract', 'All tracks', 'Just one track'); 375 return unless $task; 376 if ($task eq 'All tracks') { 377 system "$cdda2wav cddb=0 -H -B -Owav"; return; 378 } 379 $track = choose('Extract which track ?', @tracks); 380 $track =~ s/^\s*T0?//; 381 $track =~ s/:?\s+.*$//; 382 if ($track =~ /^\d$/) { $track = "0$track"; } 383 my $track = which_track('Extract'); 384 if ($track) { 385 my $filename = ask('to what filename ?', "${track}_track.wav"); 386 if ($filename && ($filename !~ /\.wav$/i)) { $filename .= '.wav'; } 387 system "$cdda2wav -H -Q -x -Owav -t $track+$track $filename"; 388 } 389} 390 391sub rip_mp3 { 392} 393sub wav2mp3 { 394 if (! $lame) { sorry("you need to install lame."); return; } 395 my @files = select_file(-FPat=>'*.wav', -Path=>$ENV{PWD}, -Chdir=>0); 396 foreach my $i (@files) { 397 my $o = $i; $o =~ s/wav$/mp3/; 398 if (-f $o && !confirm("OK to overwrite $o ?")) { next; } 399 system "$lame -h $i $o"; 400 } 401} 402sub wav2mp2 { 403 if (! $toolame) { sorry("you need to install toolame."); return; } 404 my @files = select_file(-FPat=>'*.wav', -Path=>$ENV{PWD}, -Chdir=>0); 405 foreach my $i (@files) { 406 my $o = $i; $o =~ s/wav$/mp2/; 407 if (-f $o && !confirm("OK to overwrite $o ?")) { next; } 408 system "$toolame $i"; 409 } 410} 411sub mp32wav { 412 if (! $lame) { sorry("you need to install lame."); return; } 413 if (! $normalize) { 414 sorry("you need to install normalize-audio or normalize."); return; 415 } 416 my @files = select_file(-FPat=>'*.mp3', -Path=>$ENV{PWD}, -Chdir=>0); 417 foreach my $i (@files) { 418 my $o = $i; $o =~ s/mp3$/wav/; 419 if (-f $o && !confirm("OK to overwrite $o ?")) { next; } 420 system "$lame --mp3input --decode $i $o"; 421 system "$normalize '$o'"; 422 } 423} 424sub mid2wav { 425 # should also offer replay-through-xv2020, and sox -t alsa hw:4,0 426 if (! $timidity) { sorry("you need to install timidity."); return; } 427 if (! $normalize) { 428 sorry("you need to install normalize-audio or normalize."); return; 429 } 430 my @files = select_file(-FPat=>'*.mid', -Path=>$ENV{PWD}, -Chdir=>0); 431 my $config = timiditycfg(); 432 print "config=$config\n"; 433 if (! $config) { sorry("can't find any timidity.cfg file"); return; } 434 my @wavs = (); 435 foreach my $i (@files) { 436 my $o = $i; $o =~ s/mid$/wav/; push @wavs, $o; 437 if (-f $o && !confirm("OK to overwrite $o ?")) { next; } 438 system "$timidity -Ow -c $config -o $o $i"; 439 } 440 system "$normalize '".join("' '",@wavs)."'"; 441} 442sub mid2mp3 { 443 if (! $timidity) { sorry("you need to install timidity."); return; } 444 if (! $normalize) { 445 sorry("you need to install normalize-audio or normalize."); return; 446 } 447 if (! $lame) { sorry("you need to install lame."); return; } 448 my @files = select_file(-FPat=>'*.mid', -Path=>$ENV{PWD}, -Chdir=>0); 449 my @wavs = (); 450 return unless @files; 451 foreach my $i (@files) { 452 my $o = $i; $o =~ s/mid$/wav/; push @wavs, $o; 453 if (-f $o && !confirm("OK to overwrite $o ?")) { next; } 454 system "$timidity -Ow -o $o $i"; 455 } 456 system "$normalize '".join("' '",@wavs)."'"; 457 foreach my $o (@wavs) { 458 my $oo = $o; $oo =~ s/wav$/mp3/; 459 if (-f $oo && !confirm("OK to overwrite $oo ?")) { next; } 460 system "$lame -h $o $oo"; 461 unlink $o; 462 } 463} 464sub play { 465 my $file = select_file(-Readable=>1, -Path=>$ENV{PWD}, 466 -FPat=>'{*.wav,*.mp3,*.mid}'); 467 return unless $file; 468 if ($file =~ /\.mp3$/) { 469 if ($mpg123) { inform( 470 's=stop/start b=beginning ,=rewind .=fast-forward q=quit'); 471 system "$mpg123 -C $file"; system "stty sane"; 472 return; 473 } 474 if (! $mp3_player) { 475 sorry("you need to install mpg123 or mplayer or sndfile-play."); 476 return; 477 } 478 system "$mp3_player $file"; 479 return; 480 } elsif ($file =~ /\.wav$/) { 481 if (! $wav_player) { 482 sorry("you need to install sox (play) or sndfile-play or mplayer."); 483 return; 484 } 485 system "$wav_player $file"; 486 return; 487 } 488 if (! $aplaymidi) { sorry("you need to install aplaymidi."); return; } 489 # also needed by metronome below, should factorize this code out ... 490 if (!open(P,"$aplaymidi -l |")) { die "can't run $aplaymidi -l: $!\n"; } 491 my (%outport2device, %device2outport); 492 while (<P>) { 493 if (/^\s*(\d+:\d)\s+(.*)$/) { 494 my $port = $1; 495 my $device = $2; substr ($device,0,32) = ''; $device =~ s/^\s*//; 496 $outport2device{$port} = $device; 497 $device2outport{$device} = $port; 498 } 499 } 500 close P; 501 my @outdevices = sort keys %device2outport; 502 my $outdevices; my $outport; 503 if (!@outdevices) { 504 sorry("aplaymidi can't see any midi output devices."); return; 505 } elsif (1 == @outdevices) { 506 inform("using midi device $outdevices[0]"); 507 $outport = $device2outport{$outdevices[0]}; 508 } else { 509 $outport = $device2outport{choose('To which device ?',@outdevices)}; 510 return unless $outport; 511 } 512 system "$aplaymidi -p $outport \"$file\""; 513} 514sub audio2wav { 515 if (! $rec) { 516 sorry("you need to install rec (comes with sox)."); return; 517 } 518 my $file = ask("To what .wav file ?"); 519 return unless $file; 520 if ($file !~ /\.WAV$/) { $file =~ s/\.WAV$/\.wav/; 521 } elsif ($file !~ /\.wav$/) { $file .= '.wav'; 522 } 523 # could offer options, like gain, compand, autostart on signal... 524 # must convert to 44100 Hz :-) 525 system "$rec -c 2 $file rate 44100"; 526} 527sub midi_in_port { 528 if (! $aconnect) { sorry("you need to install aconnect."); return; } 529 if (!open(P,"$aconnect -i |")) {die "can't run $aconnect -i: $!\n";} 530 my $major; my $inport; my $outport; 531 while (<P>) { 532 if (/^client\s*(\d+:)/) { $major = $1; 533 } elsif ($major>0 and /^\s+(\d)\s+'(.*)'/) { 534 my $minor = $1; my $device = $2; $device =~ s/\s+$//; 535 $inport2device{"$major$minor"} = $device; 536 $device2inport{$device} = "$major$minor"; 537 } 538 } 539 close P; 540 my @indevices = sort keys %device2inport; 541 my $inport; 542 if (!@indevices) { 543 sorry("aconnect can't see any midi input devices."); return; 544 } elsif (1 == @indevices) { 545 inform("using MIDI-input-port $indevices[0]"); 546 $inport = $device2inport{$indevices[0]}; 547 } else { 548 $inport 549 = $device2inport{choose('connect from which device ?',@indevices)}; 550 } 551 return $inport; 552} 553sub midi_out_port { 554 if (! $aconnect) { sorry("you need to install aconnect."); return; } 555 if (!open(P,"$aconnect -o |")) {die "can't run $aconnect -o: $!\n";} 556 while (<P>) { 557 if (/^client\s*(\d+:)/) { $major = $1; 558 } elsif ($major>0 and /^\s+(\d)\s+'(.*)'/) { 559 my $minor = $1; my $device = $2; $device =~ s/\s+$//; 560 $outport2device{"$major$minor"} = $device; 561 $device2outport{$device} = "$major$minor"; 562 } 563 } 564 close P; 565 my @outdevices = sort keys %device2outport; 566 my $outport; 567 if (!@outdevices) { 568 sorry("aconnect can't see any midi output devices."); return; 569 } elsif (1 == @outdevices) { 570 inform("using MIDI-output-port $outdevices[0]"); 571 $outport = $device2outport{$outdevices[0]}; 572 } else { 573 $outport 574 = $device2outport{choose('connect to which device ?',@outdevices)}; 575 } 576 return $outport; 577} 578sub connect_midi_ports { 579 if (! $aconnect) { sorry("you need to install aconnect."); return; } 580 if (!open(P,"$aconnect -ol |")) {die "can't run $aconnect -ol: $!\n";} 581 my $major = -1; 582 my %port2device = (); 583 my %device2port = (); 584 my @connections = (); 585 my $device = ''; 586 while (<P>) { 587 if (/^client\s*(\d+):/) { $major = $1; next; } 588 if ($major>0 and /^\s+(\d)\s+'(.*)'/) { 589 my $minor = $1; $device = $2; $device =~ s/\s+$//; 590 $port2device{"$major:$minor"} = $device; 591 $device2port{$device} = "$major:$minor"; 592 next; 593 } 594 if (/Connected From:\s+(.+)/) { 595 foreach (split /,\s*/, $1) { 596 push @connections, "$port2device{$_} -> $device"; 597 } 598 } 599 } 600 close P; 601 if (@connections) { 602 my @disconnect = (); 603 if (1 == @connections) { 604 my $msg = "do you want disconnect this one ?"; 605 my $disconnect = choose($msg, @connections); 606 if ($disconnect) { @disconnect = ($disconnect); } 607 } else { 608 my $msg = "do you want disconnect any of these ?"; 609 @disconnect = choose($msg, @connections); 610 } 611 my $is_ok = 0; 612 foreach (@disconnect) { 613 if (!$_) { last; } 614 if (/^(.+) -> (.+)/) { 615 system "$aconnect -d $device2port{$1} $device2port{$2}"; 616 $is_ok = 1; 617 } else { 618 warn "unrecognised connection $_\n"; 619 } 620 } 621 if ($is_ok) { inform('OK'); } 622 } 623 my $inport = midi_in_port(); 624 return unless $inport; 625 my $outport = midi_out_port(); 626 return unless $outport; 627 $MidiOutPort = $outport; 628 system "$aconnect $inport $outport"; 629} 630sub kbd2mid { 631 my $inport = midi_in_port(); 632 return unless $inport; 633 my $bpm = choose('crochets (quarter-notes) per minute ?', tempi()); 634 $bpm = $bpm || 120; 635 my $timesig = choose('time signature ?', '3/8','6/8','9/8','12/8', 636 '2/4','3/4','4/4','5/4','6/4','7/4','2/2','3/2'); 637 $timesig = $timesig || '4/4'; 638 $timesig =~ s/\//:/; 639 my $file = ask("To what midifile ?"); 640 return unless $file; 641 if ($file !~ /\.mid$/) { $file .= '.mid'; } 642 my $metronome; 643 if ($MidiOutPort) { $metronome=choose('With a metronome ?','Yes','No'); } 644 my $ok = ask("<Return> to start recording, <Ctrl-C> to stop ..."); 645 if ($metronome) { 646 system "arecordmidi -p$inport -b$bpm -i$timesig -m$MidiOutPort $file"; 647 } else { 648 system "arecordmidi -p$inport -b$bpm -i$timesig $file"; 649 } 650} 651sub changedir { 652 my $newdir = select_file(-Path=>$ENV{PWD}, -Directory=>1); 653 return unless $newdir; 654 if (! -d $newdir) { sorry("$newdir is not a directory"); return; } 655 if (! chdir $newdir) { sorry("can't chdir to $newdir: $!"); return; } 656 # assertively rename *.WAV->*.wav, *.MID->*.mid, *.MP3->*.mp3 657 if (! opendir (D, '.')) { sorry("can't opendir $newdir: $!"); return; } 658 my @allfiles = grep { !/^\./ } readdir(D); 659 closedir D; 660 my $oldname; 661 foreach $oldname (grep { /\.WAV$/} @allfiles) { 662 my $newname = $oldname; $newname =~ s/WAV$/wav/; 663 rename $oldname, $newname; 664 } 665 foreach $oldname (grep { /\.MP3$/} @allfiles) { 666 my $newname = $oldname; $newname =~ s/MP3$/mp3/; 667 rename $oldname, $newname; 668 } 669 foreach $oldname (grep { /\.MID$/} @allfiles) { 670 my $newname = $oldname; $newname =~ s/MID$/mid/; 671 rename $oldname, $newname; 672 } 673} 674 675sub list_soundfont { 676 eval 'require File::Format::RIFF'; 677 if ($@) { sorry("you need to install File::Format::RIFF."); return; } 678 679 my $config = timiditycfg(); 680 my $dir = $ENV{PWD}; 681 if (open (F, $config)) { 682 while (<F>) { if (/^dir\s+(.+)$/) { $dir = $1; last; } } close F; 683 } else { 684 inform("can't find any timidity.cfg file ..."); 685 } 686 my $file = select_file( 687 -Title=>'Which Soundfont file ?', -FPat=>'{*.sf2,*.SF2}', -Path=>$dir, 688 ); 689 return unless $file; 690 open(IN, $file) or die "Could not open $file: $!\n"; 691 692 my $riff1 = File::Format::RIFF->read(\*IN); 693 close(IN); 694 # $riff1->dump; $pdta->dump; 695 my $pdta = $riff1->at(2); 696 my $phdr = $pdta->at(0); 697 my $data = $phdr->data; 698 my %t; 699 while ($data) { 700 chop; 701 my $chunk = substr $data,0,38,''; 702 my $name = substr $chunk,0,20,''; 703 my ($preset,$bank) = unpack 'SS', $chunk; 704 $name =~ tr/ 0-9a-zA-Z_//cd; 705 if ($name =~ /^EOP/) { next; } 706 my $k = 1000*$bank + $preset; 707 $t{$k} = sprintf "%5d %5d %s", $preset,$bank,$name; 708 } 709 my @t = "$file\nPreset Bank PresetName"; 710 foreach (sort {$a<=>$b} keys %t) { push @t, $t{$_}; } 711 view("Contents of $file", join("\n", @t)."\n"); 712} 713sub configure_timidity { 714 if (! $timidity) { sorry("you need to install timidity."); return; } 715 my $f = timiditycfg(); 716 if (! $f) { 717 inform("can't find any timidity.cfg ..."); 718 } elsif (-w $f) { 719 edit($f); 720 } else { 721 inform("you don't have write permission to $f ..."); 722 if (!-w $ENV{PWD}) { 723 inform("and you don't have write permission here in $ENV{PWD}"); 724 return; 725 } 726 return unless confirm("Create a local timidity.cfg in $ENV{PWD} ?"); 727 if (! open (O, ">$ENV{PWD}/timidity.cfg")) { 728 sorry("can't write to $ENV{PWD}/timidity.cfg: $!"); return; 729 } 730 if (open (I, $f)) { 731 while (<I>) { print O $_; } close I; 732 } else { 733 print O <<'EOT'; 734# Sample timidity.cfg - see "man timidity.cfg" 735dir /directory/where/you/keep/your/soundfonts 736 737# specify default Soundfont: 738soundfont Chaos4m.sf2 739 740# but take bank0 patch0 from SteinwayGrandPiano & patch74 from Ultimate 741bank 0 7420 %font SteinwayGrandPiano1.2.sf2 0 0 74374 %font Ultimate.sf2 0 74 744 745EOT 746 } 747 close O; 748 edit("$ENV{PWD}/timidity.cfg"); 749 } 750} 751sub man { 752 my @topics = @_ || ( 753 'aconnect', 'alsamixer', 'aplaymidi', 'arecordmidi', 'atacontrol', 754 'audio_stuff', 'avconv', 'bristol', 'burncd', 755 'cdcontrol', 'cdda2wav', 'cdrecord', 'dvd+rw-mediainfo', 756 'File::Format::RIFF', 757 'genisoimage', 'icedax', 'lame', 'mkisofs', 758 'mplayer', 'normalize', 'normalize-audio', 'pciconf', 'sndfile-play', 759 'sox', 'soxexam', 'soxeffect', 760 'Term::Clui', 'Term::Clui::FileSelect', 'timidity', 'timidity.cfg', 761 'toolame', 'wodim', 762 ); 763 my $topic = choose('Which topic ?', @topics); return unless $topic; 764 if ($topic eq 'audio_stuff') { system "perldoc $0"; 765 } elsif ($topic =~ /::/) { system "perldoc $topic"; 766 } elsif ($topic =~ /bristol/) { system "$startBristol -v -h | less"; 767 } else { system "$man $topic"; 768 } 769} 770 771#----------------------- infrastructure ------------------------ 772sub which { my $f; foreach $d (@PATH) {$f="$d/$_[0]"; return $f if -x $f; }} 773 774sub tempi { 775 qw(40 42 44 46 48 50 52 54 56 58 60 63 69 72 76 80 84 8 92 96 100 104 776 108 112 116 120 126 132 138 144 152 160 168 176 184 192 200 208); 777} 778sub timiditycfg { 779 return unless $timidity; 780 my $f; 781 foreach $f ( 782 "$ENV{PWD}/timidity.cfg", 783 '/usr/local/share/timidity/timidity.cfg', 784 '/etc/timidity.cfg', 785 ) { 786 if (-f $f) { return $f; } 787 } 788 if (! open(P, "strings $timidity |")) { return ''; } 789 while (<P>) { 790 if (/^Please check (\S+.cfg)/) { close P; return $1; } 791 } 792 close P; return ''; 793} 794 795sub set_cdda_device { 796 if ($ENV{CDDA_DEVICE}) { return 1; } 797 inform(" you should set the CDDA_DEVICE environment variable!"); 798 if (-e "/dev/cdrom") { 799 $ENV{CDDA_DEVICE} = '/dev/cdrom:@'; 800 inform("using $ENV{CDDA_DEVICE} ..."); 801 return 1; 802 } elsif (-e '/dev/sr0' and ! $>) { 803 symlink '/dev/sr0', '/dev/cdrom'; 804 } 805 system "eject"; system "eject -t"; sleep 3; 806 if ($>) { 807 warn " you need to be root to run cdrecord -scanbus\n"; 808 $ENV{CDDA_DEVICE} = '0,0,0'; 809 warn "trying CDDA_DEVICE='0,0,0'\n"; 810 } elsif (! open (P, "$cdrecord -scanbus |")) { 811 warn "can't run cdrecord -scanbus: $!\n"; return 0; 812 } else { 813 my @devices; 814 while (<P>) { 815 chop; 816 s/\t/ /g; s/ +/ /g; 817 if (/^\s+\d.*[^*]$/) { push @devices, $_; } 818 } 819 close P; 820 my $device = choose("Which Device ?", @devices); 821 $device =~ s/^\s+//; 822 $device =~ s/\s.*$//; 823 $ENV{CDDA_DEVICE} = $device; 824 } 825 if (! $ENV{CDDA_DEVICE}) { 826 $ENV{CDDA_DEVICE} = ask('CDDA_DEVICE ?'); 827 } 828 if ($ENV{CDDA_DEVICE}) { return 1; } else { return 0; } 829} 830 831sub tasks { 832 my @tasks = ( 833 'run a Bristol synth', 'burn files->DataCD', 'burn WAV->AudioCD', 834 'change Directory', 'configure Timidity', 'connect MIDIports', 835 'convert MIDI->MP3', 'convert MIDI->WAV', 'convert Muscript->MIDI', 836 'copy audio CD', 'copy video DVD', 'decode MP3->WAV', 837 'edit Muscript', 'encode WAV->MP2', 'encode WAV->MP3', 'play AudioCD', 838 'rip AudioCD->WAV', 'rip MP3CD->MP3', 'play MIDI,WAV,MP3', 839 'record AudioIn->WAV', 'record Keyboard->MIDI', 'run alsamixer', 840 ); 841 if (-e './Makefile') { 842 push @tasks, ('run Make', 'edit Makefile'); 843 } else { 844 push @tasks, ('create Makefile'); 845 } 846 push @tasks, ( 847 'list Soundfont', 'consult Manual', 848 ); 849 return sort @tasks; 850} 851 852 853__END__ 854 855echo =============================================================== 856echo Getting rid of spaces in .mp3 files ... 857for i in *.[Mm][Pp]3; do mv "$i" `echo $i | tr ' ' '_'`; done 858echo Changing .MP3 to .mp3 ... 859for i in *.MP3; do mv "$i" `basename $i .MP3`.mp3; done 860echo Changing _-_ to _ ... 861for i in *_-_*.mp3; do mv "$i" `echo $i | sed s/_-_/_/`; done 862 863echo 864echo =============================================================== 865echo Decoding .mp3 files to *.wav ... 866for i in *.mp3; do lame --decode $i `basename $i .mp3`.wav; done 867 868echo 869echo =============================================================== 870echo normalising .wav files ... 871normalize -m *.wav 872 873echo 874echo =============================================================== 875echo Total size of .wav files ... 876du -kch *.wav | grep total 877 878echo 879echo =============================================================== 880echo Checking for non-44100-Hz encoded .wav files ... 881file *.wav | grep -v 44100 882 883=pod 884 885=head1 NAME 886 887audio_stuff - wrapper for aplaymidi, cdda2wav, cdrecord, lame, timidity etc. 888 889 890=head1 SYNOPSIS 891 892$ audio_stuff 893 894=head1 DESCRIPTION 895 896This script, which comes along with the I<Term::Clui> Perl-module 897in its I<examples> directory, 898integrates 899various open-source programs for handling 900Muscript, Midi, WAV, MP3, CDDA and DVD files 901into one ArrowKey-and-Return user-interface, 902 903=head1 FEATURES 904 905 burn files->DataCD burn WAV->AudioCD change Directory 906 configure Timidity connect MIDIports consult Manual 907 convert MIDI->MP3 convert MIDI->WAV convert Muscript->MIDI 908 copy audio CD copy video DVD create Makefile 909 decode MP3->WAV edit Muscript encode WAV->MP2 910 encode WAV->MP3 list Soundfont play MIDI,WAV,MP3 911 record AudioIn->WAV record Keyboard->MIDI rip AudioCD->WAV 912 rip MP3CD->MP3 run a Bristol synth run alsamixer 913 914=over 3 915 916=item I<rip AudioCD-E<gt>WAV> and I<burn WAV-E<gt>AudioCD> 917 918These features use I<cdda2wav> or I<icedax> 919and I<cdrecord> and I<wodim> to get files 920off AudioCDs into I<.wav> format, or vice-versa. 921 922=item I<copy video DVD> 923 924This feature uses I<mkisofs> or I<genisoimage> to get files off 925a Video DVD and I<growisofs> to burn them onto an empty one. 926 927=item I<rip MP3CD-E<gt>MP3> and I<burn MP3-E<gt>MP3CD> 928 929These features use I<cp> and I<cdrecord> or I<wodim> to get files 930off MP3-CDs onto local hard-disk, or vice-versa. 931 932=item I<encode WAV-E<gt>MP3> and I<decode MP3-E<gt>WAV> 933 934These features use I<lame> to get files 935from I<.wav> format into I<.mp3> format or vice-versa. 936 937=item I<play WAV,MP3,MID> 938 939Depending on which file you select, this feature 940either uses I<mplayer> or I<mpg123> to play a I<.mp3> file, 941or I<play> or I<sndfile-play> to play a I<.wav> file to the headphones, 942or I<aplaymidi> to send a I<.mid> file to a Synthesiser. 943 944=back 945 946=head1 ENVIRONMENT 947 948When copying DVDs some big temporary files are created; 949if your I</tmp> is too small you can create a B<BIGTMP> 950environment variable to use somewhere else, e.g.: 951 952 export BIGTMP=/home/tmp 953 audio_stuff 954 955=head1 AUTHOR 956 957Peter J Billam www.pjb.com.au/comp/contact.html 958 959=head1 CREDITS 960 961Based on Term::Clui, alsamixer, aplaymidi, arecordmidi, cdrecord or wodim, 962cdda2wav or icedax, lame, mkisofs or genisoimage, mpg123, 963normalize-audio, sox, sndfile_play, startBristol and timidity. 964 965=head1 SEE ALSO 966 967http://www.pjb.com.au/ , 968http://search.cpan.org/~pjb , 969http://bristol.sourceforge.net , 970Term::Clui, 971alsamixer(1), 972aplaymidi(1), 973arecordmidi(1), 974cdrecord(1), 975cdda2wav(1), 976festival(1), 977genisoimage(1), 978growisofs(1), 979icedax(1), 980lame(1), 981mpg123(1), 982mkisofs(1), 983normalize(1), 984normalize-audio(1), 985sndfile_play(1), 986sox(1), 987soxexam(7), 988soxeffect(7), 989timidity(1), 990wodim(1) 991 992=cut 993 994