diff --git a/src/App.tsx b/src/App.tsx index b80539b9..252c770a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import { LFOPanel } from './components/LFOPanel' import { AudioContextWarning } from './components/AudioContextWarning' import { HelpModal } from './components/HelpModal' import { getSampleRateFromIndex } from './config/effects' -import { engineSettings, effectSettings, lfoSettings } from './stores/settings' +import { engineSettings, effectSettings, lfoSettings, type LFOConfig } from './stores/settings' import { exitMappingMode } from './stores/mappingMode' import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts' import { useTileParams } from './hooks/useTileParams' @@ -118,7 +118,7 @@ function App() { const params = newRandomized as TileState loadTileParams(params) - playbackManagerRef.current.setEffects(params.effectParams as any) + playbackManagerRef.current.setEffects(params.effectParams) playbackManagerRef.current.setVariables( params.engineParams.a ?? DEFAULT_VARIABLES.a, params.engineParams.b ?? DEFAULT_VARIABLES.b, @@ -222,7 +222,7 @@ function App() { } const handleEffectChange = (parameterId: string, value: number | boolean | string) => { - effectSettings.setKey(parameterId as any, value as any) + effectSettings.setKey(parameterId as keyof typeof effectValues, value as never) saveCurrentTileParams() if (playbackManagerRef.current) { @@ -230,7 +230,7 @@ function App() { } } - const handleLFOChange = (lfoIndex: number, config: any) => { + const handleLFOChange = (lfoIndex: number, config: LFOConfig) => { if (playbackManagerRef.current) { playbackManagerRef.current.setLFOConfig(lfoIndex, config) } @@ -453,7 +453,7 @@ function App() { loadTileParams(randomized) if (playing === PLAYBACK_ID.CUSTOM && playbackManagerRef.current) { - playbackManagerRef.current.setEffects(randomized.effectParams as any) + playbackManagerRef.current.setEffects(randomized.effectParams) playbackManagerRef.current.setVariables( randomized.engineParams.a ?? DEFAULT_VARIABLES.a, randomized.engineParams.b ?? DEFAULT_VARIABLES.b, @@ -483,7 +483,7 @@ function App() { loadTileParams(randomized) if (playing === tileId && playbackManagerRef.current) { - playbackManagerRef.current.setEffects(randomized.effectParams as any) + playbackManagerRef.current.setEffects(randomized.effectParams) playbackManagerRef.current.setVariables( randomized.engineParams.a ?? DEFAULT_VARIABLES.a, randomized.engineParams.b ?? DEFAULT_VARIABLES.b, diff --git a/src/components/EffectsBar.tsx b/src/components/EffectsBar.tsx index 6dbddbbf..a5a75c8b 100644 --- a/src/components/EffectsBar.tsx +++ b/src/components/EffectsBar.tsx @@ -50,9 +50,9 @@ export function EffectsBar({ values, onChange, onMapClick, getMappedLFOs }: Effe {effect.bypassable && ( onChange(`${effect.id}Bypass`, !checked)} - label={Boolean(values[`${effect.id}Bypass`]) ? 'OFF' : 'ON'} + label={values[`${effect.id}Bypass`] ? 'OFF' : 'ON'} /> )} @@ -82,7 +82,7 @@ export function EffectsBar({ values, onChange, onMapClick, getMappedLFOs }: Effe onChange(param.id, checked ? 1 : 0)} - label={Boolean(values[param.id]) ? 'ON' : 'OFF'} + label={values[param.id] ? 'ON' : 'OFF'} /> diff --git a/src/components/EngineControls.tsx b/src/components/EngineControls.tsx index 5618bebd..4513d733 100644 --- a/src/components/EngineControls.tsx +++ b/src/components/EngineControls.tsx @@ -21,9 +21,10 @@ export function EngineControls({ values, onChange, onMapClick, getMappedLFOs }: return getComplexityLabel(value) case 'bitDepth': return getBitDepthLabel(value) - default: + default: { const param = ENGINE_CONTROLS[0].parameters.find(p => p.id === id) return `${value}${param?.unit || ''}` + } } } diff --git a/src/components/HelpModal.tsx b/src/components/HelpModal.tsx index 5cbec738..be544648 100644 --- a/src/components/HelpModal.tsx +++ b/src/components/HelpModal.tsx @@ -23,7 +23,7 @@ export function HelpModal({ onClose, showStartButton = false }: HelpModalProps) BRUITISTE

