Quite a big update

This commit is contained in:
2025-10-13 17:35:47 +02:00
parent fb92c3ae2a
commit b700c68b4d
12 changed files with 1366 additions and 5 deletions

View File

@ -0,0 +1,334 @@
import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
import type { PitchLock } from './base/SynthEngine';
interface DripwaterParams {
baseFreq: number;
dropRate: number;
numElements: number;
damping: number;
shakeIntensity: number;
freqRatio1: number;
freqRatio2: number;
freqRatio3: number;
attack: number;
release: number;
amplitude: number;
delayTime1: number;
delayTime2: number;
delayTime3: number;
delayTime4: number;
delayGain1: number;
delayGain2: number;
delayGain3: number;
delayGain4: number;
feedbackDelayTime: number;
feedbackAmount: number;
modDepth: number;
modRate: number;
stereoSpread: number;
combDelayTime: number;
combFeedback: number;
reverseDelayTime: number;
reverseGain: number;
cascadeDelay1: number;
cascadeDelay2: number;
cascadeFeedback: number;
}
export class Dripwater extends CsoundEngine<DripwaterParams> {
getName(): string {
return 'Weird Waters';
}
getDescription(): string {
return 'Physical model of water droplets through cascading delays, comb filters, and reverse echoes';
}
getType() {
return 'generative' as const;
}
getCategory() {
return 'Noise' as const;
}
protected getOrchestra(): string {
return `
instr 1
iBaseFreq chnget "baseFreq"
iDropRate chnget "dropRate"
iNumElements chnget "numElements"
iDamping chnget "damping"
iShakeIntensity chnget "shakeIntensity"
iFreqRatio1 chnget "freqRatio1"
iFreqRatio2 chnget "freqRatio2"
iFreqRatio3 chnget "freqRatio3"
iAttack chnget "attack"
iRelease chnget "release"
iAmplitude chnget "amplitude"
iDelayTime1 chnget "delayTime1"
iDelayTime2 chnget "delayTime2"
iDelayTime3 chnget "delayTime3"
iDelayTime4 chnget "delayTime4"
iDelayGain1 chnget "delayGain1"
iDelayGain2 chnget "delayGain2"
iDelayGain3 chnget "delayGain3"
iDelayGain4 chnget "delayGain4"
iFeedbackDelayTime chnget "feedbackDelayTime"
iFeedbackAmount chnget "feedbackAmount"
iModDepth chnget "modDepth"
iModRate chnget "modRate"
iStereoSpread chnget "stereoSpread"
iCombDelayTime chnget "combDelayTime"
iCombFeedback chnget "combFeedback"
iReverseDelayTime chnget "reverseDelayTime"
iReverseGain chnget "reverseGain"
iCascadeDelay1 chnget "cascadeDelay1"
iCascadeDelay2 chnget "cascadeDelay2"
iCascadeFeedback chnget "cascadeFeedback"
idur = p3
iAttackTime = iAttack * idur
iReleaseTime = iRelease * idur
; Scale delay times by duration so they fill the entire sound
iScaledDelayTime1 = iDelayTime1 * idur
iScaledDelayTime2 = iDelayTime2 * idur
iScaledDelayTime3 = iDelayTime3 * idur
iScaledDelayTime4 = iDelayTime4 * idur
iScaledFeedbackDelay = iFeedbackDelayTime * idur
iModDepthScaled = iModDepth * idur * 0.1
iScaledCombDelay = iCombDelayTime * idur
iScaledReverseDelay = iReverseDelayTime * idur
iScaledCascade1 = iCascadeDelay1 * idur
iScaledCascade2 = iCascadeDelay2 * idur
; Calculate resonant frequencies from base frequency and ratios
iFreq1 = iBaseFreq * iFreqRatio1
iFreq2 = iBaseFreq * iFreqRatio2
iFreq3 = iBaseFreq * iFreqRatio3
; Generate dripwater sound
; Signature: ares dripwater kamp, idettack, inum, idamp, imaxshake, ifreq, ifreq1, ifreq2
; Reduce amplitude to prevent clipping with all the delays
kAmp = iAmplitude * 0.08
aDrip dripwater kAmp, iDropRate, iNumElements, iDamping, iShakeIntensity, iFreq1, iFreq2, iFreq3
; Apply AR envelope
kEnv linsegr 0, iAttackTime, 1, iReleaseTime, 0
aDry = aDrip * kEnv
; Apply multitap delay with duration-scaled times
; Signature: a1 [, a2, a3, ...] multitap asource [, itime1, igain1, itime2, igain2, ...]
aMultitap multitap aDry, iScaledDelayTime1, iDelayGain1, iScaledDelayTime2, iDelayGain2, iScaledDelayTime3, iDelayGain3, iScaledDelayTime4, iDelayGain4
; Add modulated feedback delay
; LFO modulates delay time
kLFO oscili iModDepthScaled, iModRate
kDelayTime = iScaledFeedbackDelay + kLFO
kDelayTime = max(kDelayTime, 0.001)
; Variable delay with feedback
aBuf delayr idur
aTapL deltapi kDelayTime
delayw aDry + (aTapL * iFeedbackAmount)
; Comb filter delay (creates resonance)
aComb comb aDry, iScaledCombDelay, iCombFeedback
; Reverse delay using delayr/delayw with interpolation
aBufRev delayr iScaledReverseDelay
aReverseTap deltapi iScaledReverseDelay * (1 - (line:a(0, idur, 1)))
delayw aDry
aReverse = aReverseTap * iReverseGain
; Cascaded delays (two delays feeding into each other)
aBuf1 delayr idur
aCascade1 deltapi iScaledCascade1
aBuf2 delayr idur
aCascade2 deltapi iScaledCascade2
delayw aCascade1 * iCascadeFeedback
delayw aDry + (aCascade2 * iCascadeFeedback)
aOut = aDry + aMultitap + aTapL + aComb + aReverse + aCascade1 + aCascade2
; Create stereo width with slightly different parameters for right channel
; Slightly detune the resonant frequencies for right channel
iFreq1_R = iFreq1 * 1.003
iFreq2_R = iFreq2 * 0.998
iFreq3_R = iFreq3 * 1.002
; Use slightly different initial values for variation
iDropRate_R = iDropRate * 1.02
iShakeIntensity_R = iShakeIntensity * 0.98
kAmp_R = iAmplitude * 0.08
aDrip_R dripwater kAmp_R, iDropRate_R, iNumElements, iDamping, iShakeIntensity_R, iFreq1_R, iFreq2_R, iFreq3_R
aDry_R = aDrip_R * kEnv
; Apply multitap delay to right channel with slightly different times for stereo width
iScaledDelayTime1_R = iScaledDelayTime1 * 1.03
iScaledDelayTime2_R = iScaledDelayTime2 * 0.97
iScaledDelayTime3_R = iScaledDelayTime3 * 1.05
iScaledDelayTime4_R = iScaledDelayTime4 * 0.95
aMultitap_R multitap aDry_R, iScaledDelayTime1_R, iDelayGain1, iScaledDelayTime2_R, iDelayGain2, iScaledDelayTime3_R, iDelayGain3, iScaledDelayTime4_R, iDelayGain4
; Right channel modulated delay with stereo spread
kLFO_R oscili iModDepthScaled, iModRate * 1.07
kDelayTime_R = (iScaledFeedbackDelay * iStereoSpread) + kLFO_R
kDelayTime_R = max(kDelayTime_R, 0.001)
aBuf_R delayr idur
aTapR deltapi kDelayTime_R
delayw aDry_R + (aTapR * iFeedbackAmount)
; Comb filter for right channel
iScaledCombDelay_R = iScaledCombDelay * 1.07
aComb_R comb aDry_R, iScaledCombDelay_R, iCombFeedback
; Reverse delay for right channel
iScaledReverseDelay_R = iScaledReverseDelay * 0.93
aBufRev_R delayr iScaledReverseDelay_R
aReverseTap_R deltapi iScaledReverseDelay_R * (1 - (line:a(0, idur, 1)))
delayw aDry_R
aReverse_R = aReverseTap_R * iReverseGain
; Cascaded delays for right channel
iScaledCascade1_R = iScaledCascade1 * 1.05
iScaledCascade2_R = iScaledCascade2 * 0.95
aBuf1_R delayr idur
aCascade1_R deltapi iScaledCascade1_R
aBuf2_R delayr idur
aCascade2_R deltapi iScaledCascade2_R
delayw aCascade1_R * iCascadeFeedback
delayw aDry_R + (aCascade2_R * iCascadeFeedback)
aOut_R = aDry_R + aMultitap_R + aTapR + aComb_R + aReverse_R + aCascade1_R + aCascade2_R
outs aOut, aOut_R
endin
`;
}
protected getParametersForCsound(params: DripwaterParams): CsoundParameter[] {
return [
{ channelName: 'baseFreq', value: params.baseFreq },
{ channelName: 'dropRate', value: params.dropRate },
{ channelName: 'numElements', value: params.numElements },
{ channelName: 'damping', value: params.damping },
{ channelName: 'shakeIntensity', value: params.shakeIntensity },
{ channelName: 'freqRatio1', value: params.freqRatio1 },
{ channelName: 'freqRatio2', value: params.freqRatio2 },
{ channelName: 'freqRatio3', value: params.freqRatio3 },
{ channelName: 'attack', value: params.attack },
{ channelName: 'release', value: params.release },
{ channelName: 'amplitude', value: params.amplitude },
{ channelName: 'delayTime1', value: params.delayTime1 },
{ channelName: 'delayTime2', value: params.delayTime2 },
{ channelName: 'delayTime3', value: params.delayTime3 },
{ channelName: 'delayTime4', value: params.delayTime4 },
{ channelName: 'delayGain1', value: params.delayGain1 },
{ channelName: 'delayGain2', value: params.delayGain2 },
{ channelName: 'delayGain3', value: params.delayGain3 },
{ channelName: 'delayGain4', value: params.delayGain4 },
{ channelName: 'feedbackDelayTime', value: params.feedbackDelayTime },
{ channelName: 'feedbackAmount', value: params.feedbackAmount },
{ channelName: 'modDepth', value: params.modDepth },
{ channelName: 'modRate', value: params.modRate },
{ channelName: 'stereoSpread', value: params.stereoSpread },
{ channelName: 'combDelayTime', value: params.combDelayTime },
{ channelName: 'combFeedback', value: params.combFeedback },
{ channelName: 'reverseDelayTime', value: params.reverseDelayTime },
{ channelName: 'reverseGain', value: params.reverseGain },
{ channelName: 'cascadeDelay1', value: params.cascadeDelay1 },
{ channelName: 'cascadeDelay2', value: params.cascadeDelay2 },
{ channelName: 'cascadeFeedback', value: params.cascadeFeedback },
];
}
randomParams(pitchLock?: PitchLock): DripwaterParams {
const baseFreqChoices = [55, 82.4, 110, 146.8, 220, 293.7, 440, 587.3, 880];
const baseFreq = pitchLock?.enabled
? pitchLock.frequency
: this.randomChoice(baseFreqChoices) * this.randomRange(0.95, 1.05);
return {
baseFreq,
dropRate: this.randomRange(2, 40),
numElements: this.randomInt(4, 48),
damping: this.randomRange(0.1, 0.85),
shakeIntensity: this.randomRange(0.2, 0.9),
freqRatio1: this.randomRange(1.0, 3.5),
freqRatio2: this.randomRange(2.5, 7.0),
freqRatio3: this.randomRange(5.0, 14.0),
attack: this.randomRange(0.001, 0.04),
release: this.randomRange(0.15, 0.9),
amplitude: this.randomRange(0.6, 0.95),
delayTime1: this.randomRange(0.1, 0.3),
delayTime2: this.randomRange(0.25, 0.5),
delayTime3: this.randomRange(0.45, 0.7),
delayTime4: this.randomRange(0.65, 0.95),
delayGain1: this.randomRange(0.4, 0.7),
delayGain2: this.randomRange(0.3, 0.55),
delayGain3: this.randomRange(0.2, 0.4),
delayGain4: this.randomRange(0.1, 0.25),
feedbackDelayTime: this.randomRange(0.15, 0.5),
feedbackAmount: this.randomRange(0.3, 0.7),
modDepth: this.randomRange(0.02, 0.12),
modRate: this.randomRange(0.1, 3.0),
stereoSpread: this.randomRange(0.85, 1.15),
combDelayTime: this.randomRange(0.05, 0.25),
combFeedback: this.randomRange(0.4, 0.8),
reverseDelayTime: this.randomRange(0.3, 0.8),
reverseGain: this.randomRange(0.15, 0.4),
cascadeDelay1: this.randomRange(0.2, 0.45),
cascadeDelay2: this.randomRange(0.35, 0.65),
cascadeFeedback: this.randomRange(0.3, 0.65),
};
}
mutateParams(
params: DripwaterParams,
mutationAmount: number = 0.15,
pitchLock?: PitchLock
): DripwaterParams {
const baseFreq = pitchLock?.enabled ? pitchLock.frequency : params.baseFreq;
return {
baseFreq,
dropRate: this.mutateValue(params.dropRate, mutationAmount, 1, 50),
numElements: Math.round(this.mutateValue(params.numElements, mutationAmount, 2, 64)),
damping: this.mutateValue(params.damping, mutationAmount, 0.0, 0.95),
shakeIntensity: this.mutateValue(params.shakeIntensity, mutationAmount, 0.1, 1.0),
freqRatio1: this.mutateValue(params.freqRatio1, mutationAmount, 1.0, 4.0),
freqRatio2: this.mutateValue(params.freqRatio2, mutationAmount, 2.0, 8.0),
freqRatio3: this.mutateValue(params.freqRatio3, mutationAmount, 4.0, 16.0),
attack: this.mutateValue(params.attack, mutationAmount, 0.001, 0.05),
release: this.mutateValue(params.release, mutationAmount, 0.1, 0.95),
amplitude: this.mutateValue(params.amplitude, mutationAmount, 0.5, 1.0),
delayTime1: this.mutateValue(params.delayTime1, mutationAmount, 0.05, 0.35),
delayTime2: this.mutateValue(params.delayTime2, mutationAmount, 0.2, 0.6),
delayTime3: this.mutateValue(params.delayTime3, mutationAmount, 0.4, 0.8),
delayTime4: this.mutateValue(params.delayTime4, mutationAmount, 0.6, 0.98),
delayGain1: this.mutateValue(params.delayGain1, mutationAmount, 0.3, 0.8),
delayGain2: this.mutateValue(params.delayGain2, mutationAmount, 0.2, 0.65),
delayGain3: this.mutateValue(params.delayGain3, mutationAmount, 0.1, 0.5),
delayGain4: this.mutateValue(params.delayGain4, mutationAmount, 0.05, 0.35),
feedbackDelayTime: this.mutateValue(params.feedbackDelayTime, mutationAmount, 0.1, 0.6),
feedbackAmount: this.mutateValue(params.feedbackAmount, mutationAmount, 0.2, 0.8),
modDepth: this.mutateValue(params.modDepth, mutationAmount, 0.01, 0.15),
modRate: this.mutateValue(params.modRate, mutationAmount, 0.05, 4.0),
stereoSpread: this.mutateValue(params.stereoSpread, mutationAmount, 0.7, 1.3),
combDelayTime: this.mutateValue(params.combDelayTime, mutationAmount, 0.03, 0.3),
combFeedback: this.mutateValue(params.combFeedback, mutationAmount, 0.3, 0.85),
reverseDelayTime: this.mutateValue(params.reverseDelayTime, mutationAmount, 0.2, 0.9),
reverseGain: this.mutateValue(params.reverseGain, mutationAmount, 0.1, 0.5),
cascadeDelay1: this.mutateValue(params.cascadeDelay1, mutationAmount, 0.15, 0.5),
cascadeDelay2: this.mutateValue(params.cascadeDelay2, mutationAmount, 0.3, 0.7),
cascadeFeedback: this.mutateValue(params.cascadeFeedback, mutationAmount, 0.2, 0.75),
};
}
}

