<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 *
 * VorbisHeader reads what a Vorbis stream says about itself before any
 * sound is read.
 */
namespace seekquarry\yioop\library\av_processing;

/**
 * VorbisHeader reads the three descriptions a Vorbis stream begins
 * with. The first says how the sound was recorded, the second carries
 * the titles and names a person typed, and the third holds every table
 * the stream is written with.
 *
 * Vorbis differs from the other formats this folder reads in where its
 * tables come from. AAC and MP3 take theirs from their standards, the
 * same in every file, while a Vorbis file carries its own, chosen by
 * the encoder for the sound at hand. A decoder therefore reads them out
 * of the file before it can read a single value.
 *
 * @author Chris Pollett
 */
class VorbisHeader
{
    /**
     * IDENTIFICATION is the number the first description gives itself,
     * the one saying how the sound was recorded.
     * @var int
     */
    const IDENTIFICATION = 1;
    /**
     * NAMES is the number the second description gives itself, the one
     * carrying titles and names.
     * @var int
     */
    const NAMES = 3;
    /**
     * TABLES is the number the third description gives itself, the one
     * holding every table the stream is written with.
     * @var int
     */
    const TABLES = 5;
    /**
     * MARK is the word every description carries after its number, so a
     * reader can tell a Vorbis description from anything else.
     * @var string
     */
    const MARK = "vorbis";
    /**
     * $rate stores how many samples a second the sound was recorded at.
     * @var int
     */
    public $rate = 0;
    /**
     * $channels stores how many channels the sound carries.
     * @var int
     */
    public $channels = 1;
    /**
     * $short_block stores how many samples the shorter of the two
     * stretches covers. A stream writes a short stretch where the sound
     * changes quickly, so that a change is not smeared across a long
     * one.
     * @var int
     */
    public $short_block = 0;
    /**
     * $long_block stores how many samples the longer stretch covers,
     * which is what most of a recording is written in.
     * @var int
     */
    public $long_block = 0;
    /**
     * $lowest_rate stores the fewest bits a second the encoder aimed
     * at, or zero where it named none.
     * @var int
     */
    public $lowest_rate = 0;
    /**
     * $usual_rate stores the bits a second the encoder aimed at, or
     * zero where it named none.
     * @var int
     */
    public $usual_rate = 0;
    /**
     * $highest_rate stores the most bits a second the encoder aimed at,
     * or zero where it named none.
     * @var int
     */
    public $highest_rate = 0;
    /**
     * fromString reads the first description of a Vorbis stream, which
     * says how the sound was recorded. A caller reads it before
     * anything else, since the rate and the channel count settle how
     * every later packet is read.
     *
     * @param string $data The first packet of the stream.
     * @return VorbisHeader What the description says.
     */
    public static function fromString($data)
    {
        if (strlen($data) < 30) {
            throw new \RuntimeException("this Vorbis description is too "
                . "short to be read");
        }
        if (ord($data[0]) !== self::IDENTIFICATION
            || substr($data, 1, 6) !== self::MARK) {
            throw new \RuntimeException("this packet is not the "
                . "description a Vorbis stream begins with");
        }
        $said = new self();
        $said->channels = max(1, ord($data[11]));
        $said->rate = unpack("V", substr($data, 12, 4))[1];
        $said->highest_rate = self::signedRate(substr($data, 16, 4));
        $said->usual_rate = self::signedRate(substr($data, 20, 4));
        $said->lowest_rate = self::signedRate(substr($data, 24, 4));
        $sizes = ord($data[28]);
        $said->short_block = 1 << ($sizes & 0x0F);
        $said->long_block = 1 << (($sizes >> 4) & 0x0F);
        return $said;
    }
    /**
     * signedRate reads one of the three bit rates a description may
     * carry. A rate the encoder did not name is written as a number
     * below zero, and this hands back zero for those, so a caller need
     * not tell the two apart.
     *
     * @param string $held The four bytes the description stored.
     * @return int The rate in bits a second, or zero where none was
     *     named.
     */
    public static function signedRate($held)
    {
        $value = unpack("V", $held)[1];
        if ($value >= 2147483648) {
            $value -= 4294967296;
        }
        return ($value > 0) ? $value : 0;
    }
    /**
     * describe says what a Vorbis stream holds, without decoding any
     * sound. A caller prints this when somebody asks what is inside a
     * file.
     *
     * @param array $packets The packets of the stream, in order.
     * @return array What the stream says about itself, keyed by
     *     container, codec, rate, channels and seconds.
     */
    public static function describe($packets)
    {
        if ($packets === []) {
            throw new \RuntimeException("this file carries no Vorbis "
                . "sound");
        }
        $said = self::fromString($packets[0]);
        return ["codec" => "vorbis", "rate" => $said->rate,
            "channels" => $said->channels,
            "short_block" => $said->short_block,
            "long_block" => $said->long_block];
    }
}
