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