View File

@ -0,0 +1,296 @@
import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
import type { PitchLock } from './base/SynthEngine';
interface Form1Params {
frequency: number;
vibratoRate: number;
vibratoDepth: number;
formant1Freq: number;
formant1BW: number;
formant1Amp: number;
formant2Freq: number;
formant2BW: number;
formant2Amp: number;
formant3Freq: number;
formant3BW: number;
formant3Amp: number;
formant4Freq: number;
formant4BW: number;
formant4Amp: number;
formant5Freq: number;
formant5BW: number;
formant5Amp: number;
attack: number;
decay: number;
sustain: number;
release: number;
}
export class Form1 extends CsoundEngine<Form1Params> {
getName(): string {
return 'Throat';
}
getDescription(): string {
return 'Multi-voice formant vocal synthesizer with deep sub-bass and wide stereo field';
}
getType(): 'generative' | 'sample' | 'input' {
return 'generative';
}
getCategory(): 'Additive' | 'Subtractive' | 'FM' | 'Percussion' | 'Noise' | 'Physical' | 'Modulation' | 'Experimental' | 'Utility' {
return 'Physical';
}
getOrchestra(): string {
return `
instr 1
ifreq = chnget("frequency")
iamp = 0.3
ivibratoRate = chnget("vibratoRate")
ivibratoDepth = chnget("vibratoDepth")
if1freq = chnget("formant1Freq")
if1bw = chnget("formant1BW")
if1amp = chnget("formant1Amp")
if2freq = chnget("formant2Freq")
if2bw = chnget("formant2BW")
if2amp = chnget("formant2Amp")
if3freq = chnget("formant3Freq")
if3bw = chnget("formant3BW")
if3amp = chnget("formant3Amp")
if4freq = chnget("formant4Freq")
if4bw = chnget("formant4BW")
if4amp = chnget("formant4Amp")
if5freq = chnget("formant5Freq")
if5bw = chnget("formant5BW")
if5amp = chnget("formant5Amp")
iatt = chnget("attack") * p3
idec = chnget("decay") * p3
isus = chnget("sustain")
irel = chnget("release") * p3
; Amplitude envelope
aenv = madsr(iatt, idec, isus, irel)
; Dual vibrato LFOs for stereo width
kvibL = lfo(ivibratoDepth, ivibratoRate)
kvibR = lfo(ivibratoDepth, ivibratoRate * 1.03)
; === LEFT CHANNEL VOICES ===
; Voice 1L: Main voice
kfreq1L = ifreq * (1 + kvibL)
inumharm1L = int(sr / (2 * ifreq))
asig1L = buzz(1, kfreq1L, inumharm1L, -1)
; Voice 2L: Slightly detuned (0.987x for beating)
kfreq2L = ifreq * 0.987 * (1 + kvibL * 1.02)
inumharm2L = int(sr / (2 * ifreq * 0.987))
asig2L = buzz(0.85, kfreq2L, inumharm2L, -1)
; Voice 3L: Slightly detuned up (1.013x for beating)
kfreq3L = ifreq * 1.013 * (1 + kvibL * 0.98)
inumharm3L = int(sr / (2 * ifreq * 1.013))
asig3L = buzz(0.8, kfreq3L, inumharm3L, -1)
; Voice 4L: Octave down
kfreq4L = ifreq * 0.5 * (1 + kvibL * 1.04)
inumharm4L = int(sr / (2 * ifreq * 0.5))
asig4L = buzz(0.65, kfreq4L, inumharm4L, -1)
; Voice 5L: Octave up
kfreq5L = ifreq * 2 * (1 + kvibL * 0.96)
inumharm5L = int(sr / (2 * ifreq * 2))
asig5L = buzz(0.45, kfreq5L, inumharm5L, -1)
; === RIGHT CHANNEL VOICES (different detuning) ===
; Voice 1R: Main voice slightly different
kfreq1R = ifreq * 1.002 * (1 + kvibR)
inumharm1R = int(sr / (2 * ifreq * 1.002))
asig1R = buzz(1, kfreq1R, inumharm1R, -1)
; Voice 2R: Different detuning (0.993x)
kfreq2R = ifreq * 0.993 * (1 + kvibR * 1.01)
inumharm2R = int(sr / (2 * ifreq * 0.993))
asig2R = buzz(0.85, kfreq2R, inumharm2R, -1)
; Voice 3R: Different detuning up (1.007x)
kfreq3R = ifreq * 1.007 * (1 + kvibR * 0.99)
inumharm3R = int(sr / (2 * ifreq * 1.007))
asig3R = buzz(0.8, kfreq3R, inumharm3R, -1)
; Voice 4R: Octave down different phase
kfreq4R = ifreq * 0.501 * (1 + kvibR * 1.06)
inumharm4R = int(sr / (2 * ifreq * 0.501))
asig4R = buzz(0.6, kfreq4R, inumharm4R, -1)
; Voice 5R: Octave up different phase
kfreq5R = ifreq * 1.998 * (1 + kvibR * 0.94)
inumharm5R = int(sr / (2 * ifreq * 1.998))
asig5R = buzz(0.4, kfreq5R, inumharm5R, -1)
; Sub-bass: Buzz oscillator one octave below with harmonic content
kfreqSubL = ifreq * 0.5 * (1 + kvibL * 0.5)
kfreqSubR = ifreq * 0.502 * (1 + kvibR * 0.5)
inumharmSubL = int(sr / (2 * ifreq * 0.5))
inumharmSubR = int(sr / (2 * ifreq * 0.502))
aSubRawL = buzz(1, kfreqSubL, inumharmSubL, -1)
aSubRawR = buzz(1, kfreqSubR, inumharmSubR, -1)
; Apply very wide formants for vocal character with depth
aSubF1L = butterbp(aSubRawL, if1freq * 0.6, if1bw * 8)
aSubF2L = butterbp(aSubRawL, if2freq * 0.7, if2bw * 10)
aSubF3L = butterbp(aSubRawL, if3freq * 0.65, if3bw * 9)
aSubF1R = butterbp(aSubRawR, if1freq * 0.62, if1bw * 8.5)
aSubF2R = butterbp(aSubRawR, if2freq * 0.68, if2bw * 10.5)
aSubF3R = butterbp(aSubRawR, if3freq * 0.67, if3bw * 9.5)
; Mix sub with formants for depth and complexity
aSubMixL = (aSubRawL * 0.3) + (aSubF1L * 0.3) + (aSubF2L * 0.25) + (aSubF3L * 0.15)
aSubMixR = (aSubRawR * 0.3) + (aSubF1R * 0.3) + (aSubF2R * 0.25) + (aSubF3R * 0.15)
; Add gentle low-pass for warmth and smooth out harsh frequencies
aSubL = butterlp(aSubMixL, 1200)
aSubR = butterlp(aSubMixR, 1250)
; Scale sub-bass
aSubL = aSubL * 0.8
aSubR = aSubR * 0.8
; Mix voices per channel (sub added separately later)
asigMixL = asig1L + asig2L + asig3L + asig4L + asig5L
asigMixR = asig1R + asig2R + asig3R + asig4R + asig5R
; === LEFT CHANNEL FORMANTS ===
; Main formants
a1L = butterbp(asigMixL * if1amp, if1freq, if1bw)
a2L = butterbp(asigMixL * if2amp, if2freq, if2bw)
a3L = butterbp(asigMixL * if3amp, if3freq, if3bw)
a4L = butterbp(asigMixL * if4amp, if4freq, if4bw)
a5L = butterbp(asigMixL * if5amp, if5freq, if5bw)
; Additional formant layers
a1bL = butterbp(asigMixL * if1amp * 0.35, if1freq * 1.025, if1bw * 1.12)
a2bL = butterbp(asigMixL * if2amp * 0.3, if2freq * 0.975, if2bw * 1.18)
a3bL = butterbp(asigMixL * if3amp * 0.25, if3freq * 1.035, if3bw * 1.1)
; === RIGHT CHANNEL FORMANTS (different shifts) ===
; Main formants
a1R = butterbp(asigMixR * if1amp, if1freq * 1.005, if1bw * 1.02)
a2R = butterbp(asigMixR * if2amp, if2freq * 0.995, if2bw * 1.03)
a3R = butterbp(asigMixR * if3amp, if3freq * 1.008, if3bw * 1.01)
a4R = butterbp(asigMixR * if4amp, if4freq * 0.997, if4bw * 1.04)
a5R = butterbp(asigMixR * if5amp, if5freq * 1.003, if5bw * 1.02)
; Additional formant layers
a1bR = butterbp(asigMixR * if1amp * 0.3, if1freq * 0.98, if1bw * 1.15)
a2bR = butterbp(asigMixR * if2amp * 0.28, if2freq * 1.022, if2bw * 1.2)
a3bR = butterbp(asigMixR * if3amp * 0.22, if3freq * 0.97, if3bw * 1.12)
; Combine formants per channel
asigOutL = a1L + a2L + a3L + a4L + a5L + a1bL + a2bL + a3bL
asigOutR = a1R + a2R + a3R + a4R + a5R + a1bR + a2bR + a3bR
; Apply envelope and level
asigFormantL = asigOutL * aenv * iamp * 4
asigFormantR = asigOutR * aenv * iamp * 4
; Add sub-bass directly (bypassing formants)
aL = asigFormantL + (aSubL * aenv)
aR = asigFormantR + (aSubR * aenv)
outs aL, aR
endin
`;
}
getParametersForCsound(params: Form1Params): CsoundParameter[] {
return [
{ channelName: 'frequency', value: params.frequency },
{ channelName: 'vibratoRate', value: params.vibratoRate },
{ channelName: 'vibratoDepth', value: params.vibratoDepth },
{ channelName: 'formant1Freq', value: params.formant1Freq },
{ channelName: 'formant1BW', value: params.formant1BW },
{ channelName: 'formant1Amp', value: params.formant1Amp },
{ channelName: 'formant2Freq', value: params.formant2Freq },
{ channelName: 'formant2BW', value: params.formant2BW },
{ channelName: 'formant2Amp', value: params.formant2Amp },
{ channelName: 'formant3Freq', value: params.formant3Freq },
{ channelName: 'formant3BW', value: params.formant3BW },
{ channelName: 'formant3Amp', value: params.formant3Amp },
{ channelName: 'formant4Freq', value: params.formant4Freq },
{ channelName: 'formant4BW', value: params.formant4BW },
{ channelName: 'formant4Amp', value: params.formant4Amp },
{ channelName: 'formant5Freq', value: params.formant5Freq },
{ channelName: 'formant5BW', value: params.formant5BW },
{ channelName: 'formant5Amp', value: params.formant5Amp },
{ channelName: 'attack', value: params.attack },
{ channelName: 'decay', value: params.decay },
{ channelName: 'sustain', value: params.sustain },
{ channelName: 'release', value: params.release }
];
}
randomParams(pitchLock?: PitchLock): Form1Params {
const frequency = pitchLock?.enabled ? pitchLock.frequency : 55 * Math.pow(2, Math.random() * 4);
return {
frequency,
vibratoRate: 2 + Math.random() * 6,
vibratoDepth: 0.001 + Math.random() * 0.008,
formant1Freq: 400 + Math.random() * 800,
formant1BW: 40 + Math.random() * 100,
formant1Amp: 0.8 + Math.random() * 0.2,
formant2Freq: 800 + Math.random() * 600,
formant2BW: 50 + Math.random() * 100,
formant2Amp: 0.4 + Math.random() * 0.4,
formant3Freq: 2000 + Math.random() * 1500,
formant3BW: 80 + Math.random() * 120,
formant3Amp: 0.05 + Math.random() * 0.15,
formant4Freq: 3000 + Math.random() * 1500,
formant4BW: 100 + Math.random() * 100,
formant4Amp: 0.1 + Math.random() * 0.2,
formant5Freq: 4000 + Math.random() * 1500,
formant5BW: 100 + Math.random() * 150,
formant5Amp: 0.01 + Math.random() * 0.1,
attack: Math.random() * 0.15,
decay: 0.05 + Math.random() * 0.3,
sustain: 0.3 + Math.random() * 0.5,
release: 0.05 + Math.random() * 0.4
};
}
mutateParams(params: Form1Params, mutationAmount = 0.2, pitchLock?: PitchLock): Form1Params {
const mutate = (value: number, min: number, max: number) => {
const change = (Math.random() - 0.5) * 2 * mutationAmount * (max - min);
return Math.max(min, Math.min(max, value + change));
};
return {
frequency: pitchLock?.enabled ? pitchLock.frequency : mutate(params.frequency, 55, 55 * Math.pow(2, 4)),
vibratoRate: mutate(params.vibratoRate, 2, 8),
vibratoDepth: mutate(params.vibratoDepth, 0.001, 0.009),
formant1Freq: mutate(params.formant1Freq, 400, 1200),
formant1BW: mutate(params.formant1BW, 40, 140),
formant1Amp: mutate(params.formant1Amp, 0.8, 1.0),
formant2Freq: mutate(params.formant2Freq, 800, 1400),
formant2BW: mutate(params.formant2BW, 50, 150),
formant2Amp: mutate(params.formant2Amp, 0.4, 0.8),
formant3Freq: mutate(params.formant3Freq, 2000, 3500),
formant3BW: mutate(params.formant3BW, 80, 200),
formant3Amp: mutate(params.formant3Amp, 0.05, 0.2),
formant4Freq: mutate(params.formant4Freq, 3000, 4500),
formant4BW: mutate(params.formant4BW, 100, 200),
formant4Amp: mutate(params.formant4Amp, 0.1, 0.3),
formant5Freq: mutate(params.formant5Freq, 4000, 5500),
formant5BW: mutate(params.formant5BW, 100, 250),
formant5Amp: mutate(params.formant5Amp, 0.01, 0.11),
attack: mutate(params.attack, 0, 0.15),
decay: mutate(params.decay, 0.05, 0.35),
sustain: mutate(params.sustain, 0.3, 0.8),
release: mutate(params.release, 0.05, 0.45)
};
}
}

