feat(core/audio): add WAV writer and audio track buffer
This commit is contained in:
1 parent
e18e59beb4
commit
d14fcd126d
2 files changed
+120
No files matched your search
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Audio;
|
||||||
|
|
||||||
|
public sealed class AudioTrack
|
||||||
|
{
|
||||||
|
readonly List<short> samples = new();
|
||||||
|
readonly int sampleRate;
|
||||||
|
|
||||||
|
double squarePhase;
|
||||||
|
double patternPosition;
|
||||||
|
|
||||||
|
public AudioTrack(int sampleRate)
|
||||||
|
{
|
||||||
|
if (sampleRate <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sampleRate), "sample rate must be positive");
|
||||||
|
}
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sampleCount => samples.Count;
|
||||||
|
|
||||||
|
public double durationSeconds => (double)samples.Count / sampleRate;
|
||||||
|
|
||||||
|
public int samplesForSeconds(double seconds) => (int)Math.Round(seconds * sampleRate);
|
||||||
|
|
||||||
|
public void appendSilence(int count)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
samples.Add(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendSquare(double frequency, int count, double amplitude = 0.4, double duty = 0.5)
|
||||||
|
{
|
||||||
|
short high = AudioSynth.amplitudeToPcm(amplitude);
|
||||||
|
short low = (short)-high;
|
||||||
|
double step = frequency / sampleRate;
|
||||||
|
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
samples.Add((squarePhase % 1.0) < duty ? high : low);
|
||||||
|
squarePhase += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendPattern(byte[] pattern, double playbackRate, int count, double amplitude = 0.4)
|
||||||
|
{
|
||||||
|
bool[] bits = AudioSynth.decodePattern(pattern);
|
||||||
|
short high = AudioSynth.amplitudeToPcm(amplitude);
|
||||||
|
short low = (short)-high;
|
||||||
|
double step = playbackRate / sampleRate;
|
||||||
|
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
int bit = ((int)patternPosition) % AudioSynth.patternBits;
|
||||||
|
samples.Add(bits[bit] ? high : low);
|
||||||
|
patternPosition += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public short[] toSamples() => samples.ToArray();
|
||||||
|
|
||||||
|
public byte[] toWav() => WavFile.write(samples.ToArray(), sampleRate);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Audio;
|
||||||
|
|
||||||
|
public static class WavFile
|
||||||
|
{
|
||||||
|
public const int bitsPerSample = 16;
|
||||||
|
public const int channels = 1;
|
||||||
|
|
||||||
|
public static byte[] write(short[] samples, int sampleRate)
|
||||||
|
{
|
||||||
|
if (sampleRate <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sampleRate), "sample rate must be positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
int dataBytes = samples.Length * sizeof(short);
|
||||||
|
int blockAlign = channels * (bitsPerSample / 8);
|
||||||
|
int byteRate = sampleRate * blockAlign;
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(44 + dataBytes);
|
||||||
|
using var writer = new BinaryWriter(stream, Encoding.ASCII);
|
||||||
|
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||||
|
writer.Write(36 + dataBytes);
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||||
|
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||||
|
writer.Write(16);
|
||||||
|
writer.Write((short)1);
|
||||||
|
writer.Write((short)channels);
|
||||||
|
writer.Write(sampleRate);
|
||||||
|
writer.Write(byteRate);
|
||||||
|
writer.Write((short)blockAlign);
|
||||||
|
writer.Write((short)bitsPerSample);
|
||||||
|
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("data"));
|
||||||
|
writer.Write(dataBytes);
|
||||||
|
foreach (short sample in samples)
|
||||||
|
{
|
||||||
|
writer.Write(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Flush();
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeFile(string path, short[] samples, int sampleRate) =>
|
||||||
|
File.WriteAllBytes(path, write(samples, sampleRate));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user