=begin This script contains various utility functions and classes for dealing with audio. This is a stand-alone script. Audio.square(durationInMs,freq,volume,timbre,async) - Generates a square wave. Audio.beep(durationInMs,freq,volume,timbre,async) - Alias for Audio.square Audio.sine(durationInMs,freq,volume,timbre,async) - Generates a sine wave. Audio.triangle(durationInMs,freq,volume,timbre,async) - Generates a triangle wave. Audio.saw(durationInMs,freq,volume,async) - Generates a saw wave. Audio.noise(durationInMs,volume,async) - Generates white noise. Audio.playTone(toneFile,async) - Plays a tone in the Apple iPod alarm tone format. Parameters: durationInMs - duration of the sound in milliseconds. The module Audio::NoteLength contains useful durations for tones. If 0 or nil, the frequency is determined using the maximum duration of the given sound envelopes. freq - the frequency of the sound in Hz. The higher the frequency, the higher the pitch. If 0, no sound will be generated. The module Audio::Note contains useful frequencies for tones. freq can also be a SoundEnvelope or an array of two element arrays, as follows: freq[0] - time in ms to apply the specified frequency freq[1] - frequency to apply. In between, values will be interpolated volume - volume of the sound, from 0 through 100 volume can also be a SoundEnvelope. async - specifies whether the function will return immediately without waiting for the sound to finish (stands for asynchronous) timbre - specifies the timbre of the tone; from 0.0 through 1.0 timbre can also be a SoundEnvelope or an array of two element arrays, as follows: volume[0] - time in ms to apply the specified timbre volume[1] - timbre to apply. In between, values will be interpolated WaveData - A class for holding audio data in memory. This class is easy to serialize into the save file. intensity() - Calculates the intensity, or loudness of the data Returns a value from 0 through 127. time() - Length of the data in seconds. play() - Plays the wave data getPlayTime(filename) - Gets the length of an audio file in seconds. Supports WAV, MP3, and OGG files. getWaveData(filename) - Creates wave data from the given WAV file path. Returns a WaveData object or an integer: 1=not found; 2=invalid format; 3=format not supported; 4=no sound in the data (the last error is helpful for diagnosing whether anything was recorded, since a recording device can record even if no microphone is attached.) beginRecord() - Starts recording. Returns 0 if successful. getRecorderSample() - Gets a single sample from the microphone. The beginRecord function must have been called beforehand. stopRecord() - Stops recording without saving the recording to a file. endRecord(file) - Stops recording and saves the recording to a file. =end if !defined?(safeExists?) def safeExists?(f) ret=false File.open(f,"rb") { ret=true } rescue nil return ret end end def pbSaveSoundData(samples, freq, filename) samples="" if !samples data=[ 0x46464952,samples.length+0x2C, 0x45564157,0x20746d66,0x10, 0x01,0x01, # PCM,mono freq,freq, 1,8, # 8-bit 0x61746164,samples.length ].pack("VVVVVvvVVvvVV") f=File.open(filename,"wb") if f f.write(data) f.write(samples) f.close end end # plays 8 bit mono sound data (default: 11025 Hz) def pbPlaySoundData(samples,volume,async=false,sampleFreq=11025) return if !samples || samples.length==0 || sampleFreq==0 waveOutOpen=Win32API.new("winmm.dll","waveOutOpen","plplll","l") waveOutPrepareHeader=Win32API.new("winmm.dll","waveOutPrepareHeader","lpl","l") waveOutWrite=Win32API.new("winmm.dll","waveOutWrite","lpl","l") waveOutSetVolume=Win32API.new("winmm.dll","waveOutSetVolume","ll","l") waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") waveOutGetNumDevs=Win32API.new("winmm.dll","waveOutGetNumDevs","","l") getStringAddress=proc {|obj| return 0 if !obj buffer=" "*4 rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') stringPointer=(obj.__id__*2)+12 rtlMoveMemory_pi.call(buffer,stringPointer,4) return buffer.unpack("L")[0] } saveToTemp=proc {|samples,freq| chars="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" for i in 1...1000 name=""; 8.times { name+=chars[rand(chars.length),1] } name=ENV["TEMP"]+"\\"+name+"_tmp.wav" if !safeExists?(name) pbSaveSoundData(samples,freq,name) return name end end return nil } playThenDelete=proc {|path,volume,length,async| return if !path || !safeExists?(path) thread=Thread.new { Thread.stop _path=Thread.current[:path] _length=Thread.current[:length] sleep(_length); File.delete(_path) rescue nil } thread[:path]=path thread[:length]=length Audio.se_play(path,volume) thread.run sleep(length); } waveHdr=[getStringAddress.call(samples),samples.length, 0,0,0,0,0,0].pack("V*") # 8 bit mono sound data waveFormat=[0x01,0x01,sampleFreq,sampleFreq,1,8,0].pack("vvVVvvv") duration=samples.length waveOutHandle=" "*4 code=waveOutOpen.call(waveOutHandle,-1,waveFormat,0,0,0) if code!=0 timeLength=samples.length.to_f/sampleFreq path=saveToTemp.call(samples,sampleFreq) playThenDelete.call(path,volume,timeLength,async) return end waveOutHandle=waveOutHandle.unpack("L")[0] volume=(volume*65535/100) volume=(volume<<16)|volume waveOutSetVolume.call(waveOutHandle,volume) code=waveOutPrepareHeader.call(waveOutHandle,waveHdr,waveHdr.length) if code!=0 waveOutClose.call(waveOutHandle) return end thread=Thread.new { Thread.stop waveOut=Thread.current[:waveOut] waveHdr=Thread.current[:waveHeader] waveData=Thread.current[:waveData] waveOutUnprepareHeader=Win32API.new("winmm.dll","waveOutUnprepareHeader","lpl","l") waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") loop do sleep(1) hdr=waveHdr.unpack("V*") flags=hdr[4] if (flags&1)==1 # All done waveOutUnprepareHeader.call(waveOut,waveHdr,waveHdr.length) waveOutClose.call(waveOut) break end end } thread[:waveOut]=waveOutHandle thread[:waveHeader]=waveHdr thread[:waveData]=@samples if waveOutWrite.call(waveOutHandle,waveHdr,waveHdr.length)!=0 waveOutClose.call(waveOutHandle) return end thread.run sleep(@duration/1000.0) if !async return end class NoteEnvelope attr_accessor :fall attr_accessor :max attr_reader :envelope def initialize(fall=1200,maxPoint=30) @fall=fall # time until fall to zero @maxPoint=maxPoint # maximum point @envelope=SoundEnvelope.new end def falling(duration) return self if duration<=0 @envelope.changeDiscrete(0,@maxPoint) if duration>=@fall @envelope.change(fall,0) @envelope.change(duration-fall,0) else @envelope.change(duration,@maxPoint*duration/@fall) end @envelope.changeDiscrete(0,0) return self end def sweeping(duration,sweepDuration) return self if duration<=0 return steady(duration) if sweepDuration<=0 @envelope.changeDiscrete(0,@maxPoint) falling=true while duration>0 dur=duration>sweepDuration ? sweepDuration : duration if falling self.falling(dur) else sd=sweepDuration if sd>@fall d=[(sweepDuration-@fall),dur].min @envelope.change(d,0) dur-=d sd-=d end if d==sd @envelope.change(dur,@maxPoint) else @envelope.change(dur,@maxPoint*(@fall-(sd-dur))/@fall) end end falling=!falling duration-=sweepDuration end @envelope.changeDiscrete(0,0) return self end def rest(duration) if duration>0 @envelope.changeDiscrete(0,0) @envelope.changeDiscrete(duration,0) end return self end def steady(duration) if duration>0 @envelope.changeDiscrete(0,@maxPoint) @envelope.changeDiscrete(duration,@maxPoint) @envelope.changeDiscrete(0,0) end return self end end # A class for holding audio data in memory. This class # is easy to serialize into the save file. class WaveData def initialize(samplesPerSec,samples) @freq=samplesPerSec @samples=samples.is_a?(String) ? samples.clone : samples.pack("C*") end def setSamples(samples) @samples=samples.is_a?(String) ? samples.clone : samples end def self._load(string) data=Marshal.load(string) ret=self.new(data[0],[]) ret.setSamples(Zlib::Inflate.inflate(data[1])) return ret end def _dump(depth=100) return Marshal.dump([@freq,Zlib::Deflate.deflate(@samples)]) end def intensity distance=@samples.length/2000 i=distance/2 count=0 volume=0 while i<@samples.length vol=(@samples[i]-128).abs vol=127 if vol>127 if vol>=16 volume+=vol count+=1 end i+=distance end return 0 if count==0 return volume/count # from 0 through 127 end def time return @freq==0 ? 0.0 : (@samples.length)*1.0/@freq end def play # Play sound data asynchronously pbPlaySoundData(@samples,100,true,@freq) end def save(filename) pbSaveSoundData(@samples,@freq,filename) end end # A class for specifying volume, frequency, and timbre envelopes. class SoundEnvelope; include Enumerable def initialize(env=nil) @e=[]; set(env) if env end def self.fromToneFile(file) envelope=self.new File.open(file,"rb"){|f| f.gets if !f.eof? while !f.eof? ln=f.gets if ln[ /^(\d+)\s+(\d+)/ ] envelope.addValueChange($1.to_i,$2.to_i) end end } return envelope end def length; @e.length; end def [](x); @e[x]; end def each; @e.each {|x| yield x }; end def clear; @e.clear; end def changeAbsolute(pos,volume) return self if pos<0 velength=@e.length if velength>0 @e.push([pos,volume]) stableSort() else @e.push([pos,volume]) end return self end def self.initial(value) return self.new.change(0,value) end def self.smoothVolume(duration,volume) env=self.new return env if duration<8 env.change(0,0) env.change(5,volume) env.changeAbsolute(duration-10,volume) env.changeAbsolute(duration-5,0) env.changeAbsolute(duration,0) return env end # Creates a volume envelope using the given attack, decay, and # release times and the given sustain level. # duration - duration of the sound # attack - attack time (in ms), or time between the start of the sound # and the time where the sound reaches its maximum volume. # If this value is less than 0, the sound will decay from silence to # the sustain volume instead (see below). # decay - decay time (in ms), or time after the attack phase until # the time where the sound reaches its sustain volume # sustain - sustain volume, or normal volume of sound (0-100) # release - release time (in ms), or amount of time to fade out the # sound when it reaches its end. The sound's duration includes its # release time. def self.attackDecayRelease(duration,attack,decay,sustain,release) env=self.new if attack<=0 env.change(0,attack==0 ? 100 : 0) else env.change(attack,100) end env.change(decay,sustain) env.changeAbsolute(duration-release,sustain) if release>20 env.changeAbsolute(duration-release-4,0) end env.changeAbsolute(duration,0) return env end def self.blink(value,onDuration,offDuration,totalDuration) return self.new.addValueChanges( value,onDuration,0,offDuration).repeat(totalDuration) end def change(delta,volume) return self if delta<0 velength=@e.length if velength>0 @e.push([@e[velength-1][0]+delta,volume]) else @e.push([delta,volume]) end return self end def duration return @e.length==0 ? 0 : @e[@e.length-1][0] end def value return @e.length==0 ? 0 : @e[@e.length-1][1] end def addValueChange(value,duration) changeDiscrete(0,value) changeDiscrete(duration,value) return self end def sweep(value1,value2,duration,sweepDuration=1000/16) val=true while duration>0 dur=durationdesiredDuration deltaNew=(newDuration-self.duration) deltaDesired=(desiredDuration-self.duration) newValue=deltaNew==0 ? self.value : self.value+(item[1]-self.value)*deltaDesired/deltaNew @e.push([desiredDuration,newValue]) break else @e.push([newDuration,item[1]]) end i+=1 if i>=oldLength i=0; currentDuration+=oldDuration end end return self end # Changes the volume, frequency, etc. abruptly without blending. def changeDiscrete(delta,volume) return self if delta<0 velength=@e.length if velength>0 oldValue=@e[velength-1][1] oldDelta=@e[velength-1][0] newDelta=oldDelta+delta newValue=oldValue if newDelta!=oldDelta || newValue!=oldValue @e.push([newDelta,newValue]) oldDelta=newDelta oldValue=newValue end newValue=volume if newDelta!=oldDelta || newValue!=oldValue @e.push([newDelta,newValue]) end else @e.push([delta,volume]) end return self end def set(value) if value.is_a?(SoundEnvelope) || value.is_a?(Array) @e.clear for v in value; @e.push(v); end end return self end private def stableSort pm=1;while pm<@e.length pl=pm; while pl>0 && @e[pl-1][0]>@e[pl][0] tmp=@e[pl]; @e[pl]=@e[pl-1]; @e[pl-1]=tmp pl-=1; end pm+=1;end end end # internal class class SoundEnvelopeIterator# :nodoc: def initialize(env) @env=env @envIndex=0 end def getValue(frame) value=0 if @envIndex==@env.length value=@env[@envIndex-1][1] elsif @envIndex==0 value=@env[@envIndex][1] else lastPos=@env[@envIndex-1][0] thisPos=@env[@envIndex][0] if thisPos!=lastPos lastVolume=@env[@envIndex-1][1] thisVolume=@env[@envIndex][1] value=(thisVolume-lastVolume)*(frame-lastPos)/(thisPos-lastPos)+lastVolume else value=@env[@envIndex][1] end end while @envIndex+1<=@env.length && @env[@envIndex][0]==frame.to_i @envIndex+=1 end return value end end # internal class class WaveForm# :nodoc: SAMPLEFREQ=11025 def initialize(proc,freq,duration,timbre=0.5) @duration=duration # in ms @volumeEnvelope=SoundEnvelope.new @freqEnvelope=SoundEnvelope.new @timbreEnvelope=SoundEnvelope.new @proc=proc @freq=freq @timbre=timbre if @freq.is_a?(Array) || @freq.is_a?(SoundEnvelope) @freqEnvelope.set(@freq) @freq=@freq.length>0 ? @freq[0][1] : 800 end if @timbre.is_a?(Array) || @timbre.is_a?(SoundEnvelope) @timbreEnvelope.set(@timbre) @timbre=@timbre.length>0 ? @timbre[0][1] : 0.5 end end def setFrequencyEnvelope(env) if env.is_a?(Numeric) @freqEnvelope.clear @freqEnvelope.addValueChange(env,@duration) else @freqEnvelope.set(env) end end def setVolumeEnvelope(env) if env.is_a?(Numeric) @volumeEnvelope.clear @volumeEnvelope.addValueChange(env,@duration) else @volumeEnvelope.set(env) end end def lcm(x,y) return y if x==0; return x if y==0 return x if x==y if x>y; incr=x while x%y!=0; x+=incr; end return x else; incr=y while y%x!=0; y+=incr; end return y end end def start @i=0 @volumeIterator=SoundEnvelopeIterator.new(@volumeEnvelope) @freqIterator=SoundEnvelopeIterator.new(@freqEnvelope) @timbreIterator=SoundEnvelopeIterator.new(@timbreEnvelope) @sampleCount=@duration*SAMPLEFREQ/1000 @samples=" "*@sampleCount @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max @step=1.0/@exactSamplesPerPass @exactCounter=0 end def self.frac(x) return x-x.floor end def nextSample vol=100 fframe=@i*1000.0/SAMPLEFREQ if @volumeEnvelope.length>0 # Update volume vol=@volumeIterator.getValue(fframe) end if @proc updateBuffer=false if @freqEnvelope.length>0 # Update frequency freq=@freqIterator.getValue(fframe) if freq!=@freq # update sample buffer @freq=freq @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max @step=1.0/@exactSamplesPerPass end end if @timbreEnvelope.length>0 # Update timbre @timbre=@timbreIterator.getValue(fframe) end if @freq==0 || vol==0 @samples[@i]=0x80 else sample=@proc.call(@exactCounter,@timbre) @samples[@i]=0x80+(vol*sample).round end else # noise v=vol.abs.to_i*2 @samples[@i]=0x80+(rand(v).to_i-(v/2)) end @i+=1 @exactCounter+=@step while @exactCounter>1.0; @exactCounter-=1.0; end end def mixSamples(other) for i in 0...other.sampleCount newSamp=((@samples[i]-0x80)+(other.samples[i]-0x80))/2+0x80 @samples[i]=newSamp end end def play(volume=100, async=false) pbPlaySoundData(@samples,volume,async,SAMPLEFREQ) end def generateSound start while @i<@sampleCount nextSample end end def toWaveData return WaveData.new(SAMPLEFREQ,@samples) end def save(filename) pbSaveSoundData(@samples,SAMPLEFREQ,filename) end attr_accessor :samples,:sampleCount end module Audio module Note REST = 0 GbelowC = 196 A = 220 Asharp = 233 B = 247 C = 262 Csharp = 277 D = 294 Dsharp = 311 E = 330 F = 349 Fsharp = 370 G = 392 Gsharp = 415 end module NoteLength WHOLE = 1600 HALF = WHOLE/2 QUARTER = HALF/2 EIGHTH = QUARTER/2 SIXTEENTH = EIGHTH/2 end def self.noise(durationInMs=200, volume=100, async=false) return if durationInMs<=0 waveForm=WaveForm.new(nil,0,durationInMs) if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) waveForm.setVolumeEnvelope(volume) volume=100 end waveForm.generateSound waveForm.play(volume,async) end # internal method def self.envelopeDuration(env) return 0 if !env if env.is_a?(SoundEnvelope) || env.is_a?(Array) return SoundEnvelope.new(env).duration end return 0 end def self.oscillateDouble(durationInMs, freq1, freq2, volume1, volume2, timbre1, timbre2, proc1, proc2, async=false) return if durationInMs<0 freq1Zero=(freq1.is_a?(Numeric) && freq1<=0) || (freq1.is_a?(Array) && freq1.length==0) freq2Zero=(freq2.is_a?(Numeric) && freq2<=0) || (freq2.is_a?(Array) && freq2.length==0) if freq1Zero && freq2Zero Thread.sleep(durationInMs/1000.0) if !async return end if durationInMs==0 || durationInMs==nil durationInMs=[ envelopeDuration(freq1), envelopeDuration(freq2), envelopeDuration(volume1), envelopeDuration(volume2), envelopeDuration(timbre1), envelopeDuration(timbre2) ].max return if durationInMs<=0 end waveForm1=WaveForm.new(proc1,freq1,durationInMs,timbre1) waveForm2=WaveForm.new(proc2,freq2,durationInMs,timbre2) waveForm1.setVolumeEnvelope(volume1) waveForm2.setVolumeEnvelope(volume2) waveForm1.generateSound waveForm2.generateSound waveForm1.mixSamples(waveForm2) waveForm1.play(100,async) end def self.oscillate(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false, &block) return if durationInMs<0 if (freq.is_a?(Numeric) && freq<=0) || (freq.is_a?(Array) && freq.length==0) Thread.sleep(durationInMs/1000.0) if !async return end if durationInMs==0 || durationInMs==nil durationInMs=[ envelopeDuration(freq), envelopeDuration(volume), envelopeDuration(timbre)].max return if durationInMs<=0 end waveForm=WaveForm.new(block,freq,durationInMs,timbre) if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) waveForm.setVolumeEnvelope(volume) volume=100 end waveForm.generateSound waveForm.play(volume,async) end def self.frac(x) return x-x.floor end TWOPI=Math::PI*2 @@sineProc2=proc {|z,timbre| x=z1 for i in 0...tone.length dtmf(tone[i,1],durationInMs,volume,false) end end t1=0 t1=1209 if "14ghi7pqrs*".include?(tone) t1=1336 if "2abc5jkl8tuv0".include?(tone) t1=1477 if "3def6mno9wxyz#".include?(tone) t1=1633 if "ABCD".include?(tone) t2=0 t2=697 if "12abc3defA".include?(tone) t2=770 if "4ghi5jkl6mnoB".include?(tone) t2=852 if "7pqrs8tuv9wxyzC".include?(tone) t2=941 if "*0#D".include?(tone) return if t1==0 || t2==0 f1=Math::PI*2.0*t1/WaveForm::SAMPLEFREQ f2=Math::PI*2.0*t2/WaveForm::SAMPLEFREQ doubleSine(durationInMs,t1,t2,volume,volume,0.5,0.5,async) end def self.beep(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false) square(durationInMs,freq,volume,timbre,async) end @@triangleProc2=proc {|z,timbre| z>4 next if t==0 || t==15 freqs=[44100,22050,11025,48000] bitrates=[32,40,48,56,64,80,96,112,128,160,192,224,256,320] bitrate=bitrates[t] t=(rstr[1]>>2)&3 freq=freqs[t] t=(rstr[1]>>1)&1 filesize=FileTest.size(filename) frameLength=((144000*bitrate)/freq)+t numFrames=filesize/(frameLength+4) time=(numFrames*1152.0/freq) break end end } return time end # Creates wave data from the given WAV file path def getWaveData(filename) time=-1 fgetdw=proc{|file| (file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0)) } fgetw=proc{|file| (file.eof? ? 0 : (file.read(2).unpack("v")[0] || 0)) } if !safeExists?(filename) return 1 # Not found end File.open(filename,"rb"){|file| file.pos=0 fdw=fgetdw.call(file) if fdw==0x46464952 # "RIFF" filesize=fgetdw.call(file) wave=fgetdw.call(file) if wave!=0x45564157 # "WAVE" return 2 end fmt=fgetdw.call(file) if fmt!=0x20746d66 # "fmt " return 2 end fmtsize=fgetdw.call(file) format=fgetw.call(file) if format!=1 return 3 # unsupported end channels=fgetw.call(file) # channels (1 or 2) if channels!=1 return 3 # unsupported end rate=fgetdw.call(file) # samples per second bytessec=fgetdw.call(file) # avg bytes per second if bytessec==0 return 2 end bytessample=fgetw.call(file) # bytes per sample bitssample=fgetw.call(file) # bits per sample (8, 16, etc.) if bitssample!=8 && bitssample!=16 return 3 # unsupported end data=fgetdw.call(file) if data!=0x61746164 # "data" return 2 end datasize=fgetdw.call(file) data=file.read(datasize) samples=nil if bitssample==8 samples=data.unpack("C*") start=0 for i in 0...samples.length s=samples[i] if s<0x70 || s>=0x90 start=i break end end finish=start i=samples.length-1 while i>=start s=samples[i] if s<0x70 || s>=0x90 finish=i+1 break end i-=1 end if finish==start return 4 # Nothing was recorded end start=0 finish=samples.length wave=WaveData.new(rate,samples[start,finish-start]) return wave elsif bitssample==16 samples=data.unpack("v*") start=0 for i in 0...samples.length s=samples[i] if s>0x1000 && s<0xF000 start=i break end end finish=start i=samples.length-1 while i>=start s=samples[i] if s<0x1000 && s<0xF000 finish=i+1 break end i-=1 end if finish==start return 4 # Nothing was recorded end start=0 # Convert to 8-bit samples for i in start...finish samples[i]=((samples[i]-0x8000)>>8)&0xFF end finish=samples.length return WaveData.new(rate,samples[start,finish-start]) end end } return 2 end ############################### begin MciSendString = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','l') MciErrorString = Win32API.new('winmm','mciGetErrorString','%w(l,p,l)','l') rescue MciSendString=nil MciErrorString=nil end # Starts recording. Returns 0 if successful. def beginRecord return 256+72 if !MciSendString MciSendString.call("open new type waveaudio alias RECORDER buffer 4",0,0,0) MciSendString.call("set RECORDER channels 1",0,0,0) retval=MciSendString.call("record RECORDER",0,0,0) if retval!=0 MciSendString.call("close RECORDER",0,0,0) end return retval end # Gets a single sample from the microphone. # The beginRecord or beginRecordUI function must have been called beforehand. def getRecorderSample return 0x8000 if !MciSendString buffer="\0"*256 ret=0 MciSendString.call("stop RECORDER",0,0,0) MciSendString.call("status RECORDER bitspersample",buffer,256,0) bitspersample=buffer.to_i MciSendString.call("status RECORDER level",buffer,256,0) MciSendString.call("record RECORDER",0,0,0) if bitspersample==8 ret=buffer.to_i<<8 # max 128 else ret=buffer.to_i # max 0x8000 end return ret end def stopRecord() return if !MciSendString MciSendString.call("stop RECORDER",0,0,0) MciSendString.call("close RECORDER",0,0,0) end def endRecord(file) return if !MciSendString MciSendString.call("stop RECORDER",0,0,0) if file && file!="" MciSendString.call("save RECORDER #{file}",0,0,0) end MciSendString.call("close RECORDER",0,0,0) end #Audio.sine(140,SoundEnvelope.initial(6400).change(140,11400),50)