Skip to content
Closed
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
4 changes: 3 additions & 1 deletion hxd/res/Config.hx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Config {
"ttf" => "hxd.res.Font",
"fnt" => "hxd.res.BitmapFont",
"bdf" => "hxd.res.BDFFont",
"wav,mp3,ogg" => "hxd.res.Sound",
"wav,mp3,ogg,opus" => "hxd.res.Sound",
"tmx" => "hxd.res.TiledMap",
"atlas" => "hxd.res.Atlas",
"grd" => "hxd.res.Gradients",
Expand Down Expand Up @@ -64,6 +64,7 @@ class Config {
"cdb" => "img",
"atlas" => "png",
"ogg" => "wav",
"opus" => "wav",
"mp3" => "wav",
"l3d" => "bake",
"css" => "less,css.map",
Expand Down Expand Up @@ -94,6 +95,7 @@ class Config {
#if !stb_ogg_sound
ignoredExtensions.set("ogg", true);
#end
ignoredExtensions.set("opus", true);
}
return pf;
}
Expand Down
42 changes: 36 additions & 6 deletions hxd/res/Sound.hx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ enum SoundFormat {
Wav;
Mp3;
OggVorbis;
Opus;
}

class Sound extends Resource {
Expand All @@ -24,6 +25,12 @@ class Sound extends Resource {
#else
return false;
#end
case Opus:
#if hl
return true;
#else
return false;
#end
}
}

Expand All @@ -36,12 +43,20 @@ class Sound extends Resource {
data = new hxd.snd.WavData(bytes);
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
case 'O'.code: // Ogg container (vorbis or opus)
if( isOpusStream(bytes) ) {
#if hl
data = new hxd.snd.OpusData(bytes);
#else
throw "Opus format requires HashLink (for " + entry.path + ")";
#end
} else {
#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 All @@ -51,6 +66,21 @@ class Sound extends Resource {
return data;
}

static function isOpusStream( bytes : haxe.io.Bytes ) : Bool {
if( bytes.length < 36 ) return false;
var numSegments = bytes.get(26);
var dataOffset = 27 + numSegments;
if( bytes.length < dataOffset + 8 ) return false;
return bytes.get(dataOffset) == 'O'.code
&& bytes.get(dataOffset + 1) == 'p'.code
&& bytes.get(dataOffset + 2) == 'u'.code
&& bytes.get(dataOffset + 3) == 's'.code
&& bytes.get(dataOffset + 4) == 'H'.code
&& bytes.get(dataOffset + 5) == 'e'.code
&& bytes.get(dataOffset + 6) == 'a'.code
&& bytes.get(dataOffset + 7) == 'd'.code;
}

public function dispose() {
stop();
data = null;
Expand Down
99 changes: 99 additions & 0 deletions hxd/snd/OpusData.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package hxd.snd;

#if hl

private typedef OpusFile = hl.Abstract<"fmt_opus">;

class OpusData extends Data {

var bytes : haxe.io.Bytes;
var reader : OpusFile;
var currentSample : Int;

public function new( bytes : haxe.io.Bytes ) {
this.bytes = bytes;
reader = opus_open(bytes, bytes.length);
if( reader == null ) throw "Failed to decode Opus data";

var b = 0, f = 0, s = 0, c = 0;
opus_info(reader, b, f, s, c);
channels = c;
samples = s;
sampleFormat = I16;
samplingRate = f; // Always 48000 for Opus
}

override function resample(rate:Int, format:Data.SampleFormat, channels:Int):Data {
switch( format ) {
case I16 if( channels == this.channels && rate == this.samplingRate ):
var g = new OpusData(bytes);
return g;
case F32 if( channels == this.channels && rate == this.samplingRate ):
var g = new OpusData(bytes);
g.sampleFormat = F32;
return g;
default:
return super.resample(rate, format, channels);
}
}

override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {
if( currentSample != sampleStart ) {
currentSample = sampleStart;
if( !opus_seek(reader, sampleStart) ) throw "Invalid sample start";
}
var bpp = getBytesPerSample();
var output : hl.Bytes = out;
output = output.offset(outPos);
var format = switch( sampleFormat ) {
case I16: 2;
case F32: 3;
default:
throw "assert";
}
var bytesNeeded = sampleCount * bpp;
while( bytesNeeded > 0 ) {
var read = opus_read(reader, output, bytesNeeded, format);
if( read < 0 ) throw "Failed to decode Opus data";
if( read == 0 ) {
// EOF
output.fill(0, bytesNeeded, 0);
break;
}
bytesNeeded -= read;
output = output.offset(read);
}
currentSample += sampleCount;
}

@:hlNative("fmt", "opus_open") static function opus_open( bytes : hl.Bytes, size : Int ) : OpusFile {
return null;
}

@:hlNative("fmt", "opus_seek") static function opus_seek( o : OpusFile, sample : Int ) : Bool {
return false;
}

@:hlNative("fmt", "opus_info") static function opus_info( o : OpusFile, bitrate : hl.Ref<Int>, freq : hl.Ref<Int>, samples : hl.Ref<Int>, channels : hl.Ref<Int> ) : Void {
}

@:hlNative("fmt", "opus_read") static function opus_read( o : OpusFile, output : hl.Bytes, size : Int, format : Int ) : Int {
return 0;
}

}

#else

class OpusData extends Data {

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

override function decodeBuffer(out:haxe.io.Bytes, outPos:Int, sampleStart:Int, sampleCount:Int) {
throw "Opus support requires HashLink";
}

}

#end