Quite a big update
This commit is contained in:
334
src/lib/audio/engines/Dripwater.ts
Normal file
334
src/lib/audio/engines/Dripwater.ts
Normal 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
296
src/lib/audio/engines/Form1.ts
Normal file
296
src/lib/audio/engines/Form1.ts
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
244
src/lib/audio/engines/LaserSweep.ts
Normal file
244
src/lib/audio/engines/LaserSweep.ts
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/lib/audio/engines/Squine1.ts
Normal file
149
src/lib/audio/engines/Squine1.ts
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/lib/audio/engines/Sub8.ts
Normal file
135
src/lib/audio/engines/Sub8.ts
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,11 +45,17 @@ export abstract class CsoundEngine<T = any> implements SynthEngine<T> {
|
|||||||
|
|
||||||
await csound.terminateInstance();
|
await csound.terminateInstance();
|
||||||
|
|
||||||
const leftChannel = new Float32Array(audioBuffer.leftChannel);
|
let leftChannel = new Float32Array(audioBuffer.leftChannel);
|
||||||
const rightChannel = new Float32Array(audioBuffer.rightChannel);
|
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.applyFadeIn(leftChannel, rightChannel, sampleRate);
|
||||||
|
this.applyFadeOut(leftChannel, rightChannel, sampleRate);
|
||||||
|
|
||||||
const peak = this.findPeak(leftChannel, rightChannel);
|
const peak = this.findPeak(leftChannel, rightChannel);
|
||||||
if (peak > 0.001) {
|
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(
|
private applyFadeIn(
|
||||||
leftChannel: Float32Array,
|
leftChannel: Float32Array,
|
||||||
rightChannel: Float32Array,
|
rightChannel: Float32Array,
|
||||||
sampleRate: number
|
sampleRate: number
|
||||||
): void {
|
): void {
|
||||||
const fadeInMs = 5; // 5ms fade-in to prevent clicks
|
const fadeInMs = 5;
|
||||||
const fadeSamples = Math.floor((fadeInMs / 1000) * sampleRate);
|
const fadeSamples = Math.floor((fadeInMs / 1000) * sampleRate);
|
||||||
const actualFadeSamples = Math.min(fadeSamples, leftChannel.length);
|
const actualFadeSamples = Math.min(fadeSamples, leftChannel.length);
|
||||||
|
|
||||||
for (let i = 0; i < actualFadeSamples; i++) {
|
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;
|
leftChannel[i] *= gain;
|
||||||
rightChannel[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 {
|
protected randomRange(min: number, max: number): number {
|
||||||
return min + Math.random() * (max - min);
|
return min + Math.random() * (max - min);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,11 @@ import { RingCymbal } from './RingCymbal';
|
|||||||
import { AdditiveBass } from './AdditiveBass';
|
import { AdditiveBass } from './AdditiveBass';
|
||||||
import { FeedbackSnare } from './FeedbackSnare';
|
import { FeedbackSnare } from './FeedbackSnare';
|
||||||
import { CombResonator } from './CombResonator';
|
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[] = [
|
export const engines: SynthEngine[] = [
|
||||||
new Sample(),
|
new Sample(),
|
||||||
@ -55,4 +60,9 @@ export const engines: SynthEngine[] = [
|
|||||||
new SubtractiveThreeOsc(),
|
new SubtractiveThreeOsc(),
|
||||||
new CombResonator(),
|
new CombResonator(),
|
||||||
new MassiveAdditive(),
|
new MassiveAdditive(),
|
||||||
|
new LaserSweep(),
|
||||||
|
new Dripwater(),
|
||||||
|
new Sub8(),
|
||||||
|
new Form1(),
|
||||||
|
new Squine1(),
|
||||||
];
|
];
|
||||||
|
|||||||
27
src/lib/audio/processors/ExpFadeIn.ts
Normal file
27
src/lib/audio/processors/ExpFadeIn.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/lib/audio/processors/ExpFadeOut.ts
Normal file
27
src/lib/audio/processors/ExpFadeOut.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/lib/audio/processors/LinearFadeIn.ts
Normal file
26
src/lib/audio/processors/LinearFadeIn.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/lib/audio/processors/LinearFadeOut.ts
Normal file
26
src/lib/audio/processors/LinearFadeOut.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,6 +25,10 @@ import { Waveshaper } from './Waveshaper';
|
|||||||
import { DCOffsetRemover } from './DCOffsetRemover';
|
import { DCOffsetRemover } from './DCOffsetRemover';
|
||||||
import { TrimSilence } from './TrimSilence';
|
import { TrimSilence } from './TrimSilence';
|
||||||
import { Resonator } from './Resonator';
|
import { Resonator } from './Resonator';
|
||||||
|
import { LinearFadeOut } from './LinearFadeOut';
|
||||||
|
import { ExpFadeOut } from './ExpFadeOut';
|
||||||
|
import { LinearFadeIn } from './LinearFadeIn';
|
||||||
|
import { ExpFadeIn } from './ExpFadeIn';
|
||||||
|
|
||||||
const processors: AudioProcessor[] = [
|
const processors: AudioProcessor[] = [
|
||||||
new SegmentShuffler(),
|
new SegmentShuffler(),
|
||||||
@ -53,6 +57,10 @@ const processors: AudioProcessor[] = [
|
|||||||
new DCOffsetRemover(),
|
new DCOffsetRemover(),
|
||||||
new TrimSilence(),
|
new TrimSilence(),
|
||||||
new Resonator(),
|
new Resonator(),
|
||||||
|
new LinearFadeOut(),
|
||||||
|
new ExpFadeOut(),
|
||||||
|
new LinearFadeIn(),
|
||||||
|
new ExpFadeIn(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getRandomProcessor(): AudioProcessor {
|
export function getRandomProcessor(): AudioProcessor {
|
||||||
|
|||||||
Reference in New Issue
Block a user