Files
edgetx-gx12/radio/src/pdm_wav_recorder.h
T

107 lines
4.1 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#pragma once
#include "edgetx.h"
#if defined(PDM_CLOCK)
#include <stdint.h>
#include "ff.h"
// 16 kHz: PDM_CLOCK_FREQ(1.6 MHz)/PDM_PCM_DECIMATION(100) = 16000 Hz directly.
// Must also divide AUDIO_SAMPLE_RATE(32 kHz) evenly for the WAV player.
class PdmWavRecorder
{
public:
static constexpr uint32_t DST_RATE = 16000;
// expectedSeconds == 0 means open-ended; header is patched on stop().
// At most one recorder active at a time; concurrent start() returns FR_LOCKED.
FRESULT start(const char* path, uint32_t expectedSeconds);
FRESULT stop();
// Called from the audio task every ~4 ms.
static void audioTick();
// The tap or ENT press that ends a take is audible at the end of it.
// finalise() locates that burst and cuts from its onset, rather than
// dropping a fixed slice that would eat speech on a prompt stop.
// Shortest take a trim is allowed to leave behind.
static constexpr uint32_t MIN_TAKE_MS = 200;
// What the press detector saw. Reported so the review screen can show it.
struct PressDiag {
uint32_t cutSamples; // removed from the end as a press, 0 if none found
int32_t voiceLevel; // level the burst had to beat
int32_t burstPeak; // peak of the last loud event
uint32_t burstMs; // how long that event ran
};
// Finish a take in one go, in two passes over the card: remove the stop
// press, high-pass, compensate CIC droop, normalise, fade the edges, patch
// the WAV header, and report the peak envelope (one 0..255 level per
// column), the final length and how much of the capture hit int16
// saturation (a high value means PDM_POST_GAIN_SHIFT is too hot).
// trimSilence additionally cuts leading and trailing silence; left to the
// Auto-trim button so the take is kept whole until asked otherwise.
static FRESULT finalise(const char* path, uint8_t* env, uint16_t cols,
bool trimSilence, uint32_t* totalSamples = nullptr,
uint32_t* clippedPermille = nullptr,
PressDiag* diag = nullptr);
// Cut the take down to [from, to] in place, fading the new edges and
// refreshing the envelope. Pure sample surgery — no filtering or
// normalisation, so repeated trims never stack DSP on the same audio.
static FRESULT cut(const char* path, uint32_t from, uint32_t to,
uint8_t* env, uint16_t cols,
uint32_t* totalSamples = nullptr);
// Silence bounds of the file as it stands, for the auto-trim button.
static FRESULT silenceBounds(const char* path, uint32_t* from, uint32_t* to);
// Peak of everything written since the previous call, 0..255 of full scale.
// Feeds the live waveform; a lost update just costs one display column.
uint8_t takePeakLevel();
bool isRecording() const { return recording; }
uint32_t getSamplesWritten() const { return samplesWritten; }
uint32_t getBytesWritten() const { return samplesWritten * 2U; }
uint32_t getElapsedSeconds() const { return samplesWritten / DST_RATE; }
private:
static constexpr uint32_t PCM_MAX = 256;
bool tickLocked();
FIL file;
int16_t pcm[PCM_MAX];
// 32-bit aligned reads on Cortex-M7 are atomic — safe to read without mutex.
volatile uint32_t samplesWritten = 0;
uint32_t maxSamples = 0; // 0 = open-ended
volatile bool recording = false;
volatile uint16_t peakSinceRead = 0;
};
#endif // PDM_CLOCK