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

/**
 * SpeechTables holds the tables of likelihoods that the speech way of
 * compressing sound uses when it writes the opening of a stretch: which
 * kind of sound the stretch holds, and how loud each quarter of it is.
 *
 * A range decoder needs, for every choice a stream makes, how likely each
 * answer was when the stream was written. Those likelihoods are fixed by
 * the standard rather than carried in the file, so they live here as
 * tables. Each table counts down from the whole rather than up, which is
 * the shape RangeDecoder::decodeFromTable reads, and beside each one is
 * how many bits its likelihoods add up to.
 *
 * @author Chris Pollett
 */
class SpeechTables
{
    /**
     * WHOLE_BITS is how many bits nearly every table here adds up to. A
     * table counting down from 256 says its answers were weighed against
     * one part in 256.
     */
    const WHOLE_BITS = 8;
    /**
     * QUIET_KINDS is how likely each kind of stretch was where the
     * stretch holds no speech. Two answers: the quieter writing of the
     * pulses, then the louder one.
     */
    const QUIET_KINDS = [230, 0];
    /**
     * SPOKEN_KINDS is how likely each kind of stretch was where the
     * stretch holds speech. Four answers, in pairs: speech made without
     * the voice, quieter then louder, and speech made with the voice,
     * quieter then louder.
     */
    const SPOKEN_KINDS = [232, 158, 10, 0];
    /**
     * LOUDNESS_STEPS is how likely each step of loudness was for the
     * first quarter of a stretch, one table for each kind of sound: no
     * speech, speech without the voice, and speech with the voice. A
     * step covers six decibels, and the eight steps together cover the
     * range a stretch may be written at.
     */
    const LOUDNESS_STEPS = [
        [224, 112, 44, 15, 3, 2, 1, 0],
        [254, 237, 192, 132, 70, 23, 4, 0],
        [255, 252, 226, 155, 61, 11, 2, 0]];
    /**
     * LOUDNESS_WITHIN_STEP is how likely each of the eight places within
     * one step of loudness was. The places are equally likely, so the
     * table counts down evenly.
     */
    const LOUDNESS_WITHIN_STEP = [224, 192, 160, 128, 96, 64, 32, 0];
    /**
     * LOUDNESS_CHANGE is how likely each change in loudness was from one
     * quarter of a stretch to the next. The middle of the table is no
     * change at all, and a change of many steps is rare, so the counts
     * fall away quickly on both sides.
     */
    const LOUDNESS_CHANGE = [250, 245, 234, 203, 71, 50, 42, 38, 35,
        33, 31, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16,
        15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
    /**
     * QUARTERS_IN_STRETCH is how many quarters a stretch of speech is
     * cut into. Each carries its own loudness, so a stretch can rise or
     * fall within itself.
     */
    const QUARTERS_IN_STRETCH = 4;
    /**
     * SPARE_COPY_CHANCES is how likely each set of stretches was to be
     * the ones a packet carries spare copies of, where the packet holds
     * two stretches. A packet holding one says so with its mark alone.
     */
    const SPARE_COPY_CHANCES = [203, 150, 0];
    /**
     * CHANGES_THAT_FALL is how many of the numbers in LOUDNESS_CHANGE
     * stand for a fall in loudness. The numbers below it are falls of
     * that many steps down to one, and the numbers from it upward are
     * rises.
     */
    const CHANGES_THAT_FALL = 4;
    /**
     * PLACES_IN_STEP is how many places a step of loudness is cut into
     * where a stretch writes its loudness outright.
     */
    const PLACES_IN_STEP = 8;
    /**
     * QUIET_KIND names a stretch that holds no speech.
     */
    const QUIET_KIND = "quiet";
    /**
     * VOICELESS_KIND names a stretch of speech made without the voice,
     * such as the sound at the start of the word sun.
     */
    const VOICELESS_KIND = "voiceless";
    /**
     * VOICED_KIND names a stretch of speech made with the voice, such as
     * the sound in the middle of the word sun.
     */
    const VOICED_KIND = "voiced";
    /**
     * QUIETER_PULSES names the quieter of the two ways the pulses of a
     * stretch are written.
     */
    const QUIETER_PULSES = "quieter";
    /**
     * LOUDER_PULSES names the louder of the two ways the pulses of a
     * stretch are written.
     */
    const LOUDER_PULSES = "louder";
    /**
     * LEAST_LOUDNESS_STEP is the lowest step of loudness a quarter may
     * be written at, and MOST_LOUDNESS_STEP is the highest. A change
     * read from LOUDNESS_CHANGE is held within these.
     */
    const LEAST_LOUDNESS_STEP = 0;
    /**
     * MOST_LOUDNESS_STEP is the highest step of loudness a quarter may
     * be written at. Six steps of loudness plus the eight places within
     * a step give the range the standard fixes.
     */
    const MOST_LOUDNESS_STEP = 63;
    /**
     * kindOfSound gives the two things the opening of a stretch says
     * about the sound it holds: whether it is speech made with the
     * voice, speech made without it, or no speech at all, and whether
     * its pulses were written the quieter way or the louder way.
     *
     * A caller reads the answer before anything else in a stretch, since
     * the loudness tables and the pulse tables both differ by kind.
     *
     * @param int $chosen the number the range decoder read
     * @param bool $has_speech whether the stretch was marked as holding
     *     speech
     * @return array the kind of sound and the way its pulses were
     *     written
     */
    public static function kindOfSound($chosen, $has_speech)
    {
        if (!$has_speech) {
            return [self::QUIET_KIND, ($chosen == 0) ?
                self::QUIETER_PULSES : self::LOUDER_PULSES];
        }
        $kind = ($chosen < 2) ? self::VOICELESS_KIND : self::VOICED_KIND;
        $pulses = ($chosen % 2 == 0) ? self::QUIETER_PULSES :
            self::LOUDER_PULSES;
        return [$kind, $pulses];
    }
    /**
     * loudnessTableFor gives the table of loudness steps that goes with
     * a kind of sound. The three kinds are written at different
     * loudnesses, so each has its own table.
     *
     * @param string $kind which kind of sound the stretch holds
     * @return array the likelihoods of each loudness step
     */
    public static function loudnessTableFor($kind)
    {
        if ($kind === self::QUIET_KIND) {
            return self::LOUDNESS_STEPS[0];
        }
        if ($kind === self::VOICELESS_KIND) {
            return self::LOUDNESS_STEPS[1];
        }
        return self::LOUDNESS_STEPS[2];
    }
}
X