Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions src/logic/AcidSynth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Tone from 'tone'
import { BaseSynth } from './BaseSynth'

export class AcidSynth {
export class AcidSynth extends BaseSynth {
public synth: Tone.PolySynth<Tone.MonoSynth> | undefined
public dist: Tone.Distortion | undefined
public outputGain: Tone.Volume | undefined
Expand All @@ -10,13 +11,12 @@ export class AcidSynth {
private _envMod = 0.5
private _decay = 0.2
private _oscType: "sawtooth" | "square" | "sine" = "sawtooth"
private _volume = 0
private _slideTime = 0.1
private _distortionAmount = 0.4

private initialized = false

constructor() { }
constructor() {
super()
}

public init() {
if (this.initialized) return
Expand Down Expand Up @@ -112,13 +112,6 @@ export class AcidSynth {
}
}

setVolume(db: number) {
this._volume = db
if (this.outputGain) {
this.outputGain.volume.value = db
}
}

setParams(cutoff: number, resonance: number, envMod: number = 0.5, decay: number = 0.2) {
this._cutoff = cutoff
this._resonance = resonance
Expand Down Expand Up @@ -169,4 +162,10 @@ export class AcidSynth {
console.error("AcidSynth: triggerNote error", e)
}
}

public override dispose() {
this.synth?.dispose()
this.dist?.dispose()
super.dispose()
}
}
25 changes: 25 additions & 0 deletions src/logic/BaseSynth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Tone from 'tone'

export abstract class BaseSynth {
protected initialized = false
protected _volume = 0
public abstract outputGain: Tone.Volume | Tone.Gain | undefined

public abstract init(): void

public setVolume(db: number) {
this._volume = db
if (this.outputGain) {
if (this.outputGain instanceof Tone.Volume) {
this.outputGain.volume.rampTo(db, 0.1)
} else if (this.outputGain instanceof Tone.Gain) {
this.outputGain.gain.rampTo(Tone.dbToGain(db), 0.1)
}
}
}

public dispose() {
this.outputGain?.dispose()
this.initialized = false
}
}
93 changes: 57 additions & 36 deletions src/logic/DrumMachine.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import * as Tone from 'tone'
import { BaseSynth } from './BaseSynth'

export class DrumMachine {
public kick: Tone.MembraneSynth
public snare: Tone.NoiseSynth
public hihat: Tone.NoiseSynth
public hihatOpen: Tone.NoiseSynth
public clap: Tone.NoiseSynth
public ride: Tone.MetalSynth
comp: Tone.Compressor
public output: Tone.Volume
export class DrumMachine extends BaseSynth {
public kick: Tone.MembraneSynth | undefined
public snare: Tone.NoiseSynth | undefined
public hihat: Tone.NoiseSynth | undefined
public hihatOpen: Tone.NoiseSynth | undefined
public clap: Tone.NoiseSynth | undefined
public ride: Tone.MetalSynth | undefined
comp: Tone.Compressor | undefined
public outputGain: Tone.Volume | undefined

constructor() {
this.output = new Tone.Volume(0)
this.comp = new Tone.Compressor(-24, 4).connect(this.output)
super()
}

public init() {
if (this.initialized) return

this.outputGain = new Tone.Volume(this._volume)
this.comp = new Tone.Compressor(-24, 4).connect(this.outputGain)

this.kick = new Tone.MembraneSynth({
pitchDecay: 0.05,
Expand Down Expand Up @@ -51,72 +58,86 @@ export class DrumMachine {
octaves: 1.5,
volume: -12
}).connect(this.comp)
}

setVolume(db: number) {
this.output.volume.value = db
this.initialized = true
}

setKit(kit: '808' | '909') {
if (!this.initialized) this.init()
if (kit === '808') {
this.kick.set({
this.kick?.set({
pitchDecay: 0.05,
octaves: 10,
oscillator: { type: 'sine' },
envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 1.4 }
})
this.snare.set({
this.snare?.set({
noise: { type: 'white' },
envelope: { attack: 0.005, decay: 0.2, sustain: 0.02 }
})
this.hihat.set({ noise: { type: 'white' }, envelope: { decay: 0.05 } })
this.hihatOpen.set({ noise: { type: 'white' }, envelope: { decay: 0.3 } })
this.ride.set({ envelope: { decay: 1.0 }, harmonicity: 5.1 })
this.hihat?.set({ noise: { type: 'white' }, envelope: { decay: 0.05 } })
this.hihatOpen?.set({ noise: { type: 'white' }, envelope: { decay: 0.3 } })
this.ride?.set({ envelope: { decay: 1.0 }, harmonicity: 5.1 })
} else {
// 909 Settings
this.kick.set({
this.kick?.set({
pitchDecay: 0.02,
octaves: 4,
oscillator: { type: 'sine' },
envelope: { attack: 0.001, decay: 0.2, sustain: 0, release: 1 }
})
this.snare.set({
this.snare?.set({
noise: { type: 'pink' }, // Pink noise for 909 snare body
envelope: { attack: 0.001, decay: 0.15, sustain: 0 }
})
// 909 Hats are metallic/cymbal-like, but we use NoiseSynth. Use Pink for darker/thicker or modify envelope
this.hihat.set({ noise: { type: 'pink' }, envelope: { decay: 0.03 } })
this.hihatOpen.set({ noise: { type: 'pink' }, envelope: { decay: 0.2 } })
this.ride.set({ envelope: { decay: 2.0 }, harmonicity: 5.1 })
this.hihat?.set({ noise: { type: 'pink' }, envelope: { decay: 0.03 } })
this.hihatOpen?.set({ noise: { type: 'pink' }, envelope: { decay: 0.2 } })
this.ride?.set({ envelope: { decay: 2.0 }, harmonicity: 5.1 })
}
}

setDrumParams(drum: 'kick' | 'snare' | 'hihat' | 'hihatOpen' | 'clap' | 'ride', pitch: number, decay: number) {
if (!this.initialized) this.init()
// Simplified mapping for synth params
if (drum === 'kick') {
if (drum === 'kick' && this.kick) {
this.kick.envelope.decay = decay
// pitch mapping if needed
}
if (drum === 'hihat' || drum === 'hihatOpen') {
this[drum].envelope.decay = decay * 0.5
if ((drum === 'hihat' || drum === 'hihatOpen') && this[drum]) {
this[drum]!.envelope.decay = decay * 0.5
}
if (drum === 'ride') {
if (drum === 'ride' && this.ride) {
this.ride.envelope.decay = decay * 2
}
}

setDrumVolume(drum: 'kick' | 'snare' | 'hihat' | 'hihatOpen' | 'clap' | 'ride', volume: number) {
if (this[drum]) {
this[drum].volume.value = volume
if (!this.initialized) this.init()
const inst = (this as any)[drum]
if (inst) {
inst.volume.value = volume
}
}

triggerDrum(drum: 'kick' | 'snare' | 'hihat' | 'hihatOpen' | 'clap' | 'ride', time: number, velocity: number = 0.8) {
if (drum === 'kick') this.kick.triggerAttackRelease('C1', '8n', time, velocity)
else if (drum === 'snare') this.snare.triggerAttackRelease('8n', time, velocity)
else if (drum === 'hihat') this.hihat.triggerAttackRelease('32n', time, velocity)
else if (drum === 'hihatOpen') this.hihatOpen.triggerAttackRelease('16n', time, velocity)
else if (drum === 'clap') this.clap.triggerAttackRelease('8n', time, velocity)
else if (drum === 'ride') this.ride.triggerAttackRelease('16n', time, velocity)
if (!this.initialized) this.init()
if (drum === 'kick') this.kick?.triggerAttackRelease('C1', '8n', time, velocity)
else if (drum === 'snare') this.snare?.triggerAttackRelease('8n', time, velocity)
else if (drum === 'hihat') this.hihat?.triggerAttackRelease('32n', time, velocity)
else if (drum === 'hihatOpen') this.hihatOpen?.triggerAttackRelease('16n', time, velocity)
else if (drum === 'clap') this.clap?.triggerAttackRelease('8n', time, velocity)
else if (drum === 'ride') this.ride?.triggerAttackRelease('16n', time, velocity)
}

public override dispose() {
this.kick?.dispose()
this.snare?.dispose()
this.hihat?.dispose()
this.hihatOpen?.dispose()
this.clap?.dispose()
this.ride?.dispose()
this.comp?.dispose()
super.dispose()
}
}
23 changes: 12 additions & 11 deletions src/logic/FMBass.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import * as Tone from 'tone'
import { BaseSynth } from './BaseSynth'

export class FMBass {
export class FMBass extends BaseSynth {
private synth: Tone.FMSynth | undefined
private dist: Tone.Distortion | undefined
private outputGain: Tone.Volume | undefined
public outputGain: Tone.Volume | undefined

private _harmonicity = 1.5
private _modulationIndex = 10
private _volume = 0
private _attack = 0.01
private _decay = 0.2

private initialized = false

constructor() { }
constructor() {
super()
}

public init() {
if (this.initialized) return
Expand Down Expand Up @@ -72,11 +72,6 @@ export class FMBass {
}
}

setVolume(db: number) {
this._volume = db
if (this.outputGain) this.outputGain.volume.value = db
}

triggerNote(note: string, duration: string, time: number, velocity: number = 0.8) {
if (!this.initialized) this.init()
if (!this.synth) return
Expand All @@ -87,4 +82,10 @@ export class FMBass {
console.error("FMBass: triggerNote error", e)
}
}

public override dispose() {
this.synth?.dispose()
this.dist?.dispose()
super.dispose()
}
}
21 changes: 6 additions & 15 deletions src/logic/GlobalSequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ export function startSequencerLoop() {
if (drums.isPlaying && drumMachine && isTrackActive('drums')) {
const patterns = drums.activePatterns

if (drumMachine.output) {
drumMachine.output.volume.value = Tone.gainToDb(autoVolDrums)
}
drumMachine.setVolume(Tone.gainToDb(autoVolDrums)) // Use standardized setVolume

if (patterns.kick[step] && !drums.kick.muted) {
drumMachine.triggerDrum('kick', time)
Expand Down Expand Up @@ -127,9 +125,8 @@ export function startSequencerLoop() {

if (bassStep && bassStep.active) {
if (bass.activeInstrument === 'acid' && bassSynth) {
if (bassSynth.outputGain) {
bassSynth.outputGain.volume.value = Tone.gainToDb(autoVolBass)
}
bassSynth.setVolume(Tone.gainToDb(autoVolBass))

const isContinuing = prevBassStep?.active && prevBassStep?.slide
bassSynth.triggerNote(
bassStep.note,
Expand All @@ -153,9 +150,7 @@ export function startSequencerLoop() {

if (leadSynth && isTrackActive('lead')) {
// Apply volume
if (leadSynth.outputGain) {
leadSynth.outputGain.volume.value = Tone.gainToDb(autoVolLead)
}
leadSynth.setVolume(Tone.gainToDb(autoVolLead))

if (seq.isStagesPlaying) {
try {
Expand Down Expand Up @@ -194,9 +189,7 @@ export function startSequencerLoop() {

if (pads.active && padSynth && totalStep % 32 === 0 && isTrackActive('pads')) {
try {
if (padSynth.synth) {
padSynth.synth.volume.value = Tone.gainToDb(autoVolPads)
}
padSynth.setVolume(Tone.gainToDb(autoVolPads))
const progression = generatePadProgression(
harmony.root,
harmony.scale,
Expand All @@ -219,9 +212,7 @@ export function startSequencerLoop() {

if (harm.isPlaying && harmSynth && totalStep % 32 === 0 && isTrackActive('harm')) {
try {
if (harmSynth.outputGain) {
harmSynth.outputGain.volume.value = Tone.gainToDb(autoVolHarm)
}
harmSynth.setVolume(Tone.gainToDb(autoVolHarm))
const rootNote = harmony.root + '2'
const rootMidi = Tone.Frequency(rootNote).toMidi()
harmSynth.triggerNote(rootNote, '2n', time, 0.7)
Expand Down
25 changes: 20 additions & 5 deletions src/logic/HarmSynth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Tone from 'tone'
import { BaseSynth } from './BaseSynth'

export type HarmOscType = 'sawtooth' | 'square' | 'triangle' | 'sine'

Expand Down Expand Up @@ -207,7 +208,7 @@ class HarmVoice {
}
}

export class HarmSynth {
export class HarmSynth extends BaseSynth {
private voicePool: HarmVoice[] = []
private activeVoices: Set<HarmVoice> = new Set()
private maxVoices = 16
Expand Down Expand Up @@ -243,8 +244,9 @@ export class HarmSynth {
private sharedCurve = new Float32Array(4096)
private lastCurveParams = { timbre: -1, order: -1, harmonics: -1 }

private initialized = false
constructor() { }
constructor() {
super()
}

public init() {
if (this.initialized) return
Expand All @@ -257,7 +259,7 @@ export class HarmSynth {
this.chorus = new Tone.Chorus(4, 2.5, 0.5).start()
this.delay = new Tone.FeedbackDelay('8n', 0.5)
this.reverb = new Tone.Reverb(2)
this.outputGain = new Tone.Volume(0)
this.outputGain = new Tone.Volume(this._volume)

this.directBus.chain(this.filter1, this.filter2, this.outputGain)
this.fxBus.chain(this.distortion, this.phaser, this.chorus, this.delay, this.reverb, this.outputGain)
Expand Down Expand Up @@ -339,7 +341,6 @@ export class HarmSynth {
setDelay(time: string, feedback: number, wet: number) { if (this.delay) { this.delay.delayTime.value = time; this.delay.feedback.value = feedback; this.delay.wet.value = wet } }
setReverb(decay: number, wet: number) { if (this.reverb) { this.reverb.decay = decay; this.reverb.wet.value = wet } }
setFilter(idx: 1 | 2, freq: number, q: number, type: BiquadFilterType) { const f = idx === 1 ? this.filter1 : this.filter2; if (f) { f.frequency.value = freq; f.Q.value = q; f.type = type } }
setVolume(db: number) { this.outputGain?.volume.rampTo(db, 0.1) }
setOscType(idx: 1 | 2 | 3, type: HarmOscType) { (this.settings as any)[`osc${idx}`].type = type }
setOscDetune(idx: 1 | 2 | 3, detune: number) { (this.settings as any)[`osc${idx}`].detune = detune }
setEnv(target: 'osc1' | 'osc2' | 'osc3' | 'noise', params: ADSRParams) { (this.settings as any)[target].env = params }
Expand All @@ -365,4 +366,18 @@ export class HarmSynth {
this.settings.complex = { ...this.settings.complex, ...params }
this.activeVoices.forEach(v => this.updateWavefolder(v, this.settings.complex.timbre, this.settings.complex.order, this.settings.complex.harmonics))
}

public override dispose() {
this.voicePool.forEach(v => v.dispose())
this.fxBus?.dispose()
this.directBus?.dispose()
this.filter1?.dispose()
this.filter2?.dispose()
this.distortion?.dispose()
this.phaser?.dispose()
this.chorus?.dispose()
this.delay?.dispose()
this.reverb?.dispose()
super.dispose()
}
}
Loading