Frequency modulation sound

Started by GW, March 03, 2022, 20:45:39

Previous topic - Next topic

GW

Here is a simple example of how frequency modulation works in audio.
The frequency of one tone (carrier) is altered with another oscillator (modulator). At high speeds, the resulting combination will bring out extra harmonics.
There are no dependencies in the example.
Play with the sliders and see how the different tones are generated.
Also, the Freeaudio driver is odd, sometime I get clicking for an unknown reason. If that's the case for you, just restart the program.


SuperStrict
Framework brl.basic
Import pub.freeaudio
Import brl.glmax2d
Import brl.retro

'// If you get clicking in the audio, try restarting. Freeaudio is weird.

AppTitle = "Frequency Modulaton Example"

Const N% = 512
Const TWOPI:Double = Pi * 2
Const TWOPOS:Double = (2.0 * Pi) / N

Global Buff#[N]
Global Bp%
Global CarFreq# = 440
Global CarPhase#
Global ModFreq# = 0
Global ModPhase#
Global ModAmp# = 1
Global tim%
Global CFslider:tSlider = New tSlider(20, 10, 350, 20, "Carrier Freq")
Global MFslider:tSlider = New tSlider(20, 40, 350, 20, "Modulator Freq", 0)
Global MAslider:tSlider = New tSlider(20, 70, 350, 20, "Modulator Amp", 0)

Graphics 512, 500

While Not KeyHit(KEY_ESCAPE)
Cls
UpdateAudio()
DrawWave()
DrawSpeccy()

CFslider.Update(MouseX(), MouseY())
CarFreq = 20 + Lerp(20, 3000, CFslider.value() ^ 2)
CFslider.Draw()

MFslider.Update(MouseX(), MouseY())
ModFreq = Lerp(1, 3000, MFslider.value())
MFslider.Draw()

MAslider.Update(MouseX(), MouseY())
ModAmp = Lerp(1, 2000, MAslider.value())
MAslider.Draw()
DrawText("Mod/Car Ratio: " + (ModFreq / CarFreq), 300, 480)
Flip
tim:+1
WEnd

'------------------------------------------------------------------------------------------
Function GetAudio#()
'//Modulator
ModPhase:+ModFreq * TWOPI / 44100.0
If ModPhase > Pi Then ModPhase:-TWOPI
Local ModVal# = Sine(ModPhase) * ModAmp

'//Carrier
CarPhase:+((CarFreq + ModVal)) * TWOPI / 44100.0
If CarPhase > Pi Then CarPhase:-TWOPI

Buff[Bp & 511] = Sine(CarPhase)
Bp:+1

Return Sine(CarPhase) * 0.2
End Function
'------------------------------------------------------------------------------------------
Function UpdateAudio()
Global First:Int = True
Const FRAG:Int = 2048 * 4
Global buffer:Short[FRAG * 8]
Global writepos:Int
Global sound:Byte Ptr
Global channel:Int
Global streaming:Int

If First Then
fa_Init(0)
sound = fa_CreateSound(FRAG * 8, 16, 1, 44100, buffer, $80000000)
channel = fa_PlaySound(sound, FA_CHANNELSTATUS_STREAMING, 0)
fa_SetChannelPaused(channel, False)
First = False
Return
End If

Local readpos:Int = fa_ChannelPosition(channel)
Local Write:Int = readpos + FRAG * 1 - writepos
Local frags:Int = Write / FRAG

