diff --git a/simpleaudio.d b/simpleaudio.d
index b10031f..e136615 100644
--- a/simpleaudio.d
+++ b/simpleaudio.d
@@ -1,7 +1,7 @@
/**
The purpose of this module is to provide audio functions for
things like playback, capture, and volume on both Windows
- (via the mmsystem calls)and Linux (through ALSA).
+ (via the mmsystem calls) and Linux (through ALSA).
It is only aimed at the basics, and will be filled in as I want
a particular feature. I don't generally need super configurability
@@ -15,33 +15,32 @@
That will consist of a listening callback for volume changes and
being able to get/set the volume.
-
- HOW IT WORKS:
- You make a callback which feeds data to the device. Make an
- AudioOutput struct then feed your callback to it. Then play.
-
- Then call loop? Or that could be in play?
-
- Methods:
- setCallback
- play
- pause
-
-
TODO:
* play audio high level with options to wait until completion or return immediately
* midi mid-level stuff
- * audio callback stuff (it tells us when to fill the buffer)
- * Windows support for waveOut and waveIn. Maybe mixer too, but that's lowest priority.
+ * Windows support for mixer.
* I'll also write .mid and .wav functions at least eventually. Maybe in separate modules but probably here since they aren't that complex.
I will probably NOT do OSS anymore, since my computer doesn't even work with it now.
Ditto for Macintosh, as I don't have one and don't really care about them.
+
+ License:
+ GPL3 unless you compile with `-version=without_resampler` and do *not* use
+ the mp3 functions, in which case it is BSL-1.0.
*/
module arsd.simpleaudio;
+version(without_resampler) {
+
+} else {
+ version(X86)
+ version=with_resampler;
+ version(X86_64)
+ version=with_resampler;
+}
+
enum BUFFER_SIZE_FRAMES = 1024;//512;//2048;
enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2;
@@ -197,16 +196,24 @@ struct AudioOutputThread {
Pass `true` to enable the audio thread. Otherwise, it will
just live as a dummy mock object that you should not actually
try to use.
+
+ History:
+ Parameter `default` added on Nov 8, 2020.
+/
- this(bool enable, int SampleRate = 44100, int channels = 2) {
+ this(bool enable, int SampleRate = 44100, int channels = 2, string device = "default") {
if(enable) {
- impl = new AudioPcmOutThreadImplementation(SampleRate, channels);
+ impl = new AudioPcmOutThreadImplementation(SampleRate, channels, device);
impl.refcount++;
impl.start();
impl.waitForInitialization();
}
}
+ /// ditto
+ this(bool enable, string device, int SampleRate = 44100, int channels = 2) {
+ this(enable, SampleRate, channels, device);
+ }
+
/// Keeps an internal refcount.
this(this) {
if(impl)
@@ -259,6 +266,10 @@ struct AudioOutputThread {
if(impl)
impl.playMp3(filename);
}
+ void playWav()(string filename) {
+ if(impl)
+ impl.playWav(filename);
+ }
/// provides automatic [arsd.jsvar] script wrapping capability. Make sure the
@@ -306,11 +317,12 @@ import core.thread;
---
+/
final class AudioPcmOutThreadImplementation : Thread {
- private this(int SampleRate, int channels) {
+ private this(int SampleRate, int channels, string device = "default") {
this.isDaemon = true;
this.SampleRate = SampleRate;
this.channels = channels;
+ this.device = device;
super(&run);
}
@@ -318,6 +330,7 @@ final class AudioPcmOutThreadImplementation : Thread {
private int SampleRate;
private int channels;
private int refcount;
+ private string device;
private void waitForInitialization() {
shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao;
@@ -421,36 +434,83 @@ final class AudioPcmOutThreadImplementation : Thread {
addSample(s);
}
- /// Requires vorbis.d to be compiled in (module arsd.vorbis)
+ /++
+ Requires vorbis.d to be compiled in (module arsd.vorbis)
+
+ History:
+ Automatic resampling support added Nov 7, 2020.
+ +/
void playOgg()(string filename, bool loop = false) {
import arsd.vorbis;
auto v = new VorbisDecoder(filename);
- addChannel(
- delegate bool(short[] buffer) {
- if(cast(int) buffer.length != buffer.length)
- throw new Exception("eeeek");
- auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length);
- if(got == 0) {
- if(loop) {
- v.seekStart();
- return true;
+ /+
+ If you want 2 channels:
+ if the file has 2+, use them.
+ If the file has 1, duplicate it for the two outputs.
+ If you want 1 channel:
+ if the file has 1, use it
+ if the file has 2, average them.
+ +/
+
+ if(v.sampleRate == SampleRate && v.chans == channels) {
+ plain_fallback:
+ addChannel(
+ delegate bool(short[] buffer) {
+ if(cast(int) buffer.length != buffer.length)
+ throw new Exception("eeeek");
+
+ plain:
+ auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length);
+ if(got == 0) {
+ if(loop) {
+ v.seekStart();
+ return true;
+ }
+
+ return false;
+ }
+ return true;
+ }
+ );
+ return;
+ } else {
+ version(with_resampler) {
+ auto resampleContext = new class ResamplingContext {
+ this() {
+ super(v.sampleRate, SampleRate, v.chans, channels);
}
- return false;
- }
- return true;
- }
- );
+ override void loadMoreSamples() {
+ float*[2] tmp;
+ tmp[0] = buffersIn[0].ptr;
+ tmp[1] = buffersIn[1].ptr;
+
+ auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
+
+ resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
+ if(v.chans > 1)
+ resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
+ }
+ };
+
+ addChannel(&resampleContext.fillBuffer);
+ } else goto plain_fallback;
+ }
}
- /// Requires mp3.d to be compiled in (module arsd.mp3) which is LGPL licensed.
+ /++
+ Requires mp3.d to be compiled in (module arsd.mp3) which is LGPL licensed.
+
+ History:
+ Automatic resampling support added Nov 7, 2020.
+ +/
void playMp3()(string filename) {
import arsd.mp3;
import std.stdio;
- auto fi = File(filename);
+ auto fi = new File(filename); // just let the GC close it... otherwise random segfaults happen... blargh
scope auto reader = delegate(void[] buf) {
return cast(int) fi.rawRead(buf[]).length;
};
@@ -459,38 +519,196 @@ final class AudioPcmOutThreadImplementation : Thread {
if(!mp3.valid)
throw new Exception("no file");
- auto next = mp3.frameSamples;
+ if(mp3.sampleRate == SampleRate && mp3.channels == channels) {
+ plain_fallback:
- addChannel(
- delegate bool(short[] buffer) {
- if(cast(int) buffer.length != buffer.length)
- throw new Exception("eeeek");
+ auto next = mp3.frameSamples;
- more:
- if(next.length >= buffer.length) {
- buffer[] = next[0 .. buffer.length];
- next = next[buffer.length .. $];
- } else {
- buffer[0 .. next.length] = next[];
- buffer = buffer[next.length .. $];
+ addChannel(
+ delegate bool(short[] buffer) {
+ if(cast(int) buffer.length != buffer.length)
+ throw new Exception("eeeek");
- next = next[$..$];
+ more:
+ if(next.length >= buffer.length) {
+ buffer[] = next[0 .. buffer.length];
+ next = next[buffer.length .. $];
+ } else {
+ buffer[0 .. next.length] = next[];
+ buffer = buffer[next.length .. $];
- if(buffer.length) {
- if(mp3.valid) {
- mp3.decodeNextFrame(reader);
- next = mp3.frameSamples;
- goto more;
- } else {
- buffer[] = 0;
- return false;
+ next = next[$..$];
+
+ if(buffer.length) {
+ if(mp3.valid) {
+ mp3.decodeNextFrame(reader);
+ next = mp3.frameSamples;
+ goto more;
+ } else {
+ buffer[] = 0;
+ return false;
+ }
}
}
+
+ return true;
+ }
+ );
+ } else {
+ version(with_resampler) {
+ auto next = mp3.frameSamples;
+
+ auto resampleContext = new class ResamplingContext {
+ this() {
+ super(mp3.sampleRate, SampleRate, mp3.channels, channels);
+ }
+
+ override void loadMoreSamples() {
+ if(mp3.channels == 1) {
+ int actuallyGot;
+
+ foreach(ref b; buffersIn[0]) {
+ if(next.length == 0) break;
+ b = cast(float) next[0] / short.max;
+ next = next[1 .. $];
+ if(next.length == 0) {
+ mp3.decodeNextFrame(reader);
+ next = mp3.frameSamples;
+ }
+ actuallyGot++;
+ }
+ resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
+ } else {
+ int actuallyGot;
+
+ foreach(idx, ref b; buffersIn[0]) {
+ if(next.length == 0) break;
+ b = cast(float) next[0] / short.max;
+ next = next[1 .. $];
+ if(next.length == 0) {
+ mp3.decodeNextFrame(reader);
+ next = mp3.frameSamples;
+ }
+ buffersIn[1][idx] = cast(float) next[0] / short.max;
+ next = next[1 .. $];
+ if(next.length == 0) {
+ mp3.decodeNextFrame(reader);
+ next = mp3.frameSamples;
+ }
+ actuallyGot++;
+ }
+ resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
+ resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
+ }
+ }
+ };
+
+ addChannel(&resampleContext.fillBuffer);
+
+ } else goto plain_fallback;
+ }
+ }
+
+ /++
+ Requires [arsd.wav]. Only supports simple 8 or 16 bit wav files, no extensible or float formats at this time.
+
+ Also requires the resampler to be compiled in at this time, but that may change in the future, I was just lazy.
+
+ History:
+ Added Nov 8, 2020.
+ +/
+ void playWav()(string filename) {
+ version(with_resampler) {
+ auto resampleContext = new class ResamplingContext {
+ import arsd.wav;
+
+ this() {
+ reader = wavReader(filename);
+
+ super(reader.sampleRate, SampleRate, reader.numberOfChannels, channels);
}
- return true;
- }
- );
+ WavReader!CFileChunks reader;
+ const(ubyte)[] next;
+
+ override void loadMoreSamples() {
+
+ bool moar() {
+ if(next.length == 0) {
+ if(reader.empty)
+ return false;
+ reader.popFront;
+ next = reader.front;
+ if(next.length == 0)
+ return false;
+ }
+ return true;
+ }
+
+ if(reader.numberOfChannels == 1) {
+ int actuallyGot;
+
+ foreach(ref b; buffersIn[0]) {
+ if(!moar) break;
+ if(reader.bitsPerSample == 8) {
+ b = (cast(float) next[0] - 128.0f) / 127.0f;
+ next = next[1 .. $];
+ } else if(reader.bitsPerSample == 16) {
+ short n = next[0];
+ next = next[1 .. $];
+ if(!moar) break;
+ n |= cast(ushort)(next[0]) << 8;
+ next = next[1 .. $];
+
+ b = (cast(float) n) / short.max;
+ } else assert(0);
+
+ actuallyGot++;
+ }
+ resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
+ } else {
+ int actuallyGot;
+
+ foreach(idx, ref b; buffersIn[0]) {
+ if(!moar) break;
+ if(reader.bitsPerSample == 8) {
+ b = (cast(float) next[0] - 128.0f) / 127.0f;
+ next = next[1 .. $];
+
+ if(!moar) break;
+ buffersIn[1][idx] = (cast(float) next[0] - 128.0f) / 127.0f;
+ next = next[1 .. $];
+ } else if(reader.bitsPerSample == 16) {
+ short n = next[0];
+ next = next[1 .. $];
+ if(!moar) break;
+ n |= cast(ushort)(next[0]) << 8;
+ next = next[1 .. $];
+
+ b = (cast(float) n) / short.max;
+
+ if(!moar) break;
+ n = next[0];
+ next = next[1 .. $];
+ if(!moar) break;
+ n |= cast(ushort)(next[0]) << 8;
+ next = next[1 .. $];
+
+ buffersIn[1][idx] = (cast(float) n) / short.max;
+ } else assert(0);
+
+
+ actuallyGot++;
+ }
+ resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot];
+ resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot];
+ }
+ }
+ };
+
+ addChannel(&resampleContext.fillBuffer);
+
+ } else static assert(0, "I was lazy and didn't implement straight-through playing");
}
@@ -682,7 +900,6 @@ final class AudioPcmOutThreadImplementation : Thread {
}
private void run() {
-
version(linux) {
// this thread has no business intercepting signals from the main thread,
// so gonna block a couple of them
@@ -750,8 +967,6 @@ version(linux) version=ALSA;
version(Windows) version=WinMM;
version(ALSA) {
- enum cardName = "default";
-
// this is the virtual rawmidi device on my computer at least
// maybe later i'll make it probe
//
@@ -773,9 +988,6 @@ version(ALSA) {
easier to setup but for now I'm using the rawmidi so you
gotta get them connected somehow.
*/
- enum midiName = "hw:3,0";
-
- enum midiCaptureName = "hw:4,0";
// fyi raw midi dump: amidi -d --port hw:4,0
// connect my midi out to fluidsynth: aconnect 28:0 128:0
@@ -813,14 +1025,25 @@ struct AudioInput {
/// Always pass card == 0.
this(int card, int SampleRate = 44100, int channels = 2) {
assert(card == 0);
+ this("default", SampleRate, channels);
+ }
+ /++
+ `device` is a device name. On Linux, it is the ALSA string.
+ On Windows, it is currently ignored, so you should pass "default"
+ or null so when it does get implemented your code won't break.
+
+ History:
+ Added Nov 8, 2020.
+ +/
+ this(string device, int SampleRate = 44100, int channels = 2) {
assert(channels == 1 || channels == 2);
this.channels = channels;
this.SampleRate = SampleRate;
version(ALSA) {
- handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels);
+ handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels, device);
} else version(WinMM) {
event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null);
@@ -984,17 +1207,22 @@ struct AudioOutput {
private int SampleRate;
private int channels;
- /// Always pass card == 0.
- this(int card, int SampleRate = 44100, int channels = 2) {
- assert(card == 0);
+ /++
+ `device` is a device name. On Linux, it is the ALSA string.
+ On Windows, it is currently ignored, so you should pass "default"
+ or null so when it does get implemented your code won't break.
+ History:
+ Added Nov 8, 2020.
+ +/
+ this(string device, int SampleRate = 44100, int channels = 2) {
assert(channels == 1 || channels == 2);
this.SampleRate = SampleRate;
this.channels = channels;
version(ALSA) {
- handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels);
+ handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels, device);
} else version(WinMM) {
WAVEFORMATEX format;
format.wFormatTag = WAVE_FORMAT_PCM;
@@ -1009,6 +1237,13 @@ struct AudioOutput {
} else static assert(0);
}
+ /// Always pass card == 0.
+ this(int card, int SampleRate = 44100, int channels = 2) {
+ assert(card == 0);
+
+ this("default", SampleRate, channels);
+ }
+
/// passes a buffer of data to fill
///
/// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor)
@@ -1185,8 +1420,20 @@ B0 40 00 # sustain pedal off
this(int card) {
assert(card == 0);
+ this("default"); // "hw:4,0"
+ }
+
+ /++
+ `device` is a device name. On Linux, it is the ALSA string.
+ On Windows, it is currently ignored, so you should pass "default"
+ or null so when it does get implemented your code won't break.
+
+ History:
+ Added Nov 8, 2020.
+ +/
+ this(string device) {
version(ALSA) {
- if(auto err = snd_rawmidi_open(&handle, null, midiCaptureName, 0))
+ if(auto err = snd_rawmidi_open(&handle, null, device.toStringz, 0))
throw new AlsaException("rawmidi open", err);
} else version(WinMM) {
if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION))
@@ -1327,8 +1574,20 @@ struct MidiOutput {
this(int card) {
assert(card == 0);
+ this("default"); // "hw:3,0"
+ }
+
+ /++
+ `device` is a device name. On Linux, it is the ALSA string.
+ On Windows, it is currently ignored, so you should pass "default"
+ or null so when it does get implemented your code won't break.
+
+ History:
+ Added Nov 8, 2020.
+ +/
+ this(string device) {
version(ALSA) {
- if(auto err = snd_rawmidi_open(null, &handle, midiName, 0))
+ if(auto err = snd_rawmidi_open(null, &handle, device.toStringz, 0))
throw new AlsaException("rawmidi open", err);
} else version(WinMM) {
if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
@@ -1458,12 +1717,24 @@ struct AudioMixer {
this(int cardId) {
assert(cardId == 0, "Pass 0 to use default sound card.");
+ this("default");
+ }
+
+ /++
+ `device` is a device name. On Linux, it is the ALSA string.
+ On Windows, it is currently ignored, so you should pass "default"
+ or null so when it does get implemented your code won't break.
+
+ History:
+ Added Nov 8, 2020.
+ +/
+ this(string device) {
version(ALSA) {
if(auto err = snd_mixer_open(&handle, 0))
throw new AlsaException("open sound", err);
scope(failure)
snd_mixer_close(handle);
- if(auto err = snd_mixer_attach(handle, cardName))
+ if(auto err = snd_mixer_attach(handle, device.toStringz))
throw new AlsaException("attach to sound card", err);
if(auto err = snd_mixer_selem_register(handle, null, null))
throw new AlsaException("register mixer", err);
@@ -1723,15 +1994,19 @@ Pitch Bend Change. 0mmmmmmm This message is sent to indicate a change in the pit
// OS specific helper stuff follows
// ****************
+private const(char)* toStringz(string s) {
+ return s.ptr; // FIXME jic
+}
+
version(ALSA)
// Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W.
-snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels) {
+snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels, string cardName = "default") {
snd_pcm_t* handle;
snd_pcm_hw_params_t* hwParams;
/* Open PCM and initialize hardware */
- if (auto err = snd_pcm_open(&handle, cardName, direction, 0))
+ if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0))
throw new AlsaException("open device", err);
scope(failure)
snd_pcm_close(handle);
@@ -2180,4 +2455,1228 @@ extern(Windows):
+/
}
+version(with_resampler) {
+ /* Copyright (C) 2007-2008 Jean-Marc Valin
+ * Copyright (C) 2008 Thorvald Natvig
+ * D port by Ketmar // Invisible Vector
+ *
+ * Arbitrary resampling code
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /* A-a-a-and now... D port is covered by the following license!
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+ //module iv.follin.resampler /*is aliced*/;
+ //import iv.alice;
+
+ /*
+ The design goals of this code are:
+ - Very fast algorithm
+ - SIMD-friendly algorithm
+ - Low memory requirement
+ - Good *perceptual* quality (and not best SNR)
+
+ Warning: This resampler is relatively new. Although I think I got rid of
+ all the major bugs and I don't expect the API to change anymore, there
+ may be something I've missed. So use with caution.
+
+ This algorithm is based on this original resampling algorithm:
+ Smith, Julius O. Digital Audio Resampling Home Page
+ Center for Computer Research in Music and Acoustics (CCRMA),
+ Stanford University, 2007.
+ Web published at http://www-ccrma.stanford.edu/~jos/resample/.
+
+ There is one main difference, though. This resampler uses cubic
+ interpolation instead of linear interpolation in the above paper. This
+ makes the table much smaller and makes it possible to compute that table
+ on a per-stream basis. In turn, being able to tweak the table for each
+ stream makes it possible to both reduce complexity on simple ratios
+ (e.g. 2/3), and get rid of the rounding operations in the inner loop.
+ The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
+ */
+ version = sincresample_use_full_table;
+ version(X86) {
+ version(sincresample_disable_sse) {
+ } else {
+ version(D_PIC) {} else version = sincresample_use_sse;
+ }
+ }
+
+
+ // ////////////////////////////////////////////////////////////////////////// //
+ public struct SpeexResampler {
+ public:
+ alias Quality = int;
+ enum : uint {
+ Fastest = 0,
+ Voip = 3,
+ Default = 4,
+ Desktop = 5,
+ Music = 8,
+ Best = 10,
+ }
+
+ enum Error {
+ OK = 0,
+ NoMemory,
+ BadState,
+ BadArgument,
+ BadData,
+ }
+
+ private:
+ nothrow @trusted @nogc:
+ alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen);
+
+ private:
+ uint inRate;
+ uint outRate;
+ uint numRate; // from
+ uint denRate; // to
+
+ Quality srQuality;
+ uint chanCount;
+ uint filterLen;
+ uint memAllocSize;
+ uint bufferSize;
+ int intAdvance;
+ int fracAdvance;
+ float cutoff;
+ uint oversample;
+ bool started;
+
+ // these are per-channel
+ int[64] lastSample;
+ uint[64] sampFracNum;
+ uint[64] magicSamples;
+
+ float* mem;
+ uint realMemLen; // how much memory really allocated
+ float* sincTable;
+ uint sincTableLen;
+ uint realSincTableLen; // how much memory really allocated
+ ResamplerFn resampler;
+
+ int inStride;
+ int outStride;
+
+ public:
+ static string errorStr (int err) {
+ switch (err) with (Error) {
+ case OK: return "success";
+ case NoMemory: return "memory allocation failed";
+ case BadState: return "bad resampler state";
+ case BadArgument: return "invalid argument";
+ case BadData: return "bad data passed";
+ default:
+ }
+ return "unknown error";
+ }
+
+ public:
+ @disable this (this);
+ ~this () { deinit(); }
+
+ @property bool inited () const pure { return (resampler !is null); }
+
+ void deinit () {
+ import core.stdc.stdlib : free;
+ if (mem !is null) { free(mem); mem = null; }
+ if (sincTable !is null) { free(sincTable); sincTable = null; }
+ /*
+ memAllocSize = realMemLen = 0;
+ sincTableLen = realSincTableLen = 0;
+ resampler = null;
+ started = false;
+ */
+ inRate = outRate = numRate = denRate = 0;
+ srQuality = cast(Quality)666;
+ chanCount = 0;
+ filterLen = 0;
+ memAllocSize = 0;
+ bufferSize = 0;
+ intAdvance = 0;
+ fracAdvance = 0;
+ cutoff = 0;
+ oversample = 0;
+ started = 0;
+
+ mem = null;
+ realMemLen = 0; // how much memory really allocated
+ sincTable = null;
+ sincTableLen = 0;
+ realSincTableLen = 0; // how much memory really allocated
+ resampler = null;
+
+ inStride = outStride = 0;
+ }
+
+ /** Create a new resampler with integer input and output rates.
+ *
+ * Params:
+ * chans = Number of channels to be processed
+ * inRate = Input sampling rate (integer number of Hz).
+ * outRate = Output sampling rate (integer number of Hz).
+ * aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
+ *
+ * Returns:
+ * 0 or error code
+ */
+ Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) {
+ //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
+ import core.stdc.stdlib : malloc, free;
+
+ deinit();
+ if (aquality < 0) aquality = 0;
+ if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
+ if (chans < 1 || chans > 16) return Error.BadArgument;
+
+ started = false;
+ inRate = 0;
+ outRate = 0;
+ numRate = 0;
+ denRate = 0;
+ srQuality = cast(Quality)666; // it's ok
+ sincTableLen = 0;
+ memAllocSize = 0;
+ filterLen = 0;
+ mem = null;
+ resampler = null;
+
+ cutoff = 1.0f;
+ chanCount = chans;
+ inStride = 1;
+ outStride = 1;
+
+ bufferSize = 160;
+
+ // per channel data
+ lastSample[] = 0;
+ magicSamples[] = 0;
+ sampFracNum[] = 0;
+
+ setQuality(aquality);
+ setRate(ainRate, aoutRate);
+
+ if (auto filterErr = updateFilter()) { deinit(); return filterErr; }
+ skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
+
+ return Error.OK;
+ }
+
+ /** Set (change) the input/output sampling rates (integer value).
+ *
+ * Params:
+ * ainRate = Input sampling rate (integer number of Hz).
+ * aoutRate = Output sampling rate (integer number of Hz).
+ *
+ * Returns:
+ * 0 or error code
+ */
+ Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) {
+ //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); }
+ if (inRate == ainRate && outRate == aoutRate) return Error.OK;
+ //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); }
+
+ uint oldDen = denRate;
+ inRate = ainRate;
+ outRate = aoutRate;
+ auto div = gcd(ainRate, aoutRate);
+ numRate = ainRate/div;
+ denRate = aoutRate/div;
+
+ if (oldDen > 0) {
+ foreach (ref v; sampFracNum.ptr[0..chanCount]) {
+ v = v*denRate/oldDen;
+ // safety net
+ if (v >= denRate) v = denRate-1;
+ }
+ }
+
+ return (inited ? updateFilter() : Error.OK);
+ }
+
+ /** Get the current input/output sampling rates (integer value).
+ *
+ * Params:
+ * ainRate = Input sampling rate (integer number of Hz) copied.
+ * aoutRate = Output sampling rate (integer number of Hz) copied.
+ */
+ void getRate (out uint ainRate, out uint aoutRate) {
+ ainRate = inRate;
+ aoutRate = outRate;
+ }
+
+ @property uint getInRate () { return inRate; }
+ @property uint getOutRate () { return outRate; }
+
+ @property uint getChans () { return chanCount; }
+
+ /** Get the current resampling ratio. This will be reduced to the least common denominator.
+ *
+ * Params:
+ * ratioNum = Numerator of the sampling rate ratio copied
+ * ratioDen = Denominator of the sampling rate ratio copied
+ */
+ void getRatio (out uint ratioNum, out uint ratioDen) {
+ ratioNum = numRate;
+ ratioDen = denRate;
+ }
+
+ /** Set (change) the conversion quality.
+ *
+ * Params:
+ * quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
+ *
+ * Returns:
+ * 0 or error code
+ */
+ Error setQuality (Quality aquality) {
+ if (aquality < 0) aquality = 0;
+ if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best;
+ if (srQuality == aquality) return Error.OK;
+ srQuality = aquality;
+ return (inited ? updateFilter() : Error.OK);
+ }
+
+ /** Get the conversion quality.
+ *
+ * Returns:
+ * Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
+ */
+ int getQuality () { return srQuality; }
+
+ /** Get the latency introduced by the resampler measured in input samples.
+ *
+ * Returns:
+ * Input latency;
+ */
+ int inputLatency () { return filterLen/2; }
+
+ /** Get the latency introduced by the resampler measured in output samples.
+ *
+ * Returns:
+ * Output latency.
+ */
+ int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; }
+
+ /* Make sure that the first samples to go out of the resamplers don't have
+ * leading zeros. This is only useful before starting to use a newly created
+ * resampler. It is recommended to use that when resampling an audio file, as
+ * it will generate a file with the same length. For real-time processing,
+ * it is probably easier not to use this call (so that the output duration
+ * is the same for the first frame).
+ *
+ * Setup/reset sequence will automatically call this, so it is private.
+ */
+ private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; }
+
+ static struct Data {
+ const(float)[] dataIn;
+ float[] dataOut;
+ uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
+ uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
+ }
+
+ /** Resample (an interleaved) float array. The input and output buffers must *not* overlap.
+ * `data.dataIn` can be empty, but `data.dataOut` can't.
+ * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`,
+ * and number of produced samples in `data.outputSamplesUsed`.
+ * You should provide enough samples for all channels, and all channels will be processed.
+ *
+ * Params:
+ * data = input and output buffers, number of frames consumed and produced
+ *
+ * Returns:
+ * 0 or error code
+ */
+ Error process(string mode="interleaved") (ref Data data) {
+ static assert(mode == "interleaved" || mode == "sequential");
+
+ data.inputSamplesUsed = data.outputSamplesUsed = 0;
+ if (!inited) return Error.BadState;
+
+ if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData;
+ if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData;
+
+ static if (mode == "interleaved") {
+ inStride = outStride = chanCount;
+ } else {
+ inStride = outStride = 1;
+ }
+ uint iofs = 0, oofs = 0;
+ immutable uint idclen = cast(uint)(data.dataIn.length/chanCount);
+ immutable uint odclen = cast(uint)(data.dataOut.length/chanCount);
+ foreach (immutable i; 0..chanCount) {
+ data.inputSamplesUsed = idclen;
+ data.outputSamplesUsed = odclen;
+ if (data.dataIn.length) {
+ processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
+ } else {
+ processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed);
+ }
+ static if (mode == "interleaved") {
+ ++iofs;
+ ++oofs;
+ } else {
+ iofs += idclen;
+ oofs += odclen;
+ }
+ }
+ data.inputSamplesUsed *= chanCount;
+ data.outputSamplesUsed *= chanCount;
+ return Error.OK;
+ }
+
+
+ //HACK for libswresample
+ // return -1 or number of outframes
+ int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) {
+ if (!inited || outframes < 1 || inframes < 0) return -1;
+ inStride = outStride = 1;
+ Data data;
+ foreach (immutable i; 0..chanCount) {
+ data.dataIn = (inframes ? inbuf[i][0..inframes] : null);
+ data.dataOut = (outframes ? outbuf[i][0..outframes] : null);
+ data.inputSamplesUsed = inframes;
+ data.outputSamplesUsed = outframes;
+ if (inframes > 0) {
+ processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
+ } else {
+ processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed);
+ }
+ }
+ return data.outputSamplesUsed;
+ }
+
+ /// Reset a resampler so a new (unrelated) stream can be processed.
+ void reset () {
+ lastSample[] = 0;
+ magicSamples[] = 0;
+ sampFracNum[] = 0;
+ //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0;
+ if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0;
+ skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros
+ }
+
+ private:
+ Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) {
+ uint ilen = *indataLen;
+ uint olen = *outdataLen;
+ float* x = mem+chanIdx*memAllocSize;
+ immutable int filterOfs = filterLen-1;
+ immutable uint xlen = memAllocSize-filterOfs;
+ immutable int istride = inStride;
+ if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen);
+ if (!magicSamples.ptr[chanIdx]) {
+ while (ilen && olen) {
+ uint ichunk = (ilen > xlen ? xlen : ilen);
+ uint ochunk = olen;
+ if (indata !is null) {
+ //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride];
+ if (istride == 1) {
+ x[filterOfs..filterOfs+ichunk] = indata[0..ichunk];
+ } else {
+ auto sp = indata;
+ auto dp = x+filterOfs;
+ foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; }
+ }
+ } else {
+ //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0;
+ x[filterOfs..filterOfs+ichunk] = 0;
+ }
+ processNative(chanIdx, &ichunk, outdata, &ochunk);
+ ilen -= ichunk;
+ olen -= ochunk;
+ outdata += ochunk*outStride;
+ if (indata !is null) indata += ichunk*istride;
+ }
+ }
+ *indataLen -= ilen;
+ *outdataLen -= olen;
+ return Error.OK;
+ }
+
+ Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) {
+ immutable N = filterLen;
+ int outSample = 0;
+ float* x = mem+chanIdx*memAllocSize;
+ uint ilen;
+ started = true;
+ // call the right resampler through the function ptr
+ outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen);
+ if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx];
+ *outdataLen = outSample;
+ lastSample.ptr[chanIdx] -= *indataLen;
+ ilen = *indataLen;
+ foreach (immutable j; 0..N-1) x[j] = x[j+ilen];
+ return Error.OK;
+ }
+
+ int magic (uint chanIdx, float **outdata, uint outdataLen) {
+ uint tempInLen = magicSamples.ptr[chanIdx];
+ float* x = mem+chanIdx*memAllocSize;
+ processNative(chanIdx, &tempInLen, *outdata, &outdataLen);
+ magicSamples.ptr[chanIdx] -= tempInLen;
+ // if we couldn't process all "magic" input samples, save the rest for next time
+ if (magicSamples.ptr[chanIdx]) {
+ immutable N = filterLen;
+ foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen];
+ }
+ *outdata += outdataLen*outStride;
+ return outdataLen;
+ }
+
+ Error updateFilter () {
+ uint oldFilterLen = filterLen;
+ uint oldAllocSize = memAllocSize;
+ bool useDirect;
+ uint minSincTableLen;
+ uint minAllocSize;
+
+ intAdvance = numRate/denRate;
+ fracAdvance = numRate%denRate;
+ oversample = qualityMap.ptr[srQuality].oversample;
+ filterLen = qualityMap.ptr[srQuality].baseLength;
+
+ if (numRate > denRate) {
+ // down-sampling
+ cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate;
+ // FIXME: divide the numerator and denominator by a certain amount if they're too large
+ filterLen = filterLen*numRate/denRate;
+ // round up to make sure we have a multiple of 8 for SSE
+ filterLen = ((filterLen-1)&(~0x7))+8;
+ if (2*denRate < numRate) oversample >>= 1;
+ if (4*denRate < numRate) oversample >>= 1;
+ if (8*denRate < numRate) oversample >>= 1;
+ if (16*denRate < numRate) oversample >>= 1;
+ if (oversample < 1) oversample = 1;
+ } else {
+ // up-sampling
+ cutoff = qualityMap.ptr[srQuality].upsampleBandwidth;
+ }
+
+ // choose the resampling type that requires the least amount of memory
+ version(sincresample_use_full_table) {
+ useDirect = true;
+ if (int.max/float.sizeof/denRate < filterLen) goto fail;
+ } else {
+ useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen);
+ }
+
+ if (useDirect) {
+ minSincTableLen = filterLen*denRate;
+ } else {
+ if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail;
+ minSincTableLen = filterLen*oversample+8;
+ }
+
+ if (sincTableLen < minSincTableLen) {
+ import core.stdc.stdlib : realloc;
+ auto nslen = cast(uint)(minSincTableLen*float.sizeof);
+ if (nslen > realSincTableLen) {
+ if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb?
+ auto x = cast(float*)realloc(sincTable, nslen);
+ if (!x) goto fail;
+ sincTable = x;
+ realSincTableLen = nslen;
+ }
+ sincTableLen = minSincTableLen;
+ }
+
+ if (useDirect) {
+ foreach (int i; 0..denRate) {
+ foreach (int j; 0..filterLen) {
+ sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc);
+ }
+ }
+ if (srQuality > 8) {
+ resampler = &resamplerBasicDirect!double;
+ } else {
+ resampler = &resamplerBasicDirect!float;
+ }
+ } else {
+ foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) {
+ sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc);
+ }
+ if (srQuality > 8) {
+ resampler = &resamplerBasicInterpolate!double;
+ } else {
+ resampler = &resamplerBasicInterpolate!float;
+ }
+ }
+
+ /* Here's the place where we update the filter memory to take into account
+ the change in filter length. It's probably the messiest part of the code
+ due to handling of lots of corner cases. */
+
+ // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above
+ minAllocSize = filterLen-1+bufferSize;
+ if (minAllocSize > memAllocSize) {
+ import core.stdc.stdlib : realloc;
+ if (int.max/float.sizeof/chanCount < minAllocSize) goto fail;
+ auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof);
+ if (nslen > realMemLen) {
+ if (nslen < 16384) nslen = 16384;
+ auto x = cast(float*)realloc(mem, nslen);
+ if (x is null) goto fail;
+ mem = x;
+ realMemLen = nslen;
+ }
+ memAllocSize = minAllocSize;
+ }
+ if (!started) {
+ //foreach (i=0;i oldFilterLen) {
+ // increase the filter length
+ foreach_reverse (uint i; 0..chanCount) {
+ uint j;
+ uint olen = oldFilterLen;
+ {
+ // try and remove the magic samples as if nothing had happened
+ //FIXME: this is wrong but for now we need it to avoid going over the array bounds
+ olen = oldFilterLen+2*magicSamples.ptr[i];
+ for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j];
+ //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0;
+ mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0;
+ magicSamples.ptr[i] = 0;
+ }
+ if (filterLen > olen) {
+ // if the new filter length is still bigger than the "augmented" length
+ // copy data going backward
+ for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)];
+ // then put zeros for lack of anything better
+ for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0;
+ // adjust lastSample
+ lastSample.ptr[i] += (filterLen-olen)/2;
+ } else {
+ // put back some of the magic!
+ magicSamples.ptr[i] = (olen-filterLen)/2;
+ for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
+ }
+ }
+ } else if (filterLen < oldFilterLen) {
+ // reduce filter length, this a bit tricky
+ // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s)
+ foreach (immutable i; 0..chanCount) {
+ uint j;
+ uint oldMagic = magicSamples.ptr[i];
+ magicSamples.ptr[i] = (oldFilterLen-filterLen)/2;
+ // we must copy some of the memory that's no longer used
+ // copy data going backward
+ for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) {
+ mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]];
+ }
+ magicSamples.ptr[i] += oldMagic;
+ }
+ }
+ return Error.OK;
+
+ fail:
+ resampler = null;
+ /* mem may still contain consumed input samples for the filter.
+ Restore filterLen so that filterLen-1 still points to the position after
+ the last of these samples. */
+ filterLen = oldFilterLen;
+ return Error.NoMemory;
+ }
+ }
+
+
+ // ////////////////////////////////////////////////////////////////////////// //
+ static immutable double[68] kaiser12Table = [
+ 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
+ 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
+ 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
+ 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
+ 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
+ 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
+ 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
+ 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
+ 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
+ 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
+ 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
+ 0.00001000, 0.00000000];
+
+ static immutable double[36] kaiser10Table = [
+ 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
+ 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
+ 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
+ 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
+ 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
+ 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000];
+
+ static immutable double[36] kaiser8Table = [
+ 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
+ 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
+ 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
+ 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
+ 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
+ 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000];
+
+ static immutable double[36] kaiser6Table = [
+ 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
+ 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
+ 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
+ 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
+ 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
+ 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000];
+
+ struct FuncDef {
+ immutable(double)* table;
+ int oversample;
+ }
+
+ static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64);
+ static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32);
+ static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32);
+ static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32);
+
+
+ struct QualityMapping {
+ int baseLength;
+ int oversample;
+ float downsampleBandwidth;
+ float upsampleBandwidth;
+ immutable FuncDef* windowFunc;
+ }
+
+
+ /* This table maps conversion quality to internal parameters. There are two
+ reasons that explain why the up-sampling bandwidth is larger than the
+ down-sampling bandwidth:
+ 1) When up-sampling, we can assume that the spectrum is already attenuated
+ close to the Nyquist rate (from an A/D or a previous resampling filter)
+ 2) Any aliasing that occurs very close to the Nyquist rate will be masked
+ by the sinusoids/noise just below the Nyquist rate (guaranteed only for
+ up-sampling).
+ */
+ static immutable QualityMapping[11] qualityMap = [
+ QualityMapping( 8, 4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */
+ QualityMapping( 16, 4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */
+ QualityMapping( 32, 4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */
+ QualityMapping( 48, 8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */
+ QualityMapping( 64, 8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */
+ QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */
+ QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */
+ QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */
+ QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */
+ QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */
+ QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
+ ];
+
+
+ nothrow @trusted @nogc:
+ /*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/
+ double computeFunc (float x, immutable FuncDef* func) {
+ version(Posix) import core.stdc.math : lrintf;
+ import std.math : floor;
+ //double[4] interp;
+ float y = x*func.oversample;
+ version(Posix) {
+ int ind = cast(int)lrintf(floor(y));
+ } else {
+ int ind = cast(int)(floor(y));
+ }
+ float frac = (y-ind);
+ immutable f2 = frac*frac;
+ immutable f3 = f2*frac;
+ double interp3 = -0.1666666667*frac+0.1666666667*(f3);
+ double interp2 = frac+0.5*(f2)-0.5*(f3);
+ //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3;
+ double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3);
+ // just to make sure we don't have rounding problems
+ double interp1 = 1.0f-interp3-interp2-interp0;
+ //sum = frac*accum[1]+(1-frac)*accum[2];
+ return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3];
+ }
+
+
+ // the slow way of computing a sinc for the table; should improve that some day
+ float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) {
+ version(LittleEndian) {
+ align(1) union temp_float { align(1): float f; uint n; }
+ } else {
+ static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); }
+ }
+ import std.math : sin, PI;
+ version(LittleEndian) {
+ temp_float txx = void;
+ txx.f = x;
+ txx.n &= 0x7fff_ffff; // abs
+ if (txx.f < 1.0e-6f) return cutoff;
+ if (txx.f > 0.5f*N) return 0;
+ } else {
+ if (fabs(x) < 1.0e-6f) return cutoff;
+ if (fabs(x) > 0.5f*N) return 0;
+ }
+ //FIXME: can it really be any slower than this?
+ immutable float xx = x*cutoff;
+ immutable pixx = PI*xx;
+ version(LittleEndian) {
+ return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc);
+ } else {
+ return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc);
+ }
+ }
+
+
+ void cubicCoef (in float frac, float* interp) {
+ immutable f2 = frac*frac;
+ immutable f3 = f2*frac;
+ // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
+ interp[0] = -0.16667f*frac+0.16667f*f3;
+ interp[1] = frac+0.5f*f2-0.5f*f3;
+ //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3;
+ interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3;
+ // just to make sure we don't have rounding problems
+ interp[2] = 1.0-interp[0]-interp[1]-interp[3];
+ }
+
+
+ // ////////////////////////////////////////////////////////////////////////// //
+ int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen)
+ if (is(T == float) || is(T == double))
+ {
+ auto N = st.filterLen;
+ static if (is(T == double)) assert(N%4 == 0);
+ int outSample = 0;
+ int lastSample = st.lastSample.ptr[chanIdx];
+ uint sampFracNum = st.sampFracNum.ptr[chanIdx];
+ const(float)* sincTable = st.sincTable;
+ immutable outStride = st.outStride;
+ immutable intAdvance = st.intAdvance;
+ immutable fracAdvance = st.fracAdvance;
+ immutable denRate = st.denRate;
+ T sum = void;
+ while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
+ const(float)* sinct = &sincTable[sampFracNum*N];
+ const(float)* iptr = &indata[lastSample];
+ static if (is(T == float)) {
+ // at least 2x speedup with SSE here (but for unrolled loop)
+ if (N%4 == 0) {
+ version(sincresample_use_sse) {
+ //align(64) __gshared float[4] zero = 0;
+ align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas
+ __gshared uint zeroesptr = 0;
+ if (zeroesptr == 0) {
+ zeroesptr = cast(uint)zeroesBuf.ptr;
+ if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1;
+ }
+ //assert((zeroesptr&0x3f) == 0, "wtf?!");
+ asm nothrow @safe @nogc {
+ mov ECX,[N];
+ shr ECX,2;
+ mov EAX,[zeroesptr];
+ movaps XMM0,[EAX];
+ mov EAX,[sinct];
+ mov EBX,[iptr];
+ mov EDX,16;
+ align 8;
+ rbdseeloop:
+ movups XMM1,[EAX];
+ movups XMM2,[EBX];
+ mulps XMM1,XMM2;
+ addps XMM0,XMM1;
+ add EAX,EDX;
+ add EBX,EDX;
+ dec ECX;
+ jnz rbdseeloop;
+ // store result in sum
+ movhlps XMM1,XMM0; // now low part of XMM1 contains high part of XMM0
+ addps XMM0,XMM1; // low part of XMM0 is ok
+ movaps XMM1,XMM0;
+ shufps XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1
+ addss XMM0,XMM1;
+ movss [sum],XMM0;
+ }
+ /*
+ float sum1 = 0;
+ foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j];
+ import std.math;
+ if (fabs(sum-sum1) > 0.000001f) {
+ import core.stdc.stdio;
+ printf("sum=%f; sum1=%f\n", sum, sum1);
+ assert(0);
+ }
+ */
+ } else {
+ // no SSE; for my i3 unrolled loop is almost of the speed of SSE code
+ T[4] accum = 0;
+ foreach (immutable j; 0..N/4) {
+ accum.ptr[0] += *sinct++ * *iptr++;
+ accum.ptr[1] += *sinct++ * *iptr++;
+ accum.ptr[2] += *sinct++ * *iptr++;
+ accum.ptr[3] += *sinct++ * *iptr++;
+ }
+ sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
+ }
+ } else {
+ sum = 0;
+ foreach (immutable j; 0..N) sum += *sinct++ * *iptr++;
+ }
+ outdata[outStride*outSample++] = sum;
+ } else {
+ if (N%4 == 0) {
+ //TODO: write SSE code here!
+ // for my i3 unrolled loop is ~2 times faster
+ T[4] accum = 0;
+ foreach (immutable j; 0..N/4) {
+ accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++;
+ accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++;
+ accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++;
+ accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++;
+ }
+ sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3];
+ } else {
+ sum = 0;
+ foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++;
+ }
+ outdata[outStride*outSample++] = cast(float)sum;
+ }
+ lastSample += intAdvance;
+ sampFracNum += fracAdvance;
+ if (sampFracNum >= denRate) {
+ sampFracNum -= denRate;
+ ++lastSample;
+ }
+ }
+ st.lastSample.ptr[chanIdx] = lastSample;
+ st.sampFracNum.ptr[chanIdx] = sampFracNum;
+ return outSample;
+ }
+
+
+ int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen)
+ if (is(T == float) || is(T == double))
+ {
+ immutable N = st.filterLen;
+ assert(N%4 == 0);
+ int outSample = 0;
+ int lastSample = st.lastSample.ptr[chanIdx];
+ uint sampFracNum = st.sampFracNum.ptr[chanIdx];
+ immutable outStride = st.outStride;
+ immutable intAdvance = st.intAdvance;
+ immutable fracAdvance = st.fracAdvance;
+ immutable denRate = st.denRate;
+ float sum;
+
+ float[4] interp = void;
+ T[4] accum = void;
+ while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) {
+ const(float)* iptr = &indata[lastSample];
+ const int offset = sampFracNum*st.oversample/st.denRate;
+ const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate;
+ accum[] = 0;
+ //TODO: optimize!
+ foreach (immutable j; 0..N) {
+ immutable T currIn = iptr[j];
+ accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]);
+ accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]);
+ accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]);
+ accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]);
+ }
+
+ cubicCoef(frac, interp.ptr);
+ sum = (interp.ptr[0]*accum.ptr[0])+(interp.ptr[1]*accum.ptr[1])+(interp.ptr[2]*accum.ptr[2])+(interp.ptr[3]*accum.ptr[3]);
+
+ outdata[outStride*outSample++] = sum;
+ lastSample += intAdvance;
+ sampFracNum += fracAdvance;
+ if (sampFracNum >= denRate) {
+ sampFracNum -= denRate;
+ ++lastSample;
+ }
+ }
+
+ st.lastSample.ptr[chanIdx] = lastSample;
+ st.sampFracNum.ptr[chanIdx] = sampFracNum;
+ return outSample;
+ }
+
+
+ // ////////////////////////////////////////////////////////////////////////// //
+ uint gcd (uint a, uint b) pure {
+ if (a == 0) return b;
+ if (b == 0) return a;
+ for (;;) {
+ if (a > b) {
+ a %= b;
+ if (a == 0) return b;
+ if (a == 1) return 1;
+ } else {
+ b %= a;
+ if (b == 0) return a;
+ if (b == 1) return 1;
+ }
+ }
+ }
+
+
+ // ////////////////////////////////////////////////////////////////////////// //
+ // very simple and cheap cubic upsampler
+ struct CubicUpsampler {
+ public:
+ nothrow @trusted @nogc:
+ float[2] curposfrac; // current position offset [0..1)
+ float step; // how long we should move on one step?
+ float[4][2] data; // -1..3
+ uint[2] drain;
+
+ void reset () {
+ curposfrac[] = 0.0f;
+ foreach (ref d; data) d[] = 0.0f;
+ drain[] = 0;
+ }
+
+ bool setup (float astep) {
+ if (astep >= 1.0f) return false;
+ step = astep;
+ return true;
+ }
+
+ /*
+ static struct Data {
+ const(float)[] dataIn;
+ float[] dataOut;
+ uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
+ uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count)
+ }
+ */
+
+ SpeexResampler.Error process (ref SpeexResampler.Data d) {
+ d.inputSamplesUsed = d.outputSamplesUsed = 0;
+ if (d.dataOut.length < 2) return SpeexResampler.Error.OK;
+ foreach (uint cidx; 0..2) {
+ uint inleft = cast(uint)d.dataIn.length/2;
+ uint outleft = cast(uint)d.dataOut.length/2;
+ processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx);
+ d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft;
+ d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft;
+ }
+ return SpeexResampler.Error.OK;
+ }
+
+ private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) {
+ if (outleft == 0) return;
+ if (inleft == 0 && drain.ptr[cidx] <= 1) return;
+ auto dt = data.ptr[cidx].ptr;
+ auto drn = drain.ptr+cidx;
+ auto cpf = curposfrac.ptr+cidx;
+ immutable float st = step;
+ for (;;) {
+ // fill buffer
+ while ((*drn) < 4) {
+ if (inleft == 0) return;
+ dt[(*drn)++] = *dataIn;
+ dataIn += 2;
+ --inleft;
+ }
+ if (outleft == 0) return;
+ --outleft;
+ // cubic interpolation
+ /*version(none)*/ {
+ // interpolate between y1 and y2
+ immutable float mu = (*cpf); // how far we are moved from y1 to y2
+ immutable float mu2 = mu*mu; // wow
+ immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3];
+ version(complex_cubic) {
+ immutable float z0 = 0.5*y3;
+ immutable float z1 = 0.5*y0;
+ immutable float a0 = 1.5*y1-z1-1.5*y2+z0;
+ immutable float a1 = y0-2.5*y1+2*y2-z0;
+ immutable float a2 = 0.5*y2-z1;
+ } else {
+ immutable float a0 = y3-y2-y0+y1;
+ immutable float a1 = y0-y1-a0;
+ immutable float a2 = y2-y0;
+ }
+ *dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1;
+ }// else *dataOut = dt[1];
+ dataOut += 2;
+ if (((*cpf) += st) >= 1.0f) {
+ (*cpf) -= 1.0f;
+ dt[0] = dt[1];
+ dt[1] = dt[2];
+ dt[2] = dt[3];
+ dt[3] = 0.0f;
+ --(*drn); // will request more input bytes
+ }
+ }
+ }
+ }
+}
+
+version(with_resampler)
+abstract class ResamplingContext {
+ int inputSampleRate;
+ int outputSampleRate;
+
+ int inputChannels;
+ int outputChannels;
+
+ SpeexResampler resamplerLeft;
+ SpeexResampler resamplerRight;
+
+ SpeexResampler.Data resamplerDataLeft;
+ SpeexResampler.Data resamplerDataRight;
+
+ float[][2] buffersIn;
+ float[][2] buffersOut;
+
+ uint rateNum;
+ uint rateDem;
+
+ float[][2] dataReady;
+
+
+ this(int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) {
+ this.inputSampleRate = inputSampleRate;
+ this.outputSampleRate = outputSampleRate;
+ this.inputChannels = inputChannels;
+ this.outputChannels = outputChannels;
+
+
+ if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5))
+ throw new Exception("ugh");
+ resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5);
+
+ resamplerLeft.getRatio(rateNum, rateDem);
+
+ int add = (rateNum % rateDem) ? 1 : 0;
+
+ buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
+ buffersOut[0] = new float[](BUFFER_SIZE_FRAMES);
+ if(inputChannels > 1) {
+ buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add);
+ buffersOut[1] = new float[](BUFFER_SIZE_FRAMES);
+ }
+ }
+
+ /+
+ float*[2] tmp;
+ tmp[0] = buffersIn[0].ptr;
+ tmp[1] = buffersIn[1].ptr;
+
+ auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length);
+
+ resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up
+ ditto for resamplerDataRight if the source has two channels
+ +/
+ abstract void loadMoreSamples();
+
+ bool loadMore() {
+ resamplerDataLeft.dataIn = buffersIn[0];
+ resamplerDataLeft.dataOut = buffersOut[0];
+
+ resamplerDataRight.dataIn = buffersIn[1];
+ resamplerDataRight.dataOut = buffersOut[1];
+
+ loadMoreSamples();
+
+ //resamplerLeft.reset();
+
+ if(auto err = resamplerLeft.process(resamplerDataLeft))
+ throw new Exception("ugh");
+ if(inputChannels > 1)
+ //resamplerRight.reset();
+ resamplerRight.process(resamplerDataRight);
+
+ resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed];
+ resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed];
+
+ if(resamplerDataLeft.dataOut.length == 0) {
+ return true;
+ }
+ return false;
+ }
+
+
+ bool fillBuffer(short[] buffer) {
+ if(cast(int) buffer.length != buffer.length)
+ throw new Exception("eeeek");
+
+ if(outputChannels == 1) {
+ foreach(ref s; buffer) {
+ if(resamplerDataLeft.dataOut.length == 0) {
+ if(loadMore())
+ return false;
+ }
+
+ if(inputChannels == 1) {
+ s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
+ resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
+ } else {
+ s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2);
+
+ resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
+ resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
+ }
+ }
+ } else if(outputChannels == 2) {
+ foreach(idx, ref s; buffer) {
+ if(resamplerDataLeft.dataOut.length == 0) {
+ if(loadMore())
+ return false;
+ }
+
+ if(inputChannels == 1) {
+ s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
+ if(idx & 1)
+ resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
+ } else {
+ if(idx & 1) {
+ s = cast(short) (resamplerDataRight.dataOut[0] * short.max);
+ resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $];
+ } else {
+ s = cast(short) (resamplerDataLeft.dataOut[0] * short.max);
+ resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $];
+ }
+ }
+ }
+ } else assert(0);
+
+ return true;
+ }
+}
+
private enum scriptable = "arsd_jsvar_compatible";