before bugfixing
This commit is contained in:
25
src/App.tsx
25
src/App.tsx
@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { PlaybackManager } from './services/PlaybackManager'
|
||||
import { DownloadService } from './services/DownloadService'
|
||||
import { generateFormulaGrid } from './utils/bytebeatFormulas'
|
||||
import { generateFormulaGrid, generateRandomFormula } from './utils/bytebeatFormulas'
|
||||
import { BytebeatTile } from './components/BytebeatTile'
|
||||
import { EffectsBar } from './components/EffectsBar'
|
||||
import { EngineControls } from './components/EngineControls'
|
||||
@ -18,6 +18,7 @@ function App() {
|
||||
)
|
||||
const [playing, setPlaying] = useState<string | null>(null)
|
||||
const [queued, setQueued] = useState<string | null>(null)
|
||||
const [regenerating, setRegenerating] = useState<string | null>(null)
|
||||
const [playbackPosition, setPlaybackPosition] = useState<number>(0)
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const playbackManagerRef = useRef<PlaybackManager | null>(null)
|
||||
@ -136,6 +137,26 @@ function App() {
|
||||
downloadServiceRef.current.downloadFormula(formula, filename, { duration: 10, bitDepth: 8 })
|
||||
}
|
||||
|
||||
const handleRegenerate = (row: number, col: number) => {
|
||||
const id = `${row}-${col}`
|
||||
const newFormula = generateRandomFormula(engineValues.complexity)
|
||||
|
||||
setFormulas(prevFormulas => {
|
||||
const newFormulas = [...prevFormulas]
|
||||
newFormulas[row] = [...newFormulas[row]]
|
||||
newFormulas[row][col] = newFormula
|
||||
return newFormulas
|
||||
})
|
||||
|
||||
if (playing === id && playbackManagerRef.current) {
|
||||
setRegenerating(id)
|
||||
playbackManagerRef.current.scheduleNextTrack(() => {
|
||||
playFormula(newFormula, id)
|
||||
setRegenerating(null)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
|
||||
<header className="bg-black border-b-2 border-white px-6 py-3">
|
||||
@ -172,10 +193,12 @@ function App() {
|
||||
col={j}
|
||||
isPlaying={playing === id}
|
||||
isQueued={queued === id}
|
||||
isRegenerating={regenerating === id}
|
||||
playbackPosition={playing === id ? playbackPosition : 0}
|
||||
onPlay={handleTileClick}
|
||||
onDoubleClick={handleTileDoubleClick}
|
||||
onDownload={handleDownloadFormula}
|
||||
onRegenerate={handleRegenerate}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useRef, useEffect } from 'react'
|
||||
import { Download } from 'lucide-react'
|
||||
import { Download, RefreshCw } from 'lucide-react'
|
||||
import { generateWaveformData, drawWaveform } from '../utils/waveformGenerator'
|
||||
|
||||
interface BytebeatTileProps {
|
||||
@ -8,13 +8,15 @@ interface BytebeatTileProps {
|
||||
col: number
|
||||
isPlaying: boolean
|
||||
isQueued: boolean
|
||||
isRegenerating: boolean
|
||||
playbackPosition: number
|
||||
onPlay: (formula: string, row: number, col: number) => void
|
||||
onDoubleClick: (formula: string, row: number, col: number) => void
|
||||
onDownload: (formula: string, filename: string) => void
|
||||
onRegenerate: (row: number, col: number) => void
|
||||
}
|
||||
|
||||
export function BytebeatTile({ formula, row, col, isPlaying, isQueued, playbackPosition, onPlay, onDoubleClick, onDownload }: BytebeatTileProps) {
|
||||
export function BytebeatTile({ formula, row, col, isPlaying, isQueued, isRegenerating, playbackPosition, onPlay, onDoubleClick, onDownload, onRegenerate }: BytebeatTileProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@ -35,12 +37,17 @@ export function BytebeatTile({ formula, row, col, isPlaying, isQueued, playbackP
|
||||
onDownload(formula, `bytebeat_${row}_${col}.wav`)
|
||||
}
|
||||
|
||||
const handleRegenerate = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
onRegenerate(row, col)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => onPlay(formula, row, col)}
|
||||
onDoubleClick={() => onDoubleClick(formula, row, col)}
|
||||
className={`relative hover:scale-[0.98] transition-all duration-150 font-mono p-3 flex items-center justify-between gap-3 cursor-pointer overflow-hidden ${
|
||||
isPlaying ? 'bg-white text-black' : isQueued ? 'bg-black text-white animate-pulse' : 'bg-black text-white'
|
||||
isPlaying ? 'bg-white text-black' : isQueued ? 'bg-black text-white animate-pulse' : isRegenerating ? 'bg-black text-white border-2 border-white' : 'bg-black text-white'
|
||||
}`}
|
||||
>
|
||||
<canvas
|
||||
@ -56,9 +63,20 @@ export function BytebeatTile({ formula, row, col, isPlaying, isQueued, playbackP
|
||||
<div className="text-xs break-all font-light flex-1 relative z-10">
|
||||
{formula}
|
||||
</div>
|
||||
<div className="flex gap-2 flex-shrink-0 relative z-10">
|
||||
<div
|
||||
onClick={handleRegenerate}
|
||||
className={`p-2 border transition-all duration-150 cursor-pointer hover:scale-105 ${
|
||||
isPlaying
|
||||
? 'bg-black text-white border-black'
|
||||
: 'bg-white text-black border-white'
|
||||
}`}
|
||||
>
|
||||
<RefreshCw size={14} strokeWidth={2} className={isRegenerating ? 'animate-spin' : ''} />
|
||||
</div>
|
||||
<div
|
||||
onClick={handleDownload}
|
||||
className={`p-2 border transition-all duration-150 cursor-pointer hover:scale-105 flex-shrink-0 relative z-10 ${
|
||||
className={`p-2 border transition-all duration-150 cursor-pointer hover:scale-105 ${
|
||||
isPlaying
|
||||
? 'bg-black text-white border-black'
|
||||
: 'bg-white text-black border-white'
|
||||
@ -67,5 +85,6 @@ export function BytebeatTile({ formula, row, col, isPlaying, isQueued, playbackP
|
||||
<Download size={14} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -35,7 +35,27 @@ export function EffectsBar({ values, onChange }: EffectsBarProps) {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{effect.parameters.map(param => (
|
||||
{effect.parameters.map(param => {
|
||||
const isSwitch = param.min === 0 && param.max === 1 && param.step === 1
|
||||
|
||||
if (isSwitch) {
|
||||
return (
|
||||
<div key={param.id} className="flex flex-col gap-1 mt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono text-[9px] tracking-[0.15em] text-white">
|
||||
{param.label.toUpperCase()}
|
||||
</span>
|
||||
<Switch
|
||||
checked={Boolean(values[param.id])}
|
||||
onChange={(checked) => onChange(param.id, checked ? 1 : 0)}
|
||||
label={Boolean(values[param.id]) ? 'ON' : 'OFF'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Slider
|
||||
key={param.id}
|
||||
label={param.label}
|
||||
@ -48,7 +68,8 @@ export function EffectsBar({ values, onChange }: EffectsBarProps) {
|
||||
formatValue={param.id === 'clipMode' ? formatValue : undefined}
|
||||
valueId={param.id}
|
||||
/>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -65,8 +65,95 @@ export const ENGINE_CONTROLS: EffectConfig[] = [
|
||||
|
||||
export const EFFECTS: EffectConfig[] = [
|
||||
{
|
||||
id: 'wavefolder',
|
||||
name: 'Wavefolder',
|
||||
id: 'filter',
|
||||
name: 'Filter',
|
||||
parameters: [
|
||||
{
|
||||
id: 'hpEnable',
|
||||
label: 'HP',
|
||||
min: 0,
|
||||
max: 1,
|
||||
default: 0,
|
||||
step: 1,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
id: 'hpFreq',
|
||||
label: 'HP Freq',
|
||||
min: 20,
|
||||
max: 10000,
|
||||
default: 1000,
|
||||
step: 10,
|
||||
unit: 'Hz'
|
||||
},
|
||||
{
|
||||
id: 'hpRes',
|
||||
label: 'HP Q',
|
||||
min: 0.1,
|
||||
max: 20,
|
||||
default: 1,
|
||||
step: 0.1,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
id: 'lpEnable',
|
||||
label: 'LP',
|
||||
min: 0,
|
||||
max: 1,
|
||||
default: 0,
|
||||
step: 1,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
id: 'lpFreq',
|
||||
label: 'LP Freq',
|
||||
min: 20,
|
||||
max: 20000,
|
||||
default: 5000,
|
||||
step: 10,
|
||||
unit: 'Hz'
|
||||
},
|
||||
{
|
||||
id: 'lpRes',
|
||||
label: 'LP Q',
|
||||
min: 0.1,
|
||||
max: 20,
|
||||
default: 1,
|
||||
step: 0.1,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
id: 'bpEnable',
|
||||
label: 'BP',
|
||||
min: 0,
|
||||
max: 1,
|
||||
default: 0,
|
||||
step: 1,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
id: 'bpFreq',
|
||||
label: 'BP Freq',
|
||||
min: 20,
|
||||
max: 10000,
|
||||
default: 1000,
|
||||
step: 10,
|
||||
unit: 'Hz'
|
||||
},
|
||||
{
|
||||
id: 'bpRes',
|
||||
label: 'BP Q',
|
||||
min: 0.1,
|
||||
max: 20,
|
||||
default: 1,
|
||||
step: 0.1,
|
||||
unit: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'foldcrush',
|
||||
name: 'Fold and Crush',
|
||||
bypassable: true,
|
||||
parameters: [
|
||||
{
|
||||
@ -86,14 +173,7 @@ export const EFFECTS: EffectConfig[] = [
|
||||
default: 1,
|
||||
step: 0.1,
|
||||
unit: 'x'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'bitcrush',
|
||||
name: 'Bitcrush',
|
||||
bypassable: true,
|
||||
parameters: [
|
||||
{
|
||||
id: 'bitcrushDepth',
|
||||
label: 'Depth',
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { Effect } from './Effect.interface'
|
||||
import { FilterEffect } from './FilterEffect'
|
||||
import { FoldCrushEffect } from './FoldCrushEffect'
|
||||
import { DelayEffect } from './DelayEffect'
|
||||
import { ReverbEffect } from './ReverbEffect'
|
||||
import { BitcrushEffect } from './BitcrushEffect'
|
||||
import { WavefolderEffect } from './WavefolderEffect'
|
||||
|
||||
export class EffectsChain {
|
||||
private inputNode: GainNode
|
||||
@ -16,8 +16,8 @@ export class EffectsChain {
|
||||
this.masterGainNode = audioContext.createGain()
|
||||
|
||||
this.effects = [
|
||||
new WavefolderEffect(audioContext),
|
||||
new BitcrushEffect(audioContext),
|
||||
new FilterEffect(audioContext),
|
||||
new FoldCrushEffect(audioContext),
|
||||
new DelayEffect(audioContext),
|
||||
new ReverbEffect(audioContext)
|
||||
]
|
||||
|
||||
169
src/domain/audio/effects/FilterEffect.ts
Normal file
169
src/domain/audio/effects/FilterEffect.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import type { Effect } from './Effect.interface'
|
||||
|
||||
export class FilterEffect implements Effect {
|
||||
readonly id = 'filter'
|
||||
|
||||
private audioContext: AudioContext
|
||||
private inputNode: GainNode
|
||||
private outputNode: GainNode
|
||||
private hpFilter: BiquadFilterNode
|
||||
private lpFilter: BiquadFilterNode
|
||||
private bpFilter: BiquadFilterNode
|
||||
private hpEnabled: boolean = false
|
||||
private lpEnabled: boolean = false
|
||||
private bpEnabled: boolean = false
|
||||
|
||||
constructor(audioContext: AudioContext) {
|
||||
this.audioContext = audioContext
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
|
||||
this.hpFilter = audioContext.createBiquadFilter()
|
||||
this.hpFilter.type = 'highpass'
|
||||
this.hpFilter.frequency.value = 20
|
||||
this.hpFilter.Q.value = 1
|
||||
|
||||
this.lpFilter = audioContext.createBiquadFilter()
|
||||
this.lpFilter.type = 'lowpass'
|
||||
this.lpFilter.frequency.value = 20000
|
||||
this.lpFilter.Q.value = 1
|
||||
|
||||
this.bpFilter = audioContext.createBiquadFilter()
|
||||
this.bpFilter.type = 'allpass'
|
||||
this.bpFilter.frequency.value = 1000
|
||||
this.bpFilter.Q.value = 1
|
||||
|
||||
this.inputNode.connect(this.hpFilter)
|
||||
this.hpFilter.connect(this.lpFilter)
|
||||
this.lpFilter.connect(this.bpFilter)
|
||||
this.bpFilter.connect(this.outputNode)
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
return this.inputNode
|
||||
}
|
||||
|
||||
getOutputNode(): AudioNode {
|
||||
return this.outputNode
|
||||
}
|
||||
|
||||
setBypass(bypass: boolean): void {
|
||||
// No global bypass for filters - each filter has individual enable switch
|
||||
}
|
||||
|
||||
updateParams(values: Record<string, number>): void {
|
||||
|
||||
if (values.hpEnable !== undefined) {
|
||||
const enable = values.hpEnable === 1
|
||||
if (enable && !this.hpEnabled) {
|
||||
this.hpFilter.type = 'highpass'
|
||||
this.hpEnabled = true
|
||||
} else if (!enable && this.hpEnabled) {
|
||||
this.hpFilter.type = 'allpass'
|
||||
this.hpEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
if (values.hpFreq !== undefined && this.hpEnabled) {
|
||||
this.hpFilter.frequency.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.hpFilter.frequency.setValueAtTime(
|
||||
this.hpFilter.frequency.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.hpFilter.frequency.linearRampToValueAtTime(
|
||||
values.hpFreq,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
|
||||
if (values.hpRes !== undefined && this.hpEnabled) {
|
||||
this.hpFilter.Q.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.hpFilter.Q.setValueAtTime(
|
||||
this.hpFilter.Q.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.hpFilter.Q.linearRampToValueAtTime(
|
||||
values.hpRes,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
|
||||
if (values.lpEnable !== undefined) {
|
||||
const enable = values.lpEnable === 1
|
||||
if (enable && !this.lpEnabled) {
|
||||
this.lpFilter.type = 'lowpass'
|
||||
this.lpEnabled = true
|
||||
} else if (!enable && this.lpEnabled) {
|
||||
this.lpFilter.type = 'allpass'
|
||||
this.lpEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
if (values.lpFreq !== undefined && this.lpEnabled) {
|
||||
this.lpFilter.frequency.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.lpFilter.frequency.setValueAtTime(
|
||||
this.lpFilter.frequency.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.lpFilter.frequency.linearRampToValueAtTime(
|
||||
values.lpFreq,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
|
||||
if (values.lpRes !== undefined && this.lpEnabled) {
|
||||
this.lpFilter.Q.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.lpFilter.Q.setValueAtTime(
|
||||
this.lpFilter.Q.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.lpFilter.Q.linearRampToValueAtTime(
|
||||
values.lpRes,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
|
||||
if (values.bpEnable !== undefined) {
|
||||
const enable = values.bpEnable === 1
|
||||
if (enable && !this.bpEnabled) {
|
||||
this.bpFilter.type = 'bandpass'
|
||||
this.bpEnabled = true
|
||||
} else if (!enable && this.bpEnabled) {
|
||||
this.bpFilter.type = 'allpass'
|
||||
this.bpEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
if (values.bpFreq !== undefined && this.bpEnabled) {
|
||||
this.bpFilter.frequency.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.bpFilter.frequency.setValueAtTime(
|
||||
this.bpFilter.frequency.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.bpFilter.frequency.linearRampToValueAtTime(
|
||||
values.bpFreq,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
|
||||
if (values.bpRes !== undefined && this.bpEnabled) {
|
||||
this.bpFilter.Q.cancelScheduledValues(this.audioContext.currentTime)
|
||||
this.bpFilter.Q.setValueAtTime(
|
||||
this.bpFilter.Q.value,
|
||||
this.audioContext.currentTime
|
||||
)
|
||||
this.bpFilter.Q.linearRampToValueAtTime(
|
||||
values.bpRes,
|
||||
this.audioContext.currentTime + 0.02
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.inputNode.disconnect()
|
||||
this.outputNode.disconnect()
|
||||
this.hpFilter.disconnect()
|
||||
this.lpFilter.disconnect()
|
||||
this.bpFilter.disconnect()
|
||||
}
|
||||
}
|
||||
149
src/domain/audio/effects/FoldCrushEffect.ts
Normal file
149
src/domain/audio/effects/FoldCrushEffect.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import type { Effect } from './Effect.interface'
|
||||
|
||||
type ClipMode = 'wrap' | 'clamp' | 'fold'
|
||||
|
||||
export class FoldCrushEffect implements Effect {
|
||||
readonly id = 'foldcrush'
|
||||
|
||||
private inputNode: GainNode
|
||||
private outputNode: GainNode
|
||||
private processorNode: ScriptProcessorNode
|
||||
private wetNode: GainNode
|
||||
private dryNode: GainNode
|
||||
private clipMode: ClipMode = 'wrap'
|
||||
private drive: number = 1
|
||||
private bitDepth: number = 16
|
||||
private crushAmount: number = 0
|
||||
|
||||
constructor(audioContext: AudioContext) {
|
||||
this.inputNode = audioContext.createGain()
|
||||
this.outputNode = audioContext.createGain()
|
||||
this.processorNode = audioContext.createScriptProcessor(4096, 1, 1)
|
||||
this.wetNode = audioContext.createGain()
|
||||
this.dryNode = audioContext.createGain()
|
||||
|
||||
this.wetNode.gain.value = 1
|
||||
this.dryNode.gain.value = 0
|
||||
|
||||
this.processorNode.onaudioprocess = (e) => {
|
||||
const input = e.inputBuffer.getChannelData(0)
|
||||
const output = e.outputBuffer.getChannelData(0)
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const driven = input[i] * this.drive
|
||||
let processed = this.processWavefolder(driven)
|
||||
processed = this.processBitcrush(processed, i, output)
|
||||
output[i] = processed
|
||||
}
|
||||
}
|
||||
|
||||
this.inputNode.connect(this.dryNode)
|
||||
this.inputNode.connect(this.processorNode)
|
||||
this.processorNode.connect(this.wetNode)
|
||||
this.dryNode.connect(this.outputNode)
|
||||
this.wetNode.connect(this.outputNode)
|
||||
}
|
||||
|
||||
private processWavefolder(sample: number): number {
|
||||
switch (this.clipMode) {
|
||||
case 'wrap':
|
||||
return this.wrap(sample)
|
||||
case 'clamp':
|
||||
return this.clamp(sample)
|
||||
case 'fold':
|
||||
return this.fold(sample)
|
||||
default:
|
||||
return sample
|
||||
}
|
||||
}
|
||||
|
||||
private wrap(sample: number): number {
|
||||
const range = 2.0
|
||||
let wrapped = sample
|
||||
while (wrapped > 1.0) wrapped -= range
|
||||
while (wrapped < -1.0) wrapped += range
|
||||
return wrapped
|
||||
}
|
||||
|
||||
private clamp(sample: number): number {
|
||||
return Math.max(-1.0, Math.min(1.0, sample))
|
||||
}
|
||||
|
||||
private fold(sample: number): number {
|
||||
let folded = sample
|
||||
while (folded > 1.0 || folded < -1.0) {
|
||||
if (folded > 1.0) {
|
||||
folded = 2.0 - folded
|
||||
}
|
||||
if (folded < -1.0) {
|
||||
folded = -2.0 - folded
|
||||
}
|
||||
}
|
||||
return folded
|
||||
}
|
||||
|
||||
private bitcrushPhase: number = 0
|
||||
private lastCrushedValue: number = 0
|
||||
|
||||
private processBitcrush(sample: number, index: number, output: Float32Array): number {
|
||||
if (this.crushAmount === 0 && this.bitDepth === 16) {
|
||||
return sample
|
||||
}
|
||||
|
||||
const step = Math.pow(0.5, this.bitDepth)
|
||||
const phaseIncrement = 1 - (this.crushAmount / 100)
|
||||
|
||||
this.bitcrushPhase += phaseIncrement
|
||||
|
||||
if (this.bitcrushPhase >= 1.0) {
|
||||
this.bitcrushPhase -= 1.0
|
||||
const crushed = Math.floor(sample / step + 0.5) * step
|
||||
this.lastCrushedValue = Math.max(-1, Math.min(1, crushed))
|
||||
return this.lastCrushedValue
|
||||
} else {
|
||||
return this.lastCrushedValue
|
||||
}
|
||||
}
|
||||
|
||||
getInputNode(): AudioNode {
|
||||
return this.inputNode
|
||||
}
|
||||
|
||||
getOutputNode(): AudioNode {
|
||||
return this.outputNode
|
||||
}
|
||||
|
||||
setBypass(bypass: boolean): void {
|
||||
if (bypass) {
|
||||
this.wetNode.gain.value = 0
|
||||
this.dryNode.gain.value = 1
|
||||
} else {
|
||||
this.wetNode.gain.value = 1
|
||||
this.dryNode.gain.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
updateParams(values: Record<string, number>): void {
|
||||
if (values.clipMode !== undefined) {
|
||||
const modeIndex = values.clipMode
|
||||
this.clipMode = ['wrap', 'clamp', 'fold'][modeIndex] as ClipMode || 'wrap'
|
||||
}
|
||||
if (values.wavefolderDrive !== undefined) {
|
||||
this.drive = values.wavefolderDrive
|
||||
}
|
||||
if (values.bitcrushDepth !== undefined) {
|
||||
this.bitDepth = values.bitcrushDepth
|
||||
}
|
||||
if (values.bitcrushRate !== undefined) {
|
||||
this.crushAmount = values.bitcrushRate
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.processorNode.disconnect()
|
||||
this.wetNode.disconnect()
|
||||
this.dryNode.disconnect()
|
||||
this.inputNode.disconnect()
|
||||
this.outputNode.disconnect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user