http://www.perlmonks.org?node_id=811534


in reply to How can I play music like Basic's SOUND or PLAY?

Hi, long time ago, I've made a music playing script. It generates sound waves then sends them to a sound card. I did it as an experiment that sound generation could be done in pure perl without external apps. I was determined to write my own pure perl mp3 player and this player was just the beginning of the project, which got stale for the lack of time, but it doesn't mean that I don't still want to eventually complete that. I actually want to try to create a pure perl mp3 decoder which will read in mp3 files, decode them internally without any external codec by just perl itself into waves and send them to the sound card.

In my script, I added a wave visualization, which you can disable by setting 0 on $displayWave variable; from the script and just leave the sound playing section.

You can also save the output wave to a wav file. I have it commented out at the end.

It accepts a file with notes and plays them.

use Win32::Sound; $displayWave = 1; $range = 256/39; $virtlim = 47; $step = 24; $midofc = 20; @extremes = (0,0); %FqC = ('a', [55,110,220,440,880,1760], 'as',[58.270,116.541,233.082,466.164,932.328,1864.655] +, 'b', [61.735,123.471,246.942,493.883,987.767,1975.533] +, 'c', [65.406,130.813,261.626,523.251,1046.502,2093.005 +], 'cs',[69.296,138.591,277.183,554.365,1108.731,2217.461 +], 'd', [73.416,146.832,293.665,587.330,1174.659,2349.318 +], 'ds',[77.782,155.563,311.127,622.254,1244.508,2489.016 +], 'e', [82.407,164.814,329.628,659.255,1318.510,2637.020 +], 'f', [87.307,174.614,349.228,698.456,1396.913,2793.826 +], 'fs',[92.499,184.997,369.994,739.989,1479.978,2959.955 +], 'g', [97.999,195.998,391.995,783.991,1567.982,3135.963 +], 'gs',[103.826,207.652,415.305,830.609,1661.219,3322.43 +8], 'p',[0,0,0,0,0,0] ); %def = ('octav', 3, 'frac', 4, 'paus', 64); print "Welcome to Perl Music Player V1 by Igor A. Rylov\n". "acmecode.com\n\n\n\tPlaying '$ARGV[0]' file.\n"; open(MUS, "$ARGV[0]"); @notes = (); while(<MUS>){ $_ =~ s/[\n\r\f\cM]+$//; $_ =~ tr/#/s/; $_ = lc($_); @notes = (@notes, split(/\s+/, $_)); } close(MUS); # Create the object $WAV = new Win32::Sound::WaveOut(44100, 8, 2); $data = ""; $osc = 0; foreach $note (@notes){ ($note, $frac) = split('/', $note); $note =~ s/(\d)$//; $octav = $1; $octav = $def{'octav'} if($octav eq ''); $frac = $def{'paus'} if($frac eq '' && $note eq 'p'); $frac = $def{'frac'} if($frac eq '' || $frac eq 0); $counter = 0; $increment = $FqC{$note}[$octav]/44100; $label = (uc($note))."($octav) = $FqC{$note}[$octav]; ".(1/$frac) +." Second; Step = $step\n"; # Generate 44100 samples ( = 1 second) $st = time; for $i (1..(44100/$frac)){ # Calculate the pitch # (range 0..255 for 8 bits) $v = ($note eq 'p')?(0):(sin($counter/2*3.14) * 128 + 128); $extremes[0] = $v if($extremes[0] > $v); $extremes[1] = $v if($extremes[1] < $v); if($displayWave){ if(($osc % ($virtlim * $step)) == 0){ system(cls); print $label; } if(($osc % $step) == 0){ print ' ' x (40 - $midofc + ($v/$range)); print "X\n"; } } ++$osc; # "pack" it twice for left and right $data .= pack("cc", $v, $v); $counter += $increment; } $newt = (time - $st); $step = $newt * $frac if($step == 1); } print "\n\nExtremes: $extremes[0] <=> $extremes[1]\n\n"; $WAV->Load($data); # get it $WAV->Write(); # hear it 1 until $WAV->Status(); # wait for completion # $WAV->Save("sinus.wav"); # write to disk $WAV->Unload(); # drop it
This prog accepts a file with notes. File spec is simple. Just type notes in sequence, separated by space. Notes are defined are written in the form:
(Note)(isSharp)(Octave)length in seconds or fractions of seconds, optional. Default = 1 second
Notes are: a b c d e f g

Sharp Notes are: a# c# d# f# g#

An octave is appended to a note to define it:

a2 d#4

In the above example were used a note from the octave 2 and d sharp note from the octave 4

You can specify their lengths in complete or fractional seconds. Fraction is defined by a slash and a seconds value:

a2/2 d#4/.5 b5

In the above example: a note, octave, 2 seconds; d sharp note, 4th octave, half a second; b from the 5th octave, 1 second (the default, because the length was omitted).

There is a p note, which is a pause

An example Music file:

a4 f#4/2 c4 d4 f4/2 b4 p b4 d#4/2 d4 c4 d#4/2 a4 p a4 d4/2 p d4 e4 f4/2 p f4 g4 a5/2 p a5 b5 c5/1 p b5 d5/2 p d5 a5 b5/2
The note frequencies are defined in FqC. They are probably not the frequencies used in real instruments, but I chose them by manually listening and testing sounds. I didn't have a time to look for the info on the real frequencies for each note used. If anybody knows the real frequencies for each note in each octave, they can be changed in that variable.