@@ -3784,141 +3784,154 @@ function maybeAttachMicrohamEqBuilder(settingsForm, settingFields, plugin) {
const presets = {
const presets = {
flat : {
flat : {
hp : 250 ,
label : "Flat" ,
lp : 2800 ,
desc : "Neutrale Referenz: kaum Klangfärbung, ohne Dynamik und ohne Auto-Level. Ideal zum Vergleichen und Feintunen." ,
mids : 0 ,
hp : 150 , lp : 3000 , mids : 0 , presence : 0 ,
presence : 0 ,
deesserEnabled : false , deesserFreq : 2900 , deesserCut : 0 ,
de esse rEnabled: false ,
compr esso rEnabled: false , compressorThreshold : - 18 , compressorRatio : 2.5 ,
deesserFreq : 520 0,
gateEnabled : false , gateThreshold : - 52 , gateRelease : 18 0,
deesserCut : 0 ,
normalizerEnabled : false , normalizerPeak : 90 , normalizerMaxGain : 12 ,
compressorEnabled : false ,
limiter : true
compressorThreshold : - 18 ,
} ,
compressorRatio : 2.5 ,
level : {
gateEnabled : false ,
label : "Konstanter Pegel" ,
gateThreshold : - 52 ,
desc : "Auto-Level: hält den Sendepegel über alle Browser und Mikrofone konstant (speechnorm). Noise Gate kappt Pausen, Kompressor aus – die Normalisierung bestimmt die Lautheit." ,
gateReleas e: 18 0,
hp : 200 , lp : 2900 , mids : 1.0 , presenc e: 2. 0,
deesserEnabled : false , deesserFreq : 2900 , deesserCut : 0 ,
compressorEnabled : false , compressorThreshold : - 18 , compressorRatio : 2.5 ,
gateEnabled : true , gateThreshold : - 50 , gateRelease : 180 ,
normalizerEnabled : true , normalizerPeak : 92 , normalizerMaxGain : 22 ,
limiter : true
limiter : true
} ,
} ,
dx : {
dx : {
hp : 360 ,
label : "DX" ,
lp : 2500 ,
desc : "Durchsetzungsstark: enges Sprachband, angehobene Präsenz, Kompressor plus Auto-Level für maximale Lesbarkeit im Pile-Up." ,
mids : 1 .0,
hp : 400 , lp : 2700 , mids: 2 .0, presence : 3.5 ,
presence : 1.8 ,
deesserEnabled : true , deesserFreq : 2800 , deesserCut : 2.5 ,
de esse rEnabled: true ,
compr esso rEnabled: true , compressorThreshold : - 18 , compressorRatio : 3.5 ,
deesserFreq : 520 0,
gateEnabled : true , gateThreshold : - 50 , gateRelease : 15 0,
deesserCut : 2.0 ,
normalizerEnabled : true , normalizerPeak : 92 , normalizerMaxGain : 18 ,
compressorEnabled : true ,
compressorThreshold : - 20 ,
compressorRatio : 3.0 ,
gateEnabled : false ,
gateThreshold : - 50 ,
gateRelease : 160 ,
limiter : true
limiter : true
} ,
} ,
ragchew : {
ragchew : {
hp : 300 ,
label : "Ragchew" ,
lp : 2800 ,
desc : "Natürlicher Klang: breiteres Band, sanfte Anhebung, milder Kompressor und moderates Auto-Level für entspanntes QSO." ,
mids : 0 .5,
hp : 150 , lp : 3000 , mids : 1.0 , presence : 1 .5,
presence : 1.0 ,
deesserEnabled : true , deesserFreq : 3000 , deesserCut : 1.5 ,
de esse rEnabled: true ,
compr esso rEnabled: true , compressorThreshold : - 22 , compressorRatio : 2.2 ,
deesserFreq : 500 0,
gateEnabled : true , gateThreshold : - 55 , gateRelease : 22 0,
deesserCut : 1. 2,
normalizerEnabled : true , normalizerPeak : 88 , normalizerMaxGain : 1 2,
compressorEnabled : true ,
compressorThreshold : - 22 ,
compressorRatio : 2.2 ,
gateEnabled : true ,
gateThreshold : - 55 ,
gateRelease : 220 ,
limiter : true
limiter : true
}
}
} ;
} ;
const parsed = parseMicrohamEqArgs ( extraArgsField . element . value ) ;
const parsed = parseMicrohamEqArgs ( extraArgsField . element . value ) ;
const initial = parsed || presets . flat ;
const initial = Object . assign ( { } , presets . flat , parsed || { } ) ;
const wrap = document . createElement ( "div" ) ;
const wrap = document . createElement ( "div" ) ;
wrap . className = "plugin-eq-builder" ;
wrap . className = "plugin-eq-builder" ;
const head = document . createElement ( "div" ) ;
head . className = "eq-head" ;
const headText = document . createElement ( "div" ) ;
headText . className = "eq-head-text" ;
const title = document . createElement ( "strong" ) ;
const title = document . createElement ( "strong" ) ;
title . textContent = "TX Audio EQ ";
title . className = "eq-head-title ";
wrap . appendChild ( title ) ;
title . textContent = "TX Audio Equalizer" ;
const hint = document . createElement ( "small" ) ;
const hint = document . createElement ( "small" ) ;
hint . className = "muted " ;
hint . className = "eq-head-hint " ;
hint . textContent = parsed
hint . textContent = parsed
? "EQ aus aktuellen Extra-Args geladen"
? "Aus den aktuellen Extra-Args geladen. "
: "Preset/Regler schreiben den FFmpeg-EQ in audioFfmpegExtraArgs" ;
: "Regler und Presets schreiben di e FFmpeg-Kette in audioFfmpegExtraArgs. " ;
wrap . appendChild ( hint ) ;
headText . appendChild ( title ) ;
headText . appendChild ( hint ) ;
head . appendChild ( headText ) ;
wrap . appendChild ( head ) ;
const presetRow = document . createElement ( "div" ) ;
const presetBar = document . createElement ( "div" ) ;
presetRow . className = "plugin-eq-row " ;
presetBar . className = "eq-presets " ;
const presetOrder = [ "flat" , "level" , "dx" , "ragchew" ] ;
const presetLabel = document . createElement ( "span" ) ;
const presetButtons = { } ;
presetLabel . className = "muted" ;
presetOrder . forEach ( ( key ) => {
presetLabel . textContent = "Preset" ;
const chip = document . createElement ( "button" ) ;
presetRow . appendChild ( presetLabel ) ;
chip . type = "button" ;
chip . className = "eq-chip" + ( key === "level" ? " is-star" : "" ) ;
const presetSelect = documen t. c reateElement ( "select" ) ;
chip . datase t . p reset = key ;
presetSelect . innerHTML = [
chip . textContent = presets [ key ] . label ;
'<option value="flat">Flat - neutral</option>' ,
presetBar . appendChild ( chip ) ;
'<option value="dx">DX - durchsetzungsstark</option>' ,
presetButtons [ key ] = chip ;
'<option value="ragchew">Ragchew - angenehmer Klang</option>'
} ) ;
] . join ( "" ) ;
wrap . appendChild ( presetBar ) ;
presetRow . appendChild ( presetSelect ) ;
const presetBtn = document . createElement ( "button" ) ;
presetBtn . type = "button" ;
presetBtn . className = "ghost-btn" ;
presetBtn . textContent = "Preset anwenden" ;
presetRow . appendChild ( presetBtn ) ;
wrap . appendChild ( presetRow ) ;
const presetInfo = document . createElement ( "small" ) ;
const presetInfo = document . createElement ( "small" ) ;
presetInfo . className = "muted " ;
presetInfo . className = "eq-preset-info " ;
wrap . appendChild ( presetInfo ) ;
wrap . appendChild ( presetInfo ) ;
const hpControl = createEqSliderControl ( "Highpass (Hz)" , 80 , 600 , 10 , initial . hp ) ;
// --- Section: Klangfilter (EQ) ---
const lpControl = createEqSliderControl ( "Lowpass (Hz)" , 1800 , 4000 , 10 , initial . lp ) ;
const eqSec = createEqSection ( "Klangfilter" , { subtitle : "Bandbreite und Frequenzgang der Stimme." } ) ;
const mids Control = createEqSliderControl ( "Mitten (dB) " , - 8 , 12 , 0.5 , initial . mids ) ;
const hp Control = createEqSliderControl ( "Highpass " , 80 , 600 , 10 , initial . hp , "Hz" ) ;
const presence Control = createEqSliderControl ( "Presence (dB) " , - 8 , 12 , 0.5 , initial . presence ) ;
const l pControl = createEqSliderControl ( "Lowpass " , 2000 , 3200 , 10 , initial . lp , "Hz" ) ;
const deesserFreq Control = createEqSliderControl ( "De-Esser ( Hz) " , 3500 , 8000 , 100 , initial . deesserFreq ) ;
const mids Control = createEqSliderControl ( "Mitten · 1200 Hz" , - 8 , 12 , 0.5 , initial . mids , "dB" ) ;
const deesserCut Control = createEqSliderControl ( "De-Esser Cut (dB) " , 0 , 8 , 0.5 , initial . deesserCut ) ;
const presence Control = createEqSliderControl ( "Presence · 2100 Hz " , - 8 , 12 , 0.5 , initial . presence , "dB" ) ;
const compThresholdControl = createEqSliderControl ( "Compressor Threshold (dB)" , - 40 , - 6 , 1 , initial . compressorThreshold ) ;
[ hpControl , lpControl , midsControl , presenceControl ] . forEach ( ( c ) => eqSec . body . appendChild ( c . row ) ) ;
const compRatioControl = createEqSliderControl ( "Compressor Ratio" , 1.2 , 6 , 0.1 , initial . compressorRa tio) ;
wrap . appendChild ( eqSec . sec tion ) ;
const gateThresholdControl = createEqSliderControl ( "Gate Threshold (dB)" , - 70 , - 20 , 1 , initial . gateThreshold ) ;
const gateReleaseControl = createEqSliderControl ( "Gate Release (ms)" , 60 , 500 , 10 , initial . gateRelease ) ;
wrap . appendChild ( hpControl . row ) ;
wrap . appendChild ( lpControl . row ) ;
wrap . appendChild ( midsControl . row ) ;
wrap . appendChild ( presenceControl . row ) ;
const deesserEnabledRow = createEqCheckboxRow ( "De-Esser aktivieren" , initial . deesserEnabled ) ;
wrap . appendChild ( deesserEnabledRow . row ) ;
wrap . appendChild ( deesserFreqControl . row ) ;
wrap . appendChild ( deesserCutControl . row ) ;
const compressorEnabledRow = createEqCheckboxRow ( "Compressor aktivieren" , initial . compressorEnabled ) ;
wrap . appendChild ( compressorEnabledRow . row ) ;
wrap . appendChild ( compThresholdControl . row ) ;
wrap . appendChild ( compRatioControl . row ) ;
const gateEnabledRow = createEqCheckboxRow ( "Noise Gate aktivieren" , initial . gateEnabled ) ;
wrap . appendChild ( gateEnabledRow . row ) ;
wrap . appendChild ( gateThresholdControl . row ) ;
wrap . appendChild ( gateReleaseControl . row ) ;
const limiterRow = document . createElement ( "label" ) ;
// --- Section: De-Esser (toggle) ---
limiterRow . className = "plugin-eq-checkbox" ;
const deSec = createEqSection ( "De-Esser" , {
const limiterInput = document . createElement ( "input" ) ;
subtitle : "Zähmt harte Zischlaute – wirkt nur innerhalb des Durchlassbands." ,
limiterInput . type = "checkbox" ;
toggle : true ,
limiterInput . checked = Boolean ( initial . limiter ) ;
toggleChecked : initial . deesserEnabled
limiterRow . appendChild ( limiterInput ) ;
} ) ;
const limiterText = document . createElement ( "span ") ;
const deesserFreqControl = createEqSliderControl ( "Frequenz" , 2200 , 3400 , 50 , initial . deesserFreq , "Hz ") ;
limiterText . tex tContent = "Limiter aktivieren (alimiter=0.95)" ;
const deesserCu tControl = createEqSliderControl ( "Absenkung" , 0 , 8 , 0.5 , initial . deesserCut , "dB" ) ;
limiterRow . appendChild ( limiterText ) ;
[ deesserFreqControl , deesserCutControl ] . forEach ( ( c ) => deSec . body . appendChild ( c . row ) ) ;
wrap . appendChild ( limiterRow ) ;
wrap . appendChild ( deSec . section ) ;
const preview = document . createElement ( "small" ) ;
// --- Section: Dynamik (compressor + gate + limiter) ---
preview . className = "muted" ;
const dynSec = createEqSection ( "Dynamik" , { subtitle : "Kompression, Rauschsperre und Übersteuerungsschutz." } ) ;
wrap . appendChild ( preview ) ;
const compToggle = createEqToggle ( "Kompressor" , initial . compressorEnabled ) ;
compToggle . row . classList . add ( "eq-sub-toggle" ) ;
dynSec . body . appendChild ( compToggle . row ) ;
const compThresholdControl = createEqSliderControl ( "Threshold" , - 40 , - 6 , 1 , initial . compressorThreshold , "dB" ) ;
const compRatioControl = createEqSliderControl ( "Ratio" , 1.2 , 6 , 0.1 , initial . compressorRatio , ":1" ) ;
[ compThresholdControl , compRatioControl ] . forEach ( ( c ) => dynSec . body . appendChild ( c . row ) ) ;
const gateToggle = createEqToggle ( "Noise Gate" , initial . gateEnabled ) ;
gateToggle . row . classList . add ( "eq-sub-toggle" ) ;
dynSec . body . appendChild ( gateToggle . row ) ;
const gateThresholdControl = createEqSliderControl ( "Gate Threshold" , - 70 , - 20 , 1 , initial . gateThreshold , "dB" ) ;
const gateReleaseControl = createEqSliderControl ( "Gate Release" , 60 , 500 , 10 , initial . gateRelease , "ms" ) ;
[ gateThresholdControl , gateReleaseControl ] . forEach ( ( c ) => dynSec . body . appendChild ( c . row ) ) ;
const limiterToggle = createEqToggle ( "Limiter · alimiter=0.95" , initial . limiter ) ;
limiterToggle . row . classList . add ( "eq-sub-toggle" ) ;
dynSec . body . appendChild ( limiterToggle . row ) ;
wrap . appendChild ( dynSec . section ) ;
// --- Section: Pegel-Normalisierung (speechnorm, toggle) ---
const normSec = createEqSection ( "Pegel-Normalisierung" , {
subtitle : "Auto-Level: gleicher Sendepegel aus jedem Browser und Mikrofon (speechnorm)." ,
toggle : true ,
toggleChecked : initial . normalizerEnabled
} ) ;
const normPeakControl = createEqSliderControl ( "Ziel-Pegel" , 50 , 99 , 1 , initial . normalizerPeak , "%" ) ;
const normGainControl = createEqSliderControl ( "Max. Anhebung" , 0 , 30 , 1 , initial . normalizerMaxGain , "dB" ) ;
[ normPeakControl , normGainControl ] . forEach ( ( c ) => normSec . body . appendChild ( c . row ) ) ;
const normNote = document . createElement ( "p" ) ;
normNote . className = "eq-note" ;
normNote . textContent = "Tipp: Für konstanten Pegel das Noise Gate aktiv lassen – sonst wird Grundrauschen in Sprechpausen mit angehoben." ;
normSec . body . appendChild ( normNote ) ;
wrap . appendChild ( normSec . section ) ;
// --- Live preview of the generated chain ---
const previewWrap = document . createElement ( "div" ) ;
previewWrap . className = "eq-preview" ;
const previewLabel = document . createElement ( "span" ) ;
previewLabel . className = "eq-preview-label" ;
previewLabel . textContent = "FFmpeg-Kette" ;
const preview = document . createElement ( "code" ) ;
preview . className = "eq-preview-code" ;
previewWrap . appendChild ( previewLabel ) ;
previewWrap . appendChild ( preview ) ;
wrap . appendChild ( previewWrap ) ;
const saveButton = settingsForm . querySelector ( 'button[type="submit"]' ) ;
const saveButton = settingsForm . querySelector ( 'button[type="submit"]' ) ;
if ( saveButton && saveButton . parentNode === settingsForm ) {
if ( saveButton && saveButton . parentNode === settingsForm ) {
@@ -3927,55 +3940,51 @@ function maybeAttachMicrohamEqBuilder(settingsForm, settingFields, plugin) {
settingsForm . appendChild ( wrap ) ;
settingsForm . appendChild ( wrap ) ;
}
}
const syncDisabled = ( ) => {
deesserFreqControl . setDisabled ( ! deSec . toggle . input . checked ) ;
deesserCutControl . setDisabled ( ! deSec . toggle . input . checked ) ;
deSec . section . classList . toggle ( "is-active" , deSec . toggle . input . checked ) ;
compThresholdControl . setDisabled ( ! compToggle . input . checked ) ;
compRatioControl . setDisabled ( ! compToggle . input . checked ) ;
gateThresholdControl . setDisabled ( ! gateToggle . input . checked ) ;
gateReleaseControl . setDisabled ( ! gateToggle . input . checked ) ;
normPeakControl . setDisabled ( ! normSec . toggle . input . checked ) ;
normGainControl . setDisabled ( ! normSec . toggle . input . checked ) ;
normSec . section . classList . toggle ( "is-active" , normSec . toggle . input . checked ) ;
} ;
const applyControlsToExtraArgs = ( ) => {
const applyControlsToExtraArgs = ( ) => {
const hp = Number ( hpControl . number . value ) ;
const lp = Number ( lpControl . number . value ) ;
const mids = Number ( midsControl . number . value ) ;
const presence = Number ( presenceControl . number . value ) ;
const deesserEnabled = Boolean ( deesserEnabledRow . input . checked ) ;
const deesserFreq = Number ( deesserFreqControl . number . value ) ;
const deesserCut = Number ( deesserCutControl . number . value ) ;
const compressorEnabled = Boolean ( compressorEnabledRow . input . checked ) ;
const compressorThreshold = Number ( compThresholdControl . number . value ) ;
const compressorRatio = Number ( compRatioControl . number . value ) ;
const gateEnabled = Boolean ( gateEnabledRow . input . checked ) ;
const gateThreshold = Number ( gateThresholdControl . number . value ) ;
const gateRelease = Number ( gateReleaseControl . number . value ) ;
const limiter = Boolean ( limiterInput . checked ) ;
const args = buildMicrohamEqExtraArgs ( {
const args = buildMicrohamEqExtraArgs ( {
hp ,
hp : Number ( hpControl . number . value ) ,
lp ,
lp : Number ( lpControl . number . value ) ,
mids ,
mids : Number ( midsControl . number . value ) ,
presence ,
presence : Number ( presenceControl . number . value ) ,
deesserEnabled ,
deesserEnabled : Boolean ( deSec . toggle . input . checked ) ,
deesserFreq ,
deesserFreq : Number ( deesserFreqControl . number . value ) ,
deesserCut ,
deesserCut : Number ( deesserCutControl . number . value ) ,
compressorEnabled ,
compressorEnabled : Boolean ( compToggle . input . checked ) ,
compressorThreshold ,
compressorThreshold : Number ( compThresholdControl . number . value ) ,
compressorRatio ,
compressorRatio : Number ( compRatioControl . number . value ) ,
gateEnabled ,
gateEnabled : Boolean ( gateToggle . input . checked ) ,
gateThreshold ,
gateThreshold : Number ( gateThresholdControl . number . value ) ,
gateRelease ,
gateRelease : Number ( gateReleaseControl . number . value ) ,
limiter
normalizerEnabled : Boolean ( normSec . toggle . input . checked ) ,
normalizerPeak : Number ( normPeakControl . number . value ) ,
normalizerMaxGain : Number ( normGainControl . number . value ) ,
limiter : Boolean ( limiterToggle . input . checked )
} ) ;
} ) ;
extraArgsField . element . value = args ;
extraArgsField . element . value = args ;
preview . textContent = ` Generierte Extra-Args: ${ args } ` ;
preview . textContent = args ;
} ;
} ;
const updatePresetInfo = ( ) => {
const sliderControls = [
const selected = String ( presetSelect . value || "flat" ) ;
hpControl , lpControl , midsControl , presenceControl ,
if ( selected === "dx" ) {
deesserFreqControl , deesserCutControl ,
presetInfo . textContent = "DX: sprachfokussiert mit reduziertem Bass und moderater Praesenz, weniger kuenstlich abgestimmt." ;
compThresholdControl , compRatioControl ,
return ;
gateThresholdControl , gateReleaseControl ,
}
normPeakControl , normGainControl
if ( selected === "ragchew" ) {
] ;
presetInfo . textContent = "Ragchew: natuerlicher Sprachklang mit leicht reduziertem Bass und sanfter Mitten/Praesenz-Anhebung." ;
sliderControls . forEach ( ( control ) => {
return ;
}
presetInfo . textContent = "Flat: neutrale Basis mit wenig Klangfaerbung, gut fuer Vergleich und Feintuning." ;
} ;
const bindControlUpdate = ( control ) => {
control . range . addEventListener ( "input" , ( ) => {
control . range . addEventListener ( "input" , ( ) => {
control . number . value = control . range . value ;
control . number . value = control . range . value ;
applyControlsToExtraArgs ( ) ;
applyControlsToExtraArgs ( ) ;
@@ -3984,99 +3993,156 @@ function maybeAttachMicrohamEqBuilder(settingsForm, settingFields, plugin) {
control . range . value = control . number . value ;
control . range . value = control . number . value ;
applyControlsToExtraArgs ( ) ;
applyControlsToExtraArgs ( ) ;
} ) ;
} ) ;
} ;
bindControlUpdate ( hpControl ) ;
bindControlUpdate ( lpControl ) ;
bindControlUpdate ( midsControl ) ;
bindControlUpdate ( presenceControl ) ;
bindControlUpdate ( deesserFreqControl ) ;
bindControlUpdate ( deesserCutControl ) ;
bindControlUpdate ( compThresholdControl ) ;
bindControlUpdate ( compRatioControl ) ;
bindControlUpdate ( gateThresholdControl ) ;
bindControlUpdate ( gateReleaseControl ) ;
limiterInput . addEventListener ( "change" , applyControlsToExtraArgs ) ;
deesserEnabledRow . input . addEventListener ( "change" , applyControlsToExtraArgs ) ;
compressorEnabledRow . input . addEventListener ( "change" , applyControlsToExtraArgs ) ;
gateEnabledRow . input . addEventListener ( "change" , applyControlsToExtraArgs ) ;
presetBtn . addEventListener ( "click" , ( ) => {
const selected = presets [ presetSelect . value ] || presets . flat ;
hpControl . range . value = String ( selected . hp ) ;
hpControl . number . value = String ( selected . hp ) ;
lpControl . range . value = String ( selected . lp ) ;
lpControl . number . value = String ( selected . lp ) ;
midsControl . range . value = String ( selected . mids ) ;
midsControl . number . value = String ( selected . mids ) ;
presenceControl . range . value = String ( selected . presence ) ;
presenceControl . number . value = String ( selected . presence ) ;
deesserEnabledRow . input . checked = Boolean ( selected . deesserEnabled ) ;
deesserFreqControl . range . value = String ( selected . deesserFreq ) ;
deesserFreqControl . number . value = String ( selected . deesserFreq ) ;
deesserCutControl . range . value = String ( selected . deesserCut ) ;
deesserCutControl . number . value = String ( selected . deesserCut ) ;
compressorEnabledRow . input . checked = Boolean ( selected . compressorEnabled ) ;
compThresholdControl . range . value = String ( selected . compressorThreshold ) ;
compThresholdControl . number . value = String ( selected . compressorThreshold ) ;
compRatioControl . range . value = String ( selected . compressorRatio ) ;
compRatioControl . number . value = String ( selected . compressorRatio ) ;
gateEnabledRow . input . checked = Boolean ( selected . gateEnabled ) ;
gateThresholdControl . range . value = String ( selected . gateThreshold ) ;
gateThresholdControl . number . value = String ( selected . gateThreshold ) ;
gateReleaseControl . range . value = String ( selected . gateRelease ) ;
gateReleaseControl . number . value = String ( selected . gateRelease ) ;
limiterInput . checked = Boolean ( selected . limiter ) ;
applyControlsToExtraArgs ( ) ;
} ) ;
} ) ;
presetSelect . addEventListener ( "change" , updatePresetInfo ) ;
[ deSec . toggle . input , compToggle . input , gateToggle . input , limiterToggle . input , normSec . toggle . input ] . forEach ( ( input ) => {
input . addEventListener ( "change" , ( ) => {
syncDisabled ( ) ;
applyControlsToExtraArgs ( ) ;
} ) ;
} ) ;
preview . textContent = ` Aktuelle Extra-Args: ${ extraArgsField . element . value || "<leer>" } ` ;
const setActiveChip = ( key ) => {
updatePresetInfo ( ) ;
presetOrder . forEach ( ( k ) => presetButtons [ k ] . classList . toggle ( "is-active" , k === key ) ) ;
presetInfo . textContent = key && presets [ key ] ? presets [ key ] . desc : "Eigene Einstellung – weicht von den Presets ab." ;
} ;
const applyPreset = ( key ) => {
const selected = presets [ key ] || presets . flat ;
const set = ( control , value ) => {
control . range . value = String ( value ) ;
control . number . value = String ( value ) ;
} ;
set ( hpControl , selected . hp ) ;
set ( lpControl , selected . lp ) ;
set ( midsControl , selected . mids ) ;
set ( presenceControl , selected . presence ) ;
deSec . toggle . input . checked = Boolean ( selected . deesserEnabled ) ;
set ( deesserFreqControl , selected . deesserFreq ) ;
set ( deesserCutControl , selected . deesserCut ) ;
compToggle . input . checked = Boolean ( selected . compressorEnabled ) ;
set ( compThresholdControl , selected . compressorThreshold ) ;
set ( compRatioControl , selected . compressorRatio ) ;
gateToggle . input . checked = Boolean ( selected . gateEnabled ) ;
set ( gateThresholdControl , selected . gateThreshold ) ;
set ( gateReleaseControl , selected . gateRelease ) ;
limiterToggle . input . checked = Boolean ( selected . limiter ) ;
normSec . toggle . input . checked = Boolean ( selected . normalizerEnabled ) ;
set ( normPeakControl , selected . normalizerPeak ) ;
set ( normGainControl , selected . normalizerMaxGain ) ;
syncDisabled ( ) ;
applyControlsToExtraArgs ( ) ;
setActiveChip ( key ) ;
} ;
presetOrder . forEach ( ( key ) => {
presetButtons [ key ] . addEventListener ( "click" , ( ) => applyPreset ( key ) ) ;
} ) ;
syncDisabled ( ) ;
preview . textContent = extraArgsField . element . value || "– " ;
setActiveChip ( parsed ? null : "flat" ) ;
}
}
function createEqSliderControl ( labelText , min , max , step , value ) {
function createEqSliderControl ( labelText , min , max , step , value , unit ) {
const row = document . createElement ( "div" ) ;
const row = document . createElement ( "div" ) ;
row . className = "plugin-eq- row " ;
row . className = "eq-cont rol " ;
const head = document . createElement ( "div" ) ;
head . className = "eq-control-head" ;
const label = document . createElement ( "span" ) ;
const label = document . createElement ( "span" ) ;
label . className = "muted " ;
label . className = "eq-control-label " ;
label . textContent = labelText ;
label . textContent = labelText ;
row . appendChild ( label ) ;
head . appendChild ( label ) ;
const number = document . createElement ( "input" ) ;
number . type = "number" ;
number . className = "eq-control-value" ;
number . min = String ( min ) ;
number . max = String ( max ) ;
number . step = String ( step ) ;
number . value = String ( value ) ;
head . appendChild ( number ) ;
if ( unit ) {
const unitEl = document . createElement ( "span" ) ;
unitEl . className = "eq-control-unit" ;
unitEl . textContent = unit ;
head . appendChild ( unitEl ) ;
}
row . appendChild ( head ) ;
const range = document . createElement ( "input" ) ;
const range = document . createElement ( "input" ) ;
range . type = "range" ;
range . type = "range" ;
range . className = "eq-range" ;
range . min = String ( min ) ;
range . min = String ( min ) ;
range . max = String ( max ) ;
range . max = String ( max ) ;
range . step = String ( step ) ;
range . step = String ( step ) ;
range . value = String ( value ) ;
range . value = String ( value ) ;
row . appendChild ( range ) ;
row . appendChild ( range ) ;
const number = document . createElement ( "input" ) ;
const setDisabled = ( disabled ) => {
number . type = "number" ;
const flag = Boolean ( disabled ) ;
number . min = String ( min ) ;
row . classList . toggle ( "is-disabled" , flag ) ;
number . max = String ( max ) ;
range . disabled = flag ;
number . step = String ( step ) ;
number . disabled = flag ;
number . value = String ( value ) ;
} ;
row . appendChild ( number ) ;
return { row , range , number } ;
return { row , range , number , setDisabled } ;
}
}
function createEqCheckboxRow ( labelText , checked ) {
function createEqToggle ( labelText , checked ) {
const row = document . createElement ( "label" ) ;
const row = document . createElement ( "label" ) ;
row . className = "plugin-eq-checkbox " ;
row . className = "eq-switch " ;
const input = document . createElement ( "input" ) ;
const input = document . createElement ( "input" ) ;
input . type = "checkbox" ;
input . type = "checkbox" ;
input . checked = Boolean ( checked ) ;
input . checked = Boolean ( checked ) ;
const track = document . createElement ( "span" ) ;
track . className = "eq-switch-track" ;
track . setAttribute ( "aria-hidden" , "true" ) ;
const text = document . createElement ( "span" ) ;
text . className = "eq-switch-label" ;
text . textContent = labelText ;
row . appendChild ( input ) ;
row . appendChild ( input ) ;
const label = document . createElement ( "span" ) ;
row . appendChild ( track ) ;
label . textContent = labelText ;
row . appendChild ( text ) ;
row . appendChild ( label ) ;
return { row , input } ;
return { row , input } ;
}
}
function createEqSection ( titleText , options ) {
const opts = options || { } ;
const section = document . createElement ( "section" ) ;
section . className = "eq-section" ;
const header = document . createElement ( "div" ) ;
header . className = "eq-section-head" ;
const title = document . createElement ( "h4" ) ;
title . className = "eq-section-title" ;
title . textContent = titleText ;
header . appendChild ( title ) ;
let toggle = null ;
if ( opts . toggle ) {
toggle = createEqToggle ( opts . toggleLabel || "aktiv" , opts . toggleChecked ) ;
toggle . row . classList . add ( "eq-section-toggle" ) ;
header . appendChild ( toggle . row ) ;
}
section . appendChild ( header ) ;
if ( opts . subtitle ) {
const sub = document . createElement ( "p" ) ;
sub . className = "eq-section-sub" ;
sub . textContent = opts . subtitle ;
section . appendChild ( sub ) ;
}
const body = document . createElement ( "div" ) ;
body . className = "eq-section-body" ;
section . appendChild ( body ) ;
return { section , body , toggle } ;
}
function buildMicrohamEqExtraArgs ( {
function buildMicrohamEqExtraArgs ( {
hp ,
hp ,
lp ,
lp ,
@@ -4091,6 +4157,9 @@ function buildMicrohamEqExtraArgs({
gateEnabled ,
gateEnabled ,
gateThreshold ,
gateThreshold ,
gateRelease ,
gateRelease ,
normalizerEnabled ,
normalizerPeak ,
normalizerMaxGain ,
limiter
limiter
} ) {
} ) {
const filters = [
const filters = [
@@ -4100,7 +4169,7 @@ function buildMicrohamEqExtraArgs({
` equalizer=f=2100:t=q:w=0.9:g= ${ Number ( Number ( presence || 0 ) . toFixed ( 1 ) ) } `
` equalizer=f=2100:t=q:w=0.9:g= ${ Number ( Number ( presence || 0 ) . toFixed ( 1 ) ) } `
] ;
] ;
if ( deesserEnabled && Number ( deesserCut || 0 ) > 0 ) {
if ( deesserEnabled && Number ( deesserCut || 0 ) > 0 ) {
const freq = Math . round ( Number ( deesserFreq ) || 5 200) ;
const freq = Math . round ( Number ( deesserFreq ) || 29 00 ) ;
const cut = Number ( Number ( - Math . abs ( Number ( deesserCut || 0 ) ) ) . toFixed ( 1 ) ) ;
const cut = Number ( Number ( - Math . abs ( Number ( deesserCut || 0 ) ) ) . toFixed ( 1 ) ) ;
filters . push ( ` equalizer=f= ${ freq } :t=q:w=1.1:g= ${ cut } ` ) ;
filters . push ( ` equalizer=f= ${ freq } :t=q:w=1.1:g= ${ cut } ` ) ;
}
}
@@ -4114,7 +4183,17 @@ function buildMicrohamEqExtraArgs({
const gateThresholdDb = Number ( Number ( gateThreshold || - 55 ) . toFixed ( 1 ) ) ;
const gateThresholdDb = Number ( Number ( gateThreshold || - 55 ) . toFixed ( 1 ) ) ;
const gateThresholdLinear = Number ( Math . pow ( 10 , gateThresholdDb / 20 ) . toFixed ( 6 ) ) ;
const gateThresholdLinear = Number ( Math . pow ( 10 , gateThresholdDb / 20 ) . toFixed ( 6 ) ) ;
const release = Math . round ( Number ( gateRelease || 180 ) ) ;
const release = Math . round ( Number ( gateRelease || 180 ) ) ;
filters . push ( ` agate=threshold= ${ gateThresholdLinear } :release= ${ release } ` ) ;
filters . push ( ` agate=threshold= ${ gateThresholdLinear } :attack=5: release= ${ release } ` ) ;
}
// Adaptive level normalisation (speechnorm): lifts quiet inputs toward a target
// peak so every browser/microphone reaches the same TX level. Placed after the
// gate so background noise in pauses is cut first and not boosted.
if ( normalizerEnabled ) {
const peakPct = Math . min ( 99 , Math . max ( 50 , Math . round ( Number ( normalizerPeak || 90 ) ) ) ) ;
const peak = Number ( ( peakPct / 100 ) . toFixed ( 2 ) ) ;
const gainDb = Math . min ( 30 , Math . max ( 0 , Math . round ( Number ( normalizerMaxGain || 12 ) ) ) ) ;
const expansion = Math . max ( 1 , Number ( Math . pow ( 10 , gainDb / 20 ) . toFixed ( 2 ) ) ) ;
filters . push ( ` speechnorm=p= ${ peak } :e= ${ expansion } :l=1 ` ) ;
}
}
if ( limiter ) {
if ( limiter ) {
filters . push ( "alimiter=limit=0.95" ) ;
filters . push ( "alimiter=limit=0.95" ) ;
@@ -4149,10 +4228,37 @@ function parseMicrohamEqArgs(value) {
const compressorEnabled = Boolean ( compMatch ) ;
const compressorEnabled = Boolean ( compMatch ) ;
const compressorThreshold = compMatch ? Number ( ( 20 * Math . log10 ( Number ( compMatch [ 1 ] ) ) ) . toFixed ( 1 ) ) : - 20 ;
const compressorThreshold = compMatch ? Number ( ( 20 * Math . log10 ( Number ( compMatch [ 1 ] ) ) ) . toFixed ( 1 ) ) : - 20 ;
const compressorRatio = compMatch ? Number ( compMatch [ 2 ] ) : 3 ;
const compressorRatio = compMatch ? Number ( compMatch [ 2 ] ) : 3 ;
const gateMatch = text . match ( /agate\s*=\s*threshold\s*=\s*([0-9]+(?:\.[0-9]+)?)\s*:\s*release\s*=\s*([0-9]+(?:\.[0-9]+)? )/i ) ;
const gateToken Match = text . match ( /agate\s*=\s*([^",]+ )/i ) ;
const gateEnabled = Boolean ( gateMatch ) ;
const gateEnabled = Boolean ( gateToken Match ) ;
cons t gateThreshold = gateMatch ? Number ( ( 20 * Math . log10 ( Number ( gateMatch [ 1 ] ) ) ) . toFixed ( 1 ) ) : - 55 ;
le t gateThreshold = - 55 ;
cons t gateRelease = gateMatch ? Number ( gateMatch [ 2 ] ) : 180 ;
le t gateRelease = 180 ;
if ( gateTokenMatch ) {
const gateStr = gateTokenMatch [ 1 ] ;
const gateThrMatch = gateStr . match ( /(?:^|:)\s*threshold\s*=\s*([0-9]*\.?[0-9]+)/i ) ;
const gateRelMatch = gateStr . match ( /(?:^|:)\s*release\s*=\s*([0-9]*\.?[0-9]+)/i ) ;
if ( gateThrMatch ) {
gateThreshold = Number ( ( 20 * Math . log10 ( Number ( gateThrMatch [ 1 ] ) ) ) . toFixed ( 1 ) ) ;
}
if ( gateRelMatch ) {
gateRelease = Number ( gateRelMatch [ 1 ] ) ;
}
}
const normMatch = text . match ( /speechnorm\s*=\s*([^",]+)/i ) ;
const normalizerEnabled = Boolean ( normMatch ) ;
let normalizerPeak = 90 ;
let normalizerMaxGain = 12 ;
if ( normMatch ) {
const normStr = normMatch [ 1 ] ;
const peakMatch = normStr . match ( /(?:^|:)\s*(?:peak|p)\s*=\s*([0-9]*\.?[0-9]+)/i ) ;
const expMatch = normStr . match ( /(?:^|:)\s*(?:expansion|e)\s*=\s*([0-9]*\.?[0-9]+)/i ) ;
if ( peakMatch ) {
normalizerPeak = Math . round ( Number ( peakMatch [ 1 ] ) * 100 ) ;
}
if ( expMatch ) {
const exp = Number ( expMatch [ 1 ] ) ;
normalizerMaxGain = exp > 0 ? Math . round ( 20 * Math . log10 ( exp ) ) : 0 ;
}
}
const limiter = /alimiter\s*=\s*limit\s*=\s*0\.95/i . test ( text ) ;
const limiter = /alimiter\s*=\s*limit\s*=\s*0\.95/i . test ( text ) ;
return {
return {
hp ,
hp ,
@@ -4168,6 +4274,9 @@ function parseMicrohamEqArgs(value) {
gateEnabled ,
gateEnabled ,
gateThreshold ,
gateThreshold ,
gateRelease ,
gateRelease ,
normalizerEnabled ,
normalizerPeak ,
normalizerMaxGain ,
limiter
limiter
} ;
} ;
}
}