View File

@ -0,0 +1,244 @@
import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
interface LaserSweepParams {
startFreq: number;
endFreq: number;
sweepCurve: number;
resonance: number;
brightness: number;
delayTime: number;
delayFeedback: number;
reverbMix: number;
reverbSize: number;
noiseAmount: number;
decayShape: number;
waveformMix: number;
mainWaveType: number;
secondWaveType: number;
pulseWidth: number;
filterTracking: number;
subOctave: number;
subOctaveLevel: number;
}
export class LaserSweep extends CsoundEngine<LaserSweepParams> {
getName(): string {
return 'Laser Sweep';
}
getDescription(): string {
return 'Sweeping laser beam with delay and reverb';
}
getType(): 'generative' | 'sample' | 'input' {
return 'generative';
}
getCategory() {
return 'Experimental' as const;
}
getOrchestra(): string {
return `
sr = 44100
ksmps = 64
nchnls = 2
0dbfs = 1
instr 1
iDur = p3
iStartFreq chnget "startFreq"
iEndFreq chnget "endFreq"
iSweepCurve chnget "sweepCurve"
iResonance chnget "resonance"
iBrightness chnget "brightness"
iDelayTime chnget "delayTime"
iDelayFeedback chnget "delayFeedback"
iReverbMix chnget "reverbMix"
iReverbSize chnget "reverbSize"
iNoiseAmount chnget "noiseAmount"
iDecayShape chnget "decayShape"
iWaveformMix chnget "waveformMix"
iMainWaveType chnget "mainWaveType"
iSecondWaveType chnget "secondWaveType"
iPulseWidth chnget "pulseWidth"
iFilterTracking chnget "filterTracking"
iSubOctave chnget "subOctave"
iSubOctaveLevel chnget "subOctaveLevel"
if iDecayShape < 0.33 then
kEnv expseg 1, iDur * 0.85, 1, iDur * 0.15, 0.001
elseif iDecayShape < 0.67 then
kEnv linseg 1, iDur * 0.85, 1, iDur * 0.15, 0
else
kEnv expseg 1, iDur * 0.8, 1, iDur * 0.18, 0.001
endif
kFreq expseg iStartFreq, iDur * iSweepCurve, iEndFreq
kFreqLimited limit kFreq, 40, 16000
if iMainWaveType < 0.2 then
aMain vco2 0.6, kFreqLimited, 10
elseif iMainWaveType < 0.4 then
aMain vco2 0.6, kFreqLimited, 0
elseif iMainWaveType < 0.6 then
aMain vco2 0.6, kFreqLimited, 2, iPulseWidth
elseif iMainWaveType < 0.8 then
aMain vco2 0.6, kFreqLimited, 12
else
aMain oscili 0.6, kFreqLimited
endif
if iSecondWaveType < 0.2 then
aSecond vco2 0.5, kFreqLimited, 2, iPulseWidth
elseif iSecondWaveType < 0.4 then
aSecond vco2 0.5, kFreqLimited, 0
elseif iSecondWaveType < 0.6 then
aSecond vco2 0.5, kFreqLimited, 12
elseif iSecondWaveType < 0.8 then
aSecond vco2 0.5, kFreqLimited, 10
else
aSecond oscili 0.5, kFreqLimited * 1.5
endif
aNoise rand 0.3
kNoiseFiltFreq limit kFreqLimited * 0.5, 50, 12000
aNoise butterbp aNoise, kNoiseFiltFreq, kNoiseFiltFreq * 0.5
if iSubOctaveLevel > 0.01 then
if iSubOctave < 0.5 then
kSubFreq = kFreqLimited * 0.5
else
kSubFreq = kFreqLimited * 0.25
endif
aSub oscili iSubOctaveLevel * 0.7, kSubFreq
else
aSub = 0
endif
aOsc = aMain + aSecond * iWaveformMix + aSecond * iBrightness * 0.3 + aNoise * iNoiseAmount + aSub
kFilterFreq = kFreqLimited * (1 + (1 - iFilterTracking) * 2)
kFilterFreq limit kFilterFreq, 50, 15000
kBandwidth = kFilterFreq * (0.1 + iResonance * 0.3)
aFilt butterbp aOsc, kFilterFreq, kBandwidth
aFilt = aFilt * kEnv * 0.8
if iDelayTime > 0.01 then
aDlyL vdelay3 aFilt, iDelayTime * 1000, 2000
aDlyR vdelay3 aFilt, iDelayTime * 1000 * 1.1, 2000
aDlyL = aDlyL * iDelayFeedback * kEnv
aDlyR = aDlyR * iDelayFeedback * kEnv
else
aDlyL = 0
aDlyR = 0
endif
if iReverbMix > 0.01 then
aWetL, aWetR reverbsc aFilt + aDlyL * 0.5, aFilt + aDlyR * 0.5, iReverbSize, 8000
else
aWetL = 0
aWetR = 0
endif
aOutL = aFilt + aDlyL * 0.4 + aWetL * iReverbMix
aOutR = aFilt + aDlyR * 0.4 + aWetR * iReverbMix
outs aOutL, aOutR
endin
`;
}
getParametersForCsound(params: LaserSweepParams): CsoundParameter[] {
return [
{ channelName: 'startFreq', value: params.startFreq },
{ channelName: 'endFreq', value: params.endFreq },
{ channelName: 'sweepCurve', value: params.sweepCurve },
{ channelName: 'resonance', value: params.resonance },
{ channelName: 'brightness', value: params.brightness },
{ channelName: 'delayTime', value: params.delayTime },
{ channelName: 'delayFeedback', value: params.delayFeedback },
{ channelName: 'reverbMix', value: params.reverbMix },
{ channelName: 'reverbSize', value: params.reverbSize },
{ channelName: 'noiseAmount', value: params.noiseAmount },
{ channelName: 'decayShape', value: params.decayShape },
{ channelName: 'waveformMix', value: params.waveformMix },
{ channelName: 'mainWaveType', value: params.mainWaveType },
{ channelName: 'secondWaveType', value: params.secondWaveType },
{ channelName: 'pulseWidth', value: params.pulseWidth },
{ channelName: 'filterTracking', value: params.filterTracking },
{ channelName: 'subOctave', value: params.subOctave },
{ channelName: 'subOctaveLevel', value: params.subOctaveLevel }
];
}
randomParams(pitchLock?: number): LaserSweepParams {
const isUpward = Math.random() > 0.3;
const startFreq = isUpward
? 60 + Math.random() * 200
: 1000 + Math.random() * 10000;
const endFreq = isUpward
? 1000 + Math.random() * 10000
: 60 + Math.random() * 200;
return {
startFreq,
endFreq,
sweepCurve: 0.2 + Math.random() * 0.7,
resonance: 0.2 + Math.random() * 0.7,
brightness: 0.1 + Math.random() * 0.8,
delayTime: Math.random() < 0.3 ? 0 : 0.05 + Math.random() * 0.5,
delayFeedback: 0.1 + Math.random() * 0.7,
reverbMix: Math.random() < 0.2 ? 0 : 0.1 + Math.random() * 0.5,
reverbSize: 0.4 + Math.random() * 0.5,
noiseAmount: 0.05 + Math.random() * 0.5,
decayShape: Math.random(),
waveformMix: 0.3 + Math.random() * 0.7,
mainWaveType: Math.random(),
secondWaveType: Math.random(),
pulseWidth: 0.1 + Math.random() * 0.8,
filterTracking: 0.3 + Math.random() * 0.7,
subOctave: Math.random(),
subOctaveLevel: Math.random() < 0.4 ? 0 : 0.3 + Math.random() * 0.7
};
}
mutateParams(
params: LaserSweepParams,
mutationAmount: number = 0.3,
pitchLock?: number
): LaserSweepParams {
const mutate = (value: number, min: number, max: number): number => {
const range = max - min;
const change = (Math.random() - 0.5) * range * mutationAmount;
return Math.max(min, Math.min(max, value + change));
};
return {
startFreq: mutate(params.startFreq, 40, 12000),
endFreq: mutate(params.endFreq, 40, 12000),
sweepCurve: mutate(params.sweepCurve, 0.1, 0.95),
resonance: mutate(params.resonance, 0.1, 1.0),
brightness: mutate(params.brightness, 0.0, 1.0),
delayTime: mutate(params.delayTime, 0.0, 0.6),
delayFeedback: mutate(params.delayFeedback, 0.0, 0.85),
reverbMix: mutate(params.reverbMix, 0.0, 0.7),
reverbSize: mutate(params.reverbSize, 0.3, 0.95),
noiseAmount: mutate(params.noiseAmount, 0.0, 0.7),
decayShape: mutate(params.decayShape, 0.0, 1.0),
waveformMix: mutate(params.waveformMix, 0.0, 1.0),
mainWaveType: mutate(params.mainWaveType, 0.0, 1.0),
secondWaveType: mutate(params.secondWaveType, 0.0, 1.0),
pulseWidth: mutate(params.pulseWidth, 0.05, 0.95),
filterTracking: mutate(params.filterTracking, 0.0, 1.0),
subOctave: mutate(params.subOctave, 0.0, 1.0),
subOctaveLevel: mutate(params.subOctaveLevel, 0.0, 1.0)
};
}
}

