Use EBU R 128 based loudness measurement to implement AGC. (#1024)
* Switch AGC implementation to WebRTC. * Forgot to check in files. * Add PR #1024 to changelog. * WebRTC only supports a limited set of sample rates. * inMicLevel needs to be 0. * Forgot some calls to make AGC work. * Unconditionally enable AGC setting in Filter window. * Test to increase AGC level. * Adjust target level so that peaks are +/- 0.4. * Reduce target level a bit. * Revert "Reduce target level a bit." This reverts commitms-tcp-conn-freeze6282992544. * Add 4dB pad after AGC. * More tweaks. * First pass at AGC implementation using libebur128. * Try defining CMAKE_POLICY_VERSION_MINIMUM to get around GH build error. * Swap attack/release naming to match actual definitions. * Equalizer and speech in plotting should be based on pre-AGC audio. * Warning cleanup. * Fix CMake issues on ebur128 in GitHub environment. * Avoid overshooting target gain. * Remove CMake debugging. * current = target AGC check not 100% correct, so don't bother. * Set limiter level to -1 dB. * Switch voice keyer button label based on state. * Add 'Switch to' to beginning of Analog/Digital button. * User manual update. * Only update Voice Keyer button on state transitions. * Revert "Equalizer and speech in plotting should be based on pre-AGC audio." This reverts commit206a4d4fcb. * To Spkr/Hdphns -> Frm Decoder. * Add 'Modem' to Start/Stop button label. * all.wav is too long for rade_reporting_mpp, use shorter TX file to improve pass rate. * Increase silence threshold to -33 LUFS. * Oops, speech out plot should be before equalizer. * g_agcEnabled needs memory_order_release on modification. * Suppress monitor audio from 'From Decoder' plot during TX.
parent
276ffac3bd
commit
93a01c7e19
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade -y
|
||||
sudo apt-get install libpulse-dev sox git libasound2-dev libao-dev libgsm1-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libdbus-1-dev libgtk-3-dev python3.11-full python3.11-dev python3.11-venv
|
||||
sudo apt-get install libpulse-dev sox git libasound2-dev libao-dev libgsm1-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libdbus-1-dev libgtk-3-dev python3.11-full python3.11-dev python3.11-venv libebur128-dev
|
||||
|
||||
- name: Install Python required modules
|
||||
shell: bash
|
||||
|
|
@ -242,7 +242,7 @@ jobs:
|
|||
- name: Install common packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core
|
||||
sudo apt-get install libpulse-dev libspeexdsp-dev libsamplerate0-dev sox git portaudio19-dev libhamlib-dev libasound2-dev libao-dev libgsm1-dev libsndfile-dev xvfb pipewire pulseaudio-utils pipewire-pulse wireplumber metacity at-spi2-core libebur128-dev
|
||||
|
||||
- name: Install version-specific packages
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
- uses: gerlero/brew-install@v1
|
||||
with:
|
||||
packages: automake libtool numpy sox wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib
|
||||
packages: automake libtool numpy sox wxwidgets speexdsp portaudio libsndfile libsamplerate hamlib libebur128
|
||||
|
||||
- name: Install VB-Cable
|
||||
shell: bash
|
||||
|
|
|
|||
|
|
@ -354,6 +354,25 @@ include(cmake/BuildRADE.cmake)
|
|||
#
|
||||
include(cmake/BuildCodec2.cmake)
|
||||
|
||||
# Look for libebur128 (loudness measurement)
|
||||
if(NOT USE_STATIC_EBUR128)
|
||||
message(STATUS "Looking for ebur128...")
|
||||
find_library(LIBEBR128 ebur128)
|
||||
find_path(LIBEBUR128_INCLUDE_DIR ebur128.h)
|
||||
message(STATUS " ebur128 library: ${LIBEBUR128}")
|
||||
message(STATUS " ebur128 headers: ${LIBEBUR128_INCLUDE_DIR}")
|
||||
if(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
|
||||
list(APPEND FREEDV_LINK_LIBS ${LIBEBUR128})
|
||||
include_directories(${LIBEBUR128_INCLUDE_DIR})
|
||||
else(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
|
||||
message(STATUS "Will attempt static build of ebur128.")
|
||||
include(cmake/BuildEbur128.cmake)
|
||||
endif(LIBEBUR128 AND LIBEBUR128_INCLUDE_DIR)
|
||||
else(NOT USE_STATIC_LIBEBUR128)
|
||||
message(STATUS "Will attempt static build of ebur128.")
|
||||
include(cmake/BuildEbur128.cmake)
|
||||
endif(NOT USE_STATIC_EBUR128)
|
||||
|
||||
if (NOT USE_NATIVE_AUDIO AND LINUX)
|
||||
message(WARNING "Use of PortAudio on Linux has been deprecated and may be explicitly disallowed in a future release. It is highly recommended that you use FreeDV's built-in PulseAudio support instead as this generally gives better results, especially on distributions that now use pipewire for audio.")
|
||||
endif(NOT USE_NATIVE_AUDIO AND LINUX)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ This document describes how to build the FreeDV GUI program for various operatin
|
|||
```
|
||||
$ sudo apt install libspeexdsp-dev libsamplerate0-dev sox git \
|
||||
libwxgtk3.2-dev libhamlib-dev libasound2-dev libao-dev \
|
||||
libgsm1-dev libsndfile1-dev cmake module-assistant build-essential
|
||||
libgsm1-dev libsndfile1-dev cmake module-assistant build-essential \
|
||||
libebur128-dev
|
||||
$ git clone https://github.com/drowe67/freedv-gui.git
|
||||
$ cd freedv-gui
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ This document describes how to build the FreeDV GUI program for various operatin
|
|||
$ sudo dnf groupinstall "Development Tools"
|
||||
$ sudo dnf install cmake wxGTK3-devel libsamplerate-devel \
|
||||
libsndfile-devel speexdsp-devel hamlib-devel alsa-lib-devel libao-devel \
|
||||
gsm-devel gcc-c++ sox
|
||||
gsm-devel gcc-c++ sox libebur128-devel
|
||||
$ git clone https://github.com/drowe67/freedv-gui.git
|
||||
$ cd freedv-gui
|
||||
|
||||
|
|
|
|||
|
|
@ -734,6 +734,14 @@ button will let you hear the received signal from the SSB radio.
|
|||
Try the Test Wave Files above to get a feel for what a FreeDV signal
|
||||
looks and sounds like.
|
||||
|
||||
## Hearing myself whenever I transmit
|
||||
|
||||
Verify audio device settings in FreeDV. If your audio settings are correct,
|
||||
there are a few other possibilities:
|
||||
|
||||
1. If on Windows, make sure all audio effects are disabled on all devices used with FreeDV.
|
||||
2. Turn off TX monitoring (right-click on PTT button and make sure "Monitor transmitted audio" is unchecked).
|
||||
|
||||
## The signal is strong but FreeDV won't get sync and decode
|
||||
|
||||
Do you have the correct sideband? See USB or LSB section.
|
||||
|
|
@ -854,7 +862,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
|
|||
* Add Mic/Speaker volume control to main window. (PR #980, #1028)
|
||||
* Move less used Spectrum plot configuration to free up space on main window. (PR #996)
|
||||
* Further audio performance improvements. (PR #975)
|
||||
* Add Automatic Gain Control (AGC) to microphone input. (PR #997)
|
||||
* Add Automatic Gain Control (AGC) to microphone input. (PR #997, #1024)
|
||||
* Linux: Search for and list serial devices from /dev/serial for PTT config. (PR #999)
|
||||
* Add RADEV1 sample file and remove samples for unsupported modes. (PR #998)
|
||||
* Various GUI and FreeDV Reporter performance improvements. (PR #1002, #1026)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
set(EBUR128_VERSION "1.2.6")
|
||||
|
||||
# Static libraries on some platforms need PIC
|
||||
set(EBUR128_CMAKE_ARGS -DWITH_STATIC_PIC=1 -DBUILD_SHARED_LIBS=0)
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(EBUR128_CMAKE_ARGS ${EBUR128_CMAKE_ARGS} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
|
||||
endif()
|
||||
|
||||
# Build ebur128 library
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(build_ebur128
|
||||
SOURCE_DIR ebur128_src
|
||||
BINARY_DIR ebur128_build
|
||||
GIT_REPOSITORY https://github.com/jiixyj/libebur128.git
|
||||
GIT_TAG "v${EBUR128_VERSION}"
|
||||
CMAKE_ARGS ${EBUR128_CMAKE_ARGS}
|
||||
CMAKE_CACHE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${CMAKE_OSX_DEPLOYMENT_TARGET} -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}
|
||||
PATCH_COMMAND git apply ${CMAKE_SOURCE_DIR}/cmake/Ebur128_CMake.patch
|
||||
INSTALL_COMMAND ""
|
||||
UPDATE_DISCONNECTED 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(build_ebur128 BINARY_DIR)
|
||||
ExternalProject_Get_Property(build_ebur128 SOURCE_DIR)
|
||||
add_library(ebur128 STATIC IMPORTED)
|
||||
add_dependencies(ebur128 build_ebur128)
|
||||
|
||||
set_target_properties(ebur128 PROPERTIES
|
||||
IMPORTED_LOCATION "${BINARY_DIR}/libebur128${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
||||
IMPORTED_IMPLIB "${BINARY_DIR}/libebur128${CMAKE_IMPORT_LIBRARY_SUFFIX}"
|
||||
)
|
||||
|
||||
set(EBUR128_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/ebur128_src/ebur128 ${CMAKE_BINARY_DIR}/ebur128_build)
|
||||
list(APPEND FREEDV_LINK_LIBS ebur128)
|
||||
include_directories(${EBUR128_INCLUDE_DIRS})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index ff8baea..3140d57 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -1,4 +1,4 @@
|
||||
-cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
|
||||
+cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
|
||||
project(libebur128 C)
|
||||
|
||||
option(BUILD_SHARED_LIBS
|
||||
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
|
||||
index 6ffbf17..8eed09d 100644
|
||||
--- a/test/CMakeLists.txt
|
||||
+++ b/test/CMakeLists.txt
|
||||
@@ -1,4 +1,4 @@
|
||||
-cmake_minimum_required(VERSION 2.8.12)
|
||||
+cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(ENABLE_TESTS OFF CACHE BOOL "Build test binaries, needs libsndfile")
|
||||
set(ENABLE_FUZZER OFF CACHE BOOL "Build fuzzer binary")
|
||||
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(WebRTC_AGC)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
add_library(agc STATIC agc.c)
|
||||
target_include_directories(agc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
============================
|
||||
Copyright (c) 2011, The WebRTC project authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name of Google nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# WebRTC_AGC
|
||||
Automatic Gain Control Module Port From WebRTC
|
||||
|
||||
# Donating
|
||||
|
||||
If you found this project useful, consider buying me a coffee
|
||||
|
||||
<a href="https://www.buymeacoffee.com/gaozhihan" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/black_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_
|
||||
#define MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_
|
||||
|
||||
//#define MIC_LEVEL_FEEDBACK
|
||||
#ifdef WEBRTC_AGC_DEBUG_DUMP
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h> // NOLINT(build/include)
|
||||
#include <string.h>
|
||||
|
||||
#ifdef WEBRTC_AGC_DEBUG_DUMP
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h> // NOLINT(build/include)
|
||||
#include <assert.h>
|
||||
|
||||
// allpass filter coefficients.
|
||||
static const uint16_t kResampleAllpass1[3] = {3284, 24441, 49528};
|
||||
static const uint16_t kResampleAllpass2[3] = {12199, 37471, 60255};
|
||||
|
||||
|
||||
typedef struct {
|
||||
int32_t downState[8];
|
||||
int16_t HPstate;
|
||||
int16_t counter;
|
||||
int16_t logRatio; // log( P(active) / P(inactive) ) (Q10)
|
||||
int16_t meanLongTerm; // Q10
|
||||
int32_t varianceLongTerm; // Q8
|
||||
int16_t stdLongTerm; // Q10
|
||||
int16_t meanShortTerm; // Q10
|
||||
int32_t varianceShortTerm; // Q8
|
||||
int16_t stdShortTerm; // Q10
|
||||
} AgcVad; // total = 54 bytes
|
||||
|
||||
typedef struct {
|
||||
int32_t capacitorSlow;
|
||||
int32_t capacitorFast;
|
||||
int32_t gain;
|
||||
int32_t gainTable[32];
|
||||
int16_t gatePrevious;
|
||||
int16_t agcMode;
|
||||
AgcVad vadNearend;
|
||||
AgcVad vadFarend;
|
||||
#ifdef WEBRTC_AGC_DEBUG_DUMP
|
||||
FILE* logFile;
|
||||
int frameCounter;
|
||||
#endif
|
||||
} DigitalAgc;
|
||||
|
||||
int32_t WebRtcAgc_InitDigital(DigitalAgc *digitalAgcInst, int16_t agcMode);
|
||||
|
||||
int32_t WebRtcAgc_ProcessDigital(DigitalAgc *digitalAgcInst,
|
||||
const int16_t *const *inNear,
|
||||
size_t num_bands,
|
||||
int16_t *const *out,
|
||||
uint32_t FS,
|
||||
int16_t lowLevelSignal);
|
||||
|
||||
int32_t WebRtcAgc_AddFarendToDigital(DigitalAgc *digitalAgcInst,
|
||||
const int16_t *inFar,
|
||||
size_t nrSamples);
|
||||
|
||||
void WebRtcAgc_InitVad(AgcVad *vadInst);
|
||||
|
||||
int16_t WebRtcAgc_ProcessVad(AgcVad *vadInst, // (i) VAD state
|
||||
const int16_t *in, // (i) Speech signal
|
||||
size_t nrSamples); // (i) number of samples
|
||||
|
||||
int32_t WebRtcAgc_CalculateGainTable(int32_t *gainTable, // Q16
|
||||
int16_t compressionGaindB, // Q0 (in dB)
|
||||
int16_t targetLevelDbfs, // Q0 (in dB)
|
||||
uint8_t limiterEnable,
|
||||
int16_t analogTarget);
|
||||
|
||||
// Errors
|
||||
#define AGC_UNSPECIFIED_ERROR 18000
|
||||
#define AGC_UNSUPPORTED_FUNCTION_ERROR 18001
|
||||
#define AGC_UNINITIALIZED_ERROR 18002
|
||||
#define AGC_NULL_POINTER_ERROR 18003
|
||||
#define AGC_BAD_PARAMETER_ERROR 18004
|
||||
|
||||
// Warnings
|
||||
#define AGC_BAD_PARAMETER_WARNING 18050
|
||||
|
||||
enum {
|
||||
kAgcModeUnchanged,
|
||||
kAgcModeAdaptiveAnalog,
|
||||
kAgcModeAdaptiveDigital,
|
||||
kAgcModeFixedDigital
|
||||
};
|
||||
|
||||
enum {
|
||||
kAgcFalse = 0, kAgcTrue
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int16_t targetLevelDbfs; // default 3 (-3 dBOv)
|
||||
int16_t compressionGaindB; // default 9 dB
|
||||
uint8_t limiterEnable; // default kAgcTrue (on)
|
||||
} WebRtcAgcConfig;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This function analyses the number of samples passed to
|
||||
* farend and produces any error code that could arise.
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
* - samples : Number of samples in input vector.
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error.
|
||||
*/
|
||||
int WebRtcAgc_GetAddFarendError(void *state, size_t samples);
|
||||
|
||||
/*
|
||||
* This function processes a 10 ms frame of far-end speech to determine
|
||||
* if there is active speech. The length of the input speech vector must be
|
||||
* given in samples (80 when FS=8000, and 160 when FS=16000, FS=32000 or
|
||||
* FS=48000).
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
* - inFar : Far-end input speech vector
|
||||
* - samples : Number of samples in input vector
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_AddFarend(void *agcInst, const int16_t *inFar, size_t samples);
|
||||
|
||||
/*
|
||||
* This function processes a 10 ms frame of microphone speech to determine
|
||||
* if there is active speech. The length of the input speech vector must be
|
||||
* given in samples (80 when FS=8000, and 160 when FS=16000, FS=32000 or
|
||||
* FS=48000). For very low input levels, the input signal is increased in level
|
||||
* by multiplying and overwriting the samples in inMic[].
|
||||
*
|
||||
* This function should be called before any further processing of the
|
||||
* near-end microphone signal.
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
* - inMic : Microphone input speech vector for each band
|
||||
* - num_bands : Number of bands in input vector
|
||||
* - samples : Number of samples in input vector
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_AddMic(void *agcInst,
|
||||
int16_t *const *inMic,
|
||||
size_t num_bands,
|
||||
size_t samples);
|
||||
|
||||
/*
|
||||
* This function replaces the analog microphone with a virtual one.
|
||||
* It is a digital gain applied to the input signal and is used in the
|
||||
* agcAdaptiveDigital mode where no microphone level is adjustable. The length
|
||||
* of the input speech vector must be given in samples (80 when FS=8000, and 160
|
||||
* when FS=16000, FS=32000 or FS=48000).
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
* - inMic : Microphone input speech vector for each band
|
||||
* - num_bands : Number of bands in input vector
|
||||
* - samples : Number of samples in input vector
|
||||
* - micLevelIn : Input level of microphone (static)
|
||||
*
|
||||
* Output:
|
||||
* - inMic : Microphone output after processing (L band)
|
||||
* - inMic_H : Microphone output after processing (H band)
|
||||
* - micLevelOut : Adjusted microphone level after processing
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_VirtualMic(void *agcInst,
|
||||
int16_t *const *inMic,
|
||||
size_t num_bands,
|
||||
size_t samples,
|
||||
int32_t micLevelIn,
|
||||
int32_t *micLevelOut);
|
||||
|
||||
/*
|
||||
* This function processes a 10 ms frame and adjusts (normalizes) the gain both
|
||||
* analog and digitally. The gain adjustments are done only during active
|
||||
* periods of speech. The length of the speech vectors must be given in samples
|
||||
* (80 when FS=8000, and 160 when FS=16000, FS=32000 or FS=48000). The echo
|
||||
* parameter can be used to ensure the AGC will not adjust upward in the
|
||||
* presence of echo.
|
||||
*
|
||||
* This function should be called after processing the near-end microphone
|
||||
* signal, in any case after any echo cancellation.
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance
|
||||
* - inNear : Near-end input speech vector for each band
|
||||
* - num_bands : Number of bands in input/output vector
|
||||
* - samples : Number of samples in input/output vector
|
||||
* - inMicLevel : Current microphone volume level
|
||||
* - echo : Set to 0 if the signal passed to add_mic is
|
||||
* almost certainly free of echo; otherwise set
|
||||
* to 1. If you have no information regarding echo
|
||||
* set to 0.
|
||||
*
|
||||
* Output:
|
||||
* - outMicLevel : Adjusted microphone volume level
|
||||
* - out : Gain-adjusted near-end speech vector
|
||||
* : May be the same vector as the input.
|
||||
* - saturationWarning : A returned value of 1 indicates a saturation event
|
||||
* has occurred and the volume cannot be further
|
||||
* reduced. Otherwise will be set to 0.
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_Process(void *agcInst,
|
||||
const int16_t *const *inNear,
|
||||
size_t num_bands,
|
||||
size_t samples,
|
||||
int16_t *const *out,
|
||||
int32_t inMicLevel,
|
||||
int32_t *outMicLevel,
|
||||
int16_t echo,
|
||||
uint8_t *saturationWarning);
|
||||
|
||||
/*
|
||||
* This function sets the config parameters (targetLevelDbfs,
|
||||
* compressionGaindB and limiterEnable).
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance
|
||||
* - config : config struct
|
||||
*
|
||||
* Output:
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_set_config(void *agcInst, WebRtcAgcConfig config);
|
||||
|
||||
/*
|
||||
* This function returns the config parameters (targetLevelDbfs,
|
||||
* compressionGaindB and limiterEnable).
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance
|
||||
*
|
||||
* Output:
|
||||
* - config : config struct
|
||||
*
|
||||
* Return value:
|
||||
* : 0 - Normal operation.
|
||||
* : -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_get_config(void *agcInst, WebRtcAgcConfig *config);
|
||||
|
||||
/*
|
||||
* This function creates and returns an AGC instance, which will contain the
|
||||
* state information for one (duplex) channel.
|
||||
*/
|
||||
void *WebRtcAgc_Create(void);
|
||||
|
||||
/*
|
||||
* This function frees the AGC instance created at the beginning.
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
*/
|
||||
void WebRtcAgc_Free(void *agcInst);
|
||||
|
||||
/*
|
||||
* This function initializes an AGC instance.
|
||||
*
|
||||
* Input:
|
||||
* - agcInst : AGC instance.
|
||||
* - minLevel : Minimum possible mic level
|
||||
* - maxLevel : Maximum possible mic level
|
||||
* - agcMode : 0 - Unchanged
|
||||
* : 1 - Adaptive Analog Automatic Gain Control -3dBOv
|
||||
* : 2 - Adaptive Digital Automatic Gain Control -3dBOv
|
||||
* : 3 - Fixed Digital Gain 0dB
|
||||
* - fs : Sampling frequency
|
||||
*
|
||||
* Return value : 0 - Ok
|
||||
* -1 - Error
|
||||
*/
|
||||
int WebRtcAgc_Init(void *agcInst,
|
||||
int32_t minLevel,
|
||||
int32_t maxLevel,
|
||||
int16_t agcMode,
|
||||
uint32_t fs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Analog Automatic Gain Control variables:
|
||||
* Constant declarations (inner limits inside which no changes are done)
|
||||
* In the beginning the range is narrower to widen as soon as the measure
|
||||
* 'Rxx160_LP' is inside it. Currently the starting limits are -22.2+/-1dBm0
|
||||
* and the final limits -22.2+/-2.5dBm0. These levels makes the speech signal
|
||||
* go towards -25.4dBm0 (-31.4dBov). Tuned with wbfile-31.4dBov.pcm
|
||||
* The limits are created by running the AGC with a file having the desired
|
||||
* signal level and thereafter plotting Rxx160_LP in the dBm0-domain defined
|
||||
* by out=10*log10(in/260537279.7); Set the target level to the average level
|
||||
* of our measure Rxx160_LP. Remember that the levels are in blocks of 16 in
|
||||
* Q(-7). (Example matlab code: round(db2pow(-21.2)*16/2^7) )
|
||||
*/
|
||||
#define RXX_BUFFER_LEN 10
|
||||
|
||||
static const int16_t kMsecSpeechInner = 520;
|
||||
static const int16_t kMsecSpeechOuter = 340;
|
||||
|
||||
static const int16_t kNormalVadThreshold = 400;
|
||||
|
||||
static const int16_t kAlphaShortTerm = 6; // 1 >> 6 = 0.0156
|
||||
static const int16_t kAlphaLongTerm = 10; // 1 >> 10 = 0.000977
|
||||
|
||||
typedef struct {
|
||||
// Configurable parameters/variables
|
||||
uint32_t fs; // Sampling frequency
|
||||
int16_t compressionGaindB; // Fixed gain level in dB
|
||||
int16_t targetLevelDbfs; // Target level in -dBfs of envelope (default -3)
|
||||
int16_t agcMode; // Hard coded mode (adaptAna/adaptDig/fixedDig)
|
||||
uint8_t limiterEnable; // Enabling limiter (on/off (default off))
|
||||
WebRtcAgcConfig defaultConfig;
|
||||
WebRtcAgcConfig usedConfig;
|
||||
|
||||
// General variables
|
||||
int16_t initFlag;
|
||||
int16_t lastError;
|
||||
|
||||
// Target level parameters
|
||||
// Based on the above: analogTargetLevel = round((32767*10^(-22/20))^2*16/2^7)
|
||||
int32_t analogTargetLevel; // = RXX_BUFFER_LEN * 846805; -22 dBfs
|
||||
int32_t startUpperLimit; // = RXX_BUFFER_LEN * 1066064; -21 dBfs
|
||||
int32_t startLowerLimit; // = RXX_BUFFER_LEN * 672641; -23 dBfs
|
||||
int32_t upperPrimaryLimit; // = RXX_BUFFER_LEN * 1342095; -20 dBfs
|
||||
int32_t lowerPrimaryLimit; // = RXX_BUFFER_LEN * 534298; -24 dBfs
|
||||
int32_t upperSecondaryLimit; // = RXX_BUFFER_LEN * 2677832; -17 dBfs
|
||||
int32_t lowerSecondaryLimit; // = RXX_BUFFER_LEN * 267783; -27 dBfs
|
||||
uint16_t targetIdx; // Table index for corresponding target level
|
||||
#ifdef MIC_LEVEL_FEEDBACK
|
||||
uint16_t targetIdxOffset; // Table index offset for level compensation
|
||||
#endif
|
||||
int16_t analogTarget; // Digital reference level in ENV scale
|
||||
|
||||
// Analog AGC specific variables
|
||||
int32_t filterState[8]; // For downsampling wb to nb
|
||||
int32_t upperLimit; // Upper limit for mic energy
|
||||
int32_t lowerLimit; // Lower limit for mic energy
|
||||
int32_t Rxx160w32; // Average energy for one frame
|
||||
int32_t Rxx16_LPw32; // Low pass filtered subframe energies
|
||||
int32_t Rxx160_LPw32; // Low pass filtered frame energies
|
||||
int32_t Rxx16_LPw32Max; // Keeps track of largest energy subframe
|
||||
int32_t Rxx16_vectorw32[RXX_BUFFER_LEN]; // Array with subframe energies
|
||||
int32_t Rxx16w32_array[2][5]; // Energy values of microphone signal
|
||||
int32_t env[2][10]; // Envelope values of subframes
|
||||
|
||||
int16_t Rxx16pos; // Current position in the Rxx16_vectorw32
|
||||
int16_t envSum; // Filtered scaled envelope in subframes
|
||||
int16_t vadThreshold; // Threshold for VAD decision
|
||||
int16_t inActive; // Inactive time in milliseconds
|
||||
int16_t msTooLow; // Milliseconds of speech at a too low level
|
||||
int16_t msTooHigh; // Milliseconds of speech at a too high level
|
||||
int16_t changeToSlowMode; // Change to slow mode after some time at target
|
||||
int16_t firstCall; // First call to the process-function
|
||||
int16_t msZero; // Milliseconds of zero input
|
||||
int16_t msecSpeechOuterChange; // Min ms of speech between volume changes
|
||||
int16_t msecSpeechInnerChange; // Min ms of speech between volume changes
|
||||
int16_t activeSpeech; // Milliseconds of active speech
|
||||
int16_t muteGuardMs; // Counter to prevent mute action
|
||||
int16_t inQueue; // 10 ms batch indicator
|
||||
|
||||
// Microphone level variables
|
||||
int32_t micRef; // Remember ref. mic level for virtual mic
|
||||
uint16_t gainTableIdx; // Current position in virtual gain table
|
||||
int32_t micGainIdx; // Gain index of mic level to increase slowly
|
||||
int32_t micVol; // Remember volume between frames
|
||||
int32_t maxLevel; // Max possible vol level, incl dig gain
|
||||
int32_t maxAnalog; // Maximum possible analog volume level
|
||||
int32_t maxInit; // Initial value of "max"
|
||||
int32_t minLevel; // Minimum possible volume level
|
||||
int32_t minOutput; // Minimum output volume level
|
||||
int32_t zeroCtrlMax; // Remember max gain => don't amp low input
|
||||
int32_t lastInMicLevel;
|
||||
|
||||
int16_t scale; // Scale factor for internal volume levels
|
||||
#ifdef MIC_LEVEL_FEEDBACK
|
||||
int16_t numBlocksMicLvlSat;
|
||||
uint8_t micLvlSat;
|
||||
#endif
|
||||
// Structs for VAD and digital_agc
|
||||
AgcVad vadMic;
|
||||
DigitalAgc digitalAgc;
|
||||
|
||||
#ifdef WEBRTC_AGC_DEBUG_DUMP
|
||||
FILE* fpt;
|
||||
FILE* agcLog;
|
||||
int32_t fcount;
|
||||
#endif
|
||||
|
||||
int16_t lowLevelSignal;
|
||||
} LegacyAgc;
|
||||
|
||||
#endif // MODULES_AUDIO_PROCESSING_AGC_LEGACY_ANALOG_AGC_H_
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,268 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
//采用https://github.com/mackron/dr_libs/blob/master/dr_wav.h 解码
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
|
||||
#include "dr_wav.h"
|
||||
#include "agc.h"
|
||||
|
||||
#ifndef nullptr
|
||||
#define nullptr 0
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
||||
#endif
|
||||
|
||||
//计时
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
# include <mach/mach_time.h>
|
||||
#elif defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
|
||||
# include <windows.h>
|
||||
|
||||
#else // __linux
|
||||
# include <time.h>
|
||||
# ifndef CLOCK_MONOTONIC //_RAW
|
||||
# define CLOCK_MONOTONIC CLOCK_REALTIME
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static
|
||||
uint64_t nanotimer() {
|
||||
static int ever = 0;
|
||||
#if defined(__APPLE__)
|
||||
static mach_timebase_info_data_t frequency;
|
||||
if (!ever) {
|
||||
if (mach_timebase_info(&frequency) != KERN_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
ever = 1;
|
||||
}
|
||||
return 0;
|
||||
#elif defined(_WIN32)
|
||||
static LARGE_INTEGER frequency;
|
||||
if (!ever) {
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
ever = 1;
|
||||
}
|
||||
LARGE_INTEGER t;
|
||||
QueryPerformanceCounter(&t);
|
||||
return (t.QuadPart * (uint64_t) 1e9) / frequency.QuadPart;
|
||||
#else // __linux
|
||||
struct timespec t;
|
||||
if (!ever) {
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &t) != 0) {
|
||||
return 0;
|
||||
}
|
||||
ever = 1;
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (t.tv_sec * (uint64_t)1e9) + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static double now() {
|
||||
static uint64_t epoch = 0;
|
||||
if (!epoch) {
|
||||
epoch = nanotimer();
|
||||
}
|
||||
return (nanotimer() - epoch) / 1e9;
|
||||
};
|
||||
|
||||
double calcElapsed(double start, double end) {
|
||||
double took = -start;
|
||||
return took + end;
|
||||
}
|
||||
|
||||
//写wav文件
|
||||
void wavWrite_int16(char *filename, int16_t *buffer, size_t sampleRate, size_t totalSampleCount, unsigned int channels) {
|
||||
drwav_data_format format = {};
|
||||
format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64.
|
||||
format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes.
|
||||
format.channels = channels;
|
||||
format.sampleRate = (drwav_uint32) sampleRate;
|
||||
format.bitsPerSample = 16;
|
||||
drwav *pWav = drwav_open_file_write(filename, &format);
|
||||
if (pWav) {
|
||||
drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer);
|
||||
drwav_uninit(pWav);
|
||||
if (samplesWritten != totalSampleCount) {
|
||||
fprintf(stderr, "ERROR\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//读取wav文件
|
||||
int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount, unsigned int* channels) {
|
||||
int16_t *buffer = drwav_open_and_read_file_s16(filename, channels, sampleRate, totalSampleCount);
|
||||
if (buffer == nullptr) {
|
||||
printf("读取wav文件失败.");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//分割路径函数
|
||||
void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {
|
||||
const char *end;
|
||||
const char *p;
|
||||
const char *s;
|
||||
if (path[0] && path[1] == ':') {
|
||||
if (drv) {
|
||||
*drv++ = *path++;
|
||||
*drv++ = *path++;
|
||||
*drv = '\0';
|
||||
}
|
||||
} else if (drv)
|
||||
*drv = '\0';
|
||||
for (end = path; *end && *end != ':';)
|
||||
end++;
|
||||
for (p = end; p > path && *--p != '\\' && *p != '/';)
|
||||
if (*p == '.') {
|
||||
end = p;
|
||||
break;
|
||||
}
|
||||
if (ext)
|
||||
for (s = end; (*ext = *s++);)
|
||||
ext++;
|
||||
for (p = end; p > path;)
|
||||
if (*--p == '\\' || *p == '/') {
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
if (name) {
|
||||
for (s = p; s < end;)
|
||||
*name++ = *s++;
|
||||
*name = '\0';
|
||||
}
|
||||
if (dir) {
|
||||
for (s = path; s < p;)
|
||||
*dir++ = *s++;
|
||||
*dir = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int agcProcess(int16_t *buffer, uint32_t sampleRate, size_t samplesCount, int16_t agcMode) {
|
||||
if (buffer == nullptr) return -1;
|
||||
if (samplesCount == 0) return -1;
|
||||
WebRtcAgcConfig agcConfig;
|
||||
agcConfig.compressionGaindB = 9; // default 9 dB
|
||||
agcConfig.limiterEnable = 1; // default kAgcTrue (on)
|
||||
agcConfig.targetLevelDbfs = 3; // default 3 (-3 dBOv)
|
||||
int minLevel = 0;
|
||||
int maxLevel = 255;
|
||||
size_t samples = MIN(160, sampleRate / 100);
|
||||
if (samples == 0) return -1;
|
||||
const int maxSamples = 320;
|
||||
int16_t *input = buffer;
|
||||
size_t nTotal = (samplesCount / samples);
|
||||
void *agcInst = WebRtcAgc_Create();
|
||||
if (agcInst == NULL) return -1;
|
||||
int status = WebRtcAgc_Init(agcInst, minLevel, maxLevel, agcMode, sampleRate);
|
||||
if (status != 0) {
|
||||
printf("WebRtcAgc_Init fail\n");
|
||||
WebRtcAgc_Free(agcInst);
|
||||
return -1;
|
||||
}
|
||||
status = WebRtcAgc_set_config(agcInst, agcConfig);
|
||||
if (status != 0) {
|
||||
printf("WebRtcAgc_set_config fail\n");
|
||||
WebRtcAgc_Free(agcInst);
|
||||
return -1;
|
||||
}
|
||||
size_t num_bands = 1;
|
||||
int inMicLevel, outMicLevel = -1;
|
||||
int16_t out_buffer[maxSamples];
|
||||
int16_t *out16 = out_buffer;
|
||||
uint8_t saturationWarning = 1; //是否有溢出发生,增益放大以后的最大值超过了65536
|
||||
int16_t echo = 0; //增益放大是否考虑回声影响
|
||||
for (int i = 0; i < nTotal; i++) {
|
||||
inMicLevel = 0;
|
||||
int nAgcRet = WebRtcAgc_Process(agcInst, (const int16_t *const *) &input, num_bands, samples,
|
||||
(int16_t *const *) &out16, inMicLevel, &outMicLevel, echo,
|
||||
&saturationWarning);
|
||||
|
||||
if (nAgcRet != 0) {
|
||||
printf("failed in WebRtcAgc_Process\n");
|
||||
WebRtcAgc_Free(agcInst);
|
||||
return -1;
|
||||
}
|
||||
memcpy(input, out_buffer, samples * sizeof(int16_t));
|
||||
input += samples;
|
||||
}
|
||||
|
||||
const size_t remainedSamples = samplesCount - nTotal * samples;
|
||||
if (remainedSamples > 0) {
|
||||
if (nTotal > 0) {
|
||||
input = input - samples + remainedSamples;
|
||||
}
|
||||
|
||||
inMicLevel = 0;
|
||||
int nAgcRet = WebRtcAgc_Process(agcInst, (const int16_t *const *) &input, num_bands, samples,
|
||||
(int16_t *const *) &out16, inMicLevel, &outMicLevel, echo,
|
||||
&saturationWarning);
|
||||
|
||||
if (nAgcRet != 0) {
|
||||
printf("failed in WebRtcAgc_Process during filtering the last chunk\n");
|
||||
WebRtcAgc_Free(agcInst);
|
||||
return -1;
|
||||
}
|
||||
memcpy(&input[samples-remainedSamples], &out_buffer[samples-remainedSamples], remainedSamples * sizeof(int16_t));
|
||||
input += samples;
|
||||
}
|
||||
|
||||
WebRtcAgc_Free(agcInst);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void auto_gain(char *in_file, char *out_file) {
|
||||
//音频采样率
|
||||
uint32_t sampleRate = 0;
|
||||
//总音频采样数
|
||||
uint64_t inSampleCount = 0;
|
||||
unsigned int channels = 0;
|
||||
int16_t *inBuffer = wavRead_int16(in_file, &sampleRate, &inSampleCount, &channels);
|
||||
//如果加载成功
|
||||
if (inBuffer != nullptr) {
|
||||
// kAgcModeAdaptiveAnalog 模拟音量调节
|
||||
// kAgcModeAdaptiveDigital 自适应增益
|
||||
// kAgcModeFixedDigital 固定增益
|
||||
double startTime = now();
|
||||
|
||||
agcProcess(inBuffer, sampleRate, inSampleCount, kAgcModeAdaptiveDigital);
|
||||
|
||||
double elapsed_time = calcElapsed(startTime, now());
|
||||
|
||||
printf("time: %d ms\n ", (int) (elapsed_time * 1000));
|
||||
wavWrite_int16(out_file, inBuffer, sampleRate, inSampleCount, channels);
|
||||
free(inBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("WebRTC Automatic Gain Control\n");
|
||||
printf("博客:http://cpuimage.cnblogs.com/\n");
|
||||
printf("音频自动增益\n");
|
||||
if (argc < 2)
|
||||
return -1;
|
||||
char *in_file = argv[1];
|
||||
char drive[3];
|
||||
char dir[256];
|
||||
char fname[256];
|
||||
char ext[256];
|
||||
char out_file[1024];
|
||||
splitpath(in_file, drive, dir, fname, ext);
|
||||
sprintf(out_file, "%s%s%s_out%s", drive, dir, fname, ext);
|
||||
auto_gain(in_file, out_file);
|
||||
|
||||
printf("按任意键退出程序 \n");
|
||||
getchar();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -23,8 +23,9 @@ set(FREEDV_LINK_LIBS_OSX
|
|||
add_definitions(-DULOG_HAVE_TIME)
|
||||
add_definitions(-DULOG_NO_COLOR)
|
||||
|
||||
# Include third party headers
|
||||
# Include third party headers and components
|
||||
include_directories(3rdparty)
|
||||
add_subdirectory(3rdparty)
|
||||
|
||||
# Include generated headers
|
||||
include_directories(${CMAKE_BINARY_DIR}/generated)
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@ class PlotScalar: public PlotPanel
|
|||
int m_bar_graph; // non zero to plot bar graphs
|
||||
int m_logy; // plot graph on log scale
|
||||
|
||||
void draw(wxGraphicsContext* ctx, bool repaintDataOnly = false);
|
||||
void draw(wxGraphicsContext* ctx, bool repaintDataOnly = false) override;
|
||||
void drawGraticuleFast(wxGraphicsContext* ctx, bool repaintDataOnly);
|
||||
void OnSize(wxSizeEvent& event);
|
||||
void OnShow(wxShowEvent& event);
|
||||
void OnSize(wxSizeEvent& event) override;
|
||||
void OnShow(wxShowEvent& event) override;
|
||||
|
||||
virtual bool repaintAll_(wxPaintEvent& evt) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,15 +55,15 @@ class PlotWaterfall : public PlotPanel
|
|||
|
||||
unsigned heatmap(float val, float min, float max);
|
||||
|
||||
void OnSize(wxSizeEvent& event);
|
||||
void OnShow(wxShowEvent& event);
|
||||
void drawGraticule(wxGraphicsContext* ctx);
|
||||
void draw(wxGraphicsContext* gc, bool repaintDataOnly = false);
|
||||
void OnSize(wxSizeEvent& event) override;
|
||||
void OnShow(wxShowEvent& event) override;
|
||||
void drawGraticule(wxGraphicsContext* ctx) override;
|
||||
void draw(wxGraphicsContext* gc, bool repaintDataOnly = false) override;
|
||||
void plotPixelData();
|
||||
void OnMouseLeftDoubleClick(wxMouseEvent& event);
|
||||
void OnMouseRightDoubleClick(wxMouseEvent& event);
|
||||
void OnMouseMiddleDown(wxMouseEvent& event);
|
||||
void OnMouseWheelMoved(wxMouseEvent& event);
|
||||
void OnMouseWheelMoved(wxMouseEvent& event) override;
|
||||
void OnKeyDown(wxKeyEvent& event);
|
||||
|
||||
virtual bool repaintAll_(wxPaintEvent& evt) override;
|
||||
|
|
|
|||
|
|
@ -752,7 +752,7 @@ void FilterDlg::OnSpeexppEnable(wxScrollEvent& event) {
|
|||
|
||||
void FilterDlg::OnAgcEnable(wxScrollEvent& event) {
|
||||
wxGetApp().appConfiguration.filterConfiguration.agcEnabled = m_ckboxAgcEnabled->GetValue();
|
||||
g_agcEnabled = wxGetApp().appConfiguration.filterConfiguration.agcEnabled; // forces immediate change at pipeline level
|
||||
g_agcEnabled.store(wxGetApp().appConfiguration.filterConfiguration.agcEnabled, std::memory_order_release); // forces immediate change at pipeline level
|
||||
ExchangeData(EXCHANGE_DATA_OUT);
|
||||
}
|
||||
|
||||
|
|
@ -766,8 +766,7 @@ void FilterDlg::On700C_EQ(wxScrollEvent& event) {
|
|||
|
||||
void FilterDlg::updateControlState()
|
||||
{
|
||||
// AGC currently requires Speex.
|
||||
m_ckboxAgcEnabled->Enable(wxGetApp().appConfiguration.filterConfiguration.speexppEnable);
|
||||
m_ckboxAgcEnabled->Enable(true);
|
||||
|
||||
m_MicInBass.sliderFreq->Enable(wxGetApp().appConfiguration.filterConfiguration.micInChannel.eqEnable);
|
||||
m_MicInBass.sliderGain->Enable(wxGetApp().appConfiguration.filterConfiguration.micInChannel.eqEnable);
|
||||
|
|
|
|||
|
|
@ -736,7 +736,7 @@ void MainFrame::loadConfiguration_()
|
|||
});
|
||||
|
||||
// Load AGC state
|
||||
g_agcEnabled = wxGetApp().appConfiguration.filterConfiguration.agcEnabled;
|
||||
g_agcEnabled.store(wxGetApp().appConfiguration.filterConfiguration.agcEnabled, std::memory_order_release);
|
||||
|
||||
g_txLevel = wxGetApp().appConfiguration.transmitLevel;
|
||||
float dbLoss = g_txLevel / 10.0;
|
||||
|
|
@ -1037,7 +1037,7 @@ MainFrame::MainFrame(wxWindow *parent) : TopFrame(parent, wxID_ANY, _("FreeDV ")
|
|||
|
||||
// Add Speech Output window
|
||||
m_panelSpeechOut = new PlotScalar((wxFrame*) m_auiNbookCtrl, 1, WAVEFORM_PLOT_TIME, 1.0/WAVEFORM_PLOT_FS, -1, 1, 1, 0.2, "%2.1f", 0);
|
||||
m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("To Spkr/Hdphns"), false, wxNullBitmap);
|
||||
m_auiNbookCtrl->AddPage(m_panelSpeechOut, _("Frm Decoder"), false, wxNullBitmap);
|
||||
g_plotSpeechOutFifo = codec2_fifo_create(4*WAVEFORM_PLOT_BUF);
|
||||
|
||||
// Add Scatter Plot window
|
||||
|
|
@ -2620,7 +2620,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
|
|||
|
||||
if (m_RxRunning)
|
||||
{
|
||||
m_togBtnOnOff->SetLabel(wxT("&Stop"));
|
||||
m_togBtnOnOff->SetLabel(wxT("&Stop Modem"));
|
||||
}
|
||||
m_togBtnOnOff->SetValue(m_RxRunning);
|
||||
m_togBtnOnOff->Enable(true);
|
||||
|
|
@ -2651,7 +2651,7 @@ void MainFrame::OnTogBtnOnOff(wxCommandEvent& event)
|
|||
m_btnTogPTT->Enable(m_RxRunning);
|
||||
optionsDlg->setSessionActive(m_RxRunning);
|
||||
m_togBtnOnOff->SetValue(m_RxRunning);
|
||||
m_togBtnOnOff->SetLabel(wxT("&Start"));
|
||||
m_togBtnOnOff->SetLabel(wxT("&Start Modem"));
|
||||
m_togBtnOnOff->Enable(true);
|
||||
|
||||
if (terminating_)
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@ class MainFrame : public TopFrame
|
|||
void updateReportingFreqList_();
|
||||
|
||||
void initializeFreeDVReporter_();
|
||||
void updateVoiceKeyerButtonLabel_();
|
||||
|
||||
void onFrequencyModeChange_(IRigFrequencyController*, uint64_t freq, IRigFrequencyController::Mode mode);
|
||||
void onRadioConnected_(IRigController* ptr);
|
||||
|
|
|
|||
|
|
@ -1158,14 +1158,14 @@ void MainFrame::OnTogBtnAnalogClick (wxCommandEvent& event)
|
|||
m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(FS/2)));
|
||||
m_panelWaterfall->setFs(FS);
|
||||
|
||||
m_togBtnAnalog->SetLabel(wxT("Di&gital"));
|
||||
m_togBtnAnalog->SetLabel(wxT("Switch to Di&gital"));
|
||||
}
|
||||
else {
|
||||
g_analog = 0;
|
||||
m_panelSpectrum->setFreqScale(MODEM_STATS_NSPEC*((float)MAX_F_HZ/(freedvInterface.getRxModemSampleRate()/2)));
|
||||
m_panelWaterfall->setFs(freedvInterface.getRxModemSampleRate());
|
||||
|
||||
m_togBtnAnalog->SetLabel(wxT("A&nalog"));
|
||||
m_togBtnAnalog->SetLabel(wxT("Switch to A&nalog"));
|
||||
}
|
||||
|
||||
// Report analog change to registered reporters
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
//=========================================================================
|
||||
// Name: AgcStep.cpp
|
||||
// Purpose: Describes an AGC step in the audio pipeline.
|
||||
//
|
||||
// Authors: Mooneer Salem
|
||||
// License:
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2.1,
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
//=========================================================================
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "AgcStep.h"
|
||||
#include "../defines.h"
|
||||
#include "../util/logging/ulog.h"
|
||||
#include "ebur128.h" // from libebur128
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// AGC settings
|
||||
constexpr float AGC_LOUDNESS_TARGET_LUFS = -23.0;
|
||||
constexpr float AGC_MAX_GAIN_DB = 12.0;
|
||||
constexpr float AGC_MIN_GAIN_DB = -20.0;
|
||||
constexpr float AGC_ATTACK_TIME_SEC = 0.5;
|
||||
constexpr float AGC_RELEASE_TIME_SEC = 6.0;
|
||||
constexpr float SILENCE_THRESHOLD_LUFS = -33.0;
|
||||
constexpr int LIMITER_LEVEL_DB = -1;
|
||||
|
||||
constexpr int TEN_MS_DIVIDER = 100;
|
||||
constexpr int MAX_AGC_SAMPLES = 160;
|
||||
|
||||
AgcStep::AgcStep(int sampleRate)
|
||||
: sampleRate_(sampleRate == 8000 || sampleRate == 16000 || sampleRate == 32000 || sampleRate == 48000 ? sampleRate : 48000)
|
||||
, targetGainDb_(0.0)
|
||||
, currentGainDb_(0.0)
|
||||
, inputSampleFifo_(sampleRate / 2)
|
||||
{
|
||||
numSamplesPerRun_ = std::min(MAX_AGC_SAMPLES, sampleRate_ / TEN_MS_DIVIDER); // 10ms blocks, 160 max samples
|
||||
assert(numSamplesPerRun_ > 0);
|
||||
|
||||
// Configure WebRTC as solely a limiter (i.e. no AGC)
|
||||
agcState_ = WebRtcAgc_Create();
|
||||
assert(agcState_ != nullptr);
|
||||
|
||||
auto status = WebRtcAgc_Init(agcState_, 0, 255, kAgcModeUnchanged, sampleRate_);
|
||||
if (status != 0)
|
||||
{
|
||||
log_error("Could not initialize WebRTC AGC (err = %d)", status);
|
||||
WebRtcAgc_Free(agcState_);
|
||||
agcState_ = nullptr;
|
||||
}
|
||||
|
||||
// Set AGC configuration
|
||||
agcConfig_.compressionGaindB = 0; // default 9 dB
|
||||
agcConfig_.limiterEnable = 1; // default kAgcTrue (on)
|
||||
agcConfig_.targetLevelDbfs = -LIMITER_LEVEL_DB; // default 3 (-3 dBOv)
|
||||
status = WebRtcAgc_set_config(agcState_, agcConfig_);
|
||||
if (status != 0)
|
||||
{
|
||||
log_error("Could not initialize WebRTC AGC config (err = %d)", status);
|
||||
WebRtcAgc_Free(agcState_);
|
||||
agcState_ = nullptr;
|
||||
}
|
||||
|
||||
ebur128State_ = ebur128_init(1, sampleRate_, EBUR128_MODE_S);
|
||||
assert(ebur128State_ != nullptr);
|
||||
|
||||
// Pre-allocate buffers so we don't have to do so during real-time operation.
|
||||
outputSamples_ = std::make_unique<short[]>(sampleRate);
|
||||
assert(outputSamples_ != nullptr);
|
||||
|
||||
tmpInput_ = std::make_unique<short[]>(numSamplesPerRun_);
|
||||
assert(tmpInput_ != nullptr);
|
||||
}
|
||||
|
||||
AgcStep::~AgcStep()
|
||||
{
|
||||
outputSamples_ = nullptr;
|
||||
WebRtcAgc_Free(agcState_);
|
||||
ebur128_destroy((ebur128_state**)&ebur128State_);
|
||||
}
|
||||
|
||||
int AgcStep::getInputSampleRate() const
|
||||
{
|
||||
return sampleRate_;
|
||||
}
|
||||
|
||||
int AgcStep::getOutputSampleRate() const
|
||||
{
|
||||
return sampleRate_;
|
||||
}
|
||||
|
||||
short* AgcStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
|
||||
{
|
||||
ebur128_state* state = static_cast<ebur128_state*>(ebur128State_);
|
||||
|
||||
*numOutputSamples = 0;
|
||||
short* outputSamples = outputSamples_.get();
|
||||
|
||||
int numRuns = (inputSampleFifo_.numUsed() + numInputSamples) / numSamplesPerRun_;
|
||||
if (numRuns > 0)
|
||||
{
|
||||
*numOutputSamples = numRuns * numSamplesPerRun_;
|
||||
|
||||
short* tmpOutput = outputSamples;
|
||||
short* tmpInput = tmpInput_.get();
|
||||
|
||||
inputSampleFifo_.write(inputSamples, numInputSamples);
|
||||
while (inputSampleFifo_.numUsed() >= numSamplesPerRun_)
|
||||
{
|
||||
inputSampleFifo_.read(tmpInput, numSamplesPerRun_);
|
||||
|
||||
// Step 1: feed samples into ebur128 and return current
|
||||
// loudness in LUFS.
|
||||
ebur128_add_frames_short(state, tmpInput, numSamplesPerRun_);
|
||||
double lufs = 0.0;
|
||||
auto result = ebur128_loudness_momentary(state, &lufs);
|
||||
if (result == EBUR128_SUCCESS && lufs != -HUGE_VAL && lufs > SILENCE_THRESHOLD_LUFS)
|
||||
{
|
||||
// Returned loudness is valid.
|
||||
// Step 2: calculate target gain. Assume LUFS = dbFS (?)
|
||||
targetGainDb_ = AGC_LOUDNESS_TARGET_LUFS - lufs;
|
||||
if (targetGainDb_ >= AGC_MAX_GAIN_DB) targetGainDb_ = AGC_MAX_GAIN_DB;
|
||||
if (targetGainDb_ <= AGC_MIN_GAIN_DB) targetGainDb_ = AGC_MIN_GAIN_DB;
|
||||
|
||||
// Step 3: increment/decrement current gain in the direction of target.
|
||||
float agcInterval = 0;
|
||||
if (targetGainDb_ < currentGainDb_)
|
||||
{
|
||||
agcInterval = AGC_ATTACK_TIME_SEC;
|
||||
}
|
||||
else
|
||||
{
|
||||
agcInterval = AGC_RELEASE_TIME_SEC;
|
||||
}
|
||||
currentGainDb_ += ((targetGainDb_ - currentGainDb_) / agcInterval) * ((float)numSamplesPerRun_ / sampleRate_);
|
||||
//log_info("LUFS: %f, targetGain: %f, currentGain: %f", lufs, targetGainDb_, currentGainDb_);
|
||||
}
|
||||
|
||||
// Scale samples based on current gain.
|
||||
float scaleFactor = exp(currentGainDb_/20.0 * log(10.0));
|
||||
for (auto ctr = 0; ctr < numSamplesPerRun_; ctr++)
|
||||
{
|
||||
tmpInput[ctr] *= scaleFactor;
|
||||
}
|
||||
|
||||
// Run WebRTC to make sure we don't clip.
|
||||
int outMicLevel = 0;
|
||||
int inMicLevel = 0;
|
||||
short echo = 0;
|
||||
unsigned char saturationWarning = 1;
|
||||
auto status = WebRtcAgc_Process(
|
||||
agcState_, const_cast<const int16_t *const *>(&tmpInput), 1, numSamplesPerRun_,
|
||||
const_cast<int16_t *const *>(&tmpOutput), inMicLevel, &outMicLevel, echo, &saturationWarning);
|
||||
if (status != 0)
|
||||
{
|
||||
// XXX - not RT-safe
|
||||
log_error("Failed processing AGC (err = %d)", status);
|
||||
}
|
||||
tmpOutput += numSamplesPerRun_;
|
||||
}
|
||||
}
|
||||
else if (numInputSamples > 0 && inputSamples != nullptr)
|
||||
{
|
||||
inputSampleFifo_.write(inputSamples, numInputSamples);
|
||||
}
|
||||
|
||||
return outputSamples;
|
||||
}
|
||||
|
||||
void AgcStep::reset()
|
||||
{
|
||||
inputSampleFifo_.reset();
|
||||
currentGainDb_ = 0;
|
||||
targetGainDb_ = 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//=========================================================================
|
||||
// Name: AgcStep.h
|
||||
// Purpose: Describes an AGC step in the audio pipeline.
|
||||
//
|
||||
// Authors: Mooneer Salem
|
||||
// License:
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2.1,
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
//=========================================================================
|
||||
|
||||
#ifndef AUDIO_PIPELINE__AGC_STEP_H
|
||||
#define AUDIO_PIPELINE__AGC_STEP_H
|
||||
|
||||
#include "IPipelineStep.h"
|
||||
#include "../util/GenericFIFO.h"
|
||||
#include "agc.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class AgcStep : public IPipelineStep
|
||||
{
|
||||
public:
|
||||
AgcStep(int sampleRate);
|
||||
virtual ~AgcStep();
|
||||
|
||||
virtual int getInputSampleRate() const override;
|
||||
virtual int getOutputSampleRate() const override;
|
||||
virtual short* execute(short* inputSamples, int numInputSamples, int* numOutputSamples) override;
|
||||
virtual void reset() override;
|
||||
|
||||
private:
|
||||
int sampleRate_;
|
||||
float targetGainDb_;
|
||||
float currentGainDb_;
|
||||
WebRtcAgcConfig agcConfig_;
|
||||
void* agcState_;
|
||||
|
||||
void* ebur128State_;
|
||||
|
||||
int numSamplesPerRun_;
|
||||
GenericFIFO<short> inputSampleFifo_;
|
||||
std::unique_ptr<short[]> outputSamples_;
|
||||
std::unique_ptr<short[]> tmpInput_;
|
||||
};
|
||||
|
||||
|
||||
#endif // AUDIO_PIPELINE__AGC_STEP_H
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
add_library(fdv_audio_pipeline STATIC
|
||||
AgcStep.cpp
|
||||
AudioPipeline.cpp
|
||||
ComputeRfSpectrumStep.cpp
|
||||
EitherOrStep.cpp
|
||||
|
|
@ -23,9 +24,10 @@ add_library(fdv_audio_pipeline STATIC
|
|||
rade_text.c
|
||||
)
|
||||
|
||||
target_link_libraries(fdv_audio_pipeline PRIVATE ebur128 agc)
|
||||
target_include_directories(fdv_audio_pipeline PRIVATE ${CODEC2_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/..)
|
||||
|
||||
add_dependencies(fdv_audio_pipeline rade opus codec2)
|
||||
add_dependencies(fdv_audio_pipeline rade opus codec2 ebur128 agc)
|
||||
|
||||
if(BOOTSTRAP_WXWIDGETS)
|
||||
add_dependencies(fdv_audio_pipeline wx::core wx::base wx::aui wx::html wx::net wx::adv wx::propgrid wx::xrc)
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@
|
|||
|
||||
#include <assert.h>
|
||||
|
||||
extern std::atomic<bool> g_agcEnabled;
|
||||
|
||||
SpeexStep::SpeexStep(int sampleRate)
|
||||
: sampleRate_(sampleRate)
|
||||
, numSamplesPerSpeexRun_((FRAME_DURATION_MS * sampleRate_) / MS_TO_SEC)
|
||||
|
|
@ -41,8 +39,6 @@ SpeexStep::SpeexStep(int sampleRate)
|
|||
sampleRate_);
|
||||
assert(speexStateObj_ != nullptr);
|
||||
|
||||
updateAgcState_();
|
||||
|
||||
// Pre-allocate buffers so we don't have to do so during real-time operation.
|
||||
outputSamples_ = std::make_unique<short[]>(sampleRate);
|
||||
assert(outputSamples_ != nullptr);
|
||||
|
|
@ -66,8 +62,6 @@ int SpeexStep::getOutputSampleRate() const
|
|||
|
||||
short* SpeexStep::execute(short* inputSamples, int numInputSamples, int* numOutputSamples)
|
||||
{
|
||||
updateAgcState_();
|
||||
|
||||
*numOutputSamples = 0;
|
||||
|
||||
short* outputSamples = outputSamples_.get();
|
||||
|
|
@ -99,20 +93,3 @@ void SpeexStep::reset()
|
|||
{
|
||||
inputSampleFifo_.reset();
|
||||
}
|
||||
|
||||
void SpeexStep::updateAgcState_()
|
||||
{
|
||||
int32_t newAgcState = g_agcEnabled ? 1 : 0;
|
||||
|
||||
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC, &newAgcState);
|
||||
if (newAgcState)
|
||||
{
|
||||
// Experimentally determined to be such that normal speaking creates peaks +/- ~0.4.
|
||||
// Used MacBook Pro built-in microphone for tests.
|
||||
float newAgcLevel = 12000;
|
||||
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC_LEVEL, &newAgcLevel);
|
||||
|
||||
uint32_t maxGainDb = 40;
|
||||
speex_preprocess_ctl(speexStateObj_, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &maxGainDb);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ private:
|
|||
int numSamplesPerSpeexRun_;
|
||||
GenericFIFO<short> inputSampleFifo_;
|
||||
std::unique_ptr<short[]> outputSamples_;
|
||||
|
||||
void updateAgcState_();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
#include <chrono>
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// WebRTC uses FS, which is defined in defines.h. Thus, it needs to be included
|
||||
// first.
|
||||
#include "AgcStep.h"
|
||||
|
||||
// This forces us to use freedv-gui's version rather than another one.
|
||||
// TBD -- may not be needed once we fully switch over to the audio pipeline.
|
||||
#include "../defines.h"
|
||||
|
|
@ -101,6 +105,7 @@ extern float g_RxFreqOffsetHz;
|
|||
extern float g_sig_pwr_av;
|
||||
extern std::atomic<bool> g_voice_keyer_tx;
|
||||
extern bool g_eoo_enqueued;
|
||||
extern std::atomic<bool> g_agcEnabled;
|
||||
|
||||
#include <speex/speex_preprocess.h>
|
||||
|
||||
|
|
@ -196,7 +201,20 @@ void TxRxThread::initializePipeline_()
|
|||
eitherOrProcessSpeex,
|
||||
eitherOrBypassSpeex);
|
||||
pipeline_->appendPipelineStep(eitherOrSpeexStep);
|
||||
|
||||
|
||||
// AGC step (optional)
|
||||
auto eitherOrProcessAgc = new AudioPipeline(inputSampleRate_, inputSampleRate_);
|
||||
auto eitherOrBypassAgc = new AudioPipeline(inputSampleRate_, inputSampleRate_);
|
||||
|
||||
auto agcStep = new AgcStep(inputSampleRate_);
|
||||
eitherOrProcessAgc->appendPipelineStep(agcStep);
|
||||
|
||||
auto eitherOrAgcStep = new EitherOrStep(
|
||||
[]() { return g_agcEnabled.load(std::memory_order_acquire); },
|
||||
eitherOrProcessAgc,
|
||||
eitherOrBypassAgc);
|
||||
pipeline_->appendPipelineStep(eitherOrAgcStep);
|
||||
|
||||
// Equalizer step (optional based on filter state)
|
||||
auto equalizerStep = new EqualizerStep(
|
||||
inputSampleRate_,
|
||||
|
|
@ -388,6 +406,18 @@ void TxRxThread::initializePipeline_()
|
|||
helper_
|
||||
);
|
||||
rfDemodulationPipeline->appendPipelineStep(rfDemodulationStep);
|
||||
|
||||
// Resample for plot step (speech out)
|
||||
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);
|
||||
auto resampleForPlotOutPipeline = new AudioPipeline(outputSampleRate_, resampleForPlotOutStep->getOutputSampleRate());
|
||||
#if defined(ENABLE_FASTER_PLOTS)
|
||||
auto resampleForPlotOutResampler = new ResampleStep(outputSampleRate_, resampleForPlotOutStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
|
||||
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutResampler);
|
||||
#endif // defined(ENABLE_FASTER_PLOTS)
|
||||
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutStep);
|
||||
|
||||
auto resampleForPlotOutTap = new TapStep(outputSampleRate_, resampleForPlotOutPipeline);
|
||||
rfDemodulationPipeline->appendPipelineStep(resampleForPlotOutTap);
|
||||
|
||||
// Replace received audio with microphone audio if we're monitoring TX/voice keyer recording.
|
||||
if (equalizedMicAudioLink_ != nullptr)
|
||||
|
|
@ -456,18 +486,6 @@ void TxRxThread::initializePipeline_()
|
|||
&g_rxUserdata->sbqSpkOutVol);
|
||||
pipeline_->appendPipelineStep(equalizerStep);
|
||||
|
||||
// Resample for plot step (speech out)
|
||||
auto resampleForPlotOutStep = new ResampleForPlotStep(g_plotSpeechOutFifo);
|
||||
auto resampleForPlotOutPipeline = new AudioPipeline(outputSampleRate_, resampleForPlotOutStep->getOutputSampleRate());
|
||||
#if defined(ENABLE_FASTER_PLOTS)
|
||||
auto resampleForPlotOutResampler = new ResampleStep(outputSampleRate_, resampleForPlotOutStep->getInputSampleRate(), true); // need to create manually to get access to "plot only" optimizations
|
||||
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutResampler);
|
||||
#endif // defined(ENABLE_FASTER_PLOTS)
|
||||
resampleForPlotOutPipeline->appendPipelineStep(resampleForPlotOutStep);
|
||||
|
||||
auto resampleForPlotOutTap = new TapStep(outputSampleRate_, resampleForPlotOutPipeline);
|
||||
pipeline_->appendPipelineStep(resampleForPlotOutTap);
|
||||
|
||||
// Clear anything in the FIFO before resuming decode.
|
||||
clearFifos_();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -732,21 +732,21 @@ TopFrame::TopFrame(wxWindow* parent, wxWindowID id, const wxString& title, const
|
|||
//-------------------------------
|
||||
// Stop/Stop signal processing (rx and tx)
|
||||
//-------------------------------
|
||||
m_togBtnOnOff = new wxToggleButton(controlBox, wxID_ANY, _("&Start"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnOnOff = new wxToggleButton(controlBox, wxID_ANY, _("&Start Modem"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnOnOff->SetToolTip(_("Begin/End receiving data."));
|
||||
sbSizer5->Add(m_togBtnOnOff, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
//------------------------------
|
||||
// Analog Passthrough Toggle
|
||||
//------------------------------
|
||||
m_togBtnAnalog = new wxToggleButton(controlBox, wxID_ANY, _("A&nalog"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnAnalog = new wxToggleButton(controlBox, wxID_ANY, _("Switch to A&nalog"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnAnalog->SetToolTip(_("Toggle analog/digital operation."));
|
||||
sbSizer5->Add(m_togBtnAnalog, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
//------------------------------
|
||||
// Voice Keyer Toggle
|
||||
//------------------------------
|
||||
m_togBtnVoiceKeyer = new wxToggleButton(controlBox, wxID_ANY, _("Voice &Keyer"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnVoiceKeyer = new wxToggleButton(controlBox, wxID_ANY, _("Start Voice &Keyer"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_togBtnVoiceKeyer->SetToolTip(_("Toggle Voice Keyer. Right-click for additional options."));
|
||||
sbSizer5->Add(m_togBtnVoiceKeyer, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
|
|
@ -989,7 +989,7 @@ TopFrame::~TopFrame()
|
|||
|
||||
void TopFrame::setVoiceKeyerButtonLabel_(wxString filename)
|
||||
{
|
||||
wxString vkLabel = _("Voice Keyer");
|
||||
wxString vkLabel = _("Start Voice &Keyer");
|
||||
int vkLabelWidth = 0;
|
||||
int filenameWidth = 0;
|
||||
int tmp = 0;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ void MainFrame::OnTogBtnVoiceKeyerClick (wxCommandEvent& event)
|
|||
}
|
||||
|
||||
m_togBtnVoiceKeyer->SetValue(true);
|
||||
|
||||
auto currentLabel = m_togBtnVoiceKeyer->GetLabel();
|
||||
currentLabel.Replace(_("Start Voice Keyer"), _("Stop Voice Keyer"), false);
|
||||
m_togBtnVoiceKeyer->SetLabel(currentLabel);
|
||||
|
||||
VoiceKeyerProcessEvent(VK_START);
|
||||
}
|
||||
else
|
||||
|
|
@ -256,6 +261,15 @@ int MainFrame::VoiceKeyerStartTx(void)
|
|||
return next_state;
|
||||
}
|
||||
|
||||
void MainFrame::updateVoiceKeyerButtonLabel_()
|
||||
{
|
||||
if (!m_togBtnVoiceKeyer->GetValue())
|
||||
{
|
||||
auto currentLabel = m_togBtnVoiceKeyer->GetLabel();
|
||||
currentLabel.Replace(_("Stop Voice &Keyer"), _("Start Voice &Keyer"), false);
|
||||
m_togBtnVoiceKeyer->SetLabel(currentLabel);
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
||||
int next_state = vk_state;
|
||||
|
|
@ -288,6 +302,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
togglePTT();
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
CallAfter([&]() { StopPlayFileToMicIn(); });
|
||||
}
|
||||
|
|
@ -301,6 +316,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
if (vk_repeat_counter > vk_repeats) {
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
}
|
||||
else {
|
||||
|
|
@ -333,6 +349,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
if (vk_event == VK_SPACE_BAR) {
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
}
|
||||
|
||||
|
|
@ -347,6 +364,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
if (vk_event == VK_SPACE_BAR) {
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
}
|
||||
|
||||
|
|
@ -364,6 +382,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
if (vk_rx_sync_time >= VK_SYNC_WAIT_TIME) {
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
}
|
||||
}
|
||||
|
|
@ -378,6 +397,7 @@ void MainFrame::VoiceKeyerProcessEvent(int vk_event) {
|
|||
togglePTT();
|
||||
m_togBtnVoiceKeyer->SetValue(false);
|
||||
m_togBtnVoiceKeyer->SetBackgroundColour(wxNullColour);
|
||||
updateVoiceKeyerButtonLabel_();
|
||||
next_state = VK_IDLE;
|
||||
g_voice_keyer_tx.store(false, std::memory_order_release);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue