mirror of https://github.com/adamdruppe/arsd.git
ogg via stb_vorbis (see vorbis.d)
This commit is contained in:
parent
b6b5c0505e
commit
1bd7364676
271
simpleaudio.d
271
simpleaudio.d
|
@ -48,10 +48,43 @@ enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2;
|
||||||
version(Demo)
|
version(Demo)
|
||||||
void main() {
|
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();
|
auto thread = new AudioPcmOutThread();
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
|
thread.playOgg("test.ogg");
|
||||||
|
|
||||||
|
Thread.sleep(5.seconds);
|
||||||
|
|
||||||
//Thread.sleep(150.msecs);
|
//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.beep(800, 500);
|
||||||
Thread.sleep(500.msecs);
|
Thread.sleep(500.msecs);
|
||||||
thread.beep(366, 500);
|
thread.beep(366, 500);
|
||||||
|
@ -59,7 +92,6 @@ void main() {
|
||||||
thread.beep(800, 500);
|
thread.beep(800, 500);
|
||||||
thread.beep(366, 500);
|
thread.beep(366, 500);
|
||||||
Thread.sleep(500.msecs);
|
Thread.sleep(500.msecs);
|
||||||
/*
|
|
||||||
Thread.sleep(150.msecs);
|
Thread.sleep(150.msecs);
|
||||||
thread.beep(200);
|
thread.beep(200);
|
||||||
Thread.sleep(150.msecs);
|
Thread.sleep(150.msecs);
|
||||||
|
@ -68,6 +100,8 @@ void main() {
|
||||||
thread.noise();
|
thread.noise();
|
||||||
Thread.sleep(150.msecs);
|
Thread.sleep(150.msecs);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
thread.stop();
|
thread.stop();
|
||||||
|
|
||||||
thread.join();
|
thread.join();
|
||||||
|
@ -152,9 +186,13 @@ import core.thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.beep();
|
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() {
|
this() {
|
||||||
super(&run);
|
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 {
|
struct Sample {
|
||||||
int operation;
|
int operation;
|
||||||
int frequency; /* in samples */
|
int frequency; /* in samples */
|
||||||
int duration; /* in samples */
|
int duration; /* in samples */
|
||||||
int volume; /* between 1 and 100 */
|
int volume; /* between 1 and 100 */
|
||||||
|
|
||||||
int delay; /* in samples */
|
int delay; /* in samples */
|
||||||
|
|
||||||
|
int x;
|
||||||
|
short delegate(int x) f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSample(Sample currentSample) {
|
final void addSample(Sample currentSample) {
|
||||||
synchronized(this) {
|
int frequencyCounter;
|
||||||
/*
|
short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
|
||||||
auto spot = playBufferEnd;
|
addChannel(
|
||||||
playBuffer[spot] = s;
|
delegate bool (short[] buffer) {
|
||||||
playBufferEnd = (playBufferEnd + 1) & (playBuffer.length-1);
|
|
||||||
*/
|
|
||||||
int frequencyCounter;
|
|
||||||
short val = cast(short) (cast(int) short.max * currentSample.volume / 100);
|
|
||||||
fillDatas ~= delegate bool (short[] buffer) {
|
|
||||||
|
|
||||||
if(currentSample.duration) {
|
if(currentSample.duration) {
|
||||||
if(currentSample.operation == 0)
|
size_t i = 0;
|
||||||
for(size_t i = 0; i < buffer.length; i++) {
|
if(currentSample.delay) {
|
||||||
buffer[i] = val;
|
if(buffer.length <= currentSample.delay * 2) {
|
||||||
// left and right do the same thing so we only count
|
// whole buffer consumed by delay
|
||||||
// every other sample
|
buffer[] = 0;
|
||||||
if(i & 1) {
|
currentSample.delay -= buffer.length / 2;
|
||||||
currentSample.duration--;
|
} else {
|
||||||
if(frequencyCounter)
|
i = currentSample.delay * 2;
|
||||||
frequencyCounter--;
|
buffer[0 .. i] = 0;
|
||||||
if(frequencyCounter == 0) {
|
currentSample.delay = 0;
|
||||||
val = -val;
|
|
||||||
frequencyCounter = currentSample.frequency / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(currentSample.duration == 0) {
|
|
||||||
buffer[i .. $] = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(currentSample.operation == 1)
|
if(currentSample.delay > 0)
|
||||||
for(size_t i = 0; i < buffer.length; i++) {
|
return true;
|
||||||
import std.random;
|
|
||||||
buffer[i] = uniform(short.min, short.max);
|
|
||||||
if(i & 1) {
|
|
||||||
currentSample.duration--;
|
|
||||||
|
|
||||||
if(currentSample.duration == 0) {
|
size_t sampleFinish;
|
||||||
buffer = buffer[i .. $];
|
if(currentSample.duration * 2 <= buffer.length) {
|
||||||
return false;
|
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 {
|
} else {
|
||||||
return false;
|
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 {
|
private {
|
||||||
AudioOutput* ao;
|
AudioOutput* ao;
|
||||||
|
|
||||||
bool delegate(short[] buffer)[] fillDatas;
|
bool delegate(short[] buffer)[32] fillDatas;
|
||||||
|
int fillDatasLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run() {
|
private void run() {
|
||||||
|
@ -272,8 +428,8 @@ class AudioPcmOutThread : Thread {
|
||||||
ao.fillData = (short[] buffer) {
|
ao.fillData = (short[] buffer) {
|
||||||
short[BUFFER_SIZE_SHORT] bfr;
|
short[BUFFER_SIZE_SHORT] bfr;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
if(fillDatas.length) {
|
if(fillDatasLength) {
|
||||||
for(int idx = 0; idx < fillDatas.length; idx++) {
|
for(int idx = 0; idx < fillDatasLength; idx++) {
|
||||||
auto dg = fillDatas[idx];
|
auto dg = fillDatas[idx];
|
||||||
auto ret = dg(bfr[0 .. buffer.length][]);
|
auto ret = dg(bfr[0 .. buffer.length][]);
|
||||||
foreach(i, v; bfr[0 .. buffer.length][]) {
|
foreach(i, v; bfr[0 .. buffer.length][]) {
|
||||||
|
@ -292,8 +448,8 @@ class AudioPcmOutThread : Thread {
|
||||||
if(!ret) {
|
if(!ret) {
|
||||||
// it returned false meaning this one is finished...
|
// it returned false meaning this one is finished...
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
fillDatas[idx] = fillDatas[$ - 1];
|
fillDatas[idx] = fillDatas[fillDatasLength - 1];
|
||||||
fillDatas = fillDatas[0 .. $-1];
|
fillDatasLength--;
|
||||||
}
|
}
|
||||||
idx--;
|
idx--;
|
||||||
}
|
}
|
||||||
|
@ -340,7 +496,7 @@ version(ALSA) {
|
||||||
easier to setup but for now I'm using the rawmidi so you
|
easier to setup but for now I'm using the rawmidi so you
|
||||||
gotta get them connected somehow.
|
gotta get them connected somehow.
|
||||||
*/
|
*/
|
||||||
enum midiName = "hw:2,0";
|
enum midiName = "hw:3,0";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thrown on audio failures.
|
/// Thrown on audio failures.
|
||||||
|
@ -587,6 +743,11 @@ struct MidiOutput {
|
||||||
} else static assert(0);
|
} 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
|
/// Send a reset message, silencing all notes
|
||||||
void reset() {
|
void reset() {
|
||||||
version(ALSA) {
|
version(ALSA) {
|
||||||
|
|
Loading…
Reference in New Issue