NaaLaa
SFX library - Printable Version

+- NaaLaa (https://www.naalaa.com/forum)
+-- Forum: NaaLaa (https://www.naalaa.com/forum/forum-1.html)
+--- Forum: NaaLaa 7 Code (https://www.naalaa.com/forum/forum-4.html)
+--- Thread: SFX library (/thread-81.html)



SFX library - Marcus - 02-16-2024

Here comes a library for creating sound effects. It's an improvement of the thing I made for that Defender-clone. You now have more advanced control over the frequency and volume of the sounds you create. You can also add echo effects and save your masterpieces as wav files.

sfx_library_example.n7
Code:
' sfx_library_example
' -------------------

#win32

' Include the sfx library.
include "sfx.n7"

' Create a window and print instructions.
set window "SFX", 640, 480
wln "1 - Play a square wave sound effect"
wln "2 - Play a sine wave sound effect"
wln "3 - Play a noise sound effect"
wln
wln "Esc - Quit"

' Create an sfx instance.
sfx = SFX()

' You can use 'SetSampleRate(value)' to change the sample rate that is used when creating sounds.
' It defaults to 16000 (so the call below is just for show). There's no point setting a higher value
' than 22050, since that is the output frequency of all audio in n7.
sfx.SetSampleRate(16000)

' Use 'SetEcho(count, delay, dropOff, panning)' to enable an echo effect for all sound effects
' created. Set the 'count' parameter to 0 to disable echo. Echo is disabled by default. 'count' is
' the number of echos and 'delay' is the delay in seconds between them. 'dropOff' decides how much
' the volume will decrase for each echo. If 'dropOff' is 0.5, the first echo will have that volume,
' the second one 0.5*0.5 = 0.26 and so on. 'panning', [-1..1] can be used to make the echo bounce
' between the left and right speakers. The 'panning' value is inverted after each echo. Set
' 'panning' to 0 to disable panning.
sfx.SetEcho(2, 0.4, 0.1, 0)

' Use 'SquareWave(duration, freq, vol)' to create a square wave sound. 'duration' is the duration in
' seconds (excluding any echo effect). 'freq' is the frequency. It can be either a number or an
' array of numbers. In the case of an array, it is evaluated as a one dimensional bezier curve. It's
' not as complicated as it may sound. To make a sound start with a frequencey of 500 and end with
' 1000, simply set the 'freq' parameter to [500, 1000]. 'vol' is the volume, and just like 'freq' it
' can be a number or an array. The 'vol' value(s) should be in the range [0..1].
crispSound = sfx.SquareWave(0.5, [50, 100, -500, 1000], [0.5, 1, 0])
' You can also use 'SaveSquareWave(filename, duration, freq, vol)' to save a sound to a wav file.
'sfx.SaveSquareWave("crisp_sound.wav", 0.5, [50, 100, -500, 1000], [0.5, 1, 0])

' Change the echo effect and use 'SineWave(duration, freq, vol)' to create a sine wave sound. The
' parameters work just as they do for 'SquareWave'.
sfx.SetEcho(2, 0.15, 0.2, 0)
blubSound = sfx.SineWave(0.2, [0, 500, 25, 500], [0, 1, 0])
' You can also use 'SaveSine'Wave(filename, duration, freq, vol)' to save a sound to a wav file.
'sfx.SaveSineWave("blub_sound.wav", 0.2, [0, 500, 25, 500], [0, 1, 0])

' This time, use panning for the echo effect. 'Noise(duration, freq, vol)' creates a noise sound,
' and the parameters work just the same way as they do for 'SquareWave' and 'SineWave'.
sfx.SetEcho(2, 0.4, 0.2, -0.5)
boomSound = sfx.Noise(0.6, [5000, 100], [1, 0, 1, 0])
' You can also use 'SaveNoise(filename, duration, freq, vol)' to save a sound to a wav file.
'sfx.SaveNoise("boom_sound.wav", 0.6, [5000, 100], [1, 0, 1, 0])

' Let the user play the sound effects.
while not keydown(KEY_ESCAPE)
    if keydown(KEY_1, true)  play sound crispSound
    if keydown(KEY_2, true)  play sound blubSound
    if keydown(KEY_3, true)  play sound boomSound
    fwait 60
wend

sfx.n7
Code:
' SFX
' ---
' A library for creating simple sound effects.
'
' By Marcus.


' SFX
' ---
function SFX()
    sfx = [sr: 16000, ech: 0, echDel: 0, echDO: 0]
   
    ' GetSampleRate
    ' -------------
    ' Return number of samples per second.
    sfx.GetSampleRate = function()
        return this.sr
    endfunc
   
    ' SetSampleRate
    ' -------------
    ' Set number of samples per second.
    sfx.SetSampleRate = function(sampleRate)
        assert sampleRate >= 8000 and sampleRate <= 22050, "SFX.SetSampleRate: Invalid sample rate"
        this.sr = sampleRate
    endfunc
   
    ' SetEcho
    ' -------
    ' Add 'count' number of echos with a delay of 'delay' seconds. Set 'count' to 0 to disable echo.
    ' The volume is multiplied by 'dropOff' for each echo. 'pan', [-1..1], can be used to make the
    ' sound bounce between the left and right speaker. A value of -1 means that the first echo will
    ' be played only on the left side, and 1 means it will be played on the right side. After each
    ' echo the pan value is inverted. Set 'pan' to 0 to disable panning.
    sfx.SetEcho = function(count, delay, dropOff, pan)
        assert count >= 0, "SFX.SetEcho: Invalid count"
        assert delay >= 0, "SFX.SetEcho: Invalid delay"
        this.ech = count
        this.echDel = delay
        this.echDO = dropOff
        this.echPan = pan
    endfunc

    ' SineWaveData
    ' ------------
    ' Return sine wave data lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be in
    ' the range [0..1].
    sfx.SineWaveData = function(duration, freq, vol)
        assert duration > 0, "SFX.SineWaveData: Invalid duration"
        if typeof(freq) <> TYPE_TABLE  freq = [freq]
        if typeof(vol) <> TYPE_TABLE  vol = [vol]
        freqDegree = sizeof(freq) - 1
        freqBc = BinomialCoefficients(freqDegree)
        volDegree = sizeof(vol) - 1
        volBc = BinomialCoefficients(volDegree)
        data = []
        a = 0
        p = 0
        dp = 1/(duration*this.sr)
        for i = 0 to duration*this.sr - 1
            data[i] = sin(a)*EvaluateCurve(vol, volBc, p)
            a = a + 2*PI*EvaluateCurve(freq, freqBc, p)/this.sr
            p = p + dp
        next
        if this.ech
            return ApplyEcho(data, this.ech, int(this.echDel*this.sr), this.echDO, this.echPan)
        else
            return [data, data]
        endif
    endfunc
       
    ' SineWave
    ' --------
    ' Return a sine wave sound lasting for 'duration' seconds. 'freq' is the frequency and can be
    ' a number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be
    ' a number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.SineWave = function(duration, freq, vol)
        data = this.SineWaveData(duration, freq, vol)
        return createsound(data[0], data[1], this.sr)
    endfunc
   
    ' SaveSineWave
    ' ------------
    ' Save sine wave sound lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be in
    ' the range [0..1].
    sfx.SaveSineWave = function(filename, duration, freq, vol)
        data = this.SineWaveData(duration, freq, vol)
        return SaveWav(filename, data[0], data[1], this.sr)
    endfunc

    ' SquareWaveData
    ' --------------
    ' Return square wave data lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.SquareWaveData = function(duration, freq, vol)
        assert duration > 0, "SFX.SquareWave: Invalid duration"   
        if typeof(freq) <> TYPE_TABLE  freq = [freq]
        if typeof(vol) <> TYPE_TABLE  vol = [vol]
        freqDegree = sizeof(freq) - 1
        freqBc = BinomialCoefficients(freqDegree)
        volDegree = sizeof(vol) - 1
        volBc = BinomialCoefficients(volDegree)
        data = []
        a = 0
        p = 0
        dp = 1/(duration*this.sr)
        for i = 0 to duration*this.sr - 1
            sa = sin(a)
            if sa < 0  sa = -1
            elseif sa > 0  sa = 1
            data[i] = sa*EvaluateCurve(vol, volBc, p)
            a = a + 2*PI*EvaluateCurve(freq, freqBc, p)/this.sr
            p = p + dp
        next
        if this.ech
            return ApplyEcho(data, this.ech, int(this.echDel*this.sr), this.echDO, this.echPan)
        else
            return [data, data]
        endif
    endfunc

    ' SquareWave
    ' ----------
    ' Return a square wave sound lasting for 'duration' seconds. 'freq' is the frequency and can be
    ' a number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be
    ' a number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.SquareWave = function(duration, freq, vol)
        data = this.SquareWaveData(duration, freq, vol)
        return createsound(data[0], data[1], this.sr)
    endfunc

    ' SaveSquareWave
    ' --------------
    ' Save a square wave sound lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.SaveSquareWave = function(filename, duration, freq, vol)
        data = this.SquareWaveData(duration, freq, vol)
        return SaveWav(filename, data[0], data[1], this.sr)
    endfunc

    ' NoiseData
    ' ---------
    ' Return noise data lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.NoiseData = function(duration, freq, vol)
        assert duration > 0, "SFX.Noise: Invalid duration"   
        if typeof(freq) <> TYPE_TABLE  freq = [freq]
        if typeof(vol) <> TYPE_TABLE  vol = [vol]
        freqDegree = sizeof(freq) - 1
        freqBc = BinomialCoefficients(freqDegree)
        volDegree = sizeof(vol) - 1
        volBc = BinomialCoefficients(volDegree)
        data = []
        tick = 0
        value = 0
        deta = 0
        p = 0
        dp = 1/(duration*this.sr)
        for i = 0 to duration*this.sr - 1
            tick = tick - 1
            if tick <= 0
                tick = this.sr/EvaluateCurve(freq, freqBc, p)
                delta = ((rnd()*2 - 1) - value)/tick
            endif
            value = value + delta
            data[i] = value*EvaluateCurve(vol, volBc, p)
            p = p + dp
        next
        if this.ech
            return ApplyEcho(data, this.ech, int(this.echDel*this.sr), this.echDO, this.echPan)
        else
            return [data, data]
        endif
    endfunc

    ' Noise
    ' -----
    ' Return a noise sound lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.Noise = function(duration, freq, vol)
        data = this.NoiseData(duration, freq, vol)
        return createsound(data[0], data[1], this.sr)
    endfunc

    ' SaveNoise
    ' ---------
    ' Save a noise sound lasting for 'duration' seconds. 'freq' is the frequency and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. 'vol' is the volume and can be a
    ' number or an array of numbers, treated as a 1D bezier curve. The 'vol' value(s) should be
    ' in the range [0..1].
    sfx.SaveNoise = function(filename, duration, freq, vol)
        data = this.NoiseData(duration, freq, vol)
        return SaveWav(filename, data[0], data[1], this.sr)
    endfunc
           
    return sfx
   
    ' BinomialCoefficients
    ' --------------------
    function BinomialCoefficients(n)
        function bc(n, k)
            if n = k  return 1
            v = 1
            i = 1
            while i <= k
                v = v*int((n + 1 - i)/i)
                i = i + 1
            wend
            return v
        endfunc
        c = []
        for i = 0 to n  c[i] = bc(n, i)
        return c
    endfunc
   
    ' EvaluateCurve
    ' -------------
    function EvaluateCurve(points, bcs, param)
        n = sizeof(points) - 1
        v = 0
        for i = 0 to n  v = v + bcs[i]*(1 - param)^(n - i)*param^i*points[i]
        return v
    endfunc
   
    ' ApplyEcho
    ' ---------
    function ApplyEcho(data, count, offset, dropOff, pan)
        pan = max(min(pan, 1), -1)
        ldata = fill(0, count*offset + sizeof(data))
        rdata = fill(0, count*offset + sizeof(data))
        for i = 0 to sizeof(data) - 1
            ldata[i] = data[i]
            rdata[i] = data[i]
        next
        for i = 1 to count
            offs = i*offset
            vol = dropOff^i
            lvol = vol*cos((pan + 1)*0.5*PI*0.5)
            rvol = vol*sin((pan + 1)*0.5*PI*0.5)
            for j = 0 to sizeof(data) - 1
                ldata[offs + j] = ldata[offs + j] + data[j]*lvol
                rdata[offs + j] = rdata[offs + j] + data[j]*rvol
            next
            pan = -pan
        next
        return [ldata, rdata]
    endfunc

    ' SaveWav
    ' -------   
    function SaveWav(filename, ldata, rdata, sampleRate)
        function WriteBinChars(f, txt)
            for i = 0 to len(txt) - 1  write file f, asc(mid(txt, i)), 8
        endfunc
       
        f = createfile(filename, true)
        if typeof(f)
            if ldata = rdata  channels = 1
            else  channels = 2
            for i = 0 to sizeof(ldata) - 1  ldata[i] = max(min(ldata[i], 1), -1)
            if channels = 2  for i = 0 to sizeof(rdata) - 1  rdata[i] = max(min(rdata[i], 1), -1)
            WriteBinChars(f, "RIFF")
            frameCount = sizeof(ldata)
            bits = 16
            mul = 32767
            length = int(frameCount*channels*bits/8)
            write file f, length + 44 - 8, 32, false
            WriteBinChars(f, "WAVE")
            WriteBinChars(f, "fmt ")
            write file f, 16, 32, false
            write file f, 1, 16, false
            write file f, channels, 16, false
            write file f, sampleRate, 32, false
            write file f, int(sampleRate*bits*channels/8), 32, false
            write file f, int(channels*bits/8), 16, false
            write file f, bits, 16, false
            WriteBinChars(f, "data")
            write file f, length, 32, false
            if channels = 1
                for i = 0 to sizeof(ldata) - 1  write file f, ldata[i]*mul, 16, true
            else
                for i = 0 to sizeof(ldata) - 1
                    write file f, ldata[i]*mul, 16, true
                    write file f, rdata[i]*mul, 16, true
                next
            endif
            free file f
            return true
        else
            return false
        endif
    endfunc
endfunc



RE: SFX library - aliensoldier - 02-16-2024

Brilliant. Smile

Well, I'm going to try it in the little platform game I'm making.


RE: SFX library - kevin - 02-17-2024

Thanks Marcus - I'm looking forward to experimenting with this......


RE: SFX library - Marcus - 02-17-2024

A small program that generates random sound effects and displays their curves. You can also save them to wav files.

Code:
#win32

include "sfx.n7"

set window "Sound data", 640, 480
set redraw off

set caret 320, 400
center "RETURN - Generate and play a random sound effect"
center "SPACE - Play the last generated sound effect"
center "S - Save sound as WAV file"

sfx = SFX()
snd = unset

randomize time()
snd = unset
while not keydown(KEY_ESCAPE, true)
    if keydown(KEY_RETURN, true)
        ' Generate random sound.
        freqPointCount = 2 + rnd(3)
        volPointCount = 2 + rnd(3)
        freq = []
        for i = 0 to freqPointCount - 1  freq[i] = rnd()*1500
        vol = []
        for i = 0 to volPointCount - 2  vol[i] = rnd()
        vol[volPointCount - 1] = 0
        dur = 0.1 + rnd()*0.5
        sfx.SetEcho(rnd(3), 0.1*dur + rnd()*dur*0.8, rnd()*0.5, 0)
        type = rnd(3)
        select type
            case 0  soundData = sfx.SineWaveData(dur, freq, vol)
            case 1  soundData = sfx.SquareWaveData(dur, freq, vol)
            case 2  soundData = sfx.NoiseData(dur, freq, vol)
        endsel
        if typeof(snd)  free sound snd
        snd = createsound(soundData[0], soundData[1], sfx.GetSampleRate())
        ' Display sound data.
        set color 0, 0, 0
        draw rect 0, 0, 640, 400, true
        delta = sizeof(soundData[0])/640
        index = 0
        set color 0, 200, 0
        draw pixel 0, 240 + soundData[0][0]*100
        for i = 1 to 639
            draw line to i, 240 + soundData[0][index]*100
            index = index + delta
        next
        set color 255, 255, 255, 128
        draw line 0, 240, 640, 240
        play sound snd
    ' Already have a sound effect?
    elseif typeof(snd)
        ' Play sound?
        if keydown(KEY_SPACE, true)
            play sound snd
        ' Save sound?
        elseif keydown(KEY_S, true)
            filename = savefiledialog("wav")
            if filename
                if instr(filename, ".wav") < 0  filename = filename + ".wav"
                select type
                    case 0  sfx.SaveSineWave(filename, dur, freq, vol)
                    case 1  sfx.SaveSquareWave(filename, dur, freq, vol)
                    case 2  sfx.SaveNoise(filename, dur, freq, vol)
                endsel
            endif
        endif
    endif
   
    redraw
    fwait 60
wend