feat(core/audio): add square-wave synth and audio recorder
This commit is contained in:
1 parent
d14fcd126d
commit
8c516cccbf
2 files changed
+128
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Audio;
|
||||||
|
|
||||||
|
public sealed class AudioRecorder
|
||||||
|
{
|
||||||
|
public const int defaultSampleRate = 44100;
|
||||||
|
public const double defaultBuzzerFrequency = 440.0;
|
||||||
|
public const double frameSeconds = 1.0 / 60.0;
|
||||||
|
|
||||||
|
readonly int sampleRate;
|
||||||
|
readonly double buzzerFrequency;
|
||||||
|
|
||||||
|
public AudioRecorder(int sampleRate = defaultSampleRate, double buzzerFrequency = defaultBuzzerFrequency)
|
||||||
|
{
|
||||||
|
if (sampleRate <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sampleRate), "sample rate must be positive");
|
||||||
|
}
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
this.buzzerFrequency = buzzerFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioTrack record(Machine machine, int frames, int instructionsPerFrame)
|
||||||
|
{
|
||||||
|
if (frames < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(frames), "frame count cannot be negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
var track = new AudioTrack(sampleRate);
|
||||||
|
int samplesPerFrame = track.samplesForSeconds(frameSeconds);
|
||||||
|
|
||||||
|
for (int frame = 0; frame < frames && !machine.exited; frame++)
|
||||||
|
{
|
||||||
|
for (int cycle = 0; cycle < instructionsPerFrame; cycle++)
|
||||||
|
{
|
||||||
|
machine.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
appendFrame(machine, track, samplesPerFrame);
|
||||||
|
machine.tickTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendFrame(Machine machine, AudioTrack track, int samplesPerFrame)
|
||||||
|
{
|
||||||
|
if (!machine.soundActive)
|
||||||
|
{
|
||||||
|
track.appendSilence(samplesPerFrame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (machine.platform == Platform.XoChip)
|
||||||
|
{
|
||||||
|
track.appendPattern(machine.audioPattern, machine.audioFrequency, samplesPerFrame);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
track.appendSquare(buzzerFrequency, samplesPerFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Audio;
|
||||||
|
|
||||||
|
public static class AudioSynth
|
||||||
|
{
|
||||||
|
public const int patternBits = 128;
|
||||||
|
|
||||||
|
public static short amplitudeToPcm(double amplitude)
|
||||||
|
{
|
||||||
|
double clamped = amplitude < 0 ? 0 : amplitude > 1 ? 1 : amplitude;
|
||||||
|
return (short)(clamped * short.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool[] decodePattern(byte[] pattern)
|
||||||
|
{
|
||||||
|
var bits = new bool[patternBits];
|
||||||
|
for (int index = 0; index < patternBits; index++)
|
||||||
|
{
|
||||||
|
int sourceByte = index / 8;
|
||||||
|
int bit = 7 - (index % 8);
|
||||||
|
byte value = sourceByte < pattern.Length ? pattern[sourceByte] : (byte)0;
|
||||||
|
bits[index] = (value & (1 << bit)) != 0;
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short[] square(double frequency, int sampleRate, int sampleCount, double amplitude = 0.4, double duty = 0.5)
|
||||||
|
{
|
||||||
|
var samples = new short[sampleCount];
|
||||||
|
short high = amplitudeToPcm(amplitude);
|
||||||
|
short low = (short)-high;
|
||||||
|
double step = frequency / sampleRate;
|
||||||
|
double phase = 0.0;
|
||||||
|
|
||||||
|
for (int index = 0; index < sampleCount; index++)
|
||||||
|
{
|
||||||
|
samples[index] = (phase % 1.0) < duty ? high : low;
|
||||||
|
phase += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short[] renderPattern(byte[] pattern, double playbackRate, int sampleRate, int sampleCount, double amplitude = 0.4)
|
||||||
|
{
|
||||||
|
bool[] bits = decodePattern(pattern);
|
||||||
|
var samples = new short[sampleCount];
|
||||||
|
short high = amplitudeToPcm(amplitude);
|
||||||
|
short low = (short)-high;
|
||||||
|
double step = playbackRate / sampleRate;
|
||||||
|
double position = 0.0;
|
||||||
|
|
||||||
|
for (int index = 0; index < sampleCount; index++)
|
||||||
|
{
|
||||||
|
int bit = ((int)position) % patternBits;
|
||||||
|
samples[index] = bits[bit] ? high : low;
|
||||||
|
position += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user