This commit is contained in:
2025-10-06 16:36:59 +02:00
parent 90f2f4209c
commit 9d26ea5cd7
15 changed files with 1031 additions and 595 deletions

View File

@ -35,6 +35,16 @@ class FMProcessor extends AudioWorkletProcessor {
this.pitchLFODepth = 0.1
this.pitchLFOBaseRate = 2.0
this.sampleHoldValue = 0
this.sampleHoldCounter = 0
this.sampleHoldInterval = 500
this.driftValue = 0
this.perlinA = Math.random() * 2 - 1
this.perlinB = Math.random() * 2 - 1
this.perlinPhase = 0
this.perlinInterval = 2000
this.chaosX = 0.5
this.port.onmessage = (event) => {
const { type, value } = event.data
switch (type) {
@ -72,6 +82,13 @@ class FMProcessor extends AudioWorkletProcessor {
this.phase4 = 0
this.sampleCount = 0
this.feedbackSample = 0
this.sampleHoldValue = 0
this.sampleHoldCounter = 0
this.driftValue = 0
this.perlinA = Math.random() * 2 - 1
this.perlinB = Math.random() * 2 - 1
this.perlinPhase = 0
this.chaosX = 0.5
break
case 'loopLength':
this.loopLength = value
@ -96,6 +113,32 @@ class FMProcessor extends AudioWorkletProcessor {
return normalizedPhase % 1 < 0.5 ? 1 : -1
case 3: // sawtooth
return 2 * (normalizedPhase % 1) - 1
case 4: // sample & hold random (glitchy)
this.sampleHoldCounter++
if (this.sampleHoldCounter >= this.sampleHoldInterval) {
this.sampleHoldValue = Math.random() * 2 - 1
this.sampleHoldCounter = 0
this.sampleHoldInterval = Math.floor(100 + Math.random() * 900)
}
return this.sampleHoldValue
case 5: // drift (random walk)
this.driftValue += (Math.random() - 0.5) * 0.002
this.driftValue = Math.max(-1, Math.min(1, this.driftValue))
return this.driftValue
case 6: // perlin noise (smooth random)
this.perlinPhase++
if (this.perlinPhase >= this.perlinInterval) {
this.perlinA = this.perlinB
this.perlinB = Math.random() * 2 - 1
this.perlinPhase = 0
this.perlinInterval = Math.floor(1000 + Math.random() * 2000)
}
const t = this.perlinPhase / this.perlinInterval
const smoothT = t * t * (3 - 2 * t)
return this.perlinA + (this.perlinB - this.perlinA) * smoothT
case 7: // chaos (logistic map)
this.chaosX = 3.9 * this.chaosX * (1 - this.chaosX)
return this.chaosX * 2 - 1
default:
return 0
}
@ -103,7 +146,7 @@ class FMProcessor extends AudioWorkletProcessor {
synthesize(algorithm) {
const TWO_PI = Math.PI * 2
const sampleRate = 44100
const sampleRate = globalThis.sampleRate || 44100
const avgDiff = (Math.abs(this.opLevel1 - this.opLevel3) + Math.abs(this.opLevel2 - this.opLevel4)) / (2 * 255)
const pitchLFORate = this.pitchLFOBaseRate * (0.3 + avgDiff * 1.4)
@ -129,6 +172,17 @@ class FMProcessor extends AudioWorkletProcessor {
const level3 = (this.opLevel3 / 255.0) * (1 + this.lfoDepth * lfo3)
const level4 = (this.opLevel4 / 255.0) * (1 + this.lfoDepth * lfo4)
const nyquist = sampleRate / 2
const maxCarrierFreq = Math.max(
modulatedBaseFreq * this.frequencyRatios[0],
modulatedBaseFreq * this.frequencyRatios[1],
modulatedBaseFreq * this.frequencyRatios[2],
modulatedBaseFreq * this.frequencyRatios[3]
)
const antiAliasFactor = Math.min(1.0, nyquist / (maxCarrierFreq * 5))
const modDepth = 10 * antiAliasFactor
const modDepthLight = 1.5 * antiAliasFactor
this.lfoPhase1 += (this.lfoRate1 * TWO_PI) / sampleRate
this.lfoPhase2 += (this.lfoRate2 * TWO_PI) / sampleRate
this.lfoPhase3 += (this.lfoRate3 * TWO_PI) / sampleRate
@ -157,12 +211,12 @@ class FMProcessor extends AudioWorkletProcessor {
case 1: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod2) * level3
const mod3 = op3 * 10
const op4 = Math.sin(this.phase4 + mod3 + this.feedbackSample * this.feedback * 10) * level4
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3 + this.feedbackSample * this.feedback * modDepth) * level4
output = op4
this.feedbackSample = op4
this.phase1 += freq1 * this.playbackRate
@ -174,10 +228,10 @@ class FMProcessor extends AudioWorkletProcessor {
case 2: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const op3 = Math.sin(this.phase3) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3) * level4
output = (op2 + op4) * 0.5
this.phase1 += freq1 * this.playbackRate
@ -189,9 +243,9 @@ class FMProcessor extends AudioWorkletProcessor {
case 3: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod2) * level3
const op4 = Math.sin(this.phase4) * level4
output = (op3 + op4) * 0.5
@ -204,11 +258,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 4: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod1 + mod2) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3) * level4
output = op4
this.phase1 += freq1 * this.playbackRate
@ -220,11 +274,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 5: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod1) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
output = op4
this.phase1 += freq1 * this.playbackRate
@ -236,7 +290,7 @@ class FMProcessor extends AudioWorkletProcessor {
case 6: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const op3 = Math.sin(this.phase3) * level3
const op4 = Math.sin(this.phase4) * level4
@ -250,11 +304,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 7: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 1.5
const mod2 = op2 * modDepthLight
const op3 = Math.sin(this.phase3 + mod1) * level3
const mod3 = op3 * 1.5
const mod3 = op3 * modDepthLight
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
output = op4
this.phase1 += freq1 * this.playbackRate
@ -266,10 +320,10 @@ class FMProcessor extends AudioWorkletProcessor {
case 8: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op3 = Math.sin(this.phase3 + mod1) * level3
const op2 = Math.sin(this.phase2) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op4 = Math.sin(this.phase4 + mod2) * level4
output = (op3 + op4) * 0.5
this.phase1 += freq1 * this.playbackRate
@ -281,10 +335,10 @@ class FMProcessor extends AudioWorkletProcessor {
case 9: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op4 = Math.sin(this.phase4 + mod1) * level4
const op2 = Math.sin(this.phase2) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod2) * level3
output = (op3 + op4) * 0.5
this.phase1 += freq1 * this.playbackRate
@ -296,10 +350,10 @@ class FMProcessor extends AudioWorkletProcessor {
case 10: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const op3 = Math.sin(this.phase3) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3) * level4
output = (op2 + op4) * 0.5
this.phase1 += freq1 * this.playbackRate
@ -312,9 +366,9 @@ class FMProcessor extends AudioWorkletProcessor {
case 11: {
const op1 = Math.sin(this.phase1) * level1
const op2 = Math.sin(this.phase2) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod2) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3) * level4
output = (op1 + op4) * 0.5
this.phase1 += freq1 * this.playbackRate
@ -326,9 +380,9 @@ class FMProcessor extends AudioWorkletProcessor {
case 12: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op4 = Math.sin(this.phase4 + mod2) * level4
const op3 = Math.sin(this.phase3) * level3
output = (op3 + op4) * 0.5
@ -341,11 +395,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 13: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2 + mod1) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3 + mod1) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
output = op4
this.phase1 += freq1 * this.playbackRate
@ -357,11 +411,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 14: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op3 = Math.sin(this.phase3) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod3) * level4
const mod4 = op4 * 1.5
const mod4 = op4 * modDepthLight
const op2 = Math.sin(this.phase2 + mod1 + mod4) * level2
output = op2
this.phase1 += freq1 * this.playbackRate
@ -373,11 +427,11 @@ class FMProcessor extends AudioWorkletProcessor {
case 15: {
const op1 = Math.sin(this.phase1) * level1
const mod1 = op1 * 10
const mod1 = op1 * modDepth
const op2 = Math.sin(this.phase2) * level2
const mod2 = op2 * 10
const mod2 = op2 * modDepth
const op3 = Math.sin(this.phase3) * level3
const mod3 = op3 * 10
const mod3 = op3 * modDepth
const op4 = Math.sin(this.phase4 + mod1 + mod2 + mod3) * level4
output = op4
this.phase1 += freq1 * this.playbackRate
@ -391,11 +445,10 @@ class FMProcessor extends AudioWorkletProcessor {
output = 0
}
const TWO_PI_LIMIT = TWO_PI * 10
if (this.phase1 > TWO_PI_LIMIT) this.phase1 -= TWO_PI_LIMIT
if (this.phase2 > TWO_PI_LIMIT) this.phase2 -= TWO_PI_LIMIT
if (this.phase3 > TWO_PI_LIMIT) this.phase3 -= TWO_PI_LIMIT
if (this.phase4 > TWO_PI_LIMIT) this.phase4 -= TWO_PI_LIMIT
this.phase1 = this.phase1 % TWO_PI
this.phase2 = this.phase2 % TWO_PI
this.phase3 = this.phase3 % TWO_PI
this.phase4 = this.phase4 % TWO_PI
return output
}