System64
System64

Reputation: 11

How to get rid of clicking in my phase accumulator when I change the pitch?

I'm currently trying to make my own FM synthesizer, but instead of modulating sine waves, I modulate wavetables, or, one cycle waveforms.

I did the phase accumulator, but the problem is when I do a pitch change, like a vibrato for exemple, I have several clicks. This program was written in Nim, a compiled language, for performances and being easy to use.

import sdl2
import sdl2/audio
import math

# My callback user data
type
    cbData = object
        audioData: array[32, uint8]
        offset: int32
        stride: int32

# the playback frequency
let
    FREQ = 44100

# the number of samples in the buffer
let
    SAMPLES = 4096

# The phase, from the phase accumulator
var phase = 0

# The playback pitch, here, an A-3 note
var pitch = 440.0

# My phase accumulator function, it uses a sawtooth wave to determine which sample should I output to the buffer
proc phaseAccumulator(x: int, pitch: float = 440.0): int =
    var lenght = 32 # Lenght of the wavetable
    var pitch = pitch # Which pitch to play
    var a = -((pitch * x.float * PI)/(FREQ.float * PI))
    var o = (-lenght.float * ((2 * (a - floor(a)) - 1)) / 2) + (lenght.float / 2)
    echo (o).int
    # Some clipping
    if(o > 31):
        o = 31
    return floor(o).int


proc myCallback(userdata: pointer, stream: ptr uint8, len: cint) {.cdecl.} =
    # Casting raw pointer to cbData pointer and dereferencing it
    var data = cast[ptr cbData](userData)[]

    # Applying a vibrato!
    pitch = pitch + (sin(phase.float) * 4)
    
    # Filling the buffer
    for x in countup(0, (len div 4)):

        var ind = phaseAccumulator(phase, pitch) # getting the index of the wavetable to play with the phase accumulator
        inc phase # and increasing the phase accumulator
        # Getting the sample value at the index given by the phase accumulator, 
        # casting to float and dividing it by 255 since it was an 8-bits number.
        var sampleValue = data.audioData[ind].float / 255.0 
        # putting the sample into the buffer
        cast[ptr UncheckedArray[float32]](stream)[x] = sampleValue



proc main(): void =
    var wavetable2 = [31.uint8, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]

    # We will use this wavetable
    var wavetable = [23.uint8, 31, 31, 28, 27, 26, 19, 15, 19, 23, 17, 10, 9, 11, 8, 10, 20, 29, 28, 22, 19, 17, 9, 5, 8, 13, 8, 3, 2, 6, 5, 9 ]

    # Amplyfying it!
    for i in wavetable.mitems:
        # i = i - 16
        i = i * 3

    if(init(INIT_AUDIO).int < 0):
        echo("FATAL ERROR")

    var myData: cbData
    myData.audioData = wavetable
    myData.offset = 0 # Unused here
    myData.stride = 0.int32 # Unused here

    # SDL2 audio specs
    var mySpecs: AudioSpec
    mySpecs.format = AUDIO_F32
    mySpecs.freq = FREQ.cint
    mySpecs.channels = 1
    mySpecs.samples = SAMPLES.uint16
    mySpecs.userdata = nil
    mySpecs.padding = 0
    mySpecs.callback = myCallback
    mySpecs.userdata = myData.addr
    
    # Turing audio on
    discard openAudio(mySpecs.addr, nil)

    pauseAudio(0)

    while true:
        # Infinite loop to avoid audio shutting down. I do an operation here because
        # Nim language force me to do something inside a loop statement...
        var u = 0

# Starting the program
main()

I used a saw function to build the phase accumulator :

enter image description here

44100 is the playback frequency, l the lenght of the wavetable and f the pitch.

How can I get rid of clicks? and is there a better way to achieve a phase accumulator? thanks for your answer.

Upvotes: 1

Views: 69

Answers (0)

Related Questions