forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			858 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			858 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
package kha.audio2.ogg.vorbis;
 | 
						|
 | 
						|
import haxe.ds.Vector;
 | 
						|
import haxe.Int64;
 | 
						|
import haxe.io.Bytes;
 | 
						|
import haxe.io.Eof;
 | 
						|
import haxe.io.Input;
 | 
						|
import haxe.io.Output;
 | 
						|
import kha.audio2.ogg.tools.Crc32;
 | 
						|
import kha.audio2.ogg.tools.MathTools;
 | 
						|
import kha.audio2.ogg.vorbis.data.Codebook;
 | 
						|
import kha.audio2.ogg.vorbis.data.Floor.Floor1;
 | 
						|
import kha.audio2.ogg.vorbis.data.Header;
 | 
						|
import kha.audio2.ogg.vorbis.data.Mode;
 | 
						|
import kha.audio2.ogg.vorbis.data.Page;
 | 
						|
import kha.audio2.ogg.vorbis.data.ProbedPage;
 | 
						|
import kha.audio2.ogg.vorbis.data.ReaderError;
 | 
						|
import kha.audio2.ogg.vorbis.data.Page;
 | 
						|
import kha.audio2.ogg.vorbis.data.Residue;
 | 
						|
import kha.audio2.ogg.vorbis.data.Setting;
 | 
						|
import kha.audio2.ogg.vorbis.VorbisDecoder.DecodeInitialResult;
 | 
						|
 | 
						|
/**
 | 
						|
 * ...
 | 
						|
 * @author shohei909
 | 
						|
 */
 | 
						|
