#!/usr/bin/perl # # Programmer: Craig Stuart Sapp # Creation Date: Wed Jan 22 18:43:39 EST 2003 (converted from smsHarmonics) # Last Modified: Wed Jan 22 18:43:51 EST 2003 # Filename: smsHarmonicExtract # Syntax: perl 5 # # Description: Interface for the SMS analysis/synthesis program to # extract harmonics from a harmonic sound. Extra input # parameters can be specified on the command line like this: # smsHarmonics 2 input.wav output.wav DefaultPitch=200 # which would add the line: # DefaultPitch 200 # to the analysis file. # # Usage: smsHarmonics first-harmonic last-harmonic input.wav basename.wav [options] # # Synthesis Parameters allowed: # Type # # Interpreted Analysis Parameters: # Pitch ==\ # ===> Converted into HighestPitch and LowestPitch # CentError ==/ # # Analysis parameters which need to change when the soundfile is 44100 sampling rate: # FrameRate=344.532 # # Synthesis parameters which need to change when the soundfile is 44100 sampling rate: # SamplingRate=44100 # # use strict; my $startharmonic = int($ARGV[0]); my $endharmonic = int($ARGV[1]); my $inputfile = $ARGV[2]; my $outputfile = $ARGV[3]; $outputfile =~ s/\.wav$//i; if ((@ARGV < 4) || ($startharmonic < 1) || ($endharmonic < $startharmonic)) { print "Usage: $0 start-harmonic end-harmonic input.wav output.wav\n"; exit(1); } my %parameters; $parameters{'LowestPitch'} = 40; $parameters{'HighestPitch'} = 5000; $parameters{'SineModel'} = 1; $parameters{'nSines'} = 1; # synthesis parameters $parameters{'SamplingRate'} = 22050; # fake parameters $parameters{'CentError'} = 50; # 50 cents (quarter-tone error allowed in pitch range. my $i; for ($i=4; $i<@ARGV; $i++) { if ($ARGV[$i] =~ /(.*)=(.*)/) { $parameters{$1} = $2; } } if ($parameters{'Pitch'} !~ /^\s*$/) { my $basefreq = pitchToFrequency($parameters{'Pitch'}); my $freqdevup = centIntervalUp($basefreq, $parameters{'CentError'}); my $freqdevdn = centIntervalDown($basefreq, $parameters{'CentError'}); $parameters{'LowestPitch'} = $freqdevdn; $parameters{'HighestPitch'} = $freqdevup; } my $soundinfo = `soundinfo $inputfile`; if ($soundinfo =~ /Sampling rate:\s*(\d+)/si) { $parameters{'SamplingRate'} = $1; if ($parameters{'SamplingRate'} != 22050) { if ($parameters{'FrameRate'} =~ /^\s*$/) { $parameters{'FrameRate'} = $parameters{'SamplingRate'} / 128.0; } } } print "*** Input sound information ************\n"; print $soundinfo; print "****************************************\n"; if ($startharmonic == 1) { extractHarmonicSet(1, $inputfile, ".$outputfile:1-1.wav"); if ($endharmonic == 1) { `mv .$outputfile:1-1.wav $outputfile-1.wav`; exit(0); } } if ($endharmonic > 1) { extractHarmonicSet($endharmonic, $inputfile, ".$outputfile:1-$endharmonic.wav"); } my $i; my $m; my $h; for ($i=$endharmonic; $i>=$startharmonic-1; $i--) { $h = $i-1; extractHarmonicSet($h, $inputfile, ".$outputfile:1-$h.wav"); print "Creating file $outputfile-$i.wav\n"; $m = $i; if ($m < 10) { $m = "0$m"; } `subtract .$outputfile:1-$i.wav .$outputfile:1-$h.wav $outputfile-$m.wav`; `rm -f .$outputfile:1-$i.wav`; last if $i <= 2; } if (-r ".$outputfile:1-1.wav") { `mv .$outputfile:1-1.wav $outputfile-01.wav`; } ########################################################################## ############################## ## ## extractHarmonicSet -- ## sub extractHarmonicSet { my ($harmonic, $inputfile, $outputfile) = @_; print "Extracting harmonic set $harmonic from file $inputfile\n"; $parameters{'nSines'} = $harmonic; open (ANALYSISFILE, ">.analysis-$inputfile") || die; print ANALYSISFILE <<"EOT"; InputSoundFile $inputfile OutputSmsFile .$inputfile.sms EOT my $key; foreach $key (keys %parameters) { next if $key =~ /^Type$/; next if $key =~ /^Pitch$/; next if $key =~ /^CentError$/; next if $key =~ /^SamplingRate$/; print ANALYSISFILE "$key $parameters{$key}\n"; } close ANALYSISFILE; print "==== ANALYSIS SCORE FILE: ==============================\n"; print `cat .analysis-$inputfile`; print "=========================================================\n"; open (SYNTHESISFILE, ">.synthesis-$inputfile") || die; print SYNTHESISFILE <<"EOT"; InputSmsFile .$inputfile.sms OutputSoundFile $outputfile Type 1 EOT # print the sampling rate of the input file if it was not 22050 so that the # synthesis sound has the same sampling rate. if ($parameters{'SamplingRate'} != 22050) { print SYNTHESISFILE "SamplingRate $parameters{'SamplingRate'}\n"; } close SYNTHESISFILE; print "==== SYNTHESIS SCORE FILE: ==============================\n"; print `cat .synthesis-$inputfile`; print "=========================================================\n"; print "Analyzing $inputfile file for harmonic range 1-$harmonic\n"; `sms analysis .analysis-$inputfile`; print "Synthesizing $inputfile file for harmonic range 1-$harmonic\n"; `sms synthesis .synthesis-$inputfile`; # clean up the temporary files `rm -f .$inputfile.sms`; `rm -f .analysis-$inputfile`; `rm -f .synthesis-$inputfile`; } ############################## ## ## pitchToFrequency -- convert pitch name to frequency ## sub pitchToFrequency { my ($name) = @_; $name =~ /(\d)/; my $octave = $1; my $output = 0.0; if ($name =~ /cs/i) { $output = 17.32 * 2.0**$octave; } # c-sharp elsif ($name =~ /ds/i) { $output = 19.45 * 2.0**$octave; } # d-sharp elsif ($name =~ /fs/i) { $output = 23.12 * 2.0**$octave; } # f-sharp elsif ($name =~ /gs/i) { $output = 25.96 * 2.0**$octave; } # g-sharp elsif ($name =~ /as/i) { $output = 29.14 * 2.0**$octave; } # a-sharp elsif ($name =~ /c/i) { $output = 16.35 * 2.0**$octave; } # c elsif ($name =~ /d/i) { $output = 18.35 * 2.0**$octave; } # d elsif ($name =~ /e/i) { $output = 20.60 * 2.0**$octave; } # e elsif ($name =~ /g/i) { $output = 24.50 * 2.0**$octave; } # g elsif ($name =~ /a/i) { $output = 27.50 * 2.0**$octave; } # a elsif ($name =~ /b/i) { $output = 30.87 * 2.0**$octave; } # b elsif ($name =~ /f/i) { $output = 21.83 * 2.0**$octave; } # f return $output; } ############################## ## ## centInterval -- ## sub centIntervalUp { my ($basefreq, $cents) = @_; return $basefreq * 2.0**($cents/1200.0); } sub centIntervalDown { my ($basefreq, $cents) = @_; return $basefreq * 2.0**($cents/-1200.0); }