Generate sfxr Sound Effects, in-game
Posted: Fri Mar 04, 2011 12:26 am
Here's a nifty procedure/includefile I'd like to share...
sfxr by DrPetter (web page) is an open-source tool for generating monophonic, retro-style sound effects.
It has a good number of parameters to play with, plus some presets and a randomizer.
It can export to WAV, or you can save the parameters to a tiny file.
as3sfxr by Tom Vian (web page) is a Flash port of sfxr you can mess around with right in your browser.
as3sfxr-b by increpare (web page) is a new extended version, currently under development, that adds features but is not compatible with my code... (it can still be used to create WAVs!)
Originally I was generating WAVs to include in a game, but the total filesize was growing fast, so I decided to port the synthesis part of his code directly into PureBasic.
Now I just include the .sfs parameter files (~100 bytes each) and generate the sounds at run-time.
There are two procedures:
CatchSfxr( ID, *Address [, SaveFile] )
LoadSfxr( ID, FileName )
They work just like CatchSound() and LoadSound().
InitSound() needs to be called first.
CatchSfxr() has an optional SaveFile parameter, which will save the generated sound to an external WAV file.
EnableExplicit friendly.
Essentially the CatchSfxr() procedure is a combination of DrPetter's LoadSettings(), ResetSample(), SynthSample(), and ExportWav() functions.
There are a few limitations though:
For simplicity, I forced the 44100 / 16-bit WAV format (since there is no GUI to choose otherwise).
There is a hard-coded WAV buffer size (currently 500,000 bytes). You could increase this, or modify the code to dynamically grow the buffer...
Finally, here is the code.
I know it's ugly and uncommented... but it's mostly a direct code port.
[/size]
Also, straight from DrPetter's source:
[/size]
Enjoy
sfxr by DrPetter (web page) is an open-source tool for generating monophonic, retro-style sound effects.
It has a good number of parameters to play with, plus some presets and a randomizer.
It can export to WAV, or you can save the parameters to a tiny file.
as3sfxr by Tom Vian (web page) is a Flash port of sfxr you can mess around with right in your browser.
as3sfxr-b by increpare (web page) is a new extended version, currently under development, that adds features but is not compatible with my code... (it can still be used to create WAVs!)
Originally I was generating WAVs to include in a game, but the total filesize was growing fast, so I decided to port the synthesis part of his code directly into PureBasic.
Now I just include the .sfs parameter files (~100 bytes each) and generate the sounds at run-time.
There are two procedures:
CatchSfxr( ID, *Address [, SaveFile] )
LoadSfxr( ID, FileName )
They work just like CatchSound() and LoadSound().
InitSound() needs to be called first.
CatchSfxr() has an optional SaveFile parameter, which will save the generated sound to an external WAV file.
EnableExplicit friendly.
Essentially the CatchSfxr() procedure is a combination of DrPetter's LoadSettings(), ResetSample(), SynthSample(), and ExportWav() functions.
There are a few limitations though:
For simplicity, I forced the 44100 / 16-bit WAV format (since there is no GUI to choose otherwise).
There is a hard-coded WAV buffer size (currently 500,000 bytes). You could increase this, or modify the code to dynamically grow the buffer...
Finally, here is the code.
I know it's ugly and uncommented... but it's mostly a direct code port.
Code: Select all
; +----------+-------------------------------+
; | sfxr.pbi | by kenmo |
; +----------+-------------------------------+
; | Based on code by DrPetter: |
; | http://www.drpetter.se/project_sfxr.html |
; +------------------------------------------+
; | 2.25.2011 . Creation
; | 3.03. . Rewrite: now contained in one procedure
Procedure.i CatchSfxr(ID.i, *Address.i, SaveFile.s = "")
Protected Result.i = #Null
Protected BufferSize.i = 500000
Protected *Ptr.i = *Address
If (*Ptr)
Protected version.i = PeekL(*Ptr) : *Ptr + 4
If ((version >= 100) And (version <= 102))
Protected.i wave_type
Protected.f p_base_freq, p_freq_limit, p_freq_ramp
Protected.f p_freq_dramp, p_duty, p_duty_ramp
Protected.f p_vib_strength, p_vib_speed, p_vib_delay
Protected.f p_env_attack, p_env_sustain
Protected.f p_env_decay, p_env_punch
Protected.i filter_on
Protected.f p_lpf_resonance, p_lpf_freq, p_lpf_ramp
Protected.f p_hpf_freq, p_hpf_ramp
Protected.f p_pha_offset, p_pha_ramp
Protected.f p_repeat_speed
Protected.f p_arp_speed, p_arp_mod
Protected.f master_vol = 0.05
Protected.f sound_vol = 0.50
wave_type = PeekL(*Ptr) : *Ptr + 4
If (version >= 102)
sound_vol = PeekF(*Ptr) : *Ptr + 4
EndIf
p_base_freq = PeekF(*Ptr) : *Ptr + 4
p_freq_limit = PeekF(*Ptr) : *Ptr + 4
p_freq_ramp = PeekF(*Ptr) : *Ptr + 4
If (version >= 101)
p_freq_dramp = PeekF(*Ptr) : *Ptr + 4
EndIf
p_duty = PeekF(*Ptr) : *Ptr + 4
p_duty_ramp = PeekF(*Ptr) : *Ptr + 4
p_vib_strength = PeekF(*Ptr) : *Ptr + 4
p_vib_speed = PeekF(*Ptr) : *Ptr + 4
p_vib_delay = PeekF(*Ptr) : *Ptr + 4
p_env_attack = PeekF(*Ptr) : *Ptr + 4
p_env_sustain = PeekF(*Ptr) : *Ptr + 4
p_env_decay = PeekF(*Ptr) : *Ptr + 4
p_env_punch = PeekF(*Ptr) : *Ptr + 4
filter_on = PeekA(*Ptr) : *Ptr + 1
p_lpf_resonance = PeekF(*Ptr) : *Ptr + 4
p_lpf_freq = PeekF(*Ptr) : *Ptr + 4
p_lpf_ramp = PeekF(*Ptr) : *Ptr + 4
p_hpf_freq = PeekF(*Ptr) : *Ptr + 4
p_hpf_ramp = PeekF(*Ptr) : *Ptr + 4
p_pha_offset = PeekF(*Ptr) : *Ptr + 4
p_pha_ramp = PeekF(*Ptr) : *Ptr + 4
p_repeat_speed = PeekF(*Ptr) : *Ptr + 4
If (version >= 101)
p_arp_speed = PeekF(*Ptr) : *Ptr + 4
p_arp_mod = PeekF(*Ptr) : *Ptr + 4
EndIf
Protected.i *Buffer.i = AllocateMemory(BufferSize)
If (*Buffer)
*Ptr = *Buffer
PokeS(*Ptr, "RIFF", -1, #PB_Ascii) : *Ptr + 4
PokeL(*Ptr, 0 ) : *Ptr + 4
PokeS(*Ptr, "WAVE", -1, #PB_Ascii) : *Ptr + 4
PokeS(*Ptr, "fmt ", -1, #PB_Ascii) : *Ptr + 4
PokeL(*Ptr, 16 ) : *Ptr + 4
PokeU(*Ptr, 1 ) : *Ptr + 2
PokeU(*Ptr, 1 ) : *Ptr + 2
PokeL(*Ptr, 44100 ) : *Ptr + 4
PokeL(*Ptr, 44100 * 2 ) : *Ptr + 4
PokeU(*Ptr, 2 ) : *Ptr + 2
PokeU(*Ptr, 16 ) : *Ptr + 2
PokeS(*Ptr, "data", -1, #PB_Ascii) : *Ptr + 4
Protected foutstream_datasize = *Ptr - *Buffer
PokeL(*Ptr, 0) : *Ptr + 4
Protected.i playing_sample, phase, period
Protected.d fperiod, fmaxperiod, fslide, fdslide
Protected.f square_duty, square_slide
Protected.i env_stage, env_time
Protected Dim env_length.i(2)
Protected.f env_vol, fphase, fdphase
Protected.i iphase, ipp
Protected Dim phaser_buffer.f(1024)
Protected Dim noise_buffer.f(32)
Protected.f fltp, fltdp, fltw, fltw_d, fltdmp, fltphp
Protected.f flthp, flthp_d, vib_phase, vib_speed, vib_amp
Protected.i rep_time, rep_limit, arp_time, arp_limit
Protected.d arp_mod
Protected.i file_sampleswritten
Protected.f filesample, rfperiod, ssample, sample, fp, pp
Protected.i reset, restart, i, j, si
file_sampleswritten = 0
filesample = 0.0
reset = #True
restart = #False
playing_sample = 1
rep_time = 0
If (p_repeat_speed)
rep_limit = Int(Pow(1.0 - p_repeat_speed, 2.0) * 20000.0 + 32.0)
Else
rep_limit = 0
EndIf
While (playing_sample)
For i = 0 To 255
If (playing_sample)
rep_time + 1
If ((rep_limit) And (rep_time >= rep_limit))
rep_time = 0
reset = #True
EndIf
If (reset)
If (Not restart)
phase = 0
EndIf
fperiod = 100.0 / (p_base_freq*p_base_freq + 0.001)
period = Int(fperiod)
fmaxperiod = 100.0 / (p_freq_limit*p_freq_limit + 0.001)
fslide = 1.0 - Pow(p_freq_ramp, 3.0) * 0.01
fdslide = -Pow(p_freq_dramp, 3.0) * 0.000001
square_duty = 0.5 - p_duty * 0.5
square_slide = -p_duty_ramp * 0.00005
arp_time = 0
If (p_arp_mod >= 0.0)
arp_mod = 1.0 - Pow(p_arp_mod, 2.0) * 0.9
Else
arp_mod = 1.0 + Pow(p_arp_mod, 2.0) * 10.0
EndIf
If (p_arp_speed = 1.0)
arp_limit = 0
Else
arp_limit = Int(Pow(1.0 - p_arp_speed, 2.0) * 20000.0 + 32.0)
EndIf
If (Not restart)
fltp = 0.0
fltdp = 0.0
fltw = Pow(p_lpf_freq, 3.0) * 0.1
fltw_d = 1.0 + p_lpf_ramp * 0.0001
fltdmp = 5.0 / (1.0 + Pow(p_lpf_resonance, 2.0) * 20.0) * (0.01 + fltw)
If (fltdmp > 0.8)
fltdmp = 0.8
EndIf
fltphp = 0.0
flthp = Pow(p_hpf_freq, 2.0) * 0.1
flthp_d = 1.0 + p_hpf_ramp * 0.0003
vib_phase = 0.0
vib_speed = Pow(p_vib_speed, 2.0) * 0.01
vib_amp = p_vib_strength * 0.5
env_vol = 0.0
env_stage = 0
env_time = 0
env_length(0) = Int(p_env_attack * p_env_attack * 100000.0)
env_length(1) = Int(p_env_sustain * p_env_sustain * 100000.0)
env_length(2) = Int(p_env_decay * p_env_decay * 100000.0)
fphase = Pow(p_pha_offset, 2.0) * 1020.0
If (p_pha_offset < 0.0)
fphase = -fphase
EndIf
fdphase = Pow(p_pha_ramp, 2.0) * 1.0
If (p_pha_ramp < 0.0)
fdphase = - fdphase
EndIf
iphase = Int(Abs(fphase))
ipp = 0
For j = 0 To 1023
phaser_buffer(j) = 0.0
Next j
For j = 0 To 31
noise_buffer(j) = (2.0*Random(10000)/10000.0) - 1.0
Next j
rep_time = 0
If (p_repeat_speed)
rep_limit = Int(Pow(1.0 - p_repeat_speed, 2.0) * 20000.0 + 32.0)
Else
rep_limit = 0
EndIf
EndIf
reset = #False
restart = #True
EndIf
arp_time + 1
If ((arp_limit) And (arp_time >= arp_limit))
arp_limit = 0
fperiod * arp_mod
EndIf
fslide + fdslide
fperiod * fslide
If (fperiod > fmaxperiod)
fperiod = fmaxperiod
If (p_freq_limit > 0.0)
playing_sample = 0
EndIf
EndIf
rfperiod = fperiod
If (vib_amp > 0.0)
vib_phase + vib_speed
rfperiod = fperiod * (1.0 + Sin(vib_phase) * vib_amp)
EndIf
period = Int(rfperiod)
If (period < 8)
period = 8
EndIf
square_duty + square_slide
If (square_duty < 0.0)
square_duty = 0.0
ElseIf (square_duty > 0.5)
square_duty = 0.5
EndIf
env_time + 1
If (env_time > env_length(env_stage))
env_time = 0
env_stage + 1
If (env_stage = 3)
playing_sample = 0
EndIf
EndIf
If (env_stage = 0)
env_vol = 1.0 * env_time/env_length(0)
ElseIf (env_stage = 1)
env_vol = 1.0 + Pow(1.0 - 1.0*env_time/env_length(1), 1.0) * 2.0 * p_env_punch
ElseIf (env_stage = 2)
env_vol = 1.0 - 1.0 * env_time / env_length(2)
EndIf
fphase + fdphase
iphase = Abs(fphase)
If (iphase > 1023)
iphase = 1023
EndIf
If (flthp_d)
flthp * flthp_d
If (flthp < 0.00001)
flthp = 0.00001
ElseIf(flthp > 0.1)
flthp = 0.1
EndIf
EndIf
ssample = 0.0
For si = 0 To 7
sample = 0.0
phase + 1
If (phase >= period)
phase % period
If (wave_type = 3)
For j = 0 To 31
noise_buffer(j) = (2.0*Random(10000)/10000.0) - 1.0
Next j
EndIf
EndIf
fp = 1.0 * phase / period
Select (wave_type)
Case 0
If (fp < square_duty)
sample = 0.5
Else
sample = -0.5
EndIf
Case 1
sample = 1.0 - fp * 2.0
Case 2
sample = Sin(fp * 2 * #PI)
Case 3
sample = noise_buffer(phase*32/period)
EndSelect
pp = fltp
fltw * fltw_d
If (fltw < 0.0)
fltw = 0.0
ElseIf (fltw > 0.1)
fltw = 0.1
EndIf
If (p_lpf_freq <> 1.0)
fltdp + (sample - fltp ) * fltw
fltdp - (fltdp * fltdmp)
Else
fltp = sample
fltdp = 0.0
EndIf
fltp + fltdp
fltphp + (fltp - pp )
fltphp - (fltphp * flthp)
sample = fltphp
phaser_buffer(ipp & 1023) = sample
sample + phaser_buffer((ipp - iphase + 1024)&1023)
ipp = (ipp + 1) & 1023
ssample + (sample * env_vol)
Next si
ssample = ssample * master_vol * sound_vol
If (ssample > 1.0)
ssample = 1.0
ElseIf (ssample < -1.0)
ssample = -1.0
EndIf
filesample = ssample
PokeW(*Ptr, Int(filesample * 32000.0)) : *Ptr + 2
file_sampleswritten + 1
Else
Break
EndIf
Next i
Wend
PokeL(*Buffer + 4, foutstream_datasize - 4 + 2*file_sampleswritten)
PokeL(*Buffer + foutstream_datasize, 2*file_sampleswritten)
Result = CatchSound(ID, *Buffer)
If (SaveFile)
Protected.i FID = CreateFile(#PB_Any, SaveFile)
If (FID)
WriteData(FID, *Buffer, PeekL(*Buffer + 4) + 8)
CloseFile(FID)
EndIf
EndIf
FreeMemory(*Buffer)
EndIf
EndIf
EndIf
ProcedureReturn (Result)
EndProcedure
Procedure.i LoadSfxr(ID.i, File.s)
Protected Result.i, FID.i, Lof.i, *Buffer.i
Result = #Null
FID = ReadFile(#PB_Any, File)
If (FID)
Lof = Lof(FID)
If (Lof > 0)
*Buffer = AllocateMemory(Lof)
If (*Buffer)
If (ReadData(FID, *Buffer, Lof))
Result = CatchSfxr(ID, *Buffer)
EndIf
FreeMemory(*Buffer)
EndIf
EndIf
CloseFile(FID)
EndIf
ProcedureReturn (Result)
EndProcedure
; EOF
Also, straight from DrPetter's source:
Code: Select all
License
-------
Basically, I don't care what you do with it, anything goes.
To please all the troublesome folks who request a formal license,
I attach the "MIT license" as follows:
--
Copyright (c) 2007 Tomas Pettersson
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Enjoy
