From 103fdd6bbc9e9ce647584fe48815b3ed6fd84fe0 Mon Sep 17 00:00:00 2001 From: Remi <73385395+itsrubberduck@users.noreply.github.com> Date: Fri, 19 Sep 2025 23:00:58 +0200 Subject: [PATCH] feat: improve learn experience --- server/api/learn/state.put.ts | 2 +- shared/utils/pizzicatoLite.ts | 16 +++++++- shared/utils/radioEffects.ts | 76 ++++++++++++++++++----------------- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/server/api/learn/state.put.ts b/server/api/learn/state.put.ts index 7223ef7..5f2941c 100644 --- a/server/api/learn/state.put.ts +++ b/server/api/learn/state.put.ts @@ -65,7 +65,7 @@ function sanitizeConfig(input: Partial | undefined): LearnConfig | } if (typeof input.audioSpeed === 'number' && Number.isFinite(input.audioSpeed)) { - const rounded = Math.round(input.audioSpeed * 10) / 10 + const rounded = Math.round(input.audioSpeed * 20) / 20 config.audioSpeed = Math.min(1.3, Math.max(0.7, rounded)) } diff --git a/shared/utils/pizzicatoLite.ts b/shared/utils/pizzicatoLite.ts index 77bb0ab..d3bffc6 100644 --- a/shared/utils/pizzicatoLite.ts +++ b/shared/utils/pizzicatoLite.ts @@ -268,11 +268,16 @@ class TremoloEffect implements EffectNode { private modulationGain: GainNode private started = false private depth: number + private offset?: number + private randomizePhase: boolean inputNode: AudioNode outputNode: AudioNode - constructor(context: AudioContext, options: { speed?: number; depth?: number; type?: OscillatorType } = {}) { + constructor( + context: AudioContext, + options: { speed?: number; depth?: number; type?: OscillatorType; offset?: number; randomizePhase?: boolean } = {} + ) { this.context = context this.input = context.createGain() this.output = context.createGain() @@ -286,6 +291,8 @@ class TremoloEffect implements EffectNode { this.oscillator = context.createOscillator() this.oscillator.type = options.type ?? 'sine' this.oscillator.frequency.value = options.speed ?? 5 + this.offset = options.offset + this.randomizePhase = options.randomizePhase !== false this.input.connect(this.output) this.oscillator.connect(this.modulationGain) @@ -298,7 +305,12 @@ class TremoloEffect implements EffectNode { onActivate() { if (this.started) return try { - this.oscillator.start() + let offset = this.offset ?? 0 + if (this.offset === undefined && this.randomizePhase) { + const frequency = Math.max(0.0001, this.oscillator.frequency.value) + offset = Math.random() * (1 / frequency) + } + this.oscillator.start(0, offset) this.started = true } catch { // oscillator already started diff --git a/shared/utils/radioEffects.ts b/shared/utils/radioEffects.ts index 0e0635e..407f9c9 100644 --- a/shared/utils/radioEffects.ts +++ b/shared/utils/radioEffects.ts @@ -8,7 +8,7 @@ export type ReadabilityProfile = { } presence?: { frequency: number; q: number; gain: number } distortions: number[] - tremolos?: Array<{ depth: number; speed: number; type?: OscillatorType }> + tremolos?: Array<{ depth: number; speed: number; type?: OscillatorType; offset?: number; randomizePhase?: boolean }> gain: number compressor: Partial<{ threshold: number; knee: number; ratio: number; attack: number; release: number }> noise: { amplitude: number; bandFrequency: number; bandQ: number; crackle?: boolean } @@ -16,52 +16,56 @@ export type ReadabilityProfile = { const readabilityProfiles: Record = { 1: { - eq: { highpass: 520, highpassQ: 1.1, lowpass: 1700, lowpassQ: 0.85, bandpass: { frequency: 1100, q: 2.6 } }, - presence: { frequency: 1300, q: 2.1, gain: 4 }, - distortions: [420, 260], + eq: { highpass: 540, highpassQ: 1.2, lowpass: 1600, lowpassQ: 0.8, bandpass: { frequency: 1150, q: 2.8 } }, + presence: { frequency: 1350, q: 2.4, gain: 5 }, + distortions: [360, 240], tremolos: [ - { depth: 0.92, speed: 7.6, type: 'square' }, - { depth: 0.38, speed: 5.1 } + { depth: 0.7, speed: 4.9, type: 'sine' }, + { depth: 0.28, speed: 9.4, type: 'triangle' }, + { depth: 0.2, speed: 2.6, type: 'sine' } ], - gain: 0.84, - compressor: { threshold: -32, ratio: 14, attack: 0.004, release: 0.3 }, - noise: { amplitude: 0.12, bandFrequency: 1500, bandQ: 1.6, crackle: true } + gain: 0.82, + compressor: { threshold: -33, ratio: 16, attack: 0.004, release: 0.32 }, + noise: { amplitude: 0.13, bandFrequency: 1500, bandQ: 1.6, crackle: true } }, 2: { - eq: { highpass: 440, highpassQ: 0.95, lowpass: 2100, lowpassQ: 0.9, bandpass: { frequency: 1650, q: 1.9 } }, - presence: { frequency: 1700, q: 1.2, gain: 2.5 }, - distortions: [320], - tremolos: [{ depth: 0.24, speed: 5.2 }], - gain: 0.88, - compressor: { threshold: -30, ratio: 13, attack: 0.0035, release: 0.28 }, - noise: { amplitude: 0.085, bandFrequency: 1800, bandQ: 1.4, crackle: true } + eq: { highpass: 470, highpassQ: 1, lowpass: 2100, lowpassQ: 0.85, bandpass: { frequency: 1700, q: 2 } }, + presence: { frequency: 1750, q: 1.3, gain: 3 }, + distortions: [280, 160], + tremolos: [ + { depth: 0.42, speed: 5.6, type: 'sine' }, + { depth: 0.24, speed: 3.3, type: 'triangle' } + ], + gain: 0.86, + compressor: { threshold: -30, ratio: 14, attack: 0.0038, release: 0.3 }, + noise: { amplitude: 0.09, bandFrequency: 1850, bandQ: 1.35, crackle: true } }, 3: { - eq: { highpass: 360, highpassQ: 0.8, lowpass: 2600, lowpassQ: 0.9, bandpass: { frequency: 1850, q: 1.5 } }, - presence: { frequency: 2100, q: 1.3, gain: 1.8 }, - distortions: [220], - tremolos: [{ depth: 0.14, speed: 4.5 }], + eq: { highpass: 380, highpassQ: 0.85, lowpass: 2600, lowpassQ: 0.88, bandpass: { frequency: 1900, q: 1.5 } }, + presence: { frequency: 2150, q: 1.3, gain: 2 }, + distortions: [210], + tremolos: [{ depth: 0.18, speed: 4.2, type: 'sine' }], gain: 0.9, - compressor: { threshold: -28, ratio: 12, attack: 0.003, release: 0.26 }, - noise: { amplitude: 0.055, bandFrequency: 1900, bandQ: 1.2 } + compressor: { threshold: -28, ratio: 12, attack: 0.0032, release: 0.26 }, + noise: { amplitude: 0.055, bandFrequency: 1950, bandQ: 1.2 } }, 4: { - eq: { highpass: 310, highpassQ: 0.7, lowpass: 3050, lowpassQ: 0.85, bandpass: { frequency: 2000, q: 1.2 } }, - presence: { frequency: 2300, q: 1.4, gain: 1.4 }, - distortions: [140], - tremolos: [{ depth: 0.08, speed: 3.6 }], - gain: 0.93, - compressor: { threshold: -27, ratio: 11, attack: 0.0028, release: 0.23 }, - noise: { amplitude: 0.035, bandFrequency: 2000, bandQ: 1.1 } + eq: { highpass: 310, highpassQ: 0.7, lowpass: 3150, lowpassQ: 0.82, bandpass: { frequency: 2050, q: 1.2 } }, + presence: { frequency: 2350, q: 1.35, gain: 1.5 }, + distortions: [130], + tremolos: [{ depth: 0.08, speed: 3.1, type: 'sine' }], + gain: 0.94, + compressor: { threshold: -27, ratio: 11, attack: 0.0028, release: 0.22 }, + noise: { amplitude: 0.035, bandFrequency: 2050, bandQ: 1.08 } }, 5: { - eq: { highpass: 280, highpassQ: 0.65, lowpass: 3300, lowpassQ: 0.8, bandpass: { frequency: 2150, q: 1.1 } }, - presence: { frequency: 2450, q: 1.5, gain: 1.1 }, - distortions: [90], - tremolos: [{ depth: 0.05, speed: 3 }], - gain: 0.96, - compressor: { threshold: -26, ratio: 10, attack: 0.0025, release: 0.2 }, - noise: { amplitude: 0.02, bandFrequency: 2100, bandQ: 1 } + eq: { highpass: 260, highpassQ: 0.6, lowpass: 3600, lowpassQ: 0.78, bandpass: { frequency: 2250, q: 1.05 } }, + presence: { frequency: 2550, q: 1.5, gain: 1.1 }, + distortions: [60], + tremolos: [{ depth: 0.04, speed: 2.4, type: 'sine' }], + gain: 0.98, + compressor: { threshold: -25, ratio: 9.5, attack: 0.0025, release: 0.18 }, + noise: { amplitude: 0.016, bandFrequency: 2150, bandQ: 1 } } }