Generate sfxr Sound Effects, in-game

Share your advanced PureBasic knowledge/code with the community.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Generate sfxr Sound Effects, in-game

Post by kenmo »

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.

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
[/size]



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.
[/size]

Enjoy :)
Last edited by kenmo on Fri Apr 17, 2015 4:44 pm, edited 1 time in total.
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: Generate sfxr Sound Effects, in-game

Post by Joakim Christiansen »

Thanks for your effort, this is perfect!
I've tried to make something similar before but mine weren't as cool... (so I needed this)

I might develop some extra functions for this later, but again, thanks! :D
I like logic, hence I dislike humans but love computers.
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Generate sfxr Sound Effects, in-game

Post by kenmo »

Yep, same here!

I wrote my own Wave Library, and it handled a lot of sample rates, bit-depths, and file formats, but the generated sounds were kind of boring and simple....

PS. In my game, I cut the size of included sounds down from ~400 kB to ~1 kB. 8) It only generates them the first time it runs - then it saves them to a sub-folder for loading next time.
User avatar
Rings
Moderator
Moderator
Posts: 1435
Joined: Sat Apr 26, 2003 1:11 am

Re: Generate sfxr Sound Effects, in-game

Post by Rings »

thx for sharing.

for testing your code:

Code: Select all

InitSound()

t=catchsfxr(1,?S1)
PlaySound(1)
Delay(3000)
End
S1:
IncludeBinary "sfx2"
SPAMINATOR NR.1
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: Generate sfxr Sound Effects, in-game

Post by c4s »

Hm, now we have it (at least) 3 times. ;)

by pjay: http://www.purebasic.fr/english/viewtop ... 20#p325120 (has great visualization (using opengl))
by Demivec: http://www.purebasic.fr/english/viewtop ... 67#p309167 (has great interface)
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Generate sfxr Sound Effects, in-game

Post by kenmo »

c4s wrote:Hm, now we have it (at least) 3 times. ;)

by pjay: http://www.purebasic.fr/english/viewtop ... 20#p325120 (has great visualization (using opengl))
by Demivec: http://www.purebasic.fr/english/viewtop ... 67#p309167 (has great interface)
Yep, it's been ported to PB before, but aren't those just improved GUIs on top of the original functionality?

I had the opposite goal: cut the sound synthesis down to a single procedure you can drop directly into your code.

(Maybe someone already did that too... I will re-read those threads)

edit: OH, I see now, the newest version of Spot_FX passes all the parameters as a Base64 string? I just saw the older version that had a random seed and a couple parameters...
Post Reply