Reputation: 11
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 :
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