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