I've been stuck for days on changing the frequency of a sine wave oscillator over a period of time, and I'm completely stumped.
I've tried it many number of ways, but what always ends up happening is that the frequency over the lerp of its duration is never correct, it always either overshoots by the end, or goes too low and comes back up. You can see this very easily by entering hz values that are really high or really low in the frequency table.
I've tested every part of the program for the cause of the error, I've tried other sine oscillators (I'm currently using one that was shared by zorg in https://love2d.org/forums/viewtopic.php?t=82944), I've tried replacing variables with constants every step of the way, I've tried pretty much everything.
I've put together a some code in love.draw() to better illustrate what's going on in the wave form, and you can press space at any time to hear the sound.
As you can see in the frequency table, the sound should first descend from 130 to 65 hz over a second, then hold on 65hz for a second, then ascend again to 130 over the final second. But the middle second is the only one that works correctly, for some reason, everything else acts totally erratically and I can't understand why.
I've included my main.lua as code below, and my love game as an attachment. Thank you!
Code: Select all
sound = {}
sound.rate = 44100 --sample rate
sound.bits = 16 --bit rate
sound.channel = 1
sound.initialPhase = 0
function lerp(a, b, t) return a + (b - a) * t end -- linear interpolation
function love.load()
love.window.setMode(1200, 200, { vsync = true, highdpi = true, resizable = true })
--frequency table
frequency = {130.81278265029931, 65.406391325149656, 65.406391325149656, 130.81278265029931}
--time table
seconds = {0, 1, 2, 3}
--tone is the sound data, sound_table is the x points of the waveform
tone, sound_table = sound.get()
qs = love.audio.newQueueableSource(tone:getSampleRate(), tone:getBitDepth(), tone:getChannelCount())
end
function love.keypressed(key)--, scancode, isrepeat)
if key == "space" then
qs:queue(tone)
qs:play()
end
end
function love.draw()
-- draw waveform
if sound_table then -- draw a line dividing each second
love.graphics.setColor(255, 255, 255, 0.2)
for i=1, #seconds do
love.graphics.line(300*seconds[i], 0, 300*seconds[i], love.graphics.getHeight())
end
love.graphics.setColor(255, 255, 255, 1) --draw the waveform as dots
for i=1, #sound_table-1 do
love.graphics.points(100*seconds[1] + (100*(i-1))/sound.rate*3, (100*sound_table[i])+(100))
end
end
end
-- Constructor for a sine wave generator.
sine = function(generator)
local tau = math.pi*2
local generator = generator
local increment = 1.0 / generator.rate --/ generator.channels
local phase = generator.initialPhase
return function(freq)
phase = phase + increment
generator.phase = phase
local x = phase * freq
-- 2 ops: 1 mul, 1 trig
return math.sin(tau * x)
end
end
function sound.get()
local sound_table = {}
local length = (seconds[#seconds]-seconds[1]) * sound.rate
-- initialising sample
local sound_data = love.sound.newSoundData(length, sound.rate, sound.bits, sound.channel)
local oscillator = sine(sound)
-- writing to sample
local amplitude = 0.5
for i = 1, #seconds-1 do
from = (seconds[i]-seconds[1]) * sound.rate
till = (seconds[i+1]-seconds[1]) * sound.rate - 1
from, till = math.floor(from), math.floor(till) --rounding down the samples, just in case
for s = from, till do
now = lerp(frequency[i], frequency[i+1], (s-from)/(till-from))
sample = oscillator(now) * amplitude
sound_data:setSample(s, sample)
table.insert(sound_table, sample)
end
end
return sound_data, sound_table
end
--manual, deconstructed loop below
--[[
function sound.get(args, ...)
local sound_table = {}
local length = 3 * sound.rate
-- creating an empty sample
local sound_data = love.sound.newSoundData(length, sound.rate, sound.bits, sound.channel)
local oscillator = sine(sound)
s = 0
-- filling the sample with values
local amplitude = 0.5
for i = 0, 44100 do
s = i
now = lerp(frequency[1], frequency[2], i/44100)
--if i == 2 and s == from and (s-from)/(till-from) == 0 then debug1 = now end
--if i == 1 and s == till and (s-from)/(till-from) == 1 then debug2 = now end
sample = oscillator(now) * amplitude
sound_data:setSample(s, sample)
table.insert(sound_table, sample)
end
for i = 44100, 88200 do
s = i
now = lerp(frequency[2], frequency[3], (i-44100)/(88200-44100))
--if i == 2 and s == from and (s-from)/(till-from) == 0 then debug1 = now end
--if i == 1 and s == till and (s-from)/(till-from) == 1 then debug2 = now end
sample = oscillator(now) * amplitude
sound_data:setSample(s, sample)
table.insert(sound_table, sample)
end
for i = 88200, 132300-1 do
s = i
now = lerp(frequency[3], frequency[4], (i - 88200)/(132300-88200))
--if i == 2 and s == from and (s-from)/(till-from) == 0 then debug1 = now end
--if i == 1 and s == till and (s-from)/(till-from) == 1 then debug2 = now end
sample = oscillator(now) * amplitude
sound_data:setSample(s, sample)
table.insert(sound_table, sample)
end
return sound_data, sound_table
end]]