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