ogg via stb_vorbis (see vorbis.d)

This commit is contained in:
Adam D. Ruppe 2017-09-09 20:17:53 -04:00
parent b6b5c0505e
commit 1bd7364676
1 changed files with 216 additions and 55 deletions

View File

@ -48,10 +48,43 @@ enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2;
version(Demo)
void main() {
version(none) {
import iv.stb.vorbis;
int channels;
short* decoded;
auto v = new VorbisDecoder("test.ogg");
auto ao = AudioOutput(0);
ao.fillData = (short[] buffer) {
auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length);
if(got == 0) {
ao.stop();
}
};
ao.play();
return;
}
auto thread = new AudioPcmOutThread();
thread.start();
thread.playOgg("test.ogg");
Thread.sleep(5.seconds);
//Thread.sleep(150.msecs);
thread.beep();
Thread.sleep(250.msecs);
thread.blip();
Thread.sleep(250.msecs);
thread.boop();
Thread.sleep(1000.msecs);
/*
thread.beep(800, 500);
Thread.sleep(500.msecs);
thread.beep(366, 500);
@ -59,7 +92,6 @@ void main() {
thread.beep(800, 500);
thread.beep(366, 500);
Thread.sleep(500.msecs);
/*
Thread.sleep(150.msecs);
thread.beep(200);
Thread.sleep(150.msecs);
@ -68,6 +100,8 @@ void main() {
thread.noise();
Thread.sleep(150.msecs);
*/
thread.stop();
thread.join();
@ -152,9 +186,13 @@ import core.thread;
}
audio.beep();
// you need to keep the main program alive long enough
// to keep this thread going to hear anything
Thread.sleep(1.seconds);
---
+/
class AudioPcmOutThread : Thread {
final class AudioPcmOutThread : Thread {
///
this() {
super(&run);
@ -188,82 +226,200 @@ class AudioPcmOutThread : Thread {
}
///
void boop() {
void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = 50) {
Sample s;
s.operation = 5; // custom
s.volume = volume;
s.duration = dur * SampleRate / 1000;
s.f = delegate short(int x) {
auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack));
import std.math;
auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency);
return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * 100 / volume);
};
addSample(s);
}
///
void blip() {
void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = 50) {
Sample s;
s.operation = 5; // custom
s.volume = volume;
s.duration = dur * SampleRate / 1000;
s.f = delegate short(int x) {
auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack));
import std.math;
auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency);
return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * 100 / volume);
};
addSample(s);
}
version(none)
void custom(int dur = 150, int volume = 50) {
Sample s;
s.operation = 5; // custom
s.volume = volume;
s.duration = dur * SampleRate / 1000;
s.f = delegate short(int x) {
auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8));
import std.math;
auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency);
return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * 100 / volume);
};
addSample(s);
}
/// Requires vorbis.d to be compiled in (module iv.stb.vorbis)
void playOgg()(string filename, bool loop = false) {
import iv.stb.vorbis;
auto v = new VorbisDecoder(filename);
addChannel(
delegate bool(short[] buffer) {
auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length);
if(got == 0) {
if(loop) {
v.seekStart();
return true;
}
return false;
}
return true;
}
);
}
struct Sample {
int operation;
int frequency; /* in samples */
int duration; /* in samples */
int volume; /* between 1 and 100 */
int delay; /* in samples */
int x;
short delegate(int x) f;
}
void addSample(Sample currentSample) {
synchronized(this) {
/*
auto spot = playBufferEnd;
playBuffer[spot] = s;
playBufferEnd = (playBufferEnd + 1) & (playBuffer.length-1);
*/
int frequencyCounter;
short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
fillDatas ~= delegate bool (short[] buffer) {
final void addSample(Sample currentSample) {
int frequencyCounter;
short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
addChannel(
delegate bool (short[] buffer) {
if(currentSample.duration) {
if(currentSample.operation == 0)
for(size_t i = 0; i < buffer.length; i++) {
buffer[i] = val;
// left and right do the same thing so we only count
// every other sample
if(i & 1) {
currentSample.duration--;
if(frequencyCounter)
frequencyCounter--;
if(frequencyCounter == 0) {
val = -val;
frequencyCounter = currentSample.frequency / 2;
}
if(currentSample.duration == 0) {
buffer[i .. $] = 0;
return false;
}
size_t i = 0;
if(currentSample.delay) {
if(buffer.length <= currentSample.delay * 2) {
// whole buffer consumed by delay
buffer[] = 0;
currentSample.delay -= buffer.length / 2;
} else {
i = currentSample.delay * 2;
buffer[0 .. i] = 0;
currentSample.delay = 0;
}
}
else if(currentSample.operation == 1)
for(size_t i = 0; i < buffer.length; i++) {
import std.random;
buffer[i] = uniform(short.min, short.max);
if(i & 1) {
currentSample.duration--;
if(currentSample.delay > 0)
return true;
if(currentSample.duration == 0) {
buffer = buffer[i .. $];
return false;
}
}
size_t sampleFinish;
if(currentSample.duration * 2 <= buffer.length) {
sampleFinish = currentSample.duration * 2;
currentSample.duration = 0;
} else {
sampleFinish = buffer.length;
currentSample.duration -= buffer.length / 2;
}
return true;
switch(currentSample.operation) {
case 0: // square wave
for(; i < sampleFinish; i++) {
buffer[i] = val;
// left and right do the same thing so we only count
// every other sample
if(i & 1) {
if(frequencyCounter)
frequencyCounter--;
if(frequencyCounter == 0) {
val = -val;
frequencyCounter = currentSample.frequency / 2;
}
}
}
break;
case 1: // noise
for(; i < sampleFinish; i++) {
import std.random;
buffer[i] = uniform(short.min, short.max);
}
break;
/+
case 2: // triangle wave
for(; i < sampleFinish; i++) {
buffer[i] = val;
// left and right do the same thing so we only count
// every other sample
if(i & 1) {
if(frequencyCounter)
frequencyCounter--;
if(frequencyCounter == 0) {
val = 0;
frequencyCounter = currentSample.frequency / 2;
}
}
}
break;
case 3: // sawtooth wave
case 4: // sine wave
+/
case 5: // custom function
val = currentSample.f(currentSample.x);
for(; i < sampleFinish; i++) {
buffer[i] = val;
if(i & 1) {
currentSample.x++;
val = currentSample.f(currentSample.x);
}
}
break;
default: // unknown; use silence
currentSample.duration = 0;
}
if(i < buffer.length)
buffer[i .. $] = 0;
return currentSample.duration > 0;
} else {
return false;
}
};
}
);
}
/++
The delegate returns false when it is finished (true means keep going).
It must fill the buffer with waveform data on demand and must be latency
sensitive; as fast as possible.
+/
public void addChannel(bool delegate(short[] buffer) dg) {
synchronized(this) {
// silently drops info if we don't have room in the buffer...
// don't do a lot of long running things lol
if(fillDatasLength < fillDatas.length)
fillDatas[fillDatasLength++] = dg;
}
}
private {
AudioOutput* ao;
bool delegate(short[] buffer)[] fillDatas;
bool delegate(short[] buffer)[32] fillDatas;
int fillDatasLength = 0;
}
private void run() {
@ -272,8 +428,8 @@ class AudioPcmOutThread : Thread {
ao.fillData = (short[] buffer) {
short[BUFFER_SIZE_SHORT] bfr;
bool first = true;
if(fillDatas.length) {
for(int idx = 0; idx < fillDatas.length; idx++) {
if(fillDatasLength) {
for(int idx = 0; idx < fillDatasLength; idx++) {
auto dg = fillDatas[idx];
auto ret = dg(bfr[0 .. buffer.length][]);
foreach(i, v; bfr[0 .. buffer.length][]) {
@ -292,8 +448,8 @@ class AudioPcmOutThread : Thread {
if(!ret) {
// it returned false meaning this one is finished...
synchronized(this) {
fillDatas[idx] = fillDatas[$ - 1];
fillDatas = fillDatas[0 .. $-1];
fillDatas[idx] = fillDatas[fillDatasLength - 1];
fillDatasLength--;
}
idx--;
}
@ -340,7 +496,7 @@ version(ALSA) {
easier to setup but for now I'm using the rawmidi so you
gotta get them connected somehow.
*/
enum midiName = "hw:2,0";
enum midiName = "hw:3,0";
}
/// Thrown on audio failures.
@ -587,6 +743,11 @@ struct MidiOutput {
} else static assert(0);
}
void silenceAllNotes() {
foreach(a; 0 .. 16)
writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0);
}
/// Send a reset message, silencing all notes
void reset() {
version(ALSA) {