From f554775b894a4c4e7f36fde69d690ab713f9e628 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 9 Jun 2023 17:10:42 -0500 Subject: [PATCH 1/6] Support OGG on JS, fall back to stb (#1) --- hxd/res/Config.hx | 5 +- hxd/res/Sound.hx | 14 +--- hxd/snd/OggData.hx | 161 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 147 insertions(+), 33 deletions(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index 6307505bfd..be48e8162b 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -91,13 +91,10 @@ class Config { ignoredExtensions.set("mp3", true); #end default: - #if !stb_ogg_sound - ignoredExtensions.set("ogg", true); - #end } return pf; } public static var platform : Platform = init(); -} \ No newline at end of file +} diff --git a/hxd/res/Sound.hx b/hxd/res/Sound.hx index b412cc35ce..bc0158a27b 100644 --- a/hxd/res/Sound.hx +++ b/hxd/res/Sound.hx @@ -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 } } @@ -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 ) @@ -97,4 +87,4 @@ class Sound extends Resource { } } -} \ No newline at end of file +} diff --git a/hxd/snd/OggData.hx b/hxd/snd/OggData.hx index a9e8bbba5b..c0694caf45 100644 --- a/hxd/snd/OggData.hx +++ b/hxd/snd/OggData.hx @@ -80,8 +80,149 @@ 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); + + // is this valid or causes issues 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 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 requires -lib stb_ogg_sound"; + } + +} + +#end + +#if stb_ogg_sound + private class BytesOutput extends haxe.io.Output { var bytes : haxe.io.Bytes; @@ -110,7 +251,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; @@ -137,7 +278,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; @@ -180,20 +321,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 \ No newline at end of file +#end From b299a405a1fa6c2bf7348ab422c2c431e24802e6 Mon Sep 17 00:00:00 2001 From: MSGhero Date: Sun, 11 Jun 2023 22:03:25 -0400 Subject: [PATCH 2/6] Still require stb lib --- hxd/res/Config.hx | 4 ++++ hxd/snd/OggData.hx | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index be48e8162b..135fffe760 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -90,6 +90,10 @@ class Config { #if !heaps_enable_hl_mp3 ignoredExtensions.set("mp3", true); #end + case JS: + #if !stb_ogg_sound + haxe.macro.Context.fatalError("Build error: OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound", haxe.macro.Context.currentPos()); + #end default: } return pf; diff --git a/hxd/snd/OggData.hx b/hxd/snd/OggData.hx index c0694caf45..b76f6f0810 100644 --- a/hxd/snd/OggData.hx +++ b/hxd/snd/OggData.hx @@ -114,7 +114,6 @@ class OggData extends Data { if( ctx == null ) return; ctx.decodeAudioData(bytes.getData(), processBuffer, onError); - // is this valid or causes issues onerror? var decodedRate = Std.int(ctx.sampleRate); samples = Math.ceil(samples * decodedRate / samplingRate); samplingRate = decodedRate; @@ -172,7 +171,7 @@ class OggData extends Data { sampleFormat = stbFallback.sampleFormat; channels = stbFallback.channels; #else - throw "Ogg support requires -lib stb_ogg_sound"; + throw "Ogg support on this browser requires -lib stb_ogg_sound"; #end } @@ -214,7 +213,7 @@ class OggData extends Data { } override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) { - throw "Ogg support requires -lib stb_ogg_sound"; + throw "Ogg support on this target requires -lib stb_ogg_sound"; } } From 1fd1453fbfb0751192f6dda6f2687a8898ea4e27 Mon Sep 17 00:00:00 2001 From: MSGhero Date: Sun, 11 Jun 2023 22:33:55 -0400 Subject: [PATCH 3/6] More general handling of illegal extensions OGG is illegal on !HL without stb lib --- hxd/res/Config.hx | 18 +++++++++++++----- hxd/res/FileTree.hx | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index 135fffe760..20e574d9ae 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -43,7 +43,19 @@ class Config { "lch" => true, // labchirp source "fla" => true, // Adobe flash ]; - + + /** + File extensions that fail compilation if found by the resource scan + **/ + public static var illegalExtensions:haxe.ds.Map = [ + #if (!hl && !stb_ogg_sound) + "ogg" => "OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound" + #end + ]; + + public static function addIllegalExtension( extension, errorMsg ) { + illegalExtensions.set(extension, errorMsg); + } /** Directory names not explored by the resource scan @@ -90,10 +102,6 @@ class Config { #if !heaps_enable_hl_mp3 ignoredExtensions.set("mp3", true); #end - case JS: - #if !stb_ogg_sound - haxe.macro.Context.fatalError("Build error: OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound", haxe.macro.Context.currentPos()); - #end default: } return pf; diff --git a/hxd/res/FileTree.hx b/hxd/res/FileTree.hx index f93e2e43a6..d7e54ea55d 100644 --- a/hxd/res/FileTree.hx +++ b/hxd/res/FileTree.hx @@ -118,6 +118,8 @@ class FileTree { var noExt = extParts.shift(); var ident = makeIdent(noExt); var ext = extParts.join(".").toLowerCase(); + if( Config.illegalExtensions.exists(ext) ) + Context.fatalError("Build error: " + Config.illegalExtensions.get(ext), pos); if( Config.ignoredExtensions.exists(ext) ) continue; // override previous file definition, if any From d5e2f00c2ed4b8b5122f0aee281e7c8fb89c391e Mon Sep 17 00:00:00 2001 From: MSGhero Date: Sun, 11 Jun 2023 23:39:57 -0400 Subject: [PATCH 4/6] Revert "More general handling of illegal extensions" This reverts commit 1fd1453fbfb0751192f6dda6f2687a8898ea4e27. --- hxd/res/Config.hx | 18 +++++------------- hxd/res/FileTree.hx | 2 -- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index 20e574d9ae..135fffe760 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -43,19 +43,7 @@ class Config { "lch" => true, // labchirp source "fla" => true, // Adobe flash ]; - - /** - File extensions that fail compilation if found by the resource scan - **/ - public static var illegalExtensions:haxe.ds.Map = [ - #if (!hl && !stb_ogg_sound) - "ogg" => "OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound" - #end - ]; - - public static function addIllegalExtension( extension, errorMsg ) { - illegalExtensions.set(extension, errorMsg); - } + /** Directory names not explored by the resource scan @@ -102,6 +90,10 @@ class Config { #if !heaps_enable_hl_mp3 ignoredExtensions.set("mp3", true); #end + case JS: + #if !stb_ogg_sound + haxe.macro.Context.fatalError("Build error: OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound", haxe.macro.Context.currentPos()); + #end default: } return pf; diff --git a/hxd/res/FileTree.hx b/hxd/res/FileTree.hx index d7e54ea55d..f93e2e43a6 100644 --- a/hxd/res/FileTree.hx +++ b/hxd/res/FileTree.hx @@ -118,8 +118,6 @@ class FileTree { var noExt = extParts.shift(); var ident = makeIdent(noExt); var ext = extParts.join(".").toLowerCase(); - if( Config.illegalExtensions.exists(ext) ) - Context.fatalError("Build error: " + Config.illegalExtensions.get(ext), pos); if( Config.ignoredExtensions.exists(ext) ) continue; // override previous file definition, if any From e6b5a046991b7211a2d5197feace8048e07e8fdb Mon Sep 17 00:00:00 2001 From: MSGhero Date: Sun, 11 Jun 2023 23:45:27 -0400 Subject: [PATCH 5/6] Add back ignoring OGG on !stb --- hxd/res/Config.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index 135fffe760..f8b3e54e48 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -92,7 +92,7 @@ class Config { #end case JS: #if !stb_ogg_sound - haxe.macro.Context.fatalError("Build error: OGG fallback required for full browser coverage (Safari). Include -lib stb_ogg_sound", haxe.macro.Context.currentPos()); + ignoredExtensions.set("ogg", true); #end default: } From 320a57687210188b4d56c55d8c99d54c6886eb12 Mon Sep 17 00:00:00 2001 From: MSGhero Date: Sun, 11 Jun 2023 23:50:01 -0400 Subject: [PATCH 6/6] Typo, consider all non-HL targets --- hxd/res/Config.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hxd/res/Config.hx b/hxd/res/Config.hx index f8b3e54e48..27e1cbdcf2 100644 --- a/hxd/res/Config.hx +++ b/hxd/res/Config.hx @@ -90,11 +90,10 @@ class Config { #if !heaps_enable_hl_mp3 ignoredExtensions.set("mp3", true); #end - case JS: + default: #if !stb_ogg_sound ignoredExtensions.set("ogg", true); #end - default: } return pf; }