View File

@ -0,0 +1,149 @@
import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
import type { PitchLock } from './base/SynthEngine';
interface Squine1Params {
frequency: number;
shapeAmount1: number;
shapeAmount2: number;
modulationDepth1: number;
modulationDepth2: number;
osc2Detune: number;
osc2Level: number;
hpfCutoff: number;
attack: number;
decay: number;
sustain: number;
release: number;
}
export class Squine1 extends CsoundEngine<Squine1Params> {
getName(): string {
return 'Squine1';
}
getDescription(): string {
return 'Squinewave synthesizer with waveshaping and dual oscillators';
}
getType(): 'generative' | 'sample' | 'input' {
return 'generative';
}
getCategory(): 'Additive' | 'Subtractive' | 'FM' | 'Percussion' | 'Noise' | 'Physical' | 'Modulation' | 'Experimental' | 'Utility' {
return 'Subtractive';
}
getOrchestra(): string {
return `
instr 1
ifreq = chnget("frequency")
iamp = 0.5
ishape1 = chnget("shapeAmount1")
ishape2 = chnget("shapeAmount2")
imod1 = chnget("modulationDepth1")
imod2 = chnget("modulationDepth2")
idetune = chnget("osc2Detune")
iosc2Level = chnget("osc2Level")
ihpf = chnget("hpfCutoff")
iatt = chnget("attack") * p3
idec = chnget("decay") * p3
isus = chnget("sustain")
irel = chnget("release") * p3
; First oscillator with waveshaping (squine approximation)
aosc1 = poscil(1, ifreq)
; Create dynamic shape envelope for osc1
kshape1 = expseg(ishape1, p3, ishape1 * imod1)
; Waveshaping: blend between sine and square-like
asig1 = tanh(aosc1 * kshape1 * 5) / tanh(kshape1 * 5)
; Second oscillator slightly detuned
aosc2 = poscil(1, ifreq * (1 + idetune))
; Create dynamic shape envelope for osc2
kshape2 = expseg(ishape2, p3, ishape2 * imod2)
; Waveshaping for second oscillator
asig2 = tanh(aosc2 * kshape2 * 5) / tanh(kshape2 * 5)
; Mix oscillators
asig = asig1 + (asig2 * iosc2Level)
; High-pass filter to remove DC and rumble
kcutoff = max(ihpf, 20)
asig = butterhp(asig, kcutoff)
; Amplitude envelope
aenv = madsr(iatt, idec, isus, irel)
asig = asig * aenv * iamp
; DC blocker
asig = dcblock2(asig)
outs asig, asig
endin
`;
}
getParametersForCsound(params: Squine1Params): CsoundParameter[] {
return [
{ channelName: 'frequency', value: params.frequency },
{ channelName: 'shapeAmount1', value: params.shapeAmount1 },
{ channelName: 'shapeAmount2', value: params.shapeAmount2 },
{ channelName: 'modulationDepth1', value: params.modulationDepth1 },
{ channelName: 'modulationDepth2', value: params.modulationDepth2 },
{ channelName: 'osc2Detune', value: params.osc2Detune },
{ channelName: 'osc2Level', value: params.osc2Level },
{ channelName: 'hpfCutoff', value: params.hpfCutoff },
{ channelName: 'attack', value: params.attack },
{ channelName: 'decay', value: params.decay },
{ channelName: 'sustain', value: params.sustain },
{ channelName: 'release', value: params.release }
];
}
randomParams(pitchLock?: PitchLock): Squine1Params {
const frequency = pitchLock?.enabled ? pitchLock.frequency : 55 * Math.pow(2, Math.random() * 5);
return {
frequency,
shapeAmount1: 0.2 + Math.random() * 0.8,
shapeAmount2: 0.2 + Math.random() * 0.8,
modulationDepth1: 0.05 + Math.random() * 0.7,
modulationDepth2: 0.05 + Math.random() * 0.7,
osc2Detune: 0.0001 + Math.random() * 0.005,
osc2Level: 0.01 + Math.random() * 0.15,
hpfCutoff: frequency * 0.5 + Math.random() * frequency,
attack: 0.001 + Math.random() * 0.05,
decay: 0.05 + Math.random() * 0.3,
sustain: 0.2 + Math.random() * 0.6,
release: 0.01 + Math.random() * 0.3
};
}
mutateParams(params: Squine1Params, mutationAmount = 0.2, pitchLock?: PitchLock): Squine1Params {
const mutate = (value: number, min: number, max: number) => {
const change = (Math.random() - 0.5) * 2 * mutationAmount * (max - min);
return Math.max(min, Math.min(max, value + change));
};
const newFreq = pitchLock?.enabled ? pitchLock.frequency : mutate(params.frequency, 55, 55 * Math.pow(2, 5));
return {
frequency: newFreq,
shapeAmount1: mutate(params.shapeAmount1, 0.2, 1.0),
shapeAmount2: mutate(params.shapeAmount2, 0.2, 1.0),
modulationDepth1: mutate(params.modulationDepth1, 0.05, 0.75),
modulationDepth2: mutate(params.modulationDepth2, 0.05, 0.75),
osc2Detune: mutate(params.osc2Detune, 0.0001, 0.0051),
osc2Level: mutate(params.osc2Level, 0.01, 0.16),
hpfCutoff: mutate(params.hpfCutoff, newFreq * 0.5, newFreq * 1.5),
attack: mutate(params.attack, 0.001, 0.051),
decay: mutate(params.decay, 0.05, 0.35),
sustain: mutate(params.sustain, 0.2, 0.8),
release: mutate(params.release, 0.01, 0.31)
};
}
}

