Adding new FM synthesis mode
This commit is contained in:
391
public/worklets/fm-processor.js
Normal file
391
public/worklets/fm-processor.js
Normal file
@ -0,0 +1,391 @@
|
||||
class FMProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.phase1 = 0
|
||||
this.phase2 = 0
|
||||
this.phase3 = 0
|
||||
this.phase4 = 0
|
||||
this.baseFreq = 220
|
||||
this.algorithm = 0
|
||||
this.opLevel1 = 128
|
||||
this.opLevel2 = 128
|
||||
this.opLevel3 = 128
|
||||
this.opLevel4 = 128
|
||||
this.feedback = 0
|
||||
this.playbackRate = 1.0
|
||||
this.loopLength = 0
|
||||
this.sampleCount = 0
|
||||
this.feedbackSample = 0
|
||||
|
||||
this.frequencyRatios = [1.0, 1.0, 1.0, 1.0]
|
||||
|
||||
this.lfoPhase1 = 0
|
||||
this.lfoPhase2 = 0
|
||||
this.lfoPhase3 = 0
|
||||
this.lfoPhase4 = 0
|
||||
this.lfoRate1 = 0.37
|
||||
this.lfoRate2 = 0.53
|
||||
this.lfoRate3 = 0.71
|
||||
this.lfoRate4 = 0.43
|
||||
this.lfoDepth = 0.3
|
||||
|
||||
this.port.onmessage = (event) => {
|
||||
const { type, value } = event.data
|
||||
switch (type) {
|
||||
case 'algorithm':
|
||||
this.algorithm = value.id
|
||||
this.frequencyRatios = value.ratios
|
||||
if (value.lfoRates) {
|
||||
this.lfoRate1 = value.lfoRates[0]
|
||||
this.lfoRate2 = value.lfoRates[1]
|
||||
this.lfoRate3 = value.lfoRates[2]
|
||||
this.lfoRate4 = value.lfoRates[3]
|
||||
}
|
||||
break
|
||||
case 'operatorLevels':
|
||||
this.opLevel1 = value.a
|
||||
this.opLevel2 = value.b
|
||||
this.opLevel3 = value.c
|
||||
this.opLevel4 = value.d
|
||||
break
|
||||
case 'baseFreq':
|
||||
this.baseFreq = value
|
||||
break
|
||||
case 'feedback':
|
||||
this.feedback = value / 100.0
|
||||
break
|
||||
case 'reset':
|
||||
this.phase1 = 0
|
||||
this.phase2 = 0
|
||||
this.phase3 = 0
|
||||
this.phase4 = 0
|
||||
this.sampleCount = 0
|
||||
this.feedbackSample = 0
|
||||
break
|
||||
case 'loopLength':
|
||||
this.loopLength = value
|
||||
break
|
||||
case 'playbackRate':
|
||||
this.playbackRate = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synthesize(algorithm) {
|
||||
const TWO_PI = Math.PI * 2
|
||||
const sampleRate = 44100
|
||||
|
||||
const freq1 = (this.baseFreq * this.frequencyRatios[0] * TWO_PI) / sampleRate
|
||||
const freq2 = (this.baseFreq * this.frequencyRatios[1] * TWO_PI) / sampleRate
|
||||
const freq3 = (this.baseFreq * this.frequencyRatios[2] * TWO_PI) / sampleRate
|
||||
const freq4 = (this.baseFreq * this.frequencyRatios[3] * TWO_PI) / sampleRate
|
||||
|
||||
const lfo1 = Math.sin(this.lfoPhase1)
|
||||
const lfo2 = Math.sin(this.lfoPhase2)
|
||||
const lfo3 = Math.sin(this.lfoPhase3)
|
||||
const lfo4 = Math.sin(this.lfoPhase4)
|
||||
|
||||
const level1 = (this.opLevel1 / 255.0) * (1 + this.lfoDepth * lfo1)
|
||||
const level2 = (this.opLevel2 / 255.0) * (1 + this.lfoDepth * lfo2)
|
||||
const level3 = (this.opLevel3 / 255.0) * (1 + this.lfoDepth * lfo3)
|
||||
const level4 = (this.opLevel4 / 255.0) * (1 + this.lfoDepth * lfo4)
|
||||
|
||||
this.lfoPhase1 += (this.lfoRate1 * TWO_PI) / sampleRate
|
||||
this.lfoPhase2 += (this.lfoRate2 * TWO_PI) / sampleRate
|
||||
this.lfoPhase3 += (this.lfoRate3 * TWO_PI) / sampleRate
|
||||
this.lfoPhase4 += (this.lfoRate4 * TWO_PI) / sampleRate
|
||||
|
||||
if (this.lfoPhase1 > TWO_PI) this.lfoPhase1 -= TWO_PI
|
||||
if (this.lfoPhase2 > TWO_PI) this.lfoPhase2 -= TWO_PI
|
||||
if (this.lfoPhase3 > TWO_PI) this.lfoPhase3 -= TWO_PI
|
||||
if (this.lfoPhase4 > TWO_PI) this.lfoPhase4 -= TWO_PI
|
||||
|
||||
let output = 0
|
||||
|
||||
switch (algorithm) {
|
||||
case 0: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const op4 = Math.sin(this.phase4) * level4
|
||||
output = (op1 + op2 + op3 + op4) * 0.25
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 1: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 10
|
||||
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
|
||||
output = op4
|
||||
this.feedbackSample = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 2: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod3) * level4
|
||||
output = (op2 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 3: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod2) * level3
|
||||
const op4 = Math.sin(this.phase4) * level4
|
||||
output = (op3 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 4: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod1 + mod2) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod3) * level4
|
||||
output = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod1) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
|
||||
output = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 6: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const op4 = Math.sin(this.phase4) * level4
|
||||
output = (op2 + op3 + op4) * 0.333
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 7: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 1.5
|
||||
const op3 = Math.sin(this.phase3 + mod1) * level3
|
||||
const mod3 = op3 * 1.5
|
||||
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
|
||||
output = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 8: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod1) * level3
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod2) * level4
|
||||
output = (op3 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 9: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod1) * level4
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod2) * level3
|
||||
output = (op3 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 10: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod3) * level4
|
||||
output = (op2 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 11: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod2) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod3) * level4
|
||||
output = (op1 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 12: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod2) * level4
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
output = (op3 + op4) * 0.5
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 13: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2 + mod1) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3 + mod1) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod2 + mod3) * level4
|
||||
output = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 14: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod3) * level4
|
||||
const mod4 = op4 * 1.5
|
||||
const op2 = Math.sin(this.phase2 + mod1 + mod4) * level2
|
||||
output = op2
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
case 15: {
|
||||
const op1 = Math.sin(this.phase1) * level1
|
||||
const mod1 = op1 * 10
|
||||
const op2 = Math.sin(this.phase2) * level2
|
||||
const mod2 = op2 * 10
|
||||
const op3 = Math.sin(this.phase3) * level3
|
||||
const mod3 = op3 * 10
|
||||
const op4 = Math.sin(this.phase4 + mod1 + mod2 + mod3) * level4
|
||||
output = op4
|
||||
this.phase1 += freq1 * this.playbackRate
|
||||
this.phase2 += freq2 * this.playbackRate
|
||||
this.phase3 += freq3 * this.playbackRate
|
||||
this.phase4 += freq4 * this.playbackRate
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
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
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
process(inputs, outputs) {
|
||||
const output = outputs[0]
|
||||
|
||||
if (output.length > 0) {
|
||||
const outputChannel = output[0]
|
||||
|
||||
for (let i = 0; i < outputChannel.length; i++) {
|
||||
outputChannel[i] = this.synthesize(this.algorithm)
|
||||
|
||||
this.sampleCount++
|
||||
if (this.loopLength > 0 && this.sampleCount >= this.loopLength) {
|
||||
this.phase1 = 0
|
||||
this.phase2 = 0
|
||||
this.phase3 = 0
|
||||
this.phase4 = 0
|
||||
this.sampleCount = 0
|
||||
this.feedbackSample = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('fm-processor', FMProcessor)
|
||||
Reference in New Issue
Block a user