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

/**
 * AacEncoder compresses a run of samples into AAC frames. Samples arrive a
 * stretch at a time from whatever decoded them, in lengths that have nothing to
 * do with the lengths AAC works in. So they are gathered here until there are
 * enough for a frame, a frame is made, and the gathered samples are dropped as
 * far as the next frame still needs. Nothing more than two frames' worth is
 * held, so a recording of any length costs the same. Each frame covers twice as
 * many samples as it advances by, because neighboring frames overlap and fade
 * into each other. The first and last frames have silence on the side where
 * there is no neighbor.
 */
class AacEncoder
{
    /**
     * HOP is how many samples a frame advances by.
     */
    const HOP = 1024;
    /**
     * SPAN is how many samples a frame covers, which is twice what it advances.
     */
    const SPAN = 2048;
    /**
     * SAMPLE_RATE is samples a second the sound arrives at.
     */
    const SAMPLE_RATE = 48000;
    /**
     * waiting stores samples gathered but not yet covered by a finished frame.
     * @var array
     */
    public $waiting;
    /**
     * frames stores the finished frames of compressed sound, each
     * covering the same fraction of a second, ready for the writer
     * to put into a file.
     * @var array
     */
    public $frames;
    /**
     * budget stores how many bits a frame may take.
     * @var int
     */
    public $budget;
    /**
     * fade stores the curve each frame is faded in and out with, worked
     * out once and used for every frame, since neighboring frames
     * overlap and their fades must add to one
     * @var array
     */
    public $fade;
    /**
     * plan stores works out the tones of a frame.
     * @var object
     */
    public $plan;
    /**
     * __construct sets up an encoder at a given rate
     *
     * @param int $rate how many bits a second to aim for
     */
    public function __construct($rate = 64000)
    {
        $this->budget = (int)($rate * self::HOP / self::SAMPLE_RATE);
        $this->fade = AacBands::fadeFor(AacBands::LONG);
        $this->plan = Mdct::forSize(self::HOP);
        /* A frame covers samples before the ones it advances past, so
           the run starts with silence for the first frame to fade in
           from. */
        $this->waiting = array_fill(0, self::HOP, 0.0);
        $this->frames = [];
    }
    /**
     * take takes another run of samples and makes whatever frames it completes
     *
     * @param array $samples the samples to take
     */
    public function take($samples)
    {
        foreach ($samples as $sample) {
            $this->waiting[] = $sample;
        }
        while (count($this->waiting) >= self::SPAN) {
            $this->makeFrame();
            $this->waiting = array_slice($this->waiting, self::HOP);
        }
    }
    /**
     * finish says there are no more samples, and makes a last frame to fade out
     * into silence
     */
    public function finish()
    {
        while (count($this->waiting) > self::HOP) {
            $this->waiting[] = 0.0;
            if (count($this->waiting) >= self::SPAN) {
                $this->makeFrame();
                $this->waiting = array_slice($this->waiting, self::HOP);
            }
        }
    }
    /**
     * makeFrame makes one frame from the samples now waiting
     */
    public function makeFrame()
    {
        $shaped = [];
        for ($i = 0; $i < self::SPAN; $i++) {
            $shaped[] = $this->waiting[$i] * $this->fade[$i];
        }
        $made = AacFrame::write($this->plan->forward($shaped),
            $this->budget);
        $this->frames[] = $made["bytes"];
    }
}
