spessasynth_lib
Version:
MIDI and SoundFont2/DLS library with no compromises
330 lines (306 loc) • 11.5 kB
JavaScript
import { midiControllers } from "../../../midi_parser/midi_message.js";
import { DLSSources } from "../../dls/dls_sources.js";
import { modulatorCurveTypes, modulatorSources } from "../modulator.js";
import { generatorTypes } from "../generator.js";
import { DLSDestinations } from "../../dls/dls_destinations.js";
import { Articulator } from "./articulator.js";
import { SpessaSynthWarn } from "../../../utils/loggin.js";
/**
* @param cc {boolean}
* @param index {number}
* @returns {number|undefined}
*/
function getDLSSourceFromSf2Source(cc, index)
{
if (cc)
{
switch (index)
{
default:
// DLS supports limited controllers
return undefined;
case midiControllers.modulationWheel:
return DLSSources.modulationWheel;
case midiControllers.mainVolume:
return DLSSources.volume;
case midiControllers.pan:
return DLSSources.pan;
case midiControllers.expressionController:
return DLSSources.expression;
case midiControllers.chorusDepth:
return DLSSources.chorus;
case midiControllers.reverbDepth:
return DLSSources.reverb;
}
}
else
{
switch (index)
{
default:
// cannot be a DLS articulator
return undefined;
case modulatorSources.noteOnKeyNum:
return DLSSources.keyNum;
case modulatorSources.noteOnVelocity:
return DLSSources.velocity;
case modulatorSources.noController:
return DLSSources.none;
case modulatorSources.polyPressure:
return DLSSources.polyPressure;
case modulatorSources.channelPressure:
return DLSSources.channelPressure;
case modulatorSources.pitchWheel:
return DLSSources.pitchWheel;
case modulatorSources.pitchWheelRange:
return DLSSources.pitchWheelRange;
}
}
}
/**
* @param dest {number}
* @param amount {number}
* @returns {number|undefined|{dest: number, amount: number}}
*/
function getDLSDestinationFromSf2(dest, amount)
{
switch (dest)
{
default:
return undefined;
case generatorTypes.initialAttenuation:
// the amount does not get EMU corrected here, as this only applies to modulator attenuation
// the generator (affected) attenuation is handled in wsmp.
return { dest: DLSDestinations.gain, amount: -amount };
case generatorTypes.fineTune:
return DLSDestinations.pitch;
case generatorTypes.pan:
return DLSDestinations.pan;
case generatorTypes.keyNum:
return DLSDestinations.keyNum;
case generatorTypes.reverbEffectsSend:
return DLSDestinations.reverbSend;
case generatorTypes.chorusEffectsSend:
return DLSDestinations.chorusSend;
case generatorTypes.freqModLFO:
return DLSDestinations.modLfoFreq;
case generatorTypes.delayModLFO:
return DLSDestinations.modLfoDelay;
case generatorTypes.delayVibLFO:
return DLSDestinations.vibLfoDelay;
case generatorTypes.freqVibLFO:
return DLSDestinations.vibLfoFreq;
case generatorTypes.delayVolEnv:
return DLSDestinations.volEnvDelay;
case generatorTypes.attackVolEnv:
return DLSDestinations.volEnvAttack;
case generatorTypes.holdVolEnv:
return DLSDestinations.volEnvHold;
case generatorTypes.decayVolEnv:
return DLSDestinations.volEnvDecay;
case generatorTypes.sustainVolEnv:
return { dest: DLSDestinations.volEnvSustain, amount: 1000 - amount };
case generatorTypes.releaseVolEnv:
return DLSDestinations.volEnvRelease;
case generatorTypes.delayModEnv:
return DLSDestinations.modEnvDelay;
case generatorTypes.attackModEnv:
return DLSDestinations.modEnvAttack;
case generatorTypes.holdModEnv:
return DLSDestinations.modEnvHold;
case generatorTypes.decayModEnv:
return DLSDestinations.modEnvDecay;
case generatorTypes.sustainModEnv:
return { dest: DLSDestinations.modEnvSustain, amount: 1000 - amount };
case generatorTypes.releaseModEnv:
return DLSDestinations.modEnvRelease;
case generatorTypes.initialFilterFc:
return DLSDestinations.filterCutoff;
case generatorTypes.initialFilterQ:
return DLSDestinations.filterQ;
}
}
/**
* @param dest {number}
* @param amt {number}
* @returns {{source: DLSSources, dest: DLSDestinations, amt: number, isBipolar: boolean}|undefined}
*/
function checkSF2SpecialCombos(dest, amt)
{
switch (dest)
{
default:
return undefined;
// mod env
case generatorTypes.modEnvToFilterFc:
return { source: DLSSources.modEnv, dest: DLSDestinations.filterCutoff, amt: amt, isBipolar: false };
case generatorTypes.modEnvToPitch:
return { source: DLSSources.modEnv, dest: DLSDestinations.pitch, amt: amt, isBipolar: false };
// mod lfo
case generatorTypes.modLfoToFilterFc:
return { source: DLSSources.modLfo, dest: DLSDestinations.filterCutoff, amt: amt, isBipolar: true };
case generatorTypes.modLfoToVolume:
return { source: DLSSources.modLfo, dest: DLSDestinations.gain, amt: amt, isBipolar: true };
case generatorTypes.modLfoToPitch:
return { source: DLSSources.modLfo, dest: DLSDestinations.pitch, amt: amt, isBipolar: true };
// vib lfo
case generatorTypes.vibLfoToPitch:
return { source: DLSSources.vibratoLfo, dest: DLSDestinations.pitch, amt: amt, isBipolar: true };
// key to something
case generatorTypes.keyNumToVolEnvHold:
return {
source: DLSSources.keyNum,
dest: DLSDestinations.volEnvHold,
amt: amt,
isBipolar: true
};
case generatorTypes.keyNumToVolEnvDecay:
return {
source: DLSSources.keyNum,
dest: DLSDestinations.volEnvDecay,
amt: amt,
isBipolar: true
};
case generatorTypes.keyNumToModEnvHold:
return {
source: DLSSources.keyNum,
dest: DLSDestinations.modEnvHold,
amt: amt,
isBipolar: true
};
case generatorTypes.keyNumToModEnvDecay:
return {
source: DLSSources.keyNum,
dest: DLSDestinations.modEnvDecay,
amt: amt,
isBipolar: true
};
// Scale tuning is implemented in DLS via an articulator:
// keyNum to relative pitch at 12,800 cents.
// Change that to scale tuning * 128.
// Therefore, a regular scale is still 12,800, half is 6400, etc.
case generatorTypes.scaleTuning:
return {
source: DLSSources.keyNum,
dest: DLSDestinations.pitch,
amt: amt * 128,
isBipolar: false // according to table 4, this should be false.
};
}
}
/**
* @param gen {Generator}
* @returns {Articulator|undefined}
*/
export function getDLSArticulatorFromSf2Generator(gen)
{
const dest = getDLSDestinationFromSf2(gen.generatorType, gen.generatorValue);
let destination = dest;
let source = 0;
let amount = gen.generatorValue;
if (dest?.amount !== undefined)
{
amount = dest.amount;
destination = dest.dest;
}
// check for special combo
const combo = checkSF2SpecialCombos(gen.generatorType, gen.generatorValue);
if (combo !== undefined)
{
amount = combo.amt;
destination = combo.dest;
source = combo.source;
}
else if (destination === undefined)
{
SpessaSynthWarn(`Invalid generator type: ${gen.generatorType}`);
return undefined;
}
return new Articulator(
source,
0,
destination,
amount,
0
);
}
/**
* @param mod {Modulator}
* @returns {Articulator|undefined}
*/
export function getDLSArticulatorFromSf2Modulator(mod)
{
if (mod.transformType !== 0)
{
SpessaSynthWarn("Other transform types are not supported.");
return undefined;
}
let source = getDLSSourceFromSf2Source(mod.sourceUsesCC, mod.sourceIndex);
let sourceTransformType = mod.sourceCurveType;
let sourceBipolar = mod.sourcePolarity;
let sourceDirection = mod.sourceDirection;
if (source === undefined)
{
SpessaSynthWarn(`Invalid source: ${mod.sourceIndex}, CC: ${mod.sourceUsesCC}`);
return undefined;
}
// Attenuation is the opposite of gain. Invert.
if (mod.modulatorDestination === generatorTypes.initialAttenuation)
{
sourceDirection = sourceDirection === 1 ? 0 : 1;
}
let control = getDLSSourceFromSf2Source(mod.secSrcUsesCC, mod.secSrcIndex);
let controlTransformType = mod.secSrcCurveType;
let controlBipolar = mod.secSrcPolarity;
let controlDirection = mod.secSrcDirection;
if (control === undefined)
{
SpessaSynthWarn(`Invalid secondary source: ${mod.secSrcIndex}, CC: ${mod.secSrcUsesCC}`);
return undefined;
}
let dlsDestinationFromSf2 = getDLSDestinationFromSf2(mod.modulatorDestination, mod.transformAmount);
let destination = dlsDestinationFromSf2;
let amt = mod.transformAmount;
if (dlsDestinationFromSf2?.dest !== undefined)
{
destination = dlsDestinationFromSf2.dest;
amt = dlsDestinationFromSf2.amount;
}
const specialCombo = checkSF2SpecialCombos(mod.modulatorDestination, mod.transformAmount);
if (specialCombo !== undefined)
{
amt = specialCombo.amt;
// move the source to control
control = source;
controlTransformType = sourceTransformType;
controlBipolar = sourceBipolar;
controlDirection = sourceDirection;
// set source as static as it's either: env, lfo or key num
sourceTransformType = modulatorCurveTypes.linear;
sourceBipolar = specialCombo.isBipolar ? 1 : 0;
sourceDirection = 0;
source = specialCombo.source;
destination = specialCombo.dest;
}
else if (destination === undefined)
{
SpessaSynthWarn(`Invalid destination: ${mod.modulatorDestination}`);
return undefined;
}
// source curve type maps to a desfont curve type in section 2.10, table 9
let transform = 0;
transform |= controlTransformType << 4;
transform |= controlBipolar << 8;
transform |= controlDirection << 9;
// use the source curve in output transform
transform |= sourceTransformType;
transform |= sourceBipolar << 14;
transform |= sourceDirection << 15;
return new Articulator(
source,
control,
destination,
amt,
transform
);
}