mp3 overhaul

This commit is contained in:
Adam D. Ruppe 2022-11-24 09:11:27 -05:00
parent 1f6ead0a17
commit 3d02886298
2 changed files with 2775 additions and 2982 deletions

5692
mp3.d

File diff suppressed because it is too large Load Diff

View File

@ -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.