While frags > 0
Local Pos:Int = writepos Mod (FRAG * 8)
For Local f:Int = 0 Until FRAG
buffer[Pos + f] = GetAudio() * 30000
Next
writepos:+FRAG
frags:-1
Wend
End Function
'------------------------------------------------------------------------------------------
Function DrawWave()
SetColor 255, 255, 255
Local prev% = Buff[0]
For Local i% = 0 Until N
DrawRect(i, 150 + Buff[i] * 50, 2, 2)
prev = Buff[i]
Next
DrawRect(0, 210, N, 1)
End Function
'------------------------------------------------------------------------------------------
Function DrawSpeccy()
Global cp#[N]
Global sp#[N]
MemClear(VarPtr cp[0], SizeOf(1#) * N)
MemClear(VarPtr sp[0], SizeOf(1#) * N)
For Local b% = 0 Until N / 4
For Local k% = 0 Until 256
Local a# = 2.0 * b * Pi * k / 256
sp[b]:+Buff[k] * - 1 * Sine(a)
cp[b]:+Buff[k] * Cosine(a)
Next
Next
Local prev# = 0
For Local i% = 1 Until N / 8
Local v# = (Sqr((sp[i] * sp[i]) + (cp[i] * cp[i])) / 64)
DrawLine(i * 12, 500 - prev * 100, 12 + i * 12, 500 - v * 100)
prev = v
Next
End Function









'-------------------------------------------------------------------------------------------------
Function Sine:Double(x:Double) Inline
Return Sin(x / (Pi / 180.0))
End Function

Function Cosine:Double(x:Double) Inline
Return Sine((3.1415926535897932 / 2.0) - x)
End Function

Function Lerp:Float(a:Float, b:Float, x:Float)
    Return a*(1-x)+b*x
End Function

Function invLerp#(a#, b#, v#)
    return (v-a)/(b-a)
End Function

Type tSlider
Field x:Int, y:Int, w:Int, h:Int
Field val:Float = 0.5
Field Name$ = ""

Method New(x%, y%, w%, h%, Name$, defv# = 0.5)
Self.x = x
Self.y = y
Self.w = w
Self.h = h
Self.Name = Name
val = defv
End Method

Method MouseOver:Int(mx:Float, my:Float)
Return InsideRect(Mx, My, x, y, w, h)
End Method

Method Update(mx:Float, my:Float)
If Not MouseOver(mx, my) Then Return
If Not MouseDown(1) Then Return
Local v:Float = Normalize(mx, 0, 1, x, x + w)
val = Clamp(v, 0, 1)
End Method

Method value#()
Return val ^ 2'0.3333
End Method

Method Draw()
SetColor 32, 32, 32
DrawRect x, y, w, h
SetColor 90, 64, 64
DrawText(Left(val, 4), (x + w) / 2, y + 1)
SetColor 255, 255, 255
DrawRectHollow(x, y, w, h)
Local sv:Float = Normalize(val, x + 1, x + w - 1, 0, 1)
SetColor 0, 255, 0
DrawRect(sv, y, 2, h)
DrawText(Name, x + w + 5, y)
End Method

Function DrawRectHollow(x:Float, y:Float, w:Int, h:Int)
DrawLine(x, y, x + w, y)
DrawLine(x,y,x,y+h)
DrawLine(x+w,y,x+w,y+h)
DrawLine(x,y+h,x+w,y+h)
End Function

Function InsideRect:Int(x:Float, y:Float, x2:Float, y2:Float, w:Float, h:Float)
Return _pointinrect(x, y, x2, y2, x2 + w, y2 + h)
End Function

Function _pointinrect:Int(iPointX:Int, iPointY:Int, iXPos1:Int, iYPos1:Int, iXPos2:Int, iYPos2:Int)
Return  ((((iPointX-iXPos1) ~ (iPointX-iXPos2)) & ((iPointY-iYPos1) ~ (iPointY-iYPos2))) & $80000000)
End Function

Function Normalize:Float(val:Float, desmin:Float, desmax:Float, natmin:Float, natmax:Float)
Return desmin + (val - natmin) * (desmax - desmin) / (natmax - natmin)
End Function

Function Clamp:Float(x:Float, a:Float, b:Float)
        If x < a Then Return a
        If x > b Then Return b
        Return x
End Function
End Type


markcwm

This is very cool, thanks. It seems you have a good grasp of the subject, I tried writing an audio editor once but didn't understand waveforms very well so it wasn't great.

Baggey

Hi, Thankyou for your Demo. Im currently trying to get my head around all this audio stuff 8)

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!