View File

@ -0,0 +1,135 @@
import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine';
import type { PitchLock } from './base/SynthEngine';
interface Sub8Params {
frequency: number;
filterCutoff: number;
filterEnvAmount: number;
resonance: number;
saturation: number;
osc1Level: number;
osc2Level: number;
osc3Level: number;
attack: number;
decay: number;
sustain: number;
release: number;
}
export class Sub8 extends CsoundEngine<Sub8Params> {
getName(): string {
return 'Sub8';
}
getDescription(): string {
return 'Multi-oscillator subtractive synth with diode ladder filter and saturation';
}
getType(): 'generative' | 'sample' | 'input' {
return 'generative';
}
getCategory(): 'Additive' | 'Subtractive' | 'FM' | 'Percussion' | 'Noise' | 'Physical' | 'Modulation' | 'Experimental' | 'Utility' {
return 'Subtractive';
}
getOrchestra(): string {
return `
instr 1
ifreq = chnget("frequency")
iamp = 0.5
ifilterCutoff = chnget("filterCutoff")
ifilterEnvAmount = chnget("filterEnvAmount")
ireso = chnget("resonance")
isat = chnget("saturation")
iosc1Level = chnget("osc1Level")
iosc2Level = chnget("osc2Level")
iosc3Level = chnget("osc3Level")
iatt = chnget("attack") * p3
idec = chnget("decay") * p3
isus = chnget("sustain")
irel = chnget("release") * p3
; Three detuned oscillators
asig1 = vco2(iamp * iosc1Level, ifreq, 10)
asig2 = vco2(iamp * iosc2Level, ifreq * 2, 10)
asig3 = vco2(iamp * iosc3Level, ifreq * 3.5, 12)
asig = asig1 + asig2 + asig3
; Saturation
asig = tanh(asig * isat) / tanh(isat)
; Filter envelope
aenv = madsr(iatt, idec, isus, irel)
kcutoff = ifilterCutoff + (aenv * ifilterEnvAmount)
kcutoff = limit(kcutoff, 20, 18000)
; Diode ladder filter
asig = diode_ladder(asig, kcutoff, ireso)
; Final amplitude envelope
asig = asig * aenv * 0.7
outs asig, asig
endin
`;
}
getParametersForCsound(params: Sub8Params): CsoundParameter[] {
return [
{ channelName: 'frequency', value: params.frequency },
{ channelName: 'filterCutoff', value: params.filterCutoff },
{ channelName: 'filterEnvAmount', value: params.filterEnvAmount },
{ channelName: 'resonance', value: params.resonance },
{ channelName: 'saturation', value: params.saturation },
{ channelName: 'osc1Level', value: params.osc1Level },
{ channelName: 'osc2Level', value: params.osc2Level },
{ channelName: 'osc3Level', value: params.osc3Level },
{ channelName: 'attack', value: params.attack },
{ channelName: 'decay', value: params.decay },
{ channelName: 'sustain', value: params.sustain },
{ channelName: 'release', value: params.release }
];
}
randomParams(pitchLock?: PitchLock): Sub8Params {
const frequency = pitchLock?.enabled ? pitchLock.frequency : 55 * Math.pow(2, Math.random() * 5);
return {
frequency,
filterCutoff: 200 + Math.random() * 3800,
filterEnvAmount: Math.random() * 6000,
resonance: 0.5 + Math.random() * 14.5,
saturation: 1 + Math.random() * 9,
osc1Level: 0.5 + Math.random() * 0.5,
osc2Level: Math.random() * 0.5,
osc3Level: Math.random() * 0.3,
attack: Math.random() * 0.1,
decay: 0.05 + Math.random() * 0.3,
sustain: 0.2 + Math.random() * 0.6,
release: 0.05 + Math.random() * 0.4
};
}
mutateParams(params: Sub8Params, mutationAmount = 0.2, pitchLock?: PitchLock): Sub8Params {
const mutate = (value: number, min: number, max: number) => {
const change = (Math.random() - 0.5) * 2 * mutationAmount * (max - min);
return Math.max(min, Math.min(max, value + change));
};
return {
frequency: pitchLock?.enabled ? pitchLock.frequency : mutate(params.frequency, 55, 55 * Math.pow(2, 5)),
filterCutoff: mutate(params.filterCutoff, 200, 4000),
filterEnvAmount: mutate(params.filterEnvAmount, 0, 6000),
resonance: mutate(params.resonance, 0.5, 15),
saturation: mutate(params.saturation, 1, 10),
osc1Level: mutate(params.osc1Level, 0.5, 1),
osc2Level: mutate(params.osc2Level, 0, 0.5),
osc3Level: mutate(params.osc3Level, 0, 0.3),
attack: mutate(params.attack, 0, 0.1),
decay: mutate(params.decay, 0.05, 0.35),
sustain: mutate(params.sustain, 0.2, 0.8),
release: mutate(params.release, 0.05, 0.45)
};
}
}

