dosbox-staging/include/envelope.h
2023-07-10 08:40:26 +10:00

107 lines
4 KiB
C++

/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2020-2022 The DOSBox Staging Team
* Copyright (C) 2019-2021 kcgen <kcgen@users.noreply.github.com>
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef DOSBOX_ENVELOPE_H
#define DOSBOX_ENVELOPE_H
/* Audio Envelope
* --------------
* This class applies a step-wise earned-volume envelope with a fixed
* expiration period. The envelope is "earned" in the sense that the edge is
* expanded when a sample meets or exceeds it. This helps minimize the
* impact of unnatural waveforms that can whipsaw wildly, such as those
* generated from digital machine noise or binary data.
*
* Use
* ---
* 1. Call Update(..) to provide the Envelope with information about the audio
* stream: the frame rate (in Hz), peak possible sample amplitude (from zero
* to 2^16 -1), the expansion phase duration in milliseconds that represents
* the shortest possible time the envelope will be expanded from zero to
* peak volume if the samples "earn" it (reasonable values are <30ms), and
* the desired expiration period in seconds (reasonable values are <60s).
*
* 2. Call Process(..), passing it samples in their natural 16-bit signed
* form. Note: when the envelope is fully expanded or has expired, this
* function becomes the a null-call, eliminating further overhead.
* To be clear - there are no runtime checks you need to perform to
* determine if you should use the envelope or not. It simply goes
* dormant when done.
*
* 3. Call Reactivate() to perform another round of enveloping. Note that the
* characteristics about the envelope provided in the Update() call are
* retained and do not need to be provided after a reactivating.
*
* By default, the evenlope does nothing; it needs to be Update()'d for it to
* do work.
*/
#include "dosbox.h"
#include <cstdint>
#include <functional>
#include <string>
typedef struct AudioFrame AudioFrame_;
class Envelope {
public:
Envelope(const char* name);
void Process(const bool is_stereo, AudioFrame &frame);
void Update(const int frame_rate, const int peak_amplitude,
const uint8_t expansion_phase_ms,
const uint8_t expire_after_seconds);
void Reactivate();
private:
Envelope(const Envelope &) = delete; // prevent copying
Envelope &operator=(const Envelope &) = delete; // prevent assignment
bool ClampSample(float &sample, float next_edge);
void Apply(const bool is_stereo, AudioFrame &frame);
void Skip([[maybe_unused]] bool is_stereo, [[maybe_unused]] AudioFrame &frame)
{}
using process_f = std::function<void(Envelope &, const bool, AudioFrame &)>;
process_f process = &Envelope::Apply;
std::string channel_name = {};
int expire_after_frames = 0; // Stop enveloping when this many
// frames have been processed.
int frames_done = 0; // A tally of processed frames.
float edge = 0.0f; // The current edge of the envelope, which
// increments outward when samples press
// against it.
float edge_increment = 0.0f; // The amount the edge grows by once a
// sample is found to be beyond it.
float edge_limit = 0.0f; // Stop enveloping when the current edge is
// hits or exceeds this limit.
};
#endif