Code quality checks
This commit is contained in:
12
src/App.tsx
12
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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 || ''}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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]})`
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ export function generateWaveformData(formula: string, width: number, sampleRate:
|
||||
}
|
||||
|
||||
return waveform
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return Array(width * 2).fill(0)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user