Skip to content

Commit

Permalink
Add toWAV and saveAsWAV, functions that allow to output a WAV in file…
Browse files Browse the repository at this point in the history
… or slice form, in one single line.
  • Loading branch information
Guillaume Piolat committed Feb 28, 2023
1 parent d17cb35 commit 09301ba
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 6 deletions.
118 changes: 118 additions & 0 deletions source/audioformats/package.d
Expand Up @@ -13,10 +13,128 @@ public import audioformats.stream;


public import audioformats.internals: AudioFormatsException;
import core.stdc.stdlib: free;

/// Frees an exception thrown by audio-formats.
void destroyAudioFormatException(AudioFormatsException e) nothrow @nogc
{
import audioformats.internals;
destroyFree!AudioFormatsException(e);
}


/// Encode a slice to a WAV file.
///
/// Returns: `true` on success.
bool saveAsWAV(const(float)[] data,
const(char)[] filePath,
int numChannels = 1,
float sampleRate = 44100.0f,
EncodingOptions options = EncodingOptions.init) nothrow @nogc
{
return saveAsWAVImpl!float(data, filePath, numChannels, sampleRate, options);
}
///ditto
bool saveAsWAV(const(double)[] data,
const(char)[] filePath,
int numChannels = 1,
float sampleRate = 44100.0f,
EncodingOptions options = EncodingOptions.init) nothrow @nogc
{
return saveAsWAVImpl!double(data, filePath, numChannels, sampleRate, options);
}


/// Encode a slice to a WAV in memory.
/// The returned slice MUST be freed with `freeEncodedAudio`.
///
/// Returns: `null` in case of error.
const(ubyte)[] toWAV(const(float)[] data,
int numChannels = 1,
float sampleRate = 44100.0f,
EncodingOptions options = EncodingOptions.init) nothrow @nogc
{
return toWAVImpl!float(data, numChannels, sampleRate, options);
}
///ditto
const(ubyte)[] toWAV(const(double)[] data,
int numChannels = 1,
float sampleRate = 44100.0f,
EncodingOptions options = EncodingOptions.init) nothrow @nogc
{
return toWAVImpl!double(data, numChannels, sampleRate, options);
}


/// Disowned audio buffers (with eg. `encodeToWAV`) must be freed with this function.
void freeEncodedAudio(const(ubyte)[] encoded)
{
free(cast(void*)encoded.ptr);
}


private:


const(ubyte)[] toWAVImpl(T)(const(T)[] data, int numChannels, float sampleRate, EncodingOptions options) nothrow @nogc
{
assert(data !is null);
import core.stdc.string: strlen;

try
{
AudioStream encoder;
encoder.openToBuffer(AudioFileFormat.wav, sampleRate, numChannels, options);
static if (is(T == float))
encoder.writeSamplesFloat(data);
else
encoder.writeSamplesDouble(data);
const(ubyte)[] r = encoder.finalizeAndGetEncodedResultDisown();
return r;
}
catch (AudioFormatsException e)
{
destroyAudioFormatException(e);
return null;
}
catch(Exception e)
{
return null;
}
}

bool saveAsWAVImpl(T)(const(T)[] data,
const(char)[] filePath,
int numChannels,
float sampleRate,
EncodingOptions options) nothrow @nogc
{
if (data is null)
return false;
if (filePath is null)
return false;

import core.stdc.string: strlen;

try
{
AudioStream encoder;
encoder.openToFile(filePath, AudioFileFormat.wav, sampleRate, numChannels, options);
static if (is(T == float))
encoder.writeSamplesFloat(data);
else
encoder.writeSamplesDouble(data);
encoder.flush();
encoder.finalizeEncoding();
}
catch (AudioFormatsException e)
{
destroyAudioFormatException(e);
return false;
}
catch(Exception e)
{
return false;
}
return true;
}
30 changes: 24 additions & 6 deletions source/audioformats/stream.d
Expand Up @@ -637,7 +637,7 @@ public: // This is also part of the public API
///
/// Returns: Number of actually written frames. Multiply by `getNumChannels()` to get the number of written samples.
/// When that number is less than `frames`, it means the stream had a write error.
int writeSamplesFloat(float* inData, int frames) nothrow @nogc
int writeSamplesFloat(const(float)* inData, int frames) nothrow @nogc
{
assert(_io && _io.write !is null);

Expand Down Expand Up @@ -667,7 +667,7 @@ public: // This is also part of the public API
}
}
///ditto
int writeSamplesFloat(float[] inData) nothrow @nogc
int writeSamplesFloat(const(float)[] inData) nothrow @nogc
{
assert( (inData.length % _numChannels) == 0);
return writeSamplesFloat(inData.ptr, cast(int)(inData.length / _numChannels));
Expand All @@ -687,11 +687,11 @@ public: // This is also part of the public API
///
/// Returns: Number of actually written frames. Multiply by `getNumChannels()` to get the number of written samples.
/// When that number is less than `frames`, it means the stream had a write error.
int writeSamplesDouble(double* inData, int frames) nothrow @nogc
int writeSamplesDouble(const(double)* inData, int frames) nothrow @nogc
{
assert(_io && _io.write !is null);
assert (_io && _io.write !is null);

switch(_format)
switch (_format)
{
case AudioFileFormat.unknown:
// One shouldn't ever get there
Expand Down Expand Up @@ -721,7 +721,7 @@ public: // This is also part of the public API
}
}
///ditto
int writeSamplesDouble(double[] inData) nothrow @nogc
int writeSamplesDouble(const(double)[] inData) nothrow @nogc
{
assert( (inData.length % _numChannels) == 0);
return writeSamplesDouble(inData.ptr, cast(int)(inData.length / _numChannels));
Expand Down Expand Up @@ -1106,6 +1106,16 @@ public: // This is also part of the public API
return memoryContext.buffer[0..memoryContext.size];
}

// Finalize encoding and get internal buffer, which is disowned by the `AudioStream`.
// The caller has to call `freeEncodedAudio` manually.
// This can be exactly one time, if a growable owned buffer was used.
const(ubyte)[] finalizeAndGetEncodedResultDisown() @nogc
{
const(ubyte)[] buf = finalizeAndGetEncodedResult();
memoryContext.disownBuffer();
return buf;
}

private:
IOCallbacks* _io;

Expand Down Expand Up @@ -1729,6 +1739,14 @@ struct MemoryContext
capacity = 0;
}

// caller guarantees the buffer will be freed with `free`.
void disownBuffer() nothrow @nogc
{
assert(bufferIsOwned);
bufferIsOwned = false;
bufferCanGrow = false;
}

~this()
{
if (bufferIsOwned)
Expand Down

0 comments on commit 09301ba

Please sign in to comment.