Adding an option for rnnoise suppression on the native client

canary
WolverinDEV 2020-10-01 10:56:54 +02:00
parent 7a97a74cd5
commit 444c4d78f7
9 changed files with 82 additions and 81 deletions

View File

@ -132,8 +132,8 @@ export function running() { return typeof(currentStage) !== "undefined"; }
export function register_task(stage: Stage, task: Task) {
let callee = new Error().stack.split("\n")[2].replace(/^\s*at (Object\.\.\/)?/, "");
if(callee.match(/^.* \(([:\\/_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/)) {
callee = callee.replace(/^.* \(([:\\/_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/, "$1:$2:$3");
if(callee.match(/^.* \(([:\\/~_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/)) {
callee = callee.replace(/^.* \(([:\\/~_\-+0-9a-zA-Z.]+):([0-9]+):([0-9]+)\)$/, "$1:$2:$3");
}
if(!task.function) {
debugger;

View File

@ -1,5 +1,8 @@
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {AbstractInput, LevelMeter} from "../voice/RecorderBase";
import {Registry} from "../events";
import {Settings, settings} from "tc-shared/settings";
export type DeviceQueryResult = {}
@ -8,6 +11,9 @@ export interface AudioRecorderBacked {
createLevelMeter(device: IDevice) : Promise<LevelMeter>;
getDeviceList() : DeviceList;
isRnNoiseSupported() : boolean;
toggleRnNoise(target: boolean);
}
export interface DeviceListEvents {
@ -156,3 +162,15 @@ export function setRecorderBackend(instance: AudioRecorderBacked) {
recorderBackend = instance;
}
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "audio filter init",
priority: 10,
function: async () => {
const backend = getRecorderBackend();
if(backend.isRnNoiseSupported()) {
getRecorderBackend().toggleRnNoise(settings.static_global(Settings.KEY_RNNOISE_FILTER));
settings.globalChangeListener(Settings.KEY_RNNOISE_FILTER, value => getRecorderBackend().toggleRnNoise(value));
}
}
})

View File

@ -491,6 +491,13 @@ export class Settings extends StaticSettings {
valueType: "boolean",
};
static readonly KEY_RNNOISE_FILTER: ValuedSettingsKey<boolean> = {
key: 'rnnoise_filter',
defaultValue: true,
description: 'Enable the rnnoise filter for supressing background noise',
valueType: "boolean",
};
static readonly FN_LOG_ENABLED: (category: string) => SettingsKey<boolean> = category => {
return {
key: "log." + category.toLowerCase() + ".enabled",

View File

@ -1,77 +0,0 @@
//TODO: Use the max limit!
import {sliderfy} from "../../ui/elements/Slider";
import {createModal, Modal} from "../../ui/elements/Modal";
import {ClientEntry} from "../../tree/Client";
import * as htmltags from "../../ui/htmltags";
let modal: Modal;
export function spawnChangeVolume(client: ClientEntry, local: boolean, current: number, max: number | undefined, callback: (number) => void) {
if (modal) modal.close();
let new_value: number;
modal = createModal({
header: local ? tr("Change local volume") : tr("Change remote volume"),
body: function () {
let tag = $("#tmpl_change_volume").renderTag({
client: htmltags.generate_client_object({
add_braces: false,
client_name: client.clientNickName(),
client_unique_id: client.properties.client_unique_identifier,
client_id: client.clientId()
}),
local: local
});
const container_value = tag.find(".info .value");
const set_value = value => {
const number = value > 100 ? value - 100 : 100 - value;
container_value.html((value == 100 ? "&plusmn;" : value > 100 ? "+" : "-") + number + "%");
new_value = value / 100;
if (local) callback(new_value);
};
set_value(current * 100);
const slider_tag = tag.find(".container-slider");
const slider = sliderfy(slider_tag, {
initial_value: current * 100,
step: 1,
max_value: 200,
min_value: 0,
unit: '%'
});
slider_tag.on('change', event => set_value(parseInt(slider_tag.attr("value"))));
tag.find(".button-save").on('click', event => {
if (typeof (new_value) !== "undefined") callback(new_value);
modal.close();
});
tag.find(".button-cancel").on('click', event => {
callback(current);
modal.close();
});
tag.find(".button-reset").on('click', event => {
slider.value(100);
});
tag.find(".button-apply").on('click', event => {
callback(new_value);
new_value = undefined;
});
return tag.children();
},
footer: null,
width: 600
});
modal.close_listener.push(() => modal = undefined);
modal.open();
modal.htmlTag.find(".modal-body").addClass("modal-volume");
}

View File

@ -6,6 +6,7 @@ import * as log from "tc-shared/log";
import {LogCategory, logWarn} from "tc-shared/log";
import {defaultRecorder} from "tc-shared/voice/RecorderProfile";
import {DeviceListState, getRecorderBackend, IDevice} from "tc-shared/audio/recorder";
import {Settings, settings} from "tc-shared/settings";
export type MicrophoneSetting =
"volume"
@ -13,7 +14,8 @@ export type MicrophoneSetting =
| "ppt-key"
| "ppt-release-delay"
| "ppt-release-delay-active"
| "threshold-threshold";
| "threshold-threshold"
| "rnnoise";
export type MicrophoneDevice = {
id: string,
@ -242,6 +244,10 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
value = defaultRecorder.getPushToTalkDelay() > 0;
break;
case "rnnoise":
value = settings.static_global(Settings.KEY_RNNOISE_FILTER);
break;
default:
return;
}
@ -296,6 +302,11 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
defaultRecorder.setPushToTalkDelay(Math.abs(defaultRecorder.getPushToTalkDelay()) * (event.value ? 1 : -1));
break;
case "rnnoise":
if (!ensure_type("boolean")) return;
settings.changeGlobal(Settings.KEY_RNNOISE_FILTER, event.value);
break;
default:
return;
}

View File

@ -1,6 +1,6 @@
import * as React from "react";
import {useEffect, useRef, useState} from "react";
import {Translatable} from "tc-shared/ui/react-elements/i18n";
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
import {Button} from "tc-shared/ui/react-elements/Button";
import {Registry} from "tc-shared/events";
import {MicrophoneDevice, MicrophoneSettingsEvents} from "tc-shared/ui/modal/settings/Microphone";
@ -416,6 +416,32 @@ const PPTDelaySettings = (props: { events: Registry<MicrophoneSettingsEvents> })
);
}
const RNNoiseLabel = () => (
<VariadicTranslatable text={"Enable RNNoise cancelation ({})"}>
<a href={"https://jmvalin.ca/demo/rnnoise/"} target={"_blank"} style={{ margin: 0 }}><Translatable>more info</Translatable></a>
</VariadicTranslatable>
)
const RNNoiseSettings = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
if(__build.target === "web") {
return null;
}
const [ enabled, setEnabled ] = useState<boolean | "loading">(() => {
props.events.fire("query_setting", { setting: "rnnoise" });
return "loading";
});
props.events.reactUse("notify_setting", event => event.setting === "rnnoise" && setEnabled(event.value));
return (
<Checkbox label={<RNNoiseLabel />}
disabled={enabled === "loading"}
value={enabled === true}
onChange={value => props.events.fire("action_set_setting", { setting: "rnnoise", value: value })}
/>
)
}
const VadSelector = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
const [selectedType, setSelectedType] = useState<VadType | "loading">(() => {
props.events.fire("query_setting", {setting: "vad-type"});
@ -630,6 +656,7 @@ export const MicrophoneSettings = (props: { events: Registry<MicrophoneSettingsE
<div className={cssStyle.body}>
<div className={cssStyle.containerAdvanced}>
<PPTDelaySettings events={props.events}/>
<RNNoiseSettings events={props.events} />
</div>
</div>
</HighlightRegion>

View File

@ -75,6 +75,7 @@ export interface AbstractInput {
readonly events: Registry<InputEvents>;
currentState() : InputState;
destroy();
start() : Promise<InputStartResult>;
stop() : Promise<void>;

View File

@ -90,6 +90,12 @@ export class RecorderProfile {
this.pptHookRegistered = false;
}
destroy() {
/* TODO */
this.input?.destroy();
this.input = undefined;
}
async initialize() : Promise<void> {
{
let config = {};

View File

@ -102,6 +102,12 @@ export class WebAudioRecorder implements AudioRecorderBacked {
getDeviceList(): DeviceList {
return inputDeviceList;
}
isRnNoiseSupported() {
return false;
}
toggleRnNoise(target: boolean) { throw "not supported"; }
}
class JavascriptInput implements AbstractInput {
@ -138,6 +144,8 @@ class JavascriptInput implements AbstractInput {
this.audioScriptProcessorCallback = this.handleAudio.bind(this);
}
destroy() { }
private handleAudioInitialized() {
this.audioContext = aplayer.context();
this.audioNodeMute = this.audioContext.createGain();