initial control interface for sound files

This commit is contained in:
Adam D. Ruppe 2020-12-23 23:50:17 -05:00
parent eab34ab324
commit c950ce3ae8
1 changed files with 84 additions and 17 deletions

View File

@ -183,6 +183,44 @@ void main() {
Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish
} }
/++
Provides an interface to control a sound.
History:
Added December 23, 2020
+/
interface SampleController {
/++
Pauses playback, keeping its position. Use [resume] to pick up where it left off.
+/
void pause();
/++
Resumes playback after a call to [pause].
+/
void resume();
/++
Stops playback. Once stopped, it cannot be restarted
except by creating a new sample from the [AudioOutputThread]
object.
+/
void stop();
}
class DummySample : SampleController {
void pause() {}
void resume() {}
void stop() {}
}
class SampleControlFlags : SampleController {
void pause() { paused = true; }
void resume() { paused = false; }
void stop() { stopped = true; }
bool paused;
bool stopped;
}
/++ /++
Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better
error handling and disposal than the old way. error handling and disposal than the old way.
@ -267,17 +305,20 @@ struct AudioOutputThread {
else static assert(0); else static assert(0);
} }
void playOgg()(string filename, bool loop = false) { SampleController playOgg()(string filename, bool loop = false) {
if(impl) if(impl)
impl.playOgg(filename, loop); return impl.playOgg(filename, loop);
return new DummySample;
} }
void playMp3()(string filename) { SampleController playMp3()(string filename) {
if(impl) if(impl)
impl.playMp3(filename); return impl.playMp3(filename);
return new DummySample;
} }
void playWav()(string filename) { SampleController playWav()(string filename) {
if(impl) if(impl)
impl.playWav(filename); return impl.playWav(filename);
return new DummySample;
} }
@ -449,11 +490,13 @@ final class AudioPcmOutThreadImplementation : Thread {
History: History:
Automatic resampling support added Nov 7, 2020. Automatic resampling support added Nov 7, 2020.
+/ +/
void playOgg()(string filename, bool loop = false) { SampleController playOgg()(string filename, bool loop = false) {
import arsd.vorbis; import arsd.vorbis;
auto v = new VorbisDecoder(filename); auto v = new VorbisDecoder(filename);
auto scf = new SampleControlFlags;
/+ /+
If you want 2 channels: If you want 2 channels:
if the file has 2+, use them. if the file has 2+, use them.
@ -467,6 +510,10 @@ final class AudioPcmOutThreadImplementation : Thread {
plain_fallback: plain_fallback:
addChannel( addChannel(
delegate bool(short[] buffer) { delegate bool(short[] buffer) {
if(scf.paused) {
buffer[] = 0;
return true;
}
if(cast(int) buffer.length != buffer.length) if(cast(int) buffer.length != buffer.length)
throw new Exception("eeeek"); throw new Exception("eeeek");
@ -480,15 +527,14 @@ final class AudioPcmOutThreadImplementation : Thread {
return false; return false;
} }
return true; return !scf.stopped;
} }
); );
return;
} else { } else {
version(with_resampler) { version(with_resampler) {
auto resampleContext = new class ResamplingContext { auto resampleContext = new class ResamplingContext {
this() { this() {
super(v.sampleRate, SampleRate, v.chans, channels); super(scf, v.sampleRate, SampleRate, v.chans, channels);
} }
override void loadMoreSamples() { override void loadMoreSamples() {
@ -507,6 +553,8 @@ final class AudioPcmOutThreadImplementation : Thread {
addChannel(&resampleContext.fillBuffer); addChannel(&resampleContext.fillBuffer);
} else goto plain_fallback; } else goto plain_fallback;
} }
return scf;
} }
/++ /++
@ -515,7 +563,7 @@ final class AudioPcmOutThreadImplementation : Thread {
History: History:
Automatic resampling support added Nov 7, 2020. Automatic resampling support added Nov 7, 2020.
+/ +/
void playMp3()(string filename) { SampleControlFlags playMp3()(string filename) {
import arsd.mp3; import arsd.mp3;
import std.stdio; import std.stdio;
@ -528,6 +576,8 @@ final class AudioPcmOutThreadImplementation : Thread {
if(!mp3.valid) if(!mp3.valid)
throw new Exception("no file"); throw new Exception("no file");
auto scf = new SampleControlFlags;
if(mp3.sampleRate == SampleRate && mp3.channels == channels) { if(mp3.sampleRate == SampleRate && mp3.channels == channels) {
plain_fallback: plain_fallback:
@ -535,6 +585,11 @@ final class AudioPcmOutThreadImplementation : Thread {
addChannel( addChannel(
delegate bool(short[] buffer) { delegate bool(short[] buffer) {
if(scf.paused) {
buffer[] = 0;
return true;
}
if(cast(int) buffer.length != buffer.length) if(cast(int) buffer.length != buffer.length)
throw new Exception("eeeek"); throw new Exception("eeeek");
@ -560,7 +615,7 @@ final class AudioPcmOutThreadImplementation : Thread {
} }
} }
return true; return !scf.stopped;
} }
); );
} else { } else {
@ -569,7 +624,7 @@ final class AudioPcmOutThreadImplementation : Thread {
auto resampleContext = new class ResamplingContext { auto resampleContext = new class ResamplingContext {
this() { this() {
super(mp3.sampleRate, SampleRate, mp3.channels, channels); super(scf, mp3.sampleRate, SampleRate, mp3.channels, channels);
} }
override void loadMoreSamples() { override void loadMoreSamples() {
@ -616,6 +671,8 @@ final class AudioPcmOutThreadImplementation : Thread {
} else goto plain_fallback; } else goto plain_fallback;
} }
return scf;
} }
/++ /++
@ -626,7 +683,8 @@ final class AudioPcmOutThreadImplementation : Thread {
History: History:
Added Nov 8, 2020. Added Nov 8, 2020.
+/ +/
void playWav()(string filename) { SampleController playWav()(string filename) {
auto scf = new SampleControlFlags;
version(with_resampler) { version(with_resampler) {
auto resampleContext = new class ResamplingContext { auto resampleContext = new class ResamplingContext {
import arsd.wav; import arsd.wav;
@ -634,7 +692,7 @@ final class AudioPcmOutThreadImplementation : Thread {
this() { this() {
reader = wavReader(filename); reader = wavReader(filename);
super(reader.sampleRate, SampleRate, reader.numberOfChannels, channels); super(scf, reader.sampleRate, SampleRate, reader.numberOfChannels, channels);
} }
WavReader!CFileChunks reader; WavReader!CFileChunks reader;
@ -718,6 +776,8 @@ final class AudioPcmOutThreadImplementation : Thread {
addChannel(&resampleContext.fillBuffer); addChannel(&resampleContext.fillBuffer);
} else static assert(0, "I was lazy and didn't implement straight-through playing"); } else static assert(0, "I was lazy and didn't implement straight-through playing");
return scf;
} }
@ -3577,8 +3637,10 @@ abstract class ResamplingContext {
float[][2] dataReady; float[][2] dataReady;
SampleControlFlags scflags;
this(int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) { this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) {
this.scflags = scflags;
this.inputSampleRate = inputSampleRate; this.inputSampleRate = inputSampleRate;
this.outputSampleRate = outputSampleRate; this.outputSampleRate = outputSampleRate;
this.inputChannels = inputChannels; this.inputChannels = inputChannels;
@ -3644,6 +3706,11 @@ abstract class ResamplingContext {
if(cast(int) buffer.length != buffer.length) if(cast(int) buffer.length != buffer.length)
throw new Exception("eeeek"); throw new Exception("eeeek");
if(scflags.paused) {
buffer[] = 0;
return true;
}
if(outputChannels == 1) { if(outputChannels == 1) {
foreach(ref s; buffer) { foreach(ref s; buffer) {
if(resamplerDataLeft.dataOut.length == 0) { if(resamplerDataLeft.dataOut.length == 0) {
@ -3684,7 +3751,7 @@ abstract class ResamplingContext {
} }
} else assert(0); } else assert(0);
return true; return !scflags.stopped;
} }
} }