class VorbisDecodeState
 | 
						|
{
 | 
						|
    public static inline var INVALID_BITS = -1;
 | 
						|
 | 
						|
    public var page(default, null):Page;
 | 
						|
    public var eof(default, null):Bool;
 | 
						|
    public var pFirst(default, null):ProbedPage;
 | 
						|
    public var pLast(default, null):ProbedPage;
 | 
						|
    public var validBits(default, null):Int = 0;
 | 
						|
    public var inputPosition(default, null):Int;
 | 
						|
    public var input(default, null):Input;
 | 
						|
    public var discardSamplesDeferred:Int;
 | 
						|
    public var segments(default, null):Vector<Int>;
 | 
						|
    public var bytesInSeg:Int = 0; // uint8
 | 
						|
 | 
						|
    // decode buffer
 | 
						|
    public var channelBuffers:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
 | 
						|
    public var channelBufferStart:Int;
 | 
						|
    public var channelBufferEnd:Int;
 | 
						|
    public var currentSample(default, null):Int;
 | 
						|
 | 
						|
    public var previousWindow:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
 | 
						|
    public var previousLength:Int;
 | 
						|
    public var finalY:Vector<Array<Int>>; // [STB_VORBIS_MAX_CHANNELS];
 | 
						|
 | 
						|
 | 
						|
    var firstDecode:Bool = false;
 | 
						|
    var nextSeg:Int = 0;
 | 
						|
 | 
						|
    var acc:UInt;
 | 
						|
    var lastSeg:Bool;  // flag that we're on the last decodeState
 | 
						|
    var lastSegWhich:Int; // what was the decodeState number of the l1ast seg?
 | 
						|
 | 
						|
    var endSegWithKnownLoc:Int;
 | 
						|
    var knownLocForPacket:Int;
 | 
						|
 | 
						|
    var error:ReaderError;
 | 
						|
 | 
						|
    var currentLoc:Int; //uint32  sample location of next frame to decode
 | 
						|
    var currentLocValid:Int;
 | 
						|
 | 
						|
    var firstAudioPageOffset:UInt;
 | 
						|
 | 
						|
    public function new(input:Input)
 | 
						|
    {
 | 
						|
        this.input = input;
 | 
						|
        inputPosition = 0;
 | 
						|
        page = new Page();
 | 
						|
        Crc32.init();
 | 
						|
    }
 | 
						|
 | 
						|
    public function setup(loc0:Int, loc1:Int) {
 | 
						|
        var segmentCount = readByte();
 | 
						|
        this.segments = read(segmentCount);
 | 
						|
 | 
						|
        // assume we Don't_ know any the sample position of any segments
 | 
						|
        this.endSegWithKnownLoc = -2;
 | 
						|
        if (loc0 != 0xFFFFFFFF || loc1 != 0xFFFFFFFF) {
 | 
						|
            var i:Int = segmentCount - 1;
 | 
						|
            while (i >= 0) {
 | 
						|
                if (segments.get(i) < 255) {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                if (i >= 0) {
 | 
						|
                    this.endSegWithKnownLoc = i;
 | 
						|
                    this.knownLocForPacket = loc0;
 | 
						|
                }
 | 
						|
                i--;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (firstDecode) {
 | 
						|
            var i:Int = 0;
 | 
						|
            var len:Int = 0;
 | 
						|
            var p = new ProbedPage();
 | 
						|
 | 
						|
            for (i in 0...segmentCount) {
 | 
						|
                len += segments.get(i);
 | 
						|
            }
 | 
						|
            len += 27 + segmentCount;
 | 
						|
 | 
						|
            p.pageStart = firstAudioPageOffset;
 | 
						|
            p.pageEnd = p.pageStart + len;
 | 
						|
            p.firstDecodedSample = 0;
 | 
						|
            p.lastDecodedSample = loc0;
 | 
						|
            pFirst = p;
 | 
						|
        }
 | 
						|
 | 
						|
        nextSeg = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    public function clone(seekFunc:Int->Void)
 | 
						|
    {
 | 
						|
        var state = Type.createEmptyInstance(VorbisDecodeState);
 | 
						|
 | 
						|
        seekFunc(inputPosition);
 | 
						|
        state.input = input;
 | 
						|
 | 
						|
        // primitive
 | 
						|
        state.eof = eof;
 | 
						|
        state.validBits = validBits;
 | 
						|
        state.discardSamplesDeferred = discardSamplesDeferred;
 | 
						|
        state.firstDecode = firstDecode;
 | 
						|
        state.nextSeg = nextSeg;
 | 
						|
        state.bytesInSeg = bytesInSeg;
 | 
						|
        state.acc = state.acc;
 | 
						|
        state.lastSeg = lastSeg;
 | 
						|
        state.lastSegWhich = lastSegWhich;
 | 
						|
        state.currentLoc = currentLoc;
 | 
						|
        state.currentLocValid = currentLocValid;
 | 
						|
        state.inputPosition = inputPosition;
 | 
						|
        state.firstAudioPageOffset = firstAudioPageOffset;
 | 
						|
 | 
						|
        // sharrow copy
 | 
						|
        state.error = error;
 | 
						|
        state.segments = segments;
 | 
						|
        state.pFirst = pFirst;
 | 
						|
        state.pLast = pLast;
 | 
						|
 | 
						|
        // deep copy
 | 
						|
        state.page = page.clone();
 | 
						|
 | 
						|
        return state;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    // nextSegment
 | 
						|
    public function next():Int {
 | 
						|
        if (lastSeg) {
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        if (nextSeg == -1) {
 | 
						|
            lastSegWhich = segments.length - 1; // in case startPage fails
 | 
						|
 | 
						|
            try {
 | 
						|
                page.start(this);
 | 
						|
            } catch(e:ReaderError) {
 | 
						|
                lastSeg = true;
 | 
						|
                error = e;
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
 | 
						|
            if ((page.flag & PageFlag.CONTINUED_PACKET) == 0) {
 | 
						|
                throw new ReaderError(ReaderErrorType.CONTINUED_PACKET_FLAG_INVALID);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        var len = segments.get(nextSeg++);
 | 
						|
        if (len < 255) {
 | 
						|
            lastSeg = true;
 | 
						|
            lastSegWhich = nextSeg - 1;
 | 
						|
        }
 | 
						|
        if (nextSeg >= segments.length) {
 | 
						|
            nextSeg = -1;
 | 
						|
        }
 | 
						|
 | 
						|
        VorbisTools.assert(bytesInSeg == 0);
 | 
						|
        bytesInSeg = len;
 | 
						|
        return len;
 | 
						|
    }
 | 
						|
 | 
						|
    public function startPacket() {
 | 
						|
        while (nextSeg == -1) {
 | 
						|
            page.start(this);
 | 
						|
            if ((page.flag & PageFlag.CONTINUED_PACKET) != 0) {
 | 
						|
                throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        lastSeg = false;
 | 
						|
        validBits = 0;
 | 
						|
        bytesInSeg = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    public function maybeStartPacket():Bool
 | 
						|
    {
 | 
						|
        if (nextSeg == -1) {
 | 
						|
            var eof = false;
 | 
						|
            var x = try {
 | 
						|
                readByte();
 | 
						|
            } catch (e:Eof) {
 | 
						|
                eof = true;
 | 
						|
                0;
 | 
						|
            }
 | 
						|
 | 
						|
            if (eof) {
 | 
						|
                return false; // EOF at page boundary is not an error!
 | 
						|
            }
 | 
						|
 | 
						|
            if (x != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
 | 
						|
                throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
 | 
						|
            }
 | 
						|
 | 
						|
            page.startWithoutCapturePattern(this);
 | 
						|
        }
 | 
						|
 | 
						|
        startPacket();
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    // public inline function readBits(n:Int):Int
 | 
						|
    public function readBits(n:Int):Int // Kha: reduce output size
 | 
						|
    {
 | 
						|
        if (validBits < 0) {
 | 
						|
            return 0;
 | 
						|
        } else if (validBits < n) {
 | 
						|
            if (n > 24) {
 | 
						|
                // the accumulator technique below would not work correctly in this case
 | 
						|
                return readBits(24) + ((readBits(n - 24) << 24));
 | 
						|
            } else {
 | 
						|
                if (validBits == 0) {
 | 
						|
                    acc = 0;
 | 
						|
                }
 | 
						|
 | 
						|
                do {
 | 
						|
                    if (bytesInSeg == 0 && (lastSeg || next() == 0)) {
 | 
						|
                        validBits = INVALID_BITS;
 | 
						|
                        break;
 | 
						|
                    } else {
 | 
						|
                        bytesInSeg--;
 | 
						|
                        acc += (readByte() << validBits);
 | 
						|
                        validBits += 8;
 | 
						|
                    }
 | 
						|
                } while (validBits < n);
 | 
						|
 | 
						|
                if (validBits < 0) {
 | 
						|
                    return 0;
 | 
						|
                } else {
 | 
						|
                    var z = acc & ((1 << n) - 1);
 | 
						|
                    acc >>>= n;
 | 
						|
                    validBits -= n;
 | 
						|
                    return z;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            var z = acc & ((1 << n) - 1);
 | 
						|
            acc >>>= n;
 | 
						|
            validBits -= n;
 | 
						|
            return z;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    inline function readPacketRaw():Int {
 | 
						|
        return if (bytesInSeg == 0 && (lastSeg || next() == 0)) {  // CLANG!
 | 
						|
            VorbisTools.EOP;
 | 
						|
        } else {
 | 
						|
            //VorbisTools.assert(bytesInSeg > 0);
 | 
						|
            bytesInSeg--;
 | 
						|
            readByte();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function readPacket():Int
 | 
						|
    {
 | 
						|
        var x = readPacketRaw();
 | 
						|
        validBits = 0;
 | 
						|
        return x;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function flushPacket():Void {
 | 
						|
        while (bytesInSeg != 0 || (!lastSeg && next() != 0)) {
 | 
						|
            bytesInSeg--;
 | 
						|
            readByte();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function vorbisValidate() {
 | 
						|
        var header = Bytes.alloc(6);
 | 
						|
        for (i in 0...6) {
 | 
						|
            header.set(i, readPacket());
 | 
						|
        }
 | 
						|
        if (header.toString() != "vorbis") {
 | 
						|
            throw new ReaderError(ReaderErrorType.INVALID_SETUP, "vorbis header");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function firstPageValidate()
 | 
						|
    {
 | 
						|
        if (segments.length != 1) {
 | 
						|
            throw new ReaderError(INVALID_FIRST_PAGE, "segmentCount");
 | 
						|
        }
 | 
						|
        if (segments.get(0) != 30) {
 | 
						|
            throw new ReaderError(INVALID_FIRST_PAGE, "decodeState head");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function startFirstDecode()
 | 
						|
    {
 | 
						|
        firstAudioPageOffset = inputPosition;
 | 
						|
        firstDecode = true;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function capturePattern()
 | 
						|
    {
 | 
						|
        if (readByte() != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
 | 
						|
            throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    inline function skip(len:Int)
 | 
						|
    {
 | 
						|
        read(len);
 | 
						|
    }
 | 
						|
 | 
						|
    function prepHuffman()
 | 
						|
    {
 | 
						|
        if (validBits <= 24) {
 | 
						|
            if (validBits == 0) {
 | 
						|
                acc = 0;
 | 
						|
            }
 | 
						|
            do {
 | 
						|
                if (bytesInSeg == 0 && (lastSeg || next() == 0)) {  // CLANG!
 | 
						|
                    return;
 | 
						|
                } else {
 | 
						|
                    bytesInSeg--;
 | 
						|
                    acc += readByte() << validBits;
 | 
						|
                    validBits += 8;
 | 
						|
                }
 | 
						|
            } while (validBits <= 24);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function decode(c:Codebook):Int {
 | 
						|
        var val = decodeRaw(c);
 | 
						|
        if (c.sparse) {
 | 
						|
            val = c.sortedValues[val];
 | 
						|
        }
 | 
						|
        return val;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function decodeRaw(c:Codebook)
 | 
						|
    {
 | 
						|
        if (validBits < Setting.FAST_HUFFMAN_LENGTH){
 | 
						|
            prepHuffman();
 | 
						|
        }
 | 
						|
 | 
						|
        // fast huffman table lookup
 | 
						|
        var i = c.fastHuffman[acc & Setting.FAST_HUFFMAN_TABLE_MASK];
 | 
						|
 | 
						|
        return if (i >= 0) {
 | 
						|
            var l = c.codewordLengths[i];
 | 
						|
            acc >>>= l;
 | 
						|
            validBits -= l;
 | 
						|
            if (validBits < 0) {
 | 
						|
                validBits = 0;
 | 
						|
                -1;
 | 
						|
            } else {
 | 
						|
                i;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            decodeScalarRaw(c);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function isLastByte()
 | 
						|
    {
 | 
						|
        return bytesInSeg == 0 && lastSeg;
 | 
						|
    }
 | 
						|
 | 
						|
    public function finishDecodePacket(previousLength:Int, n:Int, r:DecodeInitialResult)
 | 
						|
    {
 | 
						|
        var left = r.left.start;
 | 
						|
        var currentLocValid = false;
 | 
						|
        var n2 = n >> 1;
 | 
						|
 | 
						|
        if (firstDecode) {
 | 
						|
            // assume we start so first non-discarded sample is sample 0
 | 
						|
            // this isn't to spec, but spec would require us to read ahead
 | 
						|
            // and decode the size of all current frames--could be done,
 | 
						|
            // but presumably it's not a commonly used feature
 | 
						|
            currentLoc = -n2; // start of first frame is positioned for discard
 | 
						|
            // we might have to discard samples "from" the next frame too,
 | 
						|
            // if we're lapping a large block then a small at the start?
 | 
						|
            discardSamplesDeferred = n - r.right.end;
 | 
						|
            currentLocValid = true;
 | 
						|
            firstDecode = false;
 | 
						|
        } else if (discardSamplesDeferred != 0) {
 | 
						|
            r.left.start += discardSamplesDeferred;
 | 
						|
            left = r.left.start;
 | 
						|
            discardSamplesDeferred = 0;
 | 
						|
        } else if (previousLength == 0 && currentLocValid) {
 | 
						|
            // we're recovering from a seek... that means we're going to discard
 | 
						|
            // the samples from this packet even though we know our position from
 | 
						|
            // the last page header, so we need to update the position based on
 | 
						|
            // the discarded samples here
 | 
						|
            // but wait, the code below is going to add this in itself even
 | 
						|
            // on a discard, so we don't need to do it here...
 | 
						|
        }
 | 
						|
 | 
						|
        // check if we have ogg information about the sample # for this packet
 | 
						|
        if (lastSegWhich == endSegWithKnownLoc) {
 | 
						|
            // if we have a valid current loc, and this is final:
 | 
						|
            if (currentLocValid && (page.flag & PageFlag.LAST_PAGE) != 0) {
 | 
						|
                var currentEnd = knownLocForPacket - (n - r.right.end);
 | 
						|
                // then let's infer the size of the (probably) short final frame
 | 
						|
                if (currentEnd < currentLoc + r.right.end) {
 | 
						|
                    var len = if (currentEnd < currentLoc) {
 | 
						|
                        // negative truncation, that's impossible!
 | 
						|
                        0;
 | 
						|
                    } else {
 | 
						|
                        currentEnd - currentLoc;
 | 
						|
                    }
 | 
						|
                    len += r.left.start;
 | 
						|
                    currentLoc += len;
 | 
						|
 | 
						|
                    return {
 | 
						|
                        len : len,
 | 
						|
                        left : left,
 | 
						|
                        right : r.right.start,
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            // otherwise, just set our sample loc
 | 
						|
            // guess that the ogg granule pos refers to the Middle_ of the
 | 
						|
            // last frame?
 | 
						|
            // set currentLoc to the position of leftStart
 | 
						|
            currentLoc = knownLocForPacket - (n2-r.left.start);
 | 
						|
            currentLocValid = true;
 | 
						|
        }
 | 
						|
 | 
						|
        if (currentLocValid) {
 | 
						|
            currentLoc += (r.right.start - r.left.start);
 | 
						|
        }
 | 
						|
 | 
						|
        // if (alloc.allocBuffer)
 | 
						|
        //assert(alloc.allocBufferLengthInBytes == tempOffset);
 | 
						|
 | 
						|
        return {
 | 
						|
            len : r.right.end,
 | 
						|
            left : left,
 | 
						|
            right : r.right.start,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function readInt32():Int
 | 
						|
    {
 | 
						|
        inputPosition += 4;
 | 
						|
        return input.readInt32();
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function readByte():Int
 | 
						|
    {
 | 
						|
        inputPosition += 1;
 | 
						|
        return input.readByte();
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function read(n:Int):Vector<Int> {
 | 
						|
        inputPosition += n;
 | 
						|
        var vec = new Vector(n);
 | 
						|
        for (i in 0...n) {
 | 
						|
            vec[i] = input.readByte();
 | 
						|
        }
 | 
						|
        return vec;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function readBytes(n:Int):Bytes {
 | 
						|
        inputPosition += n;
 | 
						|
        return input.read(n);
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function readString(n:Int):String
 | 
						|
    {
 | 
						|
        inputPosition += n;
 | 
						|
        return input.readString(n);
 | 
						|
    }
 | 
						|
 | 
						|
    public function getSampleNumber(seekFunc:Int->Void, inputLength:UInt):Int {
 | 
						|
 | 
						|
        // first, store the current decode position so we can restore it
 | 
						|
        var restoreOffset = inputPosition;
 | 
						|
 | 
						|
        // now we want to seek back 64K from the end (the last page must
 | 
						|
        // be at most a little less than 64K, but let's allow a little slop)
 | 
						|
        var previousSafe = if (inputLength >= 65536 && inputLength - 65536 >= firstAudioPageOffset) {
 | 
						|
            inputLength - 65536;
 | 
						|
        } else {
 | 
						|
            firstAudioPageOffset;
 | 
						|
        }
 | 
						|
 | 
						|
        setInputOffset(seekFunc, previousSafe);
 | 
						|
 | 
						|
        // previousSafe is now our candidate 'earliest known place that seeking
 | 
						|
        // to will lead to the final page'
 | 
						|
        var end = 0;
 | 
						|
        var last = false;
 | 
						|
        switch (findPage(seekFunc, inputLength)) {
 | 
						|
            case Found(e, l):
 | 
						|
                end = e;
 | 
						|
                last = l;
 | 
						|
            case NotFound:
 | 
						|
                throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
 | 
						|
        }
 | 
						|
 | 
						|
        // check if there are more pages
 | 
						|
        var lastPageLoc = inputPosition;
 | 
						|
 | 
						|
        // stop when the lastPage flag is set, not when we reach eof;
 | 
						|
        // this allows us to stop short of a 'fileSection' end without
 | 
						|
        // explicitly checking the length of the section
 | 
						|
        while (!last) {
 | 
						|
            setInputOffset(seekFunc, end);
 | 
						|
            switch (findPage(seekFunc, inputLength)) {
 | 
						|
                case Found(e, l):
 | 
						|
                    end = e;
 | 
						|
                    last = l;
 | 
						|
                case NotFound:
 | 
						|
                    // the last page we found didn't have the 'last page' flag
 | 
						|
                    // set. whoops!
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
 | 
						|
            previousSafe = lastPageLoc + 1;
 | 
						|
            lastPageLoc = inputPosition;
 | 
						|
        }
 | 
						|
 | 
						|
        setInputOffset(seekFunc, lastPageLoc);
 | 
						|
 | 
						|
        // parse the header
 | 
						|
        var vorbisHeader = read(6);
 | 
						|
 | 
						|
        // extract the absolute granule position
 | 
						|
        var lo = readInt32();
 | 
						|
        var hi = readInt32();
 | 
						|
        if (lo == 0xffffffff && hi == 0xffffffff || hi > 0) {
 | 
						|
            throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
 | 
						|
        }
 | 
						|
 | 
						|
        pLast = new ProbedPage();
 | 
						|
        pLast.pageStart = lastPageLoc;
 | 
						|
        pLast.pageEnd    = end;
 | 
						|
        pLast.lastDecodedSample = lo;
 | 
						|
        pLast.firstDecodedSample = null;
 | 
						|
        pLast.afterPreviousPageStart = previousSafe;
 | 
						|
 | 
						|
        setInputOffset(seekFunc, restoreOffset);
 | 
						|
        return lo;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function forcePageResync()
 | 
						|
    {
 | 
						|
        nextSeg = -1;
 | 
						|
    }
 | 
						|
 | 
						|
    public inline function setInputOffset(seekFunc:Int->Void, n:Int)
 | 
						|
    {
 | 
						|
        seekFunc(inputPosition = n);
 | 
						|
    }
 | 
						|
 | 
						|
    public function findPage(seekFunc:Int->Void, inputLength:Int):FindPageResult {
 | 
						|
        try {
 | 
						|
            while (true) {
 | 
						|
                var n = readByte();
 | 
						|
                if (n == 0x4f) { // page header
 | 
						|
                    var retryLoc = inputPosition;
 | 
						|
                    // check if we're off the end of a fileSection stream
 | 
						|
                    if (retryLoc - 25 > inputLength) {
 | 
						|
                        return FindPageResult.NotFound;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    var header = new Vector<UInt>(27);
 | 
						|
                    header[0] = 0x4f;
 | 
						|
                    header[1] = 0x67;
 | 
						|
                    header[2] = 0x67;
 | 
						|
                    header[3] = 0x53;
 | 
						|
                    for (i in 4...27) {
 | 
						|
                        header[i] = readByte();
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (header[4] != 0) {
 | 
						|
                        setInputOffset(seekFunc, retryLoc);
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    var goal:UInt = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24);
 | 
						|
                    for (i in 22...26) {
 | 
						|
                        header[i] = 0;
 | 
						|
                    }
 | 
						|
 | 
						|
                    var crc:UInt = 0;
 | 
						|
                    for (i in 0...27){
 | 
						|
                        crc = Crc32.update(crc, header[i]);
 | 
						|
                    }
 | 
						|
 | 
						|
                    var len = 0;
 | 
						|
                    try {
 | 
						|
                        for (i in 0...header[26]) {
 | 
						|
                            var s = readByte();
 | 
						|
                            crc = Crc32.update(crc, s);
 | 
						|
                            len += s;
 | 
						|
                        }
 | 
						|
                        for (i in 0...len) {
 | 
						|
                            crc = Crc32.update(crc, readByte());
 | 
						|
                        }
 | 
						|
                    } catch (e:Eof) {
 | 
						|
                        return FindPageResult.NotFound;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // finished parsing probable page
 | 
						|
                    if (crc == goal) {
 | 
						|
                        // we could now check that it's either got the last
 | 
						|
                        // page flag set, OR it's followed by the capture
 | 
						|
                        // pattern, but I guess TECHNICALLY you could have
 | 
						|
                        // a file with garbage between each ogg page and recover
 | 
						|
                        // from it automatically? So even though that paranoia
 | 
						|
                        // might decrease the chance of an invalid decode by
 | 
						|
                        // another 2^32, not worth it since it would hose those
 | 
						|
                        // invalid-but-useful files?
 | 
						|
                        var end = inputPosition;
 | 
						|
                        setInputOffset(seekFunc, retryLoc - 1);
 | 
						|
                        return FindPageResult.Found(end, (header[5] & 0x04 != 0));
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        } catch (e:Eof) {
 | 
						|
            return FindPageResult.NotFound;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function analyzePage(seekFunc:Int->Void, h:Header)
 | 
						|
    {
 | 
						|
        var z:ProbedPage = new ProbedPage();
 | 
						|
        var packetType = new Vector<Bool>(255);
 | 
						|
 | 
						|
        // record where the page starts
 | 
						|
        z.pageStart = inputPosition;
 | 
						|
 | 
						|
        // parse the header
 | 
						|
        var pageHeader = read(27);
 | 
						|
        VorbisTools.assert(pageHeader.get(0) == 0x4f && pageHeader.get(1) == 0x67 && pageHeader.get(2) == 0x67 && pageHeader.get(3) == 0x53);
 | 
						|
        var lacing = read(pageHeader.get(26));
 | 
						|
 | 
						|
        // determine the length of the payload
 | 
						|
        var len = 0;
 | 
						|
        for (i in 0...pageHeader.get(26)){
 | 
						|
            len += lacing.get(i);
 | 
						|
        }
 | 
						|
 | 
						|
        // this implies where the page ends
 | 
						|
        z.pageEnd = z.pageStart + 27 + pageHeader.get(26) + len;
 | 
						|
 | 
						|
        // read the last-decoded sample out of the data
 | 
						|
        z.lastDecodedSample = pageHeader.get(6) + (pageHeader.get(7) << 8) + (pageHeader.get(8) << 16) + (pageHeader.get(9) << 16);
 | 
						|
 | 
						|
        if ((pageHeader.get(5) & 4) != 0) {
 | 
						|
            // if this is the last page, it's not possible to work
 | 
						|
            // backwards to figure out the first sample! whoops! fuck.
 | 
						|
            z.firstDecodedSample = null;
 | 
						|
            setInputOffset(seekFunc, z.pageStart);
 | 
						|
            return z;
 | 
						|
        }
 | 
						|
 | 
						|
        // scan through the frames to determine the sample-count of each one...
 | 
						|
        // our goal is the sample # of the first fully-decoded sample on the
 | 
						|
        // page, which is the first decoded sample of the 2nd packet
 | 
						|
 | 
						|
        var numPacket = 0;
 | 
						|
        var packetStart = ((pageHeader.get(5) & 1) == 0);
 | 
						|
 | 
						|
        var modeCount = h.modes.length;
 | 
						|
 | 
						|
        for (i in 0...pageHeader.get(26)) {
 | 
						|
            if (packetStart) {
 | 
						|
                if (lacing.get(i) == 0) {
 | 
						|
 | 
						|
                    setInputOffset(seekFunc, z.pageStart);
 | 
						|
                    return null; // trying to read from zero-length packet
 | 
						|
                }
 | 
						|
                var n = readByte();
 | 
						|
 | 
						|
                // if bottom bit is non-zero, we've got corruption
 | 
						|
                if (n & 1 != 0) {
 | 
						|
                    setInputOffset(seekFunc, z.pageStart);
 | 
						|
                    return null;
 | 
						|
                }
 | 
						|
                n >>= 1;
 | 
						|
                var b = MathTools.ilog(modeCount - 1);
 | 
						|
                n &= (1 << b) - 1;
 | 
						|
                if (n >= modeCount) {
 | 
						|
                    setInputOffset(seekFunc, z.pageStart);
 | 
						|
                    return null;
 | 
						|
                }
 | 
						|
                packetType[numPacket++] = h.modes[n].blockflag;
 | 
						|
                skip(lacing.get(i)-1);
 | 
						|
            } else {
 | 
						|
                skip(lacing.get(i));
 | 
						|
            }
 | 
						|
            packetStart = (lacing.get(i) < 255);
 | 
						|
        }
 | 
						|
 | 
						|
        // now that we know the sizes of all the pages, we can start determining
 | 
						|
        // how much sample data there is.
 | 
						|
        var samples = 0;
 | 
						|
 | 
						|
        // for the last packet, we step by its whole length, because the definition
 | 
						|
        // is that we encoded the end sample loc of the 'last packet completed',
 | 
						|
        // where 'completed' refers to packets being split, and we are left to guess
 | 
						|
        // what 'end sample loc' means. we assume it means ignoring the fact that
 | 
						|
        // the last half of the data is useless without windowing against the next
 | 
						|
        // packet... (so it's not REALLY complete in that sense)
 | 
						|
        if (numPacket > 1) {
 | 
						|
            samples += packetType[numPacket-1] ? h.blocksize1 : h.blocksize0;
 | 
						|
        }
 | 
						|
 | 
						|
        var i = numPacket - 2;
 | 
						|
        while (i >= 1) {
 | 
						|
            i--;
 | 
						|
            // now, for this packet, how many samples do we have that
 | 
						|
            // do not overlap the following packet?
 | 
						|
            if (packetType[i]) {
 | 
						|
                if (packetType[i + 1]) {
 | 
						|
                    samples += h.blocksize1 >> 1;
 | 
						|
                } else {
 | 
						|
                    samples += ((h.blocksize1 - h.blocksize0) >> 2) + (h.blocksize0 >> 1);
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                samples += h.blocksize0 >> 1;
 | 
						|
            }
 | 
						|
            i--;
 | 
						|
        }
 | 
						|
        // now, at this point, we've rewound to the very beginning of the
 | 
						|
        // Second_ packet. if we entirely discard the first packet after
 | 
						|
        // a seek, this will be exactly the right sample number. HOWEVER!
 | 
						|
        // we can't as easily compute this number for the LAST page. The
 | 
						|
        // only way to get the sample offset of the LAST page is to use
 | 
						|
        // the end loc from the previous page. But what that returns us
 | 
						|
        // is _exactly_ the place where we get our first non-overlapped
 | 
						|
        // sample. (I think. Stupid spec for being ambiguous.) So for
 | 
						|
        // consistency it's better to do that here, too. However, that
 | 
						|
        // will then require us to NOT discard all of the first frame we
 | 
						|
        // decode, in some cases, which means an even weirder frame size
 | 
						|
        // and extra code. what a fucking pain.
 | 
						|
 | 
						|
        // we're going to discard the first packet if we
 | 
						|
        // start the seek here, so we don't care about it. (we could actually
 | 
						|
        // do better; if the first packet is long, and the previous packet
 | 
						|
        // is short, there's actually data in the first half of the first
 | 
						|
        // packet that doesn't need discarding... but not worth paying the
 | 
						|
        // effort of tracking that of that here and in the seeking logic)
 | 
						|
        // except crap, if we infer it from the Previous_ packet's end
 | 
						|
        // location, we DO need to use that definition... and we HAVE to
 | 
						|
        // infer the start loc of the LAST packet from the previous packet's
 | 
						|
        // end location. fuck you, ogg vorbis.
 | 
						|
 | 
						|
        z.firstDecodedSample = z.lastDecodedSample - samples;
 | 
						|
 | 
						|
        // restore file state to where we were
 | 
						|
        setInputOffset(seekFunc, z.pageStart);
 | 
						|
        return z;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    function decodeScalarRaw(c:Codebook):Int
 | 
						|
    {
 | 
						|
        prepHuffman();
 | 
						|
 | 
						|
        VorbisTools.assert(c.sortedCodewords != null || c.codewords != null);
 | 
						|
        // cases to use binary search: sortedCodewords && !codewords
 | 
						|
 | 
						|
        var codewordLengths = c.codewordLengths;
 | 
						|
        var codewords = c.codewords;
 | 
						|
        var sortedCodewords = c.sortedCodewords;
 | 
						|
 | 
						|
        if (c.entries > 8 ? (sortedCodewords != null) : codewords != null) {
 | 
						|
            // binary search
 | 
						|
            var code = VorbisTools.bitReverse(acc);
 | 
						|
            var x = 0;
 | 
						|
            var n = c.sortedEntries;
 | 
						|
 | 
						|
            while (n > 1) {
 | 
						|
                // invariant: sc[x] <= code < sc[x+n]
 | 
						|
                var m = x + (n >> 1);
 | 
						|
                if (sortedCodewords[m] <= code) {
 | 
						|
                    x = m;
 | 
						|
                    n -= (n>>1);
 | 
						|
                } else {
 | 
						|
                    n >>= 1;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // x is now the sorted index
 | 
						|
            if (!c.sparse) {
 | 
						|
                x = c.sortedValues[x];
 | 
						|
            }
 | 
						|
 | 
						|
            // x is now sorted index if sparse, or symbol otherwise
 | 
						|
            var len = codewordLengths[x];
 | 
						|
            if (validBits >= len) {
 | 
						|
                acc >>>= len;
 | 
						|
                validBits -= len;
 | 
						|
                return x;
 | 
						|
            }
 | 
						|
 | 
						|
            validBits = 0;
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
        // if small, linear search
 | 
						|
        VorbisTools.assert(!c.sparse);
 | 
						|
        for (i in 0...c.entries) {
 | 
						|
            var cl = codewordLengths[i];
 | 
						|
            if (cl == Codebook.NO_CODE) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (codewords[i] == (acc & ((1 << cl)-1))) {
 | 
						|
                if (validBits >= cl) {
 | 
						|
                    acc >>>= cl;
 | 
						|
                    validBits -= cl;
 | 
						|
                    return i;
 | 
						|
                }
 | 
						|
                validBits = 0;
 | 
						|
                return -1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        error = new ReaderError(INVALID_STREAM);
 | 
						|
        validBits = 0;
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
private enum FindPageResult {
 | 
						|
    Found(end:Int, last:Bool);
 | 
						|
    NotFound;
 | 
						|
}
 |