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