rpgmaker-linux/mkxp-z/Kawariki-patches/ports/dummyAudioUtilities.rb
2024-11-19 12:16:27 +02:00

1351 lines
38 KiB
Ruby

=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=duration<sweepDuration ? duration : sweepDuration
if val
addValueChange(value1,dur)
else
addValueChange(value2,dur)
end
val=!val
duration-=sweepDuration
end
addValueChange(0,0)
return self
end
def addValueChanges(*arg)
i=0;while i<arg.length
changeDiscrete(0,arg[i])
changeDiscrete(arg[i+1]||0,arg[i])
i+=2;end
return self
end
def repeat(desiredDuration)
return self if @e.length==0 || desiredDuration<0
oldDuration=self.duration
currentDuration=oldDuration
return self if oldDuration==0
oldLength=self.length
i=0
while currentDuration<desiredDuration
item=@e[i]
newDuration=currentDuration+item[0]
if newDuration>desiredDuration
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=z<timbre ? (z*0.5/timbre) : ((z-timbre)*0.5/(1.0-timbre))+0.5
next Math.sin(x*TWOPI)
}
# Generates a sine wave.
def self.sine(durationInMs=200, freq=800, volume=100,timbre=0.5, async=false)
oscillate(durationInMs,freq,volume,timbre,async,&@@sineProc2)
end
# Generates two sine waves played at once.
def self.doubleSine(durationInMs=200, freq1=800, freq2=800, volume1=100,
volume2=100, timbre1=0.5, timbre2=0.5, async=false)
oscillateDouble(durationInMs,freq1,freq2,volume1,volume2,
timbre1,timbre2,@@sineProc2,@@sineProc2,async)
end
@@noiseProc2=proc {|i,timbre|
n=(rand(256)/255.0)*0.1
rand(2)==0 ? 1-n : -1+n
}
def self.noise2(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false)
oscillate(durationInMs,freq,volume,timbre,async,&@@noiseProc2)
end
@@squareProc2=proc {|i,timbre| i<timbre ? 1 : -1 }
# Generates a square wave.
def self.square(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false)
oscillate(durationInMs,freq,volume,timbre,async,&@@squareProc2)
end
# Generates a telephone button tone.
def self.dtmf(tone, durationInMs=200, volume=95, async=false)
return if !tone
if tone.length>1
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<timbre ? (1-(z.to_f/timbre)*2.0) : ((((z-(1.0-timbre)).to_f/(1.0-timbre))*2.0)-1.0)
}
# Generates a triangle wave.
# "timbre" specifies the timbre of the tone; from 0 through 1
# a timbre of 0 or 1 generates a saw wave
def self.triangle(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false)
oscillate(durationInMs,freq,volume,timbre,async,&@@triangleProc2)
end
@@triangleProc3=proc {|z,timbre|
v=z<timbre ? (1-(z.to_f/timbre)*2.0).abs : -(((((z-(1.0-timbre)).to_f/(1.0-timbre))*2.0)-1.0).abs)
(v<0) ? -(0.5+(-v/2)) : 0.5+(v/2)
}
# Generates an NES triangle wave.
# "timbre" specifies the timbre of the tone; from 0 through 1
def self.triangle2(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false)
oscillate(durationInMs,freq,volume,timbre,async,&@@triangleProc3)
end
def self.playTone(tone,async=false)
beep(0,SoundEnvelope.fromToneFile(tone),80,0.5,async)
end
# Generates a saw wave.
def self.saw(durationInMs=200, freq=800, volume=100, async=false)
# same as triangle wave with timbre of 1
triangle(durationInMs,freq,volume,1.0,async)
end
end
def ringtone(duration,volume=95)
Audio.sine(duration,
SoundEnvelope.new.addValueChanges(
1209,500,1477,500,0,2000).repeat(duration),volume)
end
def backspace(duration=50,volume=95)
Audio.doubleSine(duration,852,1633,volume,volume)
end
def callwaiting(duration=50,volume=95)
Audio.doubleSine(duration,941,1633,volume,volume)
end
def callheld(duration,volume=95)
Audio.doubleSine(duration,
SoundEnvelope.new.addValueChanges(
480,200,0,150,480,200,0,150,480,200,0,1950).repeat(duration),
SoundEnvelope.new.addValueChanges(
440,200,0,150,440,200,0,150,440,200,0,1950).repeat(duration),
volume,volume)
end
def loudFastBusy(duration,volume=95)
Audio.doubleSine(duration,
SoundEnvelope.blink(480,250,250,duration),
SoundEnvelope.blink(620,250,250,duration)
)
end
def toneUS(toneType, duration, volume = 95)
case toneType
when 0 # dialtone
Audio.doubleSine(duration, 350, 440, volume)
when 1 # busy
Audio.doubleSine(duration,
SoundEnvelope.blink(480, 500, 500, duration),
SoundEnvelope.blink(620, 500, 500, duration),
volume, volume)
when 2 # ringback
Audio.doubleSine(duration,
SoundEnvelope.blink(480, 2000, 4000, duration),
SoundEnvelope.blink(440, 2000, 4000, duration),
volume, volume)
when 3 # callfailed
Audio.doubleSine(duration,
SoundEnvelope.blink(480, 300, 200, duration),
SoundEnvelope.blink(620, 300, 200, duration),
volume, volume)
end
end
def toneDE(toneType,duration,volume=95)
case toneType
when 0: # dialtone
Audio.sine(duration,425,volume)
when 1: # busy
Audio.sine(duration,SoundEnvelope.blink(425,480,480,duration),volume)
when 2: # ringback
Audio.sine(duration,SoundEnvelope.blink(425,1000,4000,duration),volume)
when 3: # callfailed
Audio.sine(duration,SoundEnvelope.blink(425,240,240,duration),volume)
end
end
def toneFR(toneType,duration,volume=95)
case toneType
when 0:
Audio.sine(duration,440,volume)
when 1:
Audio.sine(duration,SoundEnvelope.blink(440,500,500,duration),volume)
when 2:
Audio.sine(duration,SoundEnvelope.blink(440,1500,3500,duration),volume)
when 3:
Audio.sine(duration,SoundEnvelope.blink(440,250,250,duration),volume)
end
end
def toneIsrael(toneType,duration,volume=95)
case toneType
when 0:
Audio.sine(duration,400,volume)
when 1:
Audio.sine(duration,SoundEnvelope.blink(400,500,500,duration),volume)
when 2:
Audio.sine(duration,SoundEnvelope.blink(400,1000,3000,duration),volume)
when 3
Audio.sine(duration,SoundEnvelope.blink(400,250,250,duration),volume)
end
end
def toneNL(toneType,duration,volume=95)
case toneType
when 0:
Audio.sine(duration,425,volume)
when 1:
Audio.sine(duration,SoundEnvelope.blink(425,500,500,duration),volume)
when 2:
Audio.sine(duration,SoundEnvelope.blink(425,1000,4000,duration),volume)
when 3:
Audio.sine(duration,SoundEnvelope.blink(425,250,250,duration),volume)
end
end
def toneGB(toneType,duration,volume=95)
case toneType
when 0:
Audio.sine(duration,350,440,volume)
when 1:
Audio.sine(duration,SoundEnvelope.blink(400,375,375,duration),volume)
when 2:
Audio.doubleSine(duration,
SoundEnvelope.new.addValueChanges(400,400,0,200,400,400,0,2000).repeat(duration),
SoundEnvelope.new.addValueChanges(450,400,0,200,450,400,0,2000).repeat(duration),
volume,volume)
when 3:
Audio.sine(duration,
SoundEnvelope.new.addValueChanges(400,400,0,350,400,225,0,525).repeat(duration),
volume)
end
end
# internal function
def getOggPage(file)
pos=file.pos
fgetdw=proc{|file|
(file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0))
}
dw=fgetdw.call(file)
return nil if dw!=0x5367674F
header=file.read(22)
bodysize=0
hdrbodysize=(file.read(1)[0] rescue 0)
hdrbodysize.times do
bodysize+=(file.read(1)[0] rescue 0)
end
ret=[header,file.pos,bodysize,file.pos+bodysize]
return ret
end
# internal function
def oggfiletime(file)
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))
}
pages=[]
page=nil
begin
page=getOggPage(file)
if page
pages.push(page)
file.pos=page[3]
end
end while page
if pages.length==0
return -1
end
curserial=nil
i=-1
pcmlengths=[]
rates=[]
for page in pages
header=page[0]
serial=header[10,4].unpack("V")
frame=header[2,8].unpack("C*")
frameno=frame[7]
frameno=(frameno<<8)|frame[6]
frameno=(frameno<<8)|frame[5]
frameno=(frameno<<8)|frame[4]
frameno=(frameno<<8)|frame[3]
frameno=(frameno<<8)|frame[2]
frameno=(frameno<<8)|frame[1]
frameno=(frameno<<8)|frame[0]
if serial!=curserial
curserial=serial
file.pos=page[1]
packtype=(file.read(1)[0] rescue 0)
string=file.read(6)
if string!="vorbis"
return -1
end
if packtype!=1
return -1
end
i+=1
version=fgetdw.call(file)
if version!=0
return -1
end
channels=(file.read(1)[0] rescue 0)
rates[i]=fgetdw.call(file)
end
pcmlengths[i]=frameno
end
ret=0.0
for i in 0...pcmlengths.length
ret+=pcmlengths[i]*1.0/rates[i]
end
return ret
end
def getPlayTime(filename)
if safeExists?(filename)
return [getPlayTime2(filename),0].max
elsif safeExists?(filename+".wav")
return [getPlayTime2(filename+".wav"),0].max
elsif safeExists?(filename+".mp3")
return [getPlayTime2(filename+".mp3"),0].max
elsif safeExists?(filename+".ogg")
return [getPlayTime2(filename+".ogg"),0].max
else
return 0
end
end
def getPlayTime2(filename)
time=-1
return -1 if !safeExists?(filename)
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))
}
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 -1
end
fmt=fgetdw.call(file)
if fmt!=0x20746d66 # "fmt "
return -1
end
fmtsize=fgetdw.call(file)
format=fgetw.call(file)
channels=fgetw.call(file)
rate=fgetdw.call(file)
bytessec=fgetdw.call(file)
if bytessec==0
return -1
end
bytessample=fgetw.call(file)
bitssample=fgetw.call(file)
data=fgetdw.call(file)
if data!=0x61746164 # "data"
return -1
end
datasize=fgetdw.call(file)
time=(datasize*1.0)/bytessec
return time
elsif fdw==0x5367674F # "OggS"
file.pos=0
time=oggfiletime(file)
return time
end
file.pos=0
# Find the length of an MP3 file
while true
rstr=""
ateof=false
while !file.eof?
if (file.read(1)[0] rescue 0)==0xFF
begin; rstr=file.read(3); break; rescue; ateof=true; break; end
end
end
break if ateof || !rstr || rstr.length!=3
if rstr[0]==0xFB
t=rstr[1]>>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)