View File

@ -45,11 +45,17 @@ export abstract class CsoundEngine<T = any> implements SynthEngine<T> {
await csound.terminateInstance();
const leftChannel = new Float32Array(audioBuffer.leftChannel);
const rightChannel = new Float32Array(audioBuffer.rightChannel);
let leftChannel = new Float32Array(audioBuffer.leftChannel);
let rightChannel = new Float32Array(audioBuffer.rightChannel);
this.removeDCOffset(leftChannel, rightChannel);
const trimmed = this.trimToZeroCrossing(leftChannel, rightChannel, sampleRate);
leftChannel = trimmed.left;
rightChannel = trimmed.right;
// Apply short fade-in to prevent click at start
this.applyFadeIn(leftChannel, rightChannel, sampleRate);
this.applyFadeOut(leftChannel, rightChannel, sampleRate);
const peak = this.findPeak(leftChannel, rightChannel);
if (peak > 0.001) {
@ -218,22 +224,95 @@ e
}
}
private removeDCOffset(leftChannel: Float32Array, rightChannel: Float32Array): void {
let leftSum = 0;
let rightSum = 0;
const length = leftChannel.length;
for (let i = 0; i < length; i++) {
leftSum += leftChannel[i];
rightSum += rightChannel[i];
}
const leftDC = leftSum / length;
const rightDC = rightSum / length;
for (let i = 0; i < length; i++) {
leftChannel[i] -= leftDC;
rightChannel[i] -= rightDC;
}
}
private trimToZeroCrossing(
leftChannel: Float32Array,
rightChannel: Float32Array,
sampleRate: number
): { left: Float32Array; right: Float32Array } {
const maxSearchSamples = Math.min(Math.floor(sampleRate * 0.01), leftChannel.length);
let trimIndex = 0;
for (let i = 1; i < maxSearchSamples; i++) {
const prevL = leftChannel[i - 1];
const currL = leftChannel[i];
const prevR = rightChannel[i - 1];
const currR = rightChannel[i];
if (
(prevL <= 0 && currL >= 0) || (prevL >= 0 && currL <= 0) ||
(prevR <= 0 && currR >= 0) || (prevR >= 0 && currR <= 0)
) {
trimIndex = i;
break;
}
}
if (trimIndex > 0) {
const newLeft = new Float32Array(leftChannel.length - trimIndex);
const newRight = new Float32Array(rightChannel.length - trimIndex);
newLeft.set(leftChannel.subarray(trimIndex));
newRight.set(rightChannel.subarray(trimIndex));
return { left: newLeft, right: newRight };
}
return { left: leftChannel, right: rightChannel };
}
private applyFadeIn(
leftChannel: Float32Array,
rightChannel: Float32Array,
sampleRate: number
): void {
const fadeInMs = 5; // 5ms fade-in to prevent clicks
const fadeInMs = 5;
const fadeSamples = Math.floor((fadeInMs / 1000) * sampleRate);
const actualFadeSamples = Math.min(fadeSamples, leftChannel.length);
for (let i = 0; i < actualFadeSamples; i++) {
const gain = i / actualFadeSamples;
const phase = i / actualFadeSamples;
const gain = 0.5 - 0.5 * Math.cos(phase * Math.PI);
leftChannel[i] *= gain;
rightChannel[i] *= gain;
}
}
private applyFadeOut(
leftChannel: Float32Array,
rightChannel: Float32Array,
sampleRate: number
): void {
const fadeOutMs = 5;
const fadeSamples = Math.floor((fadeOutMs / 1000) * sampleRate);
const actualFadeSamples = Math.min(fadeSamples, leftChannel.length);
const startSample = leftChannel.length - actualFadeSamples;
for (let i = 0; i < actualFadeSamples; i++) {
const sampleIndex = startSample + i;
const phase = i / actualFadeSamples;
const gain = 0.5 + 0.5 * Math.cos(phase * Math.PI);
leftChannel[sampleIndex] *= gain;
rightChannel[sampleIndex] *= gain;
}
}
protected randomRange(min: number, max: number): number {
return min + Math.random() * (max - min);
}

View File

@ -26,6 +26,11 @@ import { RingCymbal } from './RingCymbal';
import { AdditiveBass } from './AdditiveBass';
import { FeedbackSnare } from './FeedbackSnare';
import { CombResonator } from './CombResonator';
import { LaserSweep } from './LaserSweep';
import { Dripwater } from './Dripwater';
import { Sub8 } from './Sub8';
import { Form1 } from './Form1';
import { Squine1 } from './Squine1';
export const engines: SynthEngine[] = [
new Sample(),
@ -55,4 +60,9 @@ export const engines: SynthEngine[] = [
new SubtractiveThreeOsc(),
new CombResonator(),
new MassiveAdditive(),
new LaserSweep(),
new Dripwater(),
new Sub8(),
new Form1(),
new Squine1(),
];

View File

@ -0,0 +1,27 @@
import type { AudioProcessor } from './AudioProcessor';
export class ExpFadeIn implements AudioProcessor {
getName(): string {
return 'Fade In (Exp)';
}
getDescription(): string {
return 'Applies an exponential fade from silence to current level';
}
process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] {
const length = leftIn.length;
const leftOut = new Float32Array(length);
const rightOut = new Float32Array(length);
for (let i = 0; i < length; i++) {
const t = i / length;
const gain = 1.0 - Math.exp(-5.0 * t);
leftOut[i] = leftIn[i] * gain;
rightOut[i] = rightIn[i] * gain;
}
return [leftOut, rightOut];
}
}

View File

@ -0,0 +1,27 @@
import type { AudioProcessor } from './AudioProcessor';
export class ExpFadeOut implements AudioProcessor {
getName(): string {
return 'Fade Out (Exp)';
}
getDescription(): string {
return 'Applies an exponential fade from current level to silence';
}
process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] {
const length = leftIn.length;
const leftOut = new Float32Array(length);
const rightOut = new Float32Array(length);
for (let i = 0; i < length; i++) {
const t = i / length;
const gain = Math.exp(-5.0 * t);
leftOut[i] = leftIn[i] * gain;
rightOut[i] = rightIn[i] * gain;
}
return [leftOut, rightOut];
}
}

View File

@ -0,0 +1,26 @@
import type { AudioProcessor } from './AudioProcessor';
export class LinearFadeIn implements AudioProcessor {
getName(): string {
return 'Fade In';
}
getDescription(): string {
return 'Applies a linear fade from silence to current level';
}
process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] {
const length = leftIn.length;
const leftOut = new Float32Array(length);
const rightOut = new Float32Array(length);
for (let i = 0; i < length; i++) {
const gain = i / length;
leftOut[i] = leftIn[i] * gain;
rightOut[i] = rightIn[i] * gain;
}
return [leftOut, rightOut];
}
}

View File

@ -0,0 +1,26 @@
import type { AudioProcessor } from './AudioProcessor';
export class LinearFadeOut implements AudioProcessor {
getName(): string {
return 'Fade Out';
}
getDescription(): string {
return 'Applies a linear fade from current level to silence';
}
process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] {
const length = leftIn.length;
const leftOut = new Float32Array(length);
const rightOut = new Float32Array(length);
for (let i = 0; i < length; i++) {
const gain = 1.0 - i / length;
leftOut[i] = leftIn[i] * gain;
rightOut[i] = rightIn[i] * gain;
}
return [leftOut, rightOut];
}
}

View File

@ -25,6 +25,10 @@ import { Waveshaper } from './Waveshaper';
import { DCOffsetRemover } from './DCOffsetRemover';
import { TrimSilence } from './TrimSilence';
import { Resonator } from './Resonator';
import { LinearFadeOut } from './LinearFadeOut';
import { ExpFadeOut } from './ExpFadeOut';
import { LinearFadeIn } from './LinearFadeIn';
import { ExpFadeIn } from './ExpFadeIn';
const processors: AudioProcessor[] = [
new SegmentShuffler(),
@ -53,6 +57,10 @@ const processors: AudioProcessor[] = [
new DCOffsetRemover(),
new TrimSilence(),
new Resonator(),
new LinearFadeOut(),
new ExpFadeOut(),
new LinearFadeIn(),
new ExpFadeIn(),
];
export function getRandomProcessor(): AudioProcessor {