<?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;
}
}