mirror of https://github.com/adamdruppe/arsd.git
mp3 overhaul
This commit is contained in:
parent
1f6ead0a17
commit
3d02886298
|
@ -236,8 +236,20 @@ interface SampleController {
|
||||||
History:
|
History:
|
||||||
Added November 20, 2022 (dub v10.10)
|
Added November 20, 2022 (dub v10.10)
|
||||||
+/
|
+/
|
||||||
|
// FIXME: this is clearly wrong on mp3s in some way.
|
||||||
void seek(float where);
|
void seek(float where);
|
||||||
|
|
||||||
|
// FIXME: would be cool to add volume and playback speed control methods too.
|
||||||
|
|
||||||
|
/++
|
||||||
|
Duration of the sample, in seconds. Please note it may be nan if unknown or inf if infinite looping.
|
||||||
|
You should check for both conditions.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added November 20, 2022 (dub v10.10)
|
||||||
|
+/
|
||||||
|
// float duration();
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Sets a delegate that will be called on the audio thread when the sample is finished
|
Sets a delegate that will be called on the audio thread when the sample is finished
|
||||||
playing; immediately after [finished] becomes `true`.
|
playing; immediately after [finished] becomes `true`.
|
||||||
|
@ -275,7 +287,7 @@ private class SampleControlFlags : SampleController {
|
||||||
bool finished() { return finished_; }
|
bool finished() { return finished_; }
|
||||||
bool paused() { return paused_; }
|
bool paused() { return paused_; }
|
||||||
|
|
||||||
void seek(float where) { synchronized(this) {requestedSeek = where;} }
|
void seek(float where) { synchronized(this) {if(where < 0) where = 0; requestedSeek = where;} }
|
||||||
|
|
||||||
float currentPosition = 0.0;
|
float currentPosition = 0.0;
|
||||||
float requestedSeek = float.init;
|
float requestedSeek = float.init;
|
||||||
|
@ -820,6 +832,8 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
If you use this function, you are opting into the GPL version 2 or later.
|
If you use this function, you are opting into the GPL version 2 or later.
|
||||||
Authors:
|
Authors:
|
||||||
Based on ketmar's code.
|
Based on ketmar's code.
|
||||||
|
Bugs:
|
||||||
|
The seek method is not yet implemented.
|
||||||
+/
|
+/
|
||||||
SampleController playEmulatedOpl3Midi()(string filename, bool loop = false) {
|
SampleController playEmulatedOpl3Midi()(string filename, bool loop = false) {
|
||||||
import std.file;
|
import std.file;
|
||||||
|
@ -996,37 +1010,39 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Requires mp3.d to be compiled in (module [arsd.mp3]) which is LGPL licensed.
|
Requires mp3.d to be compiled in (module [arsd.mp3]).
|
||||||
That LGPL license will extend to your code.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An implementation of [SampleController] which lets you pause, etc., the file.
|
An implementation of [SampleController] which lets you pause, etc., the file.
|
||||||
|
|
||||||
Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playOgg] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.
|
Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playOgg] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.
|
||||||
|
|
||||||
Bugs:
|
|
||||||
Mp3s cannot be seeked or looped in the current implementation.
|
|
||||||
|
|
||||||
History:
|
History:
|
||||||
Automatic resampling support added Nov 7, 2020.
|
Automatic resampling support added Nov 7, 2020.
|
||||||
|
|
||||||
Return value changed from `void` to a sample control object on December 23, 2020.
|
Return value changed from `void` to a sample control object on December 23, 2020.
|
||||||
|
|
||||||
The `immutable(ubyte)[]` overload was added December 30, 2020.
|
The `immutable(ubyte)[]` overload was added December 30, 2020.
|
||||||
|
|
||||||
|
The implementation of arsd.mp3 was completely changed on November 20, 2022, adding loop and seek support.
|
||||||
+/
|
+/
|
||||||
SampleController playMp3()(string filename) {
|
SampleController playMp3()(string filename) {
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
auto fi = new File(filename); // just let the GC close it... otherwise random segfaults happen... blargh
|
auto fi = new File(filename); // just let the GC close it... otherwise random segfaults happen... blargh
|
||||||
auto reader = delegate(void[] buf) {
|
auto reader = delegate(ubyte[] buf) {
|
||||||
return cast(int) fi.rawRead(buf[]).length;
|
return cast(int) fi.rawRead(buf[]).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
return playMp3(reader);
|
return playMp3(reader, (ulong pos) {
|
||||||
|
fi.seek(pos);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ditto
|
/// ditto
|
||||||
SampleController playMp3()(immutable(ubyte)[] data) {
|
SampleController playMp3()(immutable(ubyte)[] data) {
|
||||||
return playMp3( (void[] buffer) {
|
auto originalData = data;
|
||||||
|
return playMp3( (ubyte[] buffer) {
|
||||||
ubyte[] buf = cast(ubyte[]) buffer;
|
ubyte[] buf = cast(ubyte[]) buffer;
|
||||||
if(data.length >= buf.length) {
|
if(data.length >= buf.length) {
|
||||||
buf[] = data[0 .. buf.length];
|
buf[] = data[0 .. buf.length];
|
||||||
|
@ -1039,14 +1055,17 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
data = data[$ .. $];
|
data = data[$ .. $];
|
||||||
return cast(int) it;
|
return cast(int) it;
|
||||||
}
|
}
|
||||||
|
}, (ulong pos) {
|
||||||
|
data = originalData[pos .. $];
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// no compatibility guarantees, I can change this overload at any time!
|
// no compatibility guarantees, I can change this overload at any time!
|
||||||
/* private */ SampleController playMp3()(int delegate(void[]) reader) {
|
/* private */ SampleController playMp3()(int delegate(ubyte[]) reader, int delegate(size_t) seeker) {
|
||||||
import arsd.mp3;
|
import arsd.mp3;
|
||||||
|
|
||||||
auto mp3 = new MP3Decoder(reader);
|
auto mp3 = new MP3Decoder(reader, seeker);
|
||||||
if(!mp3.valid)
|
if(!mp3.valid)
|
||||||
throw new Exception("file not valid");
|
throw new Exception("file not valid");
|
||||||
|
|
||||||
|
@ -1069,7 +1088,7 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
|
|
||||||
synchronized(scf)
|
synchronized(scf)
|
||||||
if(scf.requestedSeek !is float.init) {
|
if(scf.requestedSeek !is float.init) {
|
||||||
if(mp3.seek(cast(uint) (scf.requestedSeek * v.sampleRate))) {
|
if(mp3.seek(cast(uint) (scf.requestedSeek * mp3.sampleRate * mp3.channels))) {
|
||||||
scf.currentPosition = scf.requestedSeek;
|
scf.currentPosition = scf.requestedSeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1092,7 +1111,7 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
|
|
||||||
if(buffer.length) {
|
if(buffer.length) {
|
||||||
if(mp3.valid) {
|
if(mp3.valid) {
|
||||||
mp3.decodeNextFrame(reader);
|
mp3.decodeNextFrame();
|
||||||
next = mp3.frameSamples;
|
next = mp3.frameSamples;
|
||||||
goto more;
|
goto more;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1119,6 +1138,18 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
}
|
}
|
||||||
|
|
||||||
override void loadMoreSamples() {
|
override void loadMoreSamples() {
|
||||||
|
|
||||||
|
|
||||||
|
synchronized(scf)
|
||||||
|
if(scf.requestedSeek !is float.init) {
|
||||||
|
if(mp3.seek(cast(uint) (scf.requestedSeek * mp3.sampleRate * mp3.channels))) {
|
||||||
|
scf.currentPosition = scf.requestedSeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
scf.requestedSeek = float.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(mp3.channels == 1) {
|
if(mp3.channels == 1) {
|
||||||
int actuallyGot;
|
int actuallyGot;
|
||||||
|
|
||||||
|
@ -1127,7 +1158,7 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
b = cast(float) next[0] / short.max;
|
b = cast(float) next[0] / short.max;
|
||||||
next = next[1 .. $];
|
next = next[1 .. $];
|
||||||
if(next.length == 0) {
|
if(next.length == 0) {
|
||||||
mp3.decodeNextFrame(reader);
|
mp3.decodeNextFrame();
|
||||||
next = mp3.frameSamples;
|
next = mp3.frameSamples;
|
||||||
}
|
}
|
||||||
actuallyGot++;
|
actuallyGot++;
|
||||||
|
@ -1141,13 +1172,13 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
b = cast(float) next[0] / short.max;
|
b = cast(float) next[0] / short.max;
|
||||||
next = next[1 .. $];
|
next = next[1 .. $];
|
||||||
if(next.length == 0) {
|
if(next.length == 0) {
|
||||||
mp3.decodeNextFrame(reader);
|
mp3.decodeNextFrame();
|
||||||
next = mp3.frameSamples;
|
next = mp3.frameSamples;
|
||||||
}
|
}
|
||||||
buffersIn[1][idx] = cast(float) next[0] / short.max;
|
buffersIn[1][idx] = cast(float) next[0] / short.max;
|
||||||
next = next[1 .. $];
|
next = next[1 .. $];
|
||||||
if(next.length == 0) {
|
if(next.length == 0) {
|
||||||
mp3.decodeNextFrame(reader);
|
mp3.decodeNextFrame();
|
||||||
next = mp3.frameSamples;
|
next = mp3.frameSamples;
|
||||||
}
|
}
|
||||||
actuallyGot++;
|
actuallyGot++;
|
||||||
|
@ -1175,6 +1206,8 @@ final class AudioPcmOutThreadImplementation : Thread {
|
||||||
An implementation of [SampleController] which lets you pause, etc., the file.
|
An implementation of [SampleController] which lets you pause, etc., the file.
|
||||||
|
|
||||||
Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playOgg], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.
|
Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playOgg], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible.
|
||||||
|
Bugs:
|
||||||
|
The seek method is not yet implemented.
|
||||||
History:
|
History:
|
||||||
Added Nov 8, 2020.
|
Added Nov 8, 2020.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue