FM Synth and Plotting with PowerShell

Published October 24, 2017

Beep boop, making progress on my FM synthesizer. Had a nasty popping sound with my phase-feedback logic and needed some way to rapidly graph out the logic. Powershell to the rescue:


# plot-sample.ps1
$samples = 1024;
$width   = 2048;
$height  = 512;

[Reflection.Assembly]::LoadWithPartialName("System.Drawing");

$bmp = new-object Drawing.Bitmap $width, $height;
$g = [Drawing.Graphics]::FromImage($bmp);
$g.SmoothingMode = "AntiAlias";

$penAxis  = new-object Drawing.Pen 0xFF808080, 2;
$penLineA = new-object Drawing.Pen 0xFF0000F0, 2;
$penLineB = new-object Drawing.Pen 0xFFF00000, 2;
$ptsA     = new-object Drawing.PointF[] $samples;
$ptsB     = new-object Drawing.PointF[] $samples;

0..($samples-1) |% {
  $Q = 4*[Math]::pi/$samples;
  $feedback = 0;
} {
  $ampA = [Math]::sin($_*$Q + $feedback); # A[t] = sin(t + 1.05*A[t-1])
  $ampB = [Math]::sin($_*$Q);             # B[t] = sin(t) $feedback = $ampA * 1.05;

  $x = $bmp.Width * $_/$samples;
  $yA = $bmp.Height * (-$ampA/2.1 + .5);
  $yB = $bmp.Height * (-$ampB/2.1 + .5);
  $ptsA[$_] = new-object Drawing.PointF $x, $yA;
  $ptsB[$_] = new-object Drawing.PointF $x, $yB;
} {
  $g.Clear([Drawing.Color]::White);
  $g.DrawLine($penAxis,0,$bmp.Height/2,$bmp.Width,$bmp.Height/2);
  $g.DrawLines($penLineB,$ptsB);
  $g.DrawLines($penLineA,$ptsA);
  $bmp.Save("$pwd\plot.png");
}

plot.png
Looks like the logic should be sound. I did find that the phase-feedback wasn't being sin'd in the synthesizer, and that was causing the popping noise. And here's a sample of what it sounds like right now:
fm-sample.ogg (36.4KB)

And here's the instrument used to generate that sound:


/*
unit: 
	mul   frequency multiplier
	ofs   additive frequency
	amp   volume
	feed  phase feedback

envelope: 
	A     attack rate, in degrees
	D1    decay rate to sustain level, in degrees
	D2    decay rate to silence, in degrees
	R     release rate, in degrees
	S     sustain level

program: 
	u     unit to render
	in    phase buffer input slot, 0 for null input
	out   phase buffer output slot, 0 for audio data
*/

instrument i;
int idx = 0;
//                 mul    ofs    amp    feed    A     D1    D2    R     S
i.units[idx++] = { 2.00f, 0.00f, 0.50f, 1.00f, {80.0, 20.0, 10.0, 45.0, 0.75 }};
i.units[idx++] = { 1.00f, 0.00f, 0.25f, 0.00f, {85.0, 45.0,  5.0, 60.0, 0.50 }};
i.units[idx++] = { 7.00f, 0.33f, 0.12f, 0.00f, {88.5, 60.0,  5.0, 60.0, 0.25 }};
i.units[idx++] = { 1.00f, 0.33f, 0.50f, 0.00f, {87.0, 30.0,  5.0, 60.0, 0.67 }};
i.ct_units = idx;


// program:
// ┌─┐   ┌─┐
// │0├──►│1├──┐
// └─┘   └─┘  │   ┌─┐
//            ├──►│3├──►out
//       ┌─┐  │   └─┘
//       │2├──┘
//       └─┘

idx = 0; 
//                   u  in out
i.program[idx++] = { 0, 0, 2 };
i.program[idx++] = { 1, 2, 1 };
i.program[idx++] = { 2, 0, 1 };
i.program[idx++] = { 3, 1, 0 };
i.ct_program = idx;

Hue.

4 likes 1 comments

Comments

Comments are shown oldest first so the discussion reads top to bottom.
fastcall22

(Cleaned up from IPBoard upgrade. Publishing it seems to have bumped it up to the top.)

October 24, 2017 10:22 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!