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