Function writeWAVFile

Writes an audio data array to a WAV file on disk.

void writeWAVFile(T) (
  string filePath,
  const T[] audioData,
  uint sampleRate,
  ushort numChannels
)
if (is(T == ubyte) || is(T == byte) || is(T == short) || is(T == int) || is(T == float));

This function generates a WAV file format byte array using toWAVFile() and writes it to a specified file path.

Parameters

NameDescription
filePath The path where the WAV file should be written.
audioData The raw audio data to be written.
sampleRate The sampling rate of the audio data.
numChannels The number of audio channels (1 for mono, 2 for stereo).

Example

Unit tests for the WAV audio module.

import std.array : array;
import std.stdio : writefln;
import std.file : remove;

/// RIFF (little-endian) data, WAVE audio, IEEE Float, mono 44100 Hz
enum string reffile_32f = "tests/wav_audio_ref_32f.wav";
///  RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz
enum string reffile_s16pcm = "tests/wav_audio_ref_s16pcm.wav";
///  RIFF (little-endian) data, WAVE audio, Microsoft PCM, 32 bit, mono 44100 Hz
enum string reffile_s32pcm = "tests/wav_audio_ref_s32pcm.wav";
///   RIFF (little-endian) data, WAVE audio, Microsoft PCM, 8 bit, mono 44100 Hz
enum string reffile_u8pcm = "tests/wav_audio_ref_u8pcm.wav";

enum string testfile_32f = "tests/tmp/wav_audio_32f.wav";
enum string testfile_s16pcm = "tests/tmp/wav_audio_s16pcm.wav";
enum string testfile_s32pcm = "tests/tmp/wav_audio_s32pcm.wav";
enum string testfile_u8pcm = "tests/tmp/wav_audio_u8pcm.wav";

enum testFreq = 441; // This does evenly divide the samplerate, eases the test
enum sampleRate = 44_100;
enum Duration cycleDur = dur!"hnsecs"(10 ^^ 7 / testFreq);

// pragma(msg, cycleDur);
// writeln(cycleDur);

ubyte[] readFileAsUbyte(string filePath) {
    import std.file : read;

    return cast(ubyte[]) read(filePath);
}

void dumpDiff(ubyte[] testWAV, ubyte[] expectedWAV) {
    if (testWAV != expectedWAV) {
        write(toRawDataDiff(testWAV, expectedWAV, "TEST:\n", "EXPECT:\n"));
    }
}

// writeln("START OF byte DUMP:");
{
    byte[] audioData = generateSinus!byte(testFreq.to!float, 1.0, cycleDur, sampleRate);
    assert(audioData.length == 100, "One cycle of 441Hz at 44100kHz is 100 bytes.");
    writeWAVFile(testfile_u8pcm, audioData, sampleRate, 1);
    ubyte[] testWAV = readFileAsUbyte(testfile_u8pcm);
    ubyte[] expectedWAV = readFileAsUbyte(reffile_u8pcm);
    dumpDiff(testWAV, expectedWAV);
    assert(testWAV == expectedWAV, "The written WAV file does not match the expected data.");
}
// writeln("START OF short DUMP:");
{
    short[] audioData = generateSinus!short(testFreq.to!float, 1.0, cycleDur, sampleRate);
    assert(audioData.length == 100, "One cycle of 441Hz at 44100kHz is 100 bytes.");
    writeWAVFile(testfile_s16pcm, audioData, sampleRate, 1);
    ubyte[] testWAV = readFileAsUbyte(testfile_s16pcm);
    ubyte[] expectedWAV = readFileAsUbyte(reffile_s16pcm);
    dumpDiff(testWAV, expectedWAV);
    assert(testWAV == expectedWAV, "The written WAV file does not match the expected data.");
}
// writeln("START OF int DUMP:");
{
    int[] audioData = generateSinus!int(testFreq.to!float, 1.0, cycleDur, sampleRate);
    assert(audioData.length == 100, "One cycle of 441Hz at 44100kHz is 100 bytes.");
    writeWAVFile(testfile_s32pcm, audioData, sampleRate, 1);
    ubyte[] testWAV = readFileAsUbyte(testfile_s32pcm);
    ubyte[] expectedWAV = readFileAsUbyte(reffile_s32pcm);
    dumpDiff(testWAV, expectedWAV);
    assert(testWAV == expectedWAV, "The written WAV file does not match the expected data.");
}
// writeln("START OF float DUMP:");
{
    float[] audioData = generateSinus!float(testFreq.to!float, 1.0, cycleDur, sampleRate);
    assert(audioData.length == 100, "One cycle of 441Hz at 44100kHz is 100 bytes.");
    writeWAVFile(testfile_32f, audioData, sampleRate, 1);
    ubyte[] testWAV = readFileAsUbyte(testfile_32f);
    ubyte[] expectedWAV = readFileAsUbyte(reffile_32f);
    testWAV[0x3c .. 0x40] = 0; // This is seconds since epoche - it is dynamically updated by tools, so we set it to 0 for compare.
    expectedWAV[0x3c .. 0x40] = 0;
    version (DigitalMars) {
        writefln("CAUTION: DMD gives different binary results here. @0x%x test:%s != expect:%s",
            0x118, testWAV[0x118..0x120], expectedWAV[0x118..0x120]);
        float tval = *(cast(float*)&testWAV[0x118]);
        float eval = *(cast(float*)&expectedWAV[0x118]);
        writefln("CAUTION: This binaries represent 'IEEE 754 float' test:%f and expect:%f", tval, eval);
        testWAV[0x118 .. 0x120] = expectedWAV[0x118 .. 0x120];
    }
    dumpDiff(testWAV, expectedWAV);
    assert(testWAV == expectedWAV, "The written WAV file does not match the expected data.");
}