Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OGG on JS, fall back to stb #1152

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hxd/res/Config.hx
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ class Config {

public static var platform : Platform = init();

}
}
14 changes: 2 additions & 12 deletions hxd/res/Sound.hx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@ class Sound extends Resource {

public static function supportedFormat( fmt : SoundFormat ) {
return switch( fmt ) {
case Wav, Mp3:
case Wav, Mp3, OggVorbis:
return true;
case OggVorbis:
#if (hl || stb_ogg_sound)
return true;
#else
return false;
#end
}
}

Expand All @@ -43,11 +37,7 @@ class Sound extends Resource {
case 255, 'I'.code: // MP3 (or ID3)
data = new hxd.snd.Mp3Data(bytes);
case 'O'.code: // Ogg (vorbis)
#if (hl || stb_ogg_sound)
data = new hxd.snd.OggData(bytes);
#else
throw "OGG format requires -lib stb_ogg_sound (for " + entry.path+")";
#end
default:
}
if( data == null )
Expand Down Expand Up @@ -97,4 +87,4 @@ class Sound extends Resource {
}
}

}
}
160 changes: 143 additions & 17 deletions hxd/snd/OggData.hx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,148 @@ class OggData extends Data {

}

#elseif js

class OggData extends Data {

var buffer : haxe.io.Bytes;
var onEnd : Void -> Void;

#if stb_ogg_sound
var stbFallback:OggDataSTB;
var bytesFallback:haxe.io.Bytes;
#end

public function new(bytes:haxe.io.Bytes) {

if (bytes == null) return;

// header check: OGG container, Vorbis audio, version 0 of spec
if (bytes.getString(0, 4) != "OggS" || bytes.getString(29, 6) != "vorbis" || bytes.getInt32(35) != 0) {
throw "Invalid OGG header";
}

sampleFormat = F32;
channels = bytes.get(39);
samplingRate = bytes.getInt32(40);

#if stb_ogg_sound
stbFallback = null;
bytesFallback = bytes;
#end

var ctx = hxd.snd.webaudio.Context.get();
if( ctx == null ) return;
ctx.decodeAudioData(bytes.getData(), processBuffer, onError);

var decodedRate = Std.int(ctx.sampleRate);
samples = Math.ceil(samples * decodedRate / samplingRate);
samplingRate = decodedRate;
}

override function isLoading() {
#if stb_ogg_sound
if (stbFallback != null) return stbFallback.isLoading();
#end
return buffer == null;
}

override public function load(onEnd:Void->Void) {
#if stb_ogg_sound
if (stbFallback != null) {
stbFallback.load(onEnd);
return;
}
#end
if( buffer != null ) onEnd() else this.onEnd = onEnd;
}

function processBuffer( buf : js.html.audio.AudioBuffer ) {

var left = buf.getChannelData(0);
samples = buf.length; // actual decoded samples

if( channels == 1 ) {
buffer = haxe.io.Bytes.ofData(left.buffer);
return;
}

var right = buf.numberOfChannels < 2 ? left : buf.getChannelData(1);
var join = new hxd.impl.TypedArray.Float32Array(left.length * 2);
var w = 0;
for( i in 0...buf.length ) {
join[w++] = left[i];
join[w++] = right[i];
}

buffer = haxe.io.Bytes.ofData(join.buffer);
if( onEnd != null ) {
onEnd();
onEnd = null;
}
}

function onError(_:js.html.DOMException) {
// if OGG cannot be read by browser (e.g. Safari), use STB library instead
#if stb_ogg_sound
stbFallback = new OggDataSTB(bytesFallback);
bytesFallback = null;
samples = stbFallback.samples;
samplingRate = stbFallback.samplingRate;
sampleFormat = stbFallback.sampleFormat;
channels = stbFallback.channels;
#else
throw "Ogg support on this browser requires -lib stb_ogg_sound";
#end
}

#if stb_ogg_sound
override function resample(rate:Int, format:Data.SampleFormat, channels:Int):Data {
if (stbFallback != null) return stbFallback.resample(rate, format, channels);
else return super.resample(rate, format, channels);
}
#end

override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {

#if stb_ogg_sound
if (stbFallback != null) {
stbFallback.decodeBuffer(out, outPos, sampleStart, sampleCount);
return;
}
#end

if( buffer == null ) {
// not yet available : fill with blanks
out.fill(outPos, sampleCount * 4 * channels, 0);
} else {
out.blit(outPos, buffer, sampleStart * 4 * channels, sampleCount * 4 * channels);
}
}
}

#elseif stb_ogg_sound

// standalone STB-based support for OGG
typedef OggData = OggDataSTB;

#else

class OggData extends Data {

public function new( bytes : haxe.io.Bytes ) {
}

override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {
throw "Ogg support on this target requires -lib stb_ogg_sound";
}

}

#end

#if stb_ogg_sound

private class BytesOutput extends haxe.io.Output {

var bytes : haxe.io.Bytes;
Expand Down Expand Up @@ -110,7 +250,7 @@ private class BytesOutput extends haxe.io.Output {

}

class OggData extends Data {
class OggDataSTB extends Data {

var reader : stb.format.vorbis.Reader;
var output : BytesOutput;
Expand All @@ -137,7 +277,7 @@ class OggData extends Data {
return this;
switch( format ) {
case I16, F32 if( rate % this.samplingRate == 0 && channels >= this.channels ):
var c = new OggData(null);
var c = new OggDataSTB(null);
c.reader = reader;
c.samples = samples;
c.samplingRate = samplingRate;
Expand Down Expand Up @@ -180,20 +320,6 @@ class OggData extends Data {
out.blit(outPos, decoded, (sampleStart - decodedFirst) * bpp, sampleCount * bpp);
}


}

#else

class OggData extends Data {

public function new( bytes : haxe.io.Bytes ) {
}

override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {
throw "Ogg support requires -lib stb_ogg_sound";
}

}

#end
#end