SoundEasy.pbi include file adds CreateSound() command
Posted: Sat Feb 23, 2013 6:27 pm
This was built using modified core procedures from my application, AudioPlayground
This include file will allow you to add sounds to your programs without any external sound files.
You will have a new command: CreateSound().
Syntax:
Result = CreateSound(#sound, [frequency.f], [duration.f], [amplitude], [sweepStop.f], [waveform], [fadeIn], [fadeOut])
#sound { The number to identify the new sound. #PB_Any can be used to auto-generate this number. }
frequency { The frequency in hertz of the new sound. Default = 440. Range = 20 to sample rate / 2 - 1 }
duration { The length of the new sound in seconds. Default = 1.0 seconds. Minimum = 0.1 seconds. }
amplitude { the peak waveform magnitude from 0 to 100 (full scale). Default = 25 }
sweepStop { Sets the final frequency of a frequency sweep. Default = 0 (sweep disabled). Range is same as frequency. }
waveform { Selects 1 of 9 different waveforms. Default = #WF_Sinewave. }
............ { possible waveforms are #WF_Sinewave, #WF_SawTooth, #WF_BuzzSaw, #WF_SquareWave, #WF_Triangle, #WF_Chunosta,
............ #WF_Organ, #WF_Noise, #WF_GuidedNoise }
fadeIn { turns the fadeIn effect on/off. Default (0) = off , (1) = on }
fadeOut { turns the fadeOut effect on/off. Default (0) = off , (1) = on }
Edit: SoundEasy.pbi version 2.0
.......... InitAudioBuffer() is no longer needed in your code.
.......... This is now automatically handled by the CreateSound() procedure.
.......... A new procedure: SetSoundParameters() has been added.
.......... You will not need this procedure unless you want to change the default wav sound settings: sample rate, resolution, channels.
Edit: made compatible with EnableExplicit.
Here is a demo and the 'SoundEasy' include file.
Here is the include file.
This include file will allow you to add sounds to your programs without any external sound files.
You will have a new command: CreateSound().
Syntax:
Result = CreateSound(#sound, [frequency.f], [duration.f], [amplitude], [sweepStop.f], [waveform], [fadeIn], [fadeOut])
#sound { The number to identify the new sound. #PB_Any can be used to auto-generate this number. }
frequency { The frequency in hertz of the new sound. Default = 440. Range = 20 to sample rate / 2 - 1 }
duration { The length of the new sound in seconds. Default = 1.0 seconds. Minimum = 0.1 seconds. }
amplitude { the peak waveform magnitude from 0 to 100 (full scale). Default = 25 }
sweepStop { Sets the final frequency of a frequency sweep. Default = 0 (sweep disabled). Range is same as frequency. }
waveform { Selects 1 of 9 different waveforms. Default = #WF_Sinewave. }
............ { possible waveforms are #WF_Sinewave, #WF_SawTooth, #WF_BuzzSaw, #WF_SquareWave, #WF_Triangle, #WF_Chunosta,
............ #WF_Organ, #WF_Noise, #WF_GuidedNoise }
fadeIn { turns the fadeIn effect on/off. Default (0) = off , (1) = on }
fadeOut { turns the fadeOut effect on/off. Default (0) = off , (1) = on }
Edit: SoundEasy.pbi version 2.0
.......... InitAudioBuffer() is no longer needed in your code.
.......... This is now automatically handled by the CreateSound() procedure.
.......... A new procedure: SetSoundParameters() has been added.
.......... You will not need this procedure unless you want to change the default wav sound settings: sample rate, resolution, channels.
Edit: made compatible with EnableExplicit.
Here is a demo and the 'SoundEasy' include file.
Code: Select all
; Demo of SoundEasy.pbi include file.
; Demonstrates the CreateSound() command.
; Author: BasicallyPure
; Date: 2.24.2013
IncludeFile "SoundEasy.pbi"
flags.i = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
If OpenWindow(0, 0, 0, 225, 105, "CreateSound() Demo", flags) And InitSound()
playButton_1.i = ButtonGadget(#PB_Any,10,10,100,25,"Play Sound 1")
playButton_2.i = ButtonGadget(#PB_Any,10,40,100,25,"Play Sound 2")
playButton_3.i = ButtonGadget(#PB_Any,10,70,100,25,"Play Sound 3")
; Refer to the comments in the SoundEasy.pbi file for the correct CreateSound() syntax.
snd_1.i = CreateSound(#PB_Any, 500, 2.5, 45, 1500, #WF_Triangle, 0, 1)
snd_2.i = CreateSound(#PB_Any, 1000) ; a simple sine wave 1000 Hz, 1 second
snd_3.i = CreateSound(#PB_Any, 800, 0.5, 10, 100, #WF_BuzzSaw, 1, 0)
Repeat
event = WaitWindowEvent()
If event = #PB_Event_Gadget
Select EventGadget()
Case playButton_1
If IsSound(snd_1) : PlaySound(snd_1) : EndIf
Case playButton_2
If IsSound(snd_2) : PlaySound(snd_2) : EndIf
Case playButton_3
If IsSound(snd_3) : PlaySound(snd_3) : EndIf
EndSelect
EndIf
Until event = #PB_Event_CloseWindow
EndIf
Code: Select all
; Title: SoundEasy.pbi
; Author: BasicallyPure
; Date: 2.26.2013
; Date: 5.13.2016 edit: made EnableExplicit compatible
; Version: 2.1
; OS: Windows, Linux, and Mac
; PB ver. 5.10
;
; Save this code with the filename "SoundEasy.pbi"
; Use IncludeFile "SoundEasy.pbi" in your code.
; You will have two new procedures you can use in your programs.
;
; 1. CreateSound(#sound, [frequency.f], [duration.f], [amplitude], [sweepStop.f], [waveform], [fadeIn], [fadeOut])
; Refer to the comments in the CreateSound() procedure for a description of the syntax.
;
; 2. SetSoundParameters(sampleRate, resolution, channels)
; You do not need to use this procedure unless you want to change the default sound parameters.
; Refer to the comments in the SetSoundParameters() procedure for a description and syntax.
;
;{ waveform styles
Enumeration
#WF_SineWave
#WF_SawTooth
#WF_BuzzSaw
#WF_SquareWave
#WF_Triangle
#WF_Chunosta
#WF_Organ
#WF_Noise
#WF_GuidedNoise
EndEnumeration
;}
Procedure SetSoundParameters(sampleRate = 44100, resolution = 2, channels = 1)
; use this procedure only if you need to change the default sound paramaters.
; sampleRate { number of samples per second: 5512, 11025, 22050, 44100 only}
; resolution { bytes per sample (1 [8 bits] Or 2 [16 bits] only) }
; channels { number of channels (1 [ mono ] Or 2 [ stereo] only) }
Static sr = 44100, res = 2, ch = 1
If sampleRate < 0
Select sampleRate
Case -1 ; return sampleRate
ProcedureReturn sr
Case -2 ; return resolution
ProcedureReturn res
Case -3 ; return number of channels
ProcedureReturn ch
Default
ProcedureReturn 0
EndSelect
Else
If sampleRate <= 5512 : sr = 5512
ElseIf sampleRate <= 11025 : sr = 11025
ElseIf sampleRate <= 22050 : sr = 22050
Else : sr = 44100 : EndIf
If resolution <= 1 : res = 1 : Else : res = 2 : EndIf
If channels <= 1 : ch = 1 : Else : ch = 2 : EndIf
EndIf
ProcedureReturn 1
EndProcedure
Procedure.f InitAudioBuffer(duration.f = 10)
; purpose: create an empty PCM Wave format audio buffer in memory
; duration {sound buffer length in seconds}
; procedure returns a pointer that is the beginning of the wave sound header.
; this procedure is not intended to be used outside of this include file.
Static *audBuff, length.f = 10
Protected.i Fr, By, Nc
If duration < 0
Select duration
Case -1 : ProcedureReturn *audBuff
Case -2 : ProcedureReturn length
Default : ProcedureReturn 0
EndSelect
Else
length = duration
EndIf
If length < 0.1 : length = 0.1 : EndIf
Fr = SetSoundParameters(-1) ; get samples per second
By = SetSoundParameters(-2) ; get resolution { 1 byte or 2 byte }
Nc = SetSoundParameters(-3) ; get number of channels
; calculations to complete the header info.
Protected.l Av = Fr * By * Nc ; average bytes per second
Protected.w Ba = Nc * By ; block align
Protected.w Bs = 08 * By ; bits per sample
Protected.l Ns = Fr * length ; total number of blocks
Protected.l Nb = Ns * By * Nc ; total number of audio bytes
Protected.l Cs = 36 + By * Nc * Ns ; total chunk size (file size - 8)
If Cs & 1 ; add pad byte if needed to make even number
Cs + 1
EndIf
; set a pointer to the first byte in the header DataSection
Protected *waveHdr = ?wavHeader
; modify the default wave header as specified by the calculated parameters above
PokeL(*waveHdr + 04, Cs) ; total size
PokeW(*waveHdr + 22, Nc) ; number of channels
PokeL(*waveHdr + 24, Fr) ; samples per second
PokeL(*waveHdr + 28, Av) ; average bytes per second
PokeW(*waveHdr + 32, Ba) ; block align
PokeW(*waveHdr + 34, Bs) ; bits per sample
PokeL(*waveHdr + 40, Nb) ; number of audio bytes
If *audBuff : FreeMemory(*audBuff) : EndIf
; create space in memory for the audio data
*audBuff = AllocateMemory(Cs + 8)
If By = 1 ; 1 byte (8 bit) samples are unsigned so offset to 1/2 of full scale
FillMemory(*audBuff, MemorySize(*audBuff), $80, #PB_Byte)
ElseIf By = 2 ; 2 byte (16 bit) samples are signed so no offset is needed
FillMemory(*audBuff, MemorySize(*audBuff), $0000, #PB_Word)
EndIf
; put header information at the begining of sound buffer
CopyMemory(*waveHdr,*audBuff,44)
ProcedureReturn *audBuff ; return a pointer to the first byte
DataSection ; don't change anything here
wavHeader: ; placeholder values for header info.
; Master chunk (12 bytes) ;x xxxxx, offset
Data.a 'R','I','F','F' ;4 bytes, 0, chunk ID, "RIFF"
Data.l $00000000 ;4 bytes, 4, (total file size - 8) = Cs
Data.a 'W','A','V','E' ;4 bytes, 8, wave ID, "WAVE"
; Format chunk (24 bytes)
Data.a 'f','m','t',' ' ;4 bytes, 12, chunk ID "fmt "
Data.l $00000010 ;4 bytes, 16, this chunk size = 16, (2+2+4+4+2+2)
Data.w $0001 ;2 bytes, 20, Wave_Format_PCM
Data.w $0000 ;2 bytes, 22, number fo channels, Nc
Data.l $00000000 ;4 bytes, 24, samples per second, Fr
Data.l $00000000 ;4 bytes, 28, avg bytes per second, Fr*By*Nc
Data.w $0000 ;2 bytes, 32, block align, By*Nc
Data.w $0000 ;2 bytes, 34, bits per sample, 8*By
; Begin data chunk (8 bytes)
Data.a 'd','a','t','a' ;4 bytes, 36, chunk ID, "data"
Data.l $00000000 ;4 bytes, 40, number of audio bytes, By*Nc*Ns
EndDataSection
EndProcedure
Procedure CreateSound(soundNum, frequency.f = 440, duration.f = 1.0, amplitude = 25, sweepStop.f = 0, waveform = #WF_SineWave, FadeIn = 0, FadeOut = 0)
; Syntax:
; Result = CreateSound(#sound, [frequency.f], [duration.f], [amplitude], [sweepStop.f], [waveform], [fadeIn], [fadeOut])
;
; #sound { The number to identify the new sound. #PB_Any can be used to auto-generate this number. }
; frequency { The frequency in hertz of the new sound. Default = 440. Range = 20 to sample rate / 2 - 1
; duration { The length of the new sound in seconds. Default = 1.0 seconds. Minimum = 0.1 seconds.
; amplitude { the peak waveform magnitude from 0 to 100 (full scale). Default = 25 }
; sweepStop { Sets the final frequency of a frequency sweep. Default = 0 (sweep disabled). Range is same as frequency.
; waveform { Selects 1 of 9 different waveforms. Default = #WF_Sinewave. }
; { possible waveforms are #WF_Sinewave, #WF_SawTooth, #WF_BuzzSaw, #WF_SquareWave, #WF_Triangle, #WF_Chunosta, #WF_Organ, #WF_Noise, #WF_GuidedNoise }
; fadeIn { turns the fadeIn effect on/off. Default (0) = off , (1) = on }
; fadeOut { turns the fadeOut effect on/off. Default (0) = off , (1) = on }
If duration < 0.1 : duration = 0.1 : EndIf
If duration <> InitAudioBuffer(-2) : InitAudioBuffer(duration) : EndIf
Protected *buffer = InitAudioBuffer(-1)
If *buffer = 0 : ProcedureReturn 0 : EndIf
Protected Nc.w = PeekW(*buffer + 22) ; Nc {is number of channels}
Protected Fr.l = PeekL(*buffer + 24) ; Fr {is samples per second}
Protected By.w = PeekW(*buffer + 34)/8 ; By {is bytes per sample (1 = 8 bits, 2 = 16 bits)}
Protected Ba = Nc * By ; block align (bytes per block)
Protected doSweep = #False : If sweepStop <> 0 : doSweep = #True : EndIf
Static PIx2.f = #PI * 2
Static HalfPI.f = #PI / 2
If frequency >= Fr/2 ; maximum frequency is set by sample rate
frequency = Fr/2 - 1
ElseIf frequency < 20
frequency = 20
EndIf
; tweak frequency so waveform loops without glitches
frequency = Round(frequency * duration,#PB_Round_Nearest)/duration
If amplitude > 100 : amplitude = 100
ElseIf amplitude < 0 : amplitude = 0
EndIf
If FadeIn <> 0 : FadeIn = 1 : EndIf
If FadeOut <> 0 : FadeOut = 1 : EndIf
Protected Fade = FadeIn | FadeOut << 1
Protected *audioStart = *buffer + 44 ; pointer to the first audio data byte
Protected *n = *audioStart
Protected audBuffSize = MemorySize(*buffer) ; number of bytes in sound buffer
Protected *audBuffEnd = *buffer + audBuffSize - 1 ; pointer to last byte in buffer
If doSweep = #True
Protected.f frequencyStep, frequencyThisInstant, frequencySpan, averageFrequency
If sweepStop >= Fr/2
sweepStop = Fr/2 - 1
ElseIf sweepStop < 20
sweepStop = 20
EndIf
; linear sweep
averageFrequency = (frequency + sweepStop) / 2
averageFrequency = Round(averageFrequency * duration, #PB_Round_Nearest) / duration
sweepStop = 2 * averageFrequency - frequency
frequencySpan = sweepStop - frequency
EndIf
; calculate the number of audio bytes
Protected byteCount.l = duration * Fr * By * Nc
If byteCount & 1 : byteCount + 1 : EndIf ; byteCount must be even number
PokeL(*buffer + 40, byteCount)
Protected.w value ; waveform magnitude at any instant in time
Protected.f angStp = frequency / Fr * PIx2 ; angle step size in radians
Protected.f ang = 0 ; starting angle (radians)
Protected.i loops = byteCount / Ba ; number of loop iterations
Protected.i loopCount = 0 ; loop counter
Protected.f lastVal, bias ; used for guided noise
If doSweep = #True : frequencyStep = frequencySpan / loops : EndIf
; if waveform formulas produce values from -1 to +1 use this scale factor
Protected sf.f = (Pow(2,8*By)/2 - 1) * (amplitude / 100)
; adjustments for other waveforms
Select waveform
Case #WF_Chunosta
sf * (1/(Sin(ACos(1/Sqr(3)))*Sin(2*ACos(1/Sqr(3)))))
ang + HalfPI
Case #WF_Organ
sf / 2.25
Case #WF_GuidedNoise
sf / 2
Case #WF_Triangle
ang + HalfPI
Case #WF_SawTooth, #WF_BuzzSaw
ang + #PI
EndSelect
While loopCount < loops
Select waveform
Case #WF_SineWave
value = Sin(ang) * sf
Case #WF_SawTooth
value = (ang-#PI) / #PI * sf
Case #WF_BuzzSaw
value = Pow(Abs((ang-#PI)/#PI), #PI) * Sign(ang-#PI) * sf
Case #WF_SquareWave
value = Sign(#PI-ang) * sf
Case #WF_Triangle
value = ((ang-#PI)/HalfPI * Sign(#PI-ang) + 1) * sf
Case #WF_Chunosta
value = Sin(ang) * Sin(2*ang) * sf
Case #WF_Organ
value = (Sin(ang) + Sin(2*ang) + Sin(4*ang)) * sf
Case #WF_Noise
value = sf * (((Random($7FFFFFFD)+1) / $40000000) - 1)
Case #WF_GuidedNoise
bias = (Sin(ang) - lastVal) * 0.5
lastVal + ((Random($7FFFFFFD) / $40000000) - 1) + bias
value = lastVal * sf
Default
ProcedureReturn 0 ; waveform was invalid
EndSelect
Select Fade
Case %01 ; fade in
value * (0.0 + loopCount / loops)
Case %10 ; fade out
value * (1.0 - loopCount / loops)
Case %11 ; fade in & fade out
value << 2 * (1.0 - loopCount / loops) * (loopCount / loops)
EndSelect
If By = 1 ; 8 bits/sample
PokeA(*n, value+$80) ; left/mono
If Nc = 2 ; stereo
PokeA(*n+1, value+$80) ; right
EndIf
Else ; 16 bits/sample
PokeW(*n, value) ; left/mono
If Nc = 2 ; stereo
PokeW(*n+2,value) ; right
EndIf
EndIf
If doSweep
frequencyThisInstant = loopCount * frequencyStep + frequency
angStp = frequencyThisInstant / Fr * PIx2
EndIf
ang + angStp
If ang > PIx2 : ang - PIx2 : EndIf
*n + Ba ; point to the next audio sample
If *n > *audBuffEnd : Break : EndIf
loopCount + 1
Wend
ProcedureReturn CatchSound(soundNum, *buffer)
EndProcedure