/ src / library / av_processing / AacBands.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;

/**
 * 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);
    }
}
X