Code quality checks

This commit is contained in:
2025-10-06 03:03:38 +02:00
parent 5cc10dec0c
commit ef50cc9918
17 changed files with 62 additions and 88 deletions

View File

@ -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,

View File

@ -50,9 +50,9 @@ export function EffectsBar({ values, onChange, onMapClick, getMappedLFOs }: Effe
</div>
{effect.bypassable && (
<Switch
checked={!Boolean(values[`${effect.id}Bypass`])}
checked={!values[`${effect.id}Bypass`]}
onChange={(checked) => onChange(`${effect.id}Bypass`, !checked)}
label={Boolean(values[`${effect.id}Bypass`]) ? 'OFF' : 'ON'}
label={values[`${effect.id}Bypass`] ? 'OFF' : 'ON'}
/>
)}
</div>
@ -82,7 +82,7 @@ export function EffectsBar({ values, onChange, onMapClick, getMappedLFOs }: Effe
<Switch
checked={Boolean(values[param.id])}
onChange={(checked) => onChange(param.id, checked ? 1 : 0)}
label={Boolean(values[param.id]) ? 'ON' : 'OFF'}
label={values[param.id] ? 'ON' : 'OFF'}
/>
</div>
</div>

View File

@ -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 || ''}`
}
}
}

View File

@ -23,7 +23,7 @@ export function HelpModal({ onClose, showStartButton = false }: HelpModalProps)
BRUITISTE
</h1>
<p className="font-mono text-sm text-white mb-2 leading-relaxed text-center">
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!
</p>
<p className="font-mono text-xs text-white mb-6 opacity-70 text-center">
Made by Raphaël Forment (BuboBubo) <a href="https://raphaelforment.fr" target="_blank" rel="noopener noreferrer" className="underline hover:opacity-100">raphaelforment.fr</a>

View File

@ -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 (
<div className="relative flex flex-col items-center">

View File

@ -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<HTMLCanvasElement>) => {
if (e.button === 2) return

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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<string, number | string>): void {
updateParams(): void {
// Parameters handled via specific methods
}

View File

@ -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<string, number | string>): void {
updateParams(): void {
// Uses default parameters from worklet
}

View File

@ -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<string, number | string>): void {
}
dispose(): void {
this.node.disconnect()
}
}

View File

@ -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 {

View File

@ -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':

View File

@ -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)
}

View File

@ -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]})`

View File

@ -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<typeof getDefaultEngineValues>, 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)
})
}
}

View File

@ -21,7 +21,7 @@ export function generateWaveformData(formula: string, width: number, sampleRate:
}
return waveform
} catch (error) {
} catch {
return Array(width * 2).fill(0)
}
}