Skip to content

Commit 00a2fe8

Browse files
committed
fix modulation wheel multiplier implementation
1 parent eb131b8 commit 00a2fe8

6 files changed

Lines changed: 99 additions & 90 deletions

File tree

src/soundbank/basic_soundbank/modulator.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,18 @@ export class Modulator {
7373
* - still can be disabled if the soundfont has its own modulator curve
7474
* - this fixes the very low amount of reverb by default and doesn't break soundfonts
7575
*/
76-
public readonly isEffectModulator: boolean = false;
76+
public readonly isEffectModulator;
7777

7878
/**
7979
* The default resonant modulator does not affect the filter gain.
8080
* Neither XG nor GS responded to cc #74 in that way.
8181
*/
82-
public readonly isDefaultResonantModulator: boolean = false;
82+
public readonly isDefaultResonantModulator;
83+
84+
/**
85+
* If this is a modulation wheel modulator (for modulation depth range).
86+
*/
87+
public readonly isModWheelModulator;
8388

8489
/**
8590
* The primary source of this modulator.
@@ -101,7 +106,8 @@ export class Modulator {
101106
amount = 0,
102107
transformType: ModulatorTransformType = 0,
103108
isEffectModulator = false,
104-
isDefaultResonantModulator = false
109+
isDefaultResonantModulator = false,
110+
isModWheelModulator = false
105111
) {
106112
this.primarySource = primarySource;
107113
this.secondarySource = secondarySource;
@@ -111,6 +117,7 @@ export class Modulator {
111117
this.transformType = transformType;
112118
this.isEffectModulator = isEffectModulator;
113119
this.isDefaultResonantModulator = isDefaultResonantModulator;
120+
this.isModWheelModulator = isModWheelModulator;
114121
}
115122

116123
private get destinationName() {
@@ -155,7 +162,8 @@ export class Modulator {
155162
mod.transformAmount,
156163
mod.transformType,
157164
mod.isEffectModulator,
158-
mod.isDefaultResonantModulator
165+
mod.isDefaultResonantModulator,
166+
mod.isModWheelModulator
159167
);
160168
}
161169

@@ -220,14 +228,22 @@ export class DecodedModulator extends Modulator {
220228
secondarySourceEnum === 0x0 &&
221229
destination === generatorTypes.initialFilterQ;
222230

231+
const s1 = ModulatorSource.fromSourceEnum(sourceEnum);
232+
const s2 = ModulatorSource.fromSourceEnum(secondarySourceEnum);
233+
234+
const isModWheelModulator =
235+
(s1.isCC && s1.index === midiControllers.modulationWheel) ||
236+
(s2.isCC && s2.index === midiControllers.modulationWheel);
237+
223238
super(
224-
ModulatorSource.fromSourceEnum(sourceEnum),
225-
ModulatorSource.fromSourceEnum(secondarySourceEnum),
239+
s1,
240+
s2,
226241
destination,
227242
amount,
228243
transformType as ModulatorTransformType,
229244
isEffectModulator,
230-
isDefaultResonantModulator
245+
isDefaultResonantModulator,
246+
isModWheelModulator
231247
);
232248

233249
if (this.destination > MAX_GENERATOR) {

src/soundbank/basic_soundbank/modulator_source.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export class ModulatorSource {
199199
const transformType =
200200
(this.isBipolar ? 0b10 : 0b00) | (this.isNegative ? 1 : 0);
201201

202-
return precomputedTransforms[
202+
return precomputedModulatorTransforms[
203203
MODULATOR_RESOLUTION *
204204
(this.curveType * MOD_CURVE_TYPES_AMOUNT + transformType) +
205205
rawValue
@@ -211,7 +211,7 @@ export class ModulatorSource {
211211
* To get the value, you do
212212
* MODULATOR_RESOLUTION * (MOD_CURVE_TYPES_AMOUNT * curveType + transformType) + your raw value as 14-bit number (0 - 16,383)
213213
*/
214-
const precomputedTransforms = new Float32Array(
214+
export const precomputedModulatorTransforms = new Float32Array(
215215
MODULATOR_RESOLUTION *
216216
MOD_SOURCE_TRANSFORM_POSSIBILITIES *
217217
MOD_CURVE_TYPES_AMOUNT
@@ -227,11 +227,12 @@ for (let curveType = 0; curveType < MOD_CURVE_TYPES_AMOUNT; curveType++) {
227227
MODULATOR_RESOLUTION *
228228
(curveType * MOD_CURVE_TYPES_AMOUNT + transformType);
229229
for (let value = 0; value < MODULATOR_RESOLUTION; value++) {
230-
precomputedTransforms[tableIndex + value] = getModulatorCurveValue(
231-
transformType,
232-
curveType as ModulatorCurveType,
233-
value / MODULATOR_RESOLUTION
234-
);
230+
precomputedModulatorTransforms[tableIndex + value] =
231+
getModulatorCurveValue(
232+
transformType,
233+
curveType as ModulatorCurveType,
234+
value / MODULATOR_RESOLUTION
235+
);
235236
}
236237
}
237238
}

src/synthesizer/audio_engine/engine_components/compute_modulator.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,74 @@ import { generatorLimits } from "../../../soundbank/basic_soundbank/generator_ty
22
import type { MIDIChannel } from "./midi_channel";
33
import type { Voice } from "./voice";
44
import { modulatorSources } from "../../../soundbank/enums";
5-
import { NON_CC_INDEX_OFFSET } from "./controller_tables"; /**
5+
import { NON_CC_INDEX_OFFSET } from "./controller_tables";
6+
import { customControllers } from "../../enums";
7+
8+
/**
69
* Compute_modulator.ts
710
* purpose: contains a function for computing all modulators
811
*/
12+
const EFFECT_MODULATOR_TRANSFORM_MULTIPLIER = 1000 / 200;
913

1014
/**
11-
* Compute_modulator.ts
12-
* purpose: contains a function for computing all modulators
15+
* Computes a given modulator
16+
* @param voice the voice of this modulator.
17+
* @param pitchWheel the pitch wheel value, as channel determines if it's a per-note or a global value.
18+
* @param modulatorIndex the modulator to compute
19+
* @returns the computed value
1320
*/
21+
export function computeModulator(
22+
this: MIDIChannel,
23+
voice: Voice,
24+
pitchWheel: number,
25+
modulatorIndex: number
26+
) {
27+
const modulator = voice.modulators[modulatorIndex];
28+
if (modulator.transformAmount === 0) {
29+
voice.modulatorValues[modulatorIndex] = 0;
30+
return 0;
31+
}
32+
const sourceValue = modulator.primarySource.getValue(
33+
this.midiControllers,
34+
pitchWheel,
35+
voice
36+
);
37+
const secondSrcValue = modulator.secondarySource.getValue(
38+
this.midiControllers,
39+
pitchWheel,
40+
voice
41+
);
42+
43+
// See the comment for isEffectModulator (modulator.ts in basic_soundbank) for explanation
44+
let transformAmount = modulator.transformAmount;
45+
if (modulator.isEffectModulator && transformAmount <= 1000) {
46+
transformAmount *= EFFECT_MODULATOR_TRANSFORM_MULTIPLIER;
47+
transformAmount = Math.min(transformAmount, 1000);
48+
}
49+
50+
// Compute the modulator
51+
let computedValue = sourceValue * secondSrcValue * transformAmount;
52+
53+
if (modulator.transformType === 2) {
54+
// Abs value
55+
computedValue = Math.abs(computedValue);
56+
}
57+
58+
// Resonant modulator: take its value and ensure that it won't change the final gain
59+
if (modulator.isDefaultResonantModulator) {
60+
// Half the gain, negates the filter
61+
voice.resonanceOffset = Math.max(0, computedValue / 2);
62+
}
63+
64+
// Modulation depth
65+
if (modulator.isModWheelModulator) {
66+
computedValue *=
67+
this.customControllers[customControllers.modulationMultiplier];
68+
}
69+
70+
voice.modulatorValues[modulatorIndex] = computedValue;
71+
return computedValue;
72+
}
1473

1574
/**
1675
* Computes modulators of a given voice. Source and index indicate what modulators shall be computed.
@@ -53,7 +112,7 @@ export function computeModulators(
53112
Math.max(
54113
-32_768,
55114
modulatedGenerators[mod.destination] +
56-
voice.computeModulator(this.midiControllers, pitch, i)
115+
this.computeModulator(voice, pitch, i)
57116
)
58117
);
59118
}
@@ -87,7 +146,7 @@ export function computeModulators(
87146
const destination = mod.destination;
88147
let outputValue = generators[destination];
89148
// Compute our modulator
90-
voice.computeModulator(this.midiControllers, pitch, i);
149+
this.computeModulator(voice, pitch, i);
91150

92151
// Sum the values of all modulators for this destination
93152
for (let j = 0; j < modulators.length; j++) {

src/synthesizer/audio_engine/engine_components/dsp_chain/render_voice.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { getLFOValueSine } from "./lfo";
33
import type { Voice } from "../voice";
44
import type { MIDIChannel } from "../midi_channel";
55
import { generatorTypes } from "../../../../soundbank/basic_soundbank/generator_types";
6-
import { customControllers } from "../../../enums";
76
import { midiControllers } from "../../../../midi/enums";
87
import { LowpassFilter } from "./lowpass_filter";
98

@@ -126,13 +125,7 @@ export function renderVoice(
126125
const rateInc = (vibFreqHz * sampleCount) / sampleRate;
127126
const vibLfoValue = 1 - 4 * Math.abs(voice.vibLfoPhase - 0.5);
128127
if ((voice.vibLfoPhase += rateInc) >= 1) voice.vibLfoPhase -= 1;
129-
// Use modulation multiplier (RPN modulation depth)
130-
cents +=
131-
vibLfoValue *
132-
(vibPitchDepth *
133-
this.customControllers[
134-
customControllers.modulationMultiplier
135-
]);
128+
cents += vibLfoValue * vibPitchDepth;
136129
// Low pass frequency
137130
lowpassExcursion += vibLfoValue * vibFilterDepth;
138131

@@ -164,13 +157,7 @@ export function renderVoice(
164157
const rateInc = (modFreqHz * sampleCount) / sampleRate;
165158
const modLfoValue = 1 - 4 * Math.abs(voice.modLfoPhase - 0.5);
166159
if ((voice.modLfoPhase += rateInc) >= 1) voice.modLfoPhase -= 1;
167-
// Use modulation multiplier (RPN modulation depth)
168-
cents +=
169-
modLfoValue *
170-
(modPitchDepth *
171-
this.customControllers[
172-
customControllers.modulationMultiplier
173-
]);
160+
cents += modLfoValue * modPitchDepth;
174161
// Vol env volume offset
175162
// Negate the lfo value because audigy starts with increase rather than decrease
176163
volumeExcursionCentibels += -modLfoValue * modVolDepth;

src/synthesizer/audio_engine/engine_components/midi_channel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
GENERATOR_OVERRIDE_NO_CHANGE_VALUE
2323
} from "./synth_constants";
2424
import { DynamicModulatorSystem } from "./dynamic_modulator_system";
25-
import { computeModulators } from "./compute_modulator";
25+
import { computeModulator, computeModulators } from "./compute_modulator";
2626
import {
2727
generatorLimits,
2828
GENERATORS_AMOUNT,
@@ -288,6 +288,7 @@ export class MIDIChannel {
288288
* A small optimization that disables applying overrides until at least one is set.
289289
*/
290290
protected generatorOverridesEnabled = false;
291+
protected readonly computeModulator = computeModulator.bind(this);
291292
protected readonly computeModulators = computeModulators.bind(this);
292293
/**
293294
* For tracking voice count changes

src/synthesizer/audio_engine/engine_components/voice.ts

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { type InterpolationType } from "../../enums";
1919
import { DEFAULT_MASTER_PARAMETERS } from "./master_parameters";
2020

2121
const EXCLUSIVE_CUTOFF_TIME = -2320;
22-
const EFFECT_MODULATOR_TRANSFORM_MULTIPLIER = 1000 / 200;
2322

2423
/**
2524
* Voice represents a single instance of the
@@ -255,60 +254,6 @@ export class Voice {
255254
this.filter = new LowpassFilter(sampleRate);
256255
}
257256

258-
/**
259-
* Computes a given modulator
260-
* @param controllerTable all midi controllers as 14bit values + the non-controller indexes, starting at 128
261-
* @param pitchWheel the pitch wheel value, as channel determines if it's a per-note or a global value.
262-
* @param modulatorIndex the modulator to compute
263-
* @returns the computed value
264-
*/
265-
public computeModulator(
266-
this: Voice,
267-
controllerTable: Int16Array,
268-
pitchWheel: number,
269-
modulatorIndex: number
270-
): number {
271-
const modulator = this.modulators[modulatorIndex];
272-
if (modulator.transformAmount === 0) {
273-
this.modulatorValues[modulatorIndex] = 0;
274-
return 0;
275-
}
276-
const sourceValue = modulator.primarySource.getValue(
277-
controllerTable,
278-
pitchWheel,
279-
this
280-
);
281-
const secondSrcValue = modulator.secondarySource.getValue(
282-
controllerTable,
283-
pitchWheel,
284-
this
285-
);
286-
287-
// See the comment for isEffectModulator (modulator.ts in basic_soundbank) for explanation
288-
let transformAmount = modulator.transformAmount;
289-
if (modulator.isEffectModulator && transformAmount <= 1000) {
290-
transformAmount *= EFFECT_MODULATOR_TRANSFORM_MULTIPLIER;
291-
transformAmount = Math.min(transformAmount, 1000);
292-
}
293-
294-
// Compute the modulator
295-
let computedValue = sourceValue * secondSrcValue * transformAmount;
296-
297-
if (modulator.transformType === 2) {
298-
// Abs value
299-
computedValue = Math.abs(computedValue);
300-
}
301-
302-
// Resonant modulator: take its value and ensure that it won't change the final gain
303-
if (modulator.isDefaultResonantModulator) {
304-
// Half the gain, negates the filter
305-
this.resonanceOffset = Math.max(0, computedValue / 2);
306-
}
307-
308-
this.modulatorValues[modulatorIndex] = computedValue;
309-
return computedValue;
310-
}
311-
312257
/**
313258
* Releases the voice as exclusiveClass.
314259
*/

0 commit comments

Comments
 (0)