- A harsh noise soundbox + Harsh noise soundbox made as a love statement to all weird noises, hums, audio glitches and ominous textures. Be careful, lower your volume! Tweak some parameters!

Made by Raphaël Forment (BuboBubo) — raphaelforment.fr diff --git a/src/components/Knob.tsx b/src/components/Knob.tsx index ecd5ce21..6cd2f8dc 100644 --- a/src/components/Knob.tsx +++ b/src/components/Knob.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect } from 'react' +import { useRef, useState, useEffect, useCallback } from 'react' import { useStore } from '@nanostores/react' import { mappingMode } from '../stores/mappingMode' @@ -58,7 +58,7 @@ export function Knob({ e.preventDefault() } - const handleMouseMove = (e: MouseEvent) => { + const handleMouseMove = useCallback((e: MouseEvent) => { if (!isDragging) return const deltaY = startYRef.current - e.clientY @@ -68,11 +68,11 @@ export function Knob({ const steppedValue = Math.round(newValue / step) * step onChange(steppedValue) - } + }, [isDragging, max, min, step, onChange]) - const handleMouseUp = () => { + const handleMouseUp = useCallback(() => { setIsDragging(false) - } + }, []) useEffect(() => { if (isDragging) { @@ -83,7 +83,7 @@ export function Knob({ window.removeEventListener('mouseup', handleMouseUp) } } - }, [isDragging]) + }, [isDragging, handleMouseMove, handleMouseUp]) return (

diff --git a/src/components/LFOScope.tsx b/src/components/LFOScope.tsx index ac2550ac..eb5b09ce 100644 --- a/src/components/LFOScope.tsx +++ b/src/components/LFOScope.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState, useCallback } from 'react' import { useStore } from '@nanostores/react' import { LFO, type LFOWaveform } from '../domain/modulation/LFO' import { mappingMode } from '../stores/mappingMode' @@ -34,6 +34,27 @@ export function LFOScope({ lfoIndex, waveform, frequency, phase, mappings, onCha const dragStartRef = useRef<{ x: number; y: number; freq: number; phase: number; moved: boolean } | null>(null) const mappingModeState = useStore(mappingMode) + const getLFOValueAtPhase = useCallback((phaseVal: number): number => { + const normalizedPhase = phaseVal % 1 + + switch (waveform) { + case 'sine': + return Math.sin(normalizedPhase * 2 * Math.PI) + case 'triangle': + return normalizedPhase < 0.5 + ? -1 + 4 * normalizedPhase + : 3 - 4 * normalizedPhase + case 'square': + return normalizedPhase < 0.5 ? 1 : -1 + case 'sawtooth': + return 2 * normalizedPhase - 1 + case 'random': + return Math.sin(normalizedPhase * 2 * Math.PI) + default: + return 0 + } + }, [waveform]) + useEffect(() => { if (!lfoRef.current) { lfoRef.current = new LFO(new AudioContext(), frequency, phase, waveform) @@ -98,28 +119,7 @@ export function LFOScope({ lfoIndex, waveform, frequency, phase, mappings, onCha cancelAnimationFrame(animationRef.current) } } - }, [frequency, waveform, phase]) - - const getLFOValueAtPhase = (phase: number): number => { - const normalizedPhase = phase % 1 - - switch (waveform) { - case 'sine': - return Math.sin(normalizedPhase * 2 * Math.PI) - case 'triangle': - return normalizedPhase < 0.5 - ? -1 + 4 * normalizedPhase - : 3 - 4 * normalizedPhase - case 'square': - return normalizedPhase < 0.5 ? 1 : -1 - case 'sawtooth': - return 2 * normalizedPhase - 1 - case 'random': - return Math.sin(normalizedPhase * 2 * Math.PI) - default: - return 0 - } - } + }, [frequency, waveform, phase, getLFOValueAtPhase]) const handleMouseDown = (e: React.MouseEvent) => { if (e.button === 2) return diff --git a/src/domain/audio/AudioPlayer.ts b/src/domain/audio/AudioPlayer.ts index 30adc465..0458bc62 100644 --- a/src/domain/audio/AudioPlayer.ts +++ b/src/domain/audio/AudioPlayer.ts @@ -1,6 +1,7 @@ import { EffectsChain } from './effects/EffectsChain' import { BytebeatSourceEffect } from './effects/BytebeatSourceEffect' import { ModulationEngine } from '../modulation/ModulationEngine' +import type { LFOWaveform } from '../modulation/LFO' import type { EffectValues } from '../../types/effects' export interface AudioPlayerOptions { @@ -129,10 +130,10 @@ export class AudioPlayer { ) } - setLFOConfig(lfoIndex: number, config: { frequency: number; phase: number; waveform: string; mappings: Array<{ targetParam: string; depth: number }> }): void { + setLFOConfig(lfoIndex: number, config: { frequency: number; phase: number; waveform: LFOWaveform; mappings: Array<{ targetParam: string; depth: number }> }): void { if (!this.modulationEngine) return - this.modulationEngine.updateLFO(lfoIndex, config.frequency, config.phase, config.waveform as any) + this.modulationEngine.updateLFO(lfoIndex, config.frequency, config.phase, config.waveform) this.modulationEngine.clearMappings(lfoIndex) for (const mapping of config.mappings) { diff --git a/src/domain/audio/SampleGenerator.ts b/src/domain/audio/SampleGenerator.ts index c4a2dc39..0976abe7 100644 --- a/src/domain/audio/SampleGenerator.ts +++ b/src/domain/audio/SampleGenerator.ts @@ -22,7 +22,7 @@ export function generateSamples( const value = compiledFormula(t, a, b, c, d) const byteValue = value & 0xFF buffer[t] = (byteValue - 128) / 128 - } catch (error) { + } catch { buffer[t] = 0 } } @@ -47,7 +47,7 @@ export function generateSamplesWithBitDepth( const value = compiledFormula(t, a, b, c, d) const clampedValue = value & maxValue buffer[t] = (clampedValue - midPoint) / midPoint - } catch (error) { + } catch { buffer[t] = 0 } } diff --git a/src/domain/audio/effects/BytebeatSourceEffect.ts b/src/domain/audio/effects/BytebeatSourceEffect.ts index 4e6d0f2c..532100ae 100644 --- a/src/domain/audio/effects/BytebeatSourceEffect.ts +++ b/src/domain/audio/effects/BytebeatSourceEffect.ts @@ -25,11 +25,11 @@ export class BytebeatSourceEffect implements Effect { return this.outputNode } - setBypass(_bypass: boolean): void { + setBypass(): void { // Source node doesn't support bypass } - updateParams(_values: Record): void { + updateParams(): void { // Parameters handled via specific methods } diff --git a/src/domain/audio/effects/OutputLimiter.ts b/src/domain/audio/effects/OutputLimiter.ts index 36f18fad..8a9350ec 100644 --- a/src/domain/audio/effects/OutputLimiter.ts +++ b/src/domain/audio/effects/OutputLimiter.ts @@ -30,11 +30,11 @@ export class OutputLimiter implements Effect { return this.outputNode } - setBypass(_bypass: boolean): void { + setBypass(): void { // Output limiter is always on } - updateParams(_values: Record): void { + updateParams(): void { // Uses default parameters from worklet } diff --git a/src/domain/audio/effects/PassThroughEffect.ts b/src/domain/audio/effects/PassThroughEffect.ts deleted file mode 100644 index d59721c7..00000000 --- a/src/domain/audio/effects/PassThroughEffect.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Effect } from './Effect.interface' - -export class PassThroughEffect implements Effect { - readonly id: string - private node: GainNode - - constructor(audioContext: AudioContext, id: string) { - this.id = id - this.node = audioContext.createGain() - this.node.gain.value = 1 - } - - getInputNode(): AudioNode { - return this.node - } - - getOutputNode(): AudioNode { - return this.node - } - - setBypass(_bypass: boolean): void { - } - - updateParams(_values: Record): void { - } - - dispose(): void { - this.node.disconnect() - } -} \ No newline at end of file diff --git a/src/domain/modulation/ParameterRegistry.ts b/src/domain/modulation/ParameterRegistry.ts index 85ffddc8..14f0aedb 100644 --- a/src/domain/modulation/ParameterRegistry.ts +++ b/src/domain/modulation/ParameterRegistry.ts @@ -90,8 +90,8 @@ export class ParameterRegistry { getModulatableParamsByCategory(category: 'engine' | 'effect'): string[] { return Array.from(this.metadata.entries()) - .filter(([_, meta]) => meta.category === category) - .map(([id, _]) => id) + .filter(([, meta]) => meta.category === category) + .map(([id]) => id) } clampValue(paramId: string, value: number): number { diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts index ad99a852..8b4cad8f 100644 --- a/src/hooks/useKeyboardShortcuts.ts +++ b/src/hooks/useKeyboardShortcuts.ts @@ -60,7 +60,7 @@ export function useKeyboardShortcuts(handlers: KeyboardShortcutHandlers) { h.onArrowRight?.(e.shiftKey) break - case 'Enter': + case 'Enter': { e.preventDefault() const now = Date.now() if (now - lastEnterTime < DOUBLE_ENTER_THRESHOLD) { @@ -70,6 +70,7 @@ export function useKeyboardShortcuts(handlers: KeyboardShortcutHandlers) { } lastEnterTime = now break + } case 'r': case 'R': diff --git a/src/services/PlaybackManager.ts b/src/services/PlaybackManager.ts index 03004d55..b9d1c2fb 100644 --- a/src/services/PlaybackManager.ts +++ b/src/services/PlaybackManager.ts @@ -1,4 +1,5 @@ import { AudioPlayer } from '../domain/audio/AudioPlayer' +import type { LFOWaveform } from '../domain/modulation/LFO' import type { EffectValues } from '../types/effects' import { DEFAULT_VARIABLES } from '../constants/defaults' @@ -31,7 +32,7 @@ export class PlaybackManager { this.player.updateRealtimeVariables(a, b, c, d) } - setLFOConfig(lfoIndex: number, config: { frequency: number; phase: number; waveform: string; mappings: Array<{ targetParam: string; depth: number }> }): void { + setLFOConfig(lfoIndex: number, config: { frequency: number; phase: number; waveform: LFOWaveform; mappings: Array<{ targetParam: string; depth: number }> }): void { this.player.setLFOConfig(lfoIndex, config) } diff --git a/src/utils/bytebeatFormulas.ts b/src/utils/bytebeatFormulas.ts index f502f5e2..ca704469 100644 --- a/src/utils/bytebeatFormulas.ts +++ b/src/utils/bytebeatFormulas.ts @@ -186,9 +186,9 @@ function fillTemplate(pattern: string): string { function applyParenthesizationRandomization(formula: string): string { if (Math.random() < 0.2) { - const operators = formula.match(/[\+\-\*\/\&\|\^]/g) + const operators = formula.match(/[+\-*/&|^]/g) if (operators && operators.length > 0) { - const parts = formula.split(/([+\-*\/&|^])/) + const parts = formula.split(/([+\-*/&|^])/) if (parts.length >= 3) { const idx = Math.floor(Math.random() * (parts.length - 2) / 2) * 2 parts[idx] = `(${parts[idx]})` diff --git a/src/utils/tileState.ts b/src/utils/tileState.ts index b3c00adb..50ff6530 100644 --- a/src/utils/tileState.ts +++ b/src/utils/tileState.ts @@ -28,16 +28,16 @@ export function createTileStateFromCurrent(formula: string): TileState { export function loadTileParams(tile: TileState): void { Object.entries(tile.engineParams).forEach(([key, value]) => { - engineSettings.setKey(key as any, value) + engineSettings.setKey(key as keyof ReturnType, value) }) Object.entries(tile.effectParams).forEach(([key, value]) => { - effectSettings.setKey(key as any, value as any) + effectSettings.setKey(key as never, value as never) }) if (tile.lfoConfigs) { Object.entries(tile.lfoConfigs).forEach(([key, value]) => { - lfoSettings.setKey(key as any, value) + lfoSettings.setKey(key as keyof LFOSettings, value) }) } } diff --git a/src/utils/waveformGenerator.ts b/src/utils/waveformGenerator.ts index 49f51a37..7515f9c2 100644 --- a/src/utils/waveformGenerator.ts +++ b/src/utils/waveformGenerator.ts @@ -21,7 +21,7 @@ export function generateWaveformData(formula: string, width: number, sampleRate: } return waveform - } catch (error) { + } catch { return Array(width * 2).fill(0) } }