/ src / library / av_processing / CeltBands.php
<?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
 */
namespace seekquarry\yioop\library\av_processing;

/**
 * CeltBands how Opus divides the sound into bands, and what it expects each
 * band to sound like before it reads anything. The ear does not hear every part
 * of the sound equally finely. Two tones close together low down are told apart
 * easily; the same gap high up is not heard at all. Opus follows that by
 * cutting the sound into twenty one bands, narrow at the bottom and wide at the
 * top, and giving each band one loudness. The edges here are in slots of the
 * shortest stretch Opus works in, and are widened for longer stretches by
 * doubling once per doubling of the stretch. The expected loudness of each band
 * is what the decoder assumes before it reads. What is stored in a recording is
 * only the difference from that, which is usually small, so storing the
 * difference costs far less than storing the loudness would. The numbers here
 * come from the Opus specification, RFC 6716, and the reference implementation
 * it carries, which is under the following notice. Copyright (c) 2007-2008
 * CSIRO, Copyright (c) 2007-2009 Xiph.Org Foundation, Copyright (c) 2007-2009
 * Timothy B. Terriberry, written by Timothy B. Terriberry and Jean-Marc Valin.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that redistributions of source code
 * retain the above copyright notice, this list of conditions and the following
 * disclaimer, and that redistributions in binary form reproduce them in the
 * documentation or other materials provided with the distribution. This
 * software is provided by the copyright holders and contributors as is, and any
 * express or implied warranties, including the implied warranties of
 * merchantability and fitness for a particular purpose, are disclaimed. In no
 * event shall the copyright owner or contributors be liable for any direct,
 * indirect, incidental, special, exemplary, or consequential damages however
 * caused and on any theory of liability arising in any way out of the use of
 * this software, even if advised of the possibility of such damage.
 */
class CeltBands
{
    /**
     * BAND_COUNT is how many bands the sound is cut into.
     */
    const BAND_COUNT = 21;
    /**
     * EDGES is where each band begins and ends, in slots of the shortest
     * stretch Opus works in. There is one more edge than there are bands, since
     * the last band needs an end.
     */
    const EDGES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28,
        34, 40, 48, 60, 78, 100];
    /**
     * EXPECTED is what each band is expected to sound like before anything is
     * read, as a loudness where each whole step is a fourfold change in power.
     */
    const EXPECTED = [6.4375, 6.25, 5.75, 5.3125, 5.0625, 4.8125, 4.5,
        4.375, 4.875, 4.6875, 4.5625, 4.4375, 4.875, 4.625, 4.3125, 4.5,
        4.375, 4.625, 4.75, 4.4375, 3.75];
    /**
     * CARRY_ACROSS is how much of the band before is carried into this one when
     * the stored differences follow on from the stretch before, one value per
     * length of stretch.
     */
    const CARRY_ACROSS = [29440 / 32768.0, 26112 / 32768.0,
        21248 / 32768.0, 16384 / 32768.0];
    /**
     * HOLD_BACK is how much of a difference is held back from the next band
     * when the differences follow on, one value per length of stretch.
     */
    const HOLD_BACK = [30147 / 32768.0, 22282 / 32768.0, 12124 / 32768.0,
        6554 / 32768.0];
    /**
     * HOLD_BACK_ALONE is how much is held back when the stretch stands on its
     * own rather than following on from the one before.
     */
    const HOLD_BACK_ALONE = 4915 / 32768.0;
    /**
     * FLOOR is the lowest loudness a band is allowed to carry into the next
     * stretch, so that a silent band does not drag the ones after it down
     * without limit.
     */
    const FLOOR = -9.0;
    /**
     * edgesFor where the bands sit for a stretch of a given length been doubled
     * to reach this one, from zero to three
     *
     * @param int $doublings how many times the shortest stretch has
     * @return array one more edge than there are bands, in slots
     */
    public static function edgesFor($doublings)
    {
        if ($doublings < 0 || $doublings > 3) {
            throw new \Exception("Opus has no stretch of that length");
        }
        $edges = [];
        foreach (self::EDGES as $edge) {
            $edges[] = $edge << $doublings;
        }
        return $edges;
    }
    /**
     * widthOf how many slots a given band has for a stretch of a given length
     * been doubled to reach this one
     *
     * @param int $band which band, counting from zero
     * @param int $doublings how many times the shortest stretch has
     * @return int how many slots the band has
     */
    public static function widthOf($band, $doublings)
    {
        if ($band < 0 || $band >= self::BAND_COUNT) {
            throw new \Exception("There is no such band");
        }
        return (self::EDGES[$band + 1] - self::EDGES[$band]) << $doublings;
    }
    /**
     * shapesFor how likely each stored loudness difference was, as a chance of
     * being zero and a rate of falling away, for each band. There is one set
     * for stretches that follow on from the one before and another for
     * stretches that stand on their own, since a stretch standing alone has
     * nothing to predict from and its differences are larger. been doubled to
     * reach this one
     *
     * @param int $doublings how many times the shortest stretch has
     * @param bool $alone whether the stretch stands on its own
     * @return array a chance and a rate for each band
     */
    public static function shapesFor($doublings, $alone)
    {
        static $shapes = null;
        if ($shapes === null) {
            $shapes = self::buildShapes();
        }
        return $shapes[$doublings][$alone ? 1 : 0];
    }
    /**
     * buildShapes lays out the stored likelihoods in a form the reader can use,
     * scaling each to the whole the reader works against whether the stretch
     * stands alone
     *
     * @return array the likelihoods, by length of stretch and by
     */
    public static function buildShapes()
    {
        $packed = [
            [[72, 127, 65, 129, 66, 128, 65, 128, 64, 128, 62, 128, 64, 128,
              64, 128, 92, 78, 92, 79, 92, 78, 90, 79, 116, 41, 115, 40,
              114, 40, 132, 26, 132, 26, 145, 17, 161, 12, 176, 10, 177, 11],
             [24, 179, 48, 138, 54, 135, 54, 132, 53, 134, 56, 133, 55, 132,
              55, 132, 61, 114, 70, 96, 74, 88, 75, 88, 87, 74, 89, 66,
              91, 67, 100, 59, 108, 50, 120, 40, 122, 37, 97, 43, 78, 50]],
            [[83, 78, 84, 81, 88, 75, 86, 74, 87, 71, 90, 73, 93, 74,
              93, 74, 109, 40, 114, 36, 117, 34, 117, 34, 143, 17, 145, 18,
              146, 19, 162, 12, 165, 10, 178, 7, 189, 6, 190, 8, 177, 9],
             [23, 178, 54, 115, 63, 102, 66, 98, 69, 99, 74, 89, 71, 91,
              73, 91, 78, 89, 86, 80, 92, 66, 93, 64, 102, 59, 103, 60,
              104, 60, 117, 52, 123, 44, 138, 35, 133, 31, 97, 38, 77, 45]],
            [[61, 90, 93, 60, 105, 42, 107, 41, 110, 45, 116, 38, 113, 38,
              112, 38, 124, 26, 132, 27, 136, 19, 140, 20, 155, 14, 159, 16,
              158, 18, 170, 13, 177, 10, 187, 8, 192, 6, 175, 9, 159, 10],
             [21, 178, 59, 110, 71, 86, 75, 85, 84, 83, 91, 66, 88, 73,
              87, 72, 92, 75, 98, 72, 105, 58, 107, 54, 115, 52, 114, 55,
              112, 56, 129, 51, 132, 40, 150, 33, 140, 29, 98, 35, 77, 42]],
            [[42, 121, 96, 66, 108, 43, 111, 40, 117, 44, 123, 32, 120, 36,
              119, 33, 127, 33, 134, 34, 139, 21, 147, 23, 152, 20, 158, 25,
              154, 26, 166, 21, 173, 16, 184, 13, 184, 10, 150, 13, 139, 15],
             [22, 178, 63, 114, 74, 82, 84, 83, 92, 82, 103, 62, 96, 72,
              96, 67, 101, 73, 107, 72, 113, 55, 118, 52, 125, 52, 118, 52,
              117, 55, 135, 49, 137, 39, 157, 32, 145, 29, 97, 33, 77, 40]]];
        $shapes = [];
        foreach ($packed as $doublings => $pair) {
            foreach ($pair as $alone => $flat) {
                $set = [];
                for ($band = 0; $band < self::BAND_COUNT; $band++) {
                    /* The stored pair is a chance of being zero and a
                       rate of falling away, each squeezed into a byte
                       and opened back out here. */
                    $set[] = [$flat[2 * $band] << 7,
                        $flat[2 * $band + 1] << 6];
                }
                $shapes[$doublings][$alone] = $set;
            }
        }
        return $shapes;
    }
}
X