## [SOLVED] Changing frequency while synthesising sine wave

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
busboy
Prole
Posts: 3
Joined: Mon Apr 19, 2021 7:19 am

### [SOLVED] Changing frequency while synthesising sine wave

Hello,

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

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]]

Attachments
sine.love.zip
Last edited by busboy on Tue Apr 20, 2021 2:53 pm, edited 1 time in total.
zorg
Party member
Posts: 3090
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

### Re: Changing frequency while synthesising sine wave

Haven't had time yet to look at it too much, but one thing you might try is to limit the phase in the returned generator function to the range/domain (i get these mixed up) of [0,1] with something like:

Code: Select all

phase = (phase + increment) % 1.0

...that said, i haven't noticed you dividing x by the sample rate either, that should happen if you want the correct frequencies to play.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
busboy
Prole
Posts: 3
Joined: Mon Apr 19, 2021 7:19 am

### Re: Changing frequency while synthesising sine wave

zorg wrote: Tue Apr 20, 2021 3:47 am Haven't had time yet to look at it too much, but one thing you might try is to limit the phase in the returned generator function to the range/domain (i get these mixed up) of [0,1] with something like:

Code: Select all

phase = (phase + increment) % 1.0

Unfortunately all this seemed to do is affect the continuity of the signal from one frequency point to another. Your sine oscillator not having one of these limits was also what kept it from clicking like all the others I tried.

without phase limit:

with phase limit:

zorg wrote: Tue Apr 20, 2021 3:47 am ...that said, i haven't noticed you dividing x by the sample rate either, that should happen if you want the correct frequencies to play.
Would that just be replacing the x declaration in the returned function with

Code: Select all

local x = (phase * freq)/generator.rate
Because that just flattens the line.

I'm not sure I fully understand, I apologise.
zorg
Party member
Posts: 3090
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

### Re: Changing frequency while synthesising sine wave

>Would that just be replacing the x declaration in the returned function with <code>
Yes, it would... i feel like something else is amiss, but again, i'd need to dwelve into it, and due to me working today, that's not gonna happen.
I did notice that you did multiply with sound.rate in your code, so that might be why it could have worked before... i just feel that that's in the wrong place.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
pgimeno
Party member
Posts: 2611
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Changing frequency while synthesising sine wave

This seems to work:

Code: Select all

    return function(freq)
phase = phase + tau * increment * freq
generator.phase = phase
return math.sin(phase)
end

Why is it different, I haven't stopped to analyse yet. By the way, you can save a multiplication by pre-calculating tau * increment.

Edit: Intuitively, the phase shift should be affected by frequency, and in your original formulation you don't have that; the phase is always the same for a given instant in time, which isn't right. Your original formulation works for a fixed frequency, but not for an arbitrarily changing frequency.
busboy
Prole
Posts: 3
Joined: Mon Apr 19, 2021 7:19 am

### Re: Changing frequency while synthesising sine wave

pgimeno wrote: Tue Apr 20, 2021 10:56 am

Code: Select all

    return function(freq)
phase = phase + tau * increment * freq
generator.phase = phase
return math.sin(phase)
end

This is dead on, thank you so much.
darkfrei
Party member
Posts: 222
Joined: Sat Feb 08, 2020 11:09 pm

### Re: [SOLVED] Changing frequency while synthesising sine wave

How it works?
Attachments
2021-04-20T17_38_49-Untitled.png (64.99 KiB) Viewed 1933 times
sounds-01.love
zorg
Party member
Posts: 3090
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

### Re: [SOLVED] Changing frequency while synthesising sine wave

I checked the post of mine from the thread you linked, and i got it wrong there as well... guess i'll take the blame fair and square.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.

### Who is online

Users browsing this forum: Google [Bot] and 44 guests