<?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;
/**
* AacBands how AAC divides a stretch of sound into bands, and the fades it
* joins its stretches with. AAC works the same way Opus does at heart: cut the
* sound into overlapping stretches, turn each into tones, and spend more bits
* where the ear will notice. The differences are in the sizes. A long stretch
* holds 1024 tones rather than 960, the fade runs the whole length of a stretch
* rather than tapering only at the ends, and where the sound changes suddenly a
* stretch is replaced by eight short ones of 128 tones each rather than being
* split in place. Because the fade runs the whole length, a stretch cannot
* switch straight from long to short: two shapes are offered that are long at
* one end and short at the other, and one of those runs before a burst of short
* stretches and the other after. The band edges here are those the standard
* fixes for sound recorded at forty-eight thousand samples a second, which is
* what Opus always
* gives back.
*/
class AacBands
{
/**
* LONG_TONES is how many tones a long stretch holds.
*/
const LONG_TONES = 1024;
/**
* SHORT_TONES is how many tones each of the short stretches holds.
*/
const SHORT_TONES = 128;
/**
* SHORT_COUNT is how many short stretches replace one long one.
*/
const SHORT_COUNT = 8;
/**
* LONG marks a stretch of sound covered by one long window, which
* suits steady sound.
*/
const LONG = 0;
/**
* LONG_START is a stretch long at its start and short at its end, which
* runs before a burst of short ones.
*/
const LONG_START = 1;
/**
* SHORT marks a burst of eight short windows, which suits sound that
* changes quickly, such as the start of a word.
*/
const SHORT = 2;
/**
* LONG_STOP is a stretch short at its start and long at its end, which runs
* after such a burst.
*/
const LONG_STOP = 3;
/**
* LONG_EDGES is where each band of a long stretch begins and ends, in
* tones, for sound at forty-eight thousand samples a second. There is one
* more edge than
* there are bands.
*/
const LONG_EDGES = [0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56,
64, 72, 80, 88, 96, 108, 120, 132, 144, 160, 176, 196, 216, 240,
264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640,
672, 704, 736, 768, 800, 832, 864, 896, 928, 1024];
/**
* SHORT_EDGES is where each band of one short stretch of sound
* begins and ends, counted in tones from the lowest. A short
* stretch covers an eighth of the time a long one does, so it
* has its own, narrower bands.
*/
const SHORT_EDGES = [0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96,
112, 128];
/**
* longBandCount says how many frequency bands a long stretch of
* sound is divided into. A quantizer is chosen for each band, so
* the count decides how many the encoder reads and writes.
*
* @return int How many bands a long stretch holds.
*/
public static function longBandCount()
{
return count(self::LONG_EDGES) - 1;
}
/**
* fadeFor the fade a stretch of a given shape uses. A fade rises across the
* first half of a stretch and falls across the second, and neighboring
* stretches overlap by half their length. Where one end of a stretch meets
* a burst of short ones, that end takes the short fade's shape with silence
* beside it, so that the two still cancel. offered, which lets less of one
* stretch leak into the next
*
* @param int $shape which of the four shapes
* @param bool $smooth whether to use the softer of the two fades
* @return array the fade, one value per sample
*/
public static function fadeFor($shape, $smooth = false)
{
$long = self::LONG_TONES;
$short = self::SHORT_TONES;
$rise = self::halfFade($long, $smooth);
$short_rise = self::halfFade($short, $smooth);
$fade = array_fill(0, 2 * $long, 0.0);
if ($shape == self::SHORT) {
$whole = array_fill(0, 2 * $short, 0.0);
for ($i = 0; $i < $short; $i++) {
$whole[$i] = $short_rise[$i];
$whole[2 * $short - 1 - $i] = $short_rise[$i];
}
return $whole;
}
for ($i = 0; $i < $long; $i++) {
$fade[$i] = $rise[$i];
$fade[2 * $long - 1 - $i] = $rise[$i];
}
if ($shape == self::LONG_START) {
/* The far end is short, so it holds flat and then falls
over one short stretch's length. */
$gap = intdiv($long - $short, 2);
for ($i = $long; $i < $long + $gap; $i++) {
$fade[$i] = 1.0;
}
for ($i = 0; $i < $short; $i++) {
$fade[$long + $gap + $i] = $short_rise[$short - 1 - $i];
}
for ($i = $long + $gap + $short; $i < 2 * $long; $i++) {
$fade[$i] = 0.0;
}
}
if ($shape == self::LONG_STOP) {
$gap = intdiv($long - $short, 2);
for ($i = 0; $i < $gap; $i++) {
$fade[$i] = 0.0;
}
for ($i = 0; $i < $short; $i++) {
$fade[$gap + $i] = $short_rise[$i];
}
for ($i = $gap + $short; $i < $long; $i++) {
$fade[$i] = 1.0;
}
}
return $fade;
}
/**
* halfFade gives the rising half of the curve a frame is faded in
* with. The falling half is the same numbers backward, so only
* half is worked out
*
* @param int $tones how many tones the stretch holds
* @param bool $smooth whether to use the softer of the two offered
* @return array the rising half, one value per sample
*/
public static function halfFade($tones, $smooth)
{
$rise = [];
for ($i = 0; $i < $tones; $i++) {
if ($smooth) {
$rise[] = Mdct::shapedFade(2 * $tones, 6.0)[$i];
} else {
$rise[] = sin(M_PI / (2 * $tones) * ($i + 0.5));
}
}
return $rise;
}
/**
* toTones turns a stretch of sound into the tones AAC stores gives, already
* faded
*
* @param array $sound the stretch, twice as long as the tones it
* @param int $tones how many tones to give back
* @return array the tones
*/
public static function toTones($sound, $tones)
{
return Mdct::forSize($tones)->forward($sound);
}
}