spessasynth_lib
Version:
MIDI and SoundFont2/DLS library with no compromises
316 lines (305 loc) • 14.4 kB
JavaScript
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
import { DLSDestinations } from "./dls_destinations.js";
import { DLS_1_NO_VIBRATO_MOD, DLS_1_NO_VIBRATO_PRESSURE, DLSSources } from "./dls_sources.js";
import { getSF2ModulatorFromArticulator } from "./articulator_converter.js";
import { SpessaSynthInfo, SpessaSynthWarn } from "../../utils/loggin.js";
import { consoleColors } from "../../utils/other.js";
import { Generator, generatorTypes } from "../basic_soundfont/generator.js";
import { Modulator } from "../basic_soundfont/modulator.js";
/**
* Reads the articulator chunk
* @param chunk {RiffChunk}
* @param disableVibrato {boolean} it seems that dls 1 does not have vibrato lfo, so we shall disable it
* @returns {{modulators: Modulator[], generators: Generator[]}}
*/
export function readArticulation(chunk, disableVibrato)
{
const artData = chunk.chunkData;
/**
* @type {Generator[]}
*/
const generators = [];
/**
* @type {Modulator[]}
*/
const modulators = [];
// cbSize (ignore)
readLittleEndian(artData, 4);
const connectionsAmount = readLittleEndian(artData, 4);
for (let i = 0; i < connectionsAmount; i++)
{
// read the block
const source = readLittleEndian(artData, 2);
const control = readLittleEndian(artData, 2);
const destination = readLittleEndian(artData, 2);
const transform = readLittleEndian(artData, 2);
const scale = readLittleEndian(artData, 4) | 0;
const value = scale >> 16; // convert it to 16 bit as soundfont uses that
// interpret this somehow...
// if source and control are both zero, it's a generator
if (source === 0 && control === 0 && transform === 0)
{
/**
* @type {Generator}
*/
let generator;
switch (destination)
{
case DLSDestinations.pan:
generator = new Generator(generatorTypes.pan, value); // turn percent into tenths of percent
break;
case DLSDestinations.gain:
generator = new Generator(generatorTypes.initialAttenuation, -value * 10 / 0.4); // turn to centibels and apply emu correction
break;
case DLSDestinations.filterCutoff:
generator = new Generator(generatorTypes.initialFilterFc, value);
break;
case DLSDestinations.filterQ:
generator = new Generator(generatorTypes.initialFilterQ, value);
break;
// mod lfo raw values it seems
case DLSDestinations.modLfoFreq:
generator = new Generator(generatorTypes.freqModLFO, value);
break;
case DLSDestinations.modLfoDelay:
generator = new Generator(generatorTypes.delayModLFO, value);
break;
case DLSDestinations.vibLfoFreq:
generator = new Generator(generatorTypes.freqVibLFO, value);
break;
case DLSDestinations.vibLfoDelay:
generator = new Generator(generatorTypes.delayVibLFO, value);
break;
// vol. env: all times are timecents like sf2
case DLSDestinations.volEnvDelay:
generator = new Generator(generatorTypes.delayVolEnv, value);
break;
case DLSDestinations.volEnvAttack:
generator = new Generator(generatorTypes.attackVolEnv, value);
break;
case DLSDestinations.volEnvHold:
// do not validate because keyNumToSomething
generator = new Generator(generatorTypes.holdVolEnv, value, false);
break;
case DLSDestinations.volEnvDecay:
// do not validate because keyNumToSomething
generator = new Generator(generatorTypes.decayVolEnv, value, false);
break;
case DLSDestinations.volEnvRelease:
generator = new Generator(generatorTypes.releaseVolEnv, value);
break;
case DLSDestinations.volEnvSustain:
// gain seems to be (1000 - value) / 10 = sustain dB
const sustainCb = 1000 - value;
generator = new Generator(generatorTypes.sustainVolEnv, sustainCb);
break;
// mod env
case DLSDestinations.modEnvDelay:
generator = new Generator(generatorTypes.delayModEnv, value);
break;
case DLSDestinations.modEnvAttack:
generator = new Generator(generatorTypes.attackModEnv, value);
break;
case DLSDestinations.modEnvHold:
// do not validate because keyNumToSomething
generator = new Generator(generatorTypes.holdModEnv, value, false);
break;
case DLSDestinations.modEnvDecay:
// do not validate because keyNumToSomething
generator = new Generator(generatorTypes.decayModEnv, value, false);
break;
case DLSDestinations.modEnvRelease:
generator = new Generator(generatorTypes.releaseModEnv, value);
break;
case DLSDestinations.modEnvSustain:
// dls uses 1%, desfont uses 0.1%
const percentageSustain = 1000 - value;
generator = new Generator(generatorTypes.sustainModEnv, percentageSustain);
break;
case DLSDestinations.reverbSend:
generator = new Generator(generatorTypes.reverbEffectsSend, value);
break;
case DLSDestinations.chorusSend:
generator = new Generator(generatorTypes.chorusEffectsSend, value);
break;
case DLSDestinations.pitch:
// split it up
const semi = Math.floor(value / 100);
const cents = Math.floor(value - semi * 100);
generator = new Generator(generatorTypes.fineTune, cents);
generators.push(new Generator(generatorTypes.coarseTune, semi));
break;
}
if (generator)
{
generators.push(generator);
}
}
else
// if not, modulator?
{
let isGenerator = true;
// a few special cases which are generators:
if (control === DLSSources.none)
{
// mod lfo to pitch
if (source === DLSSources.modLfo && destination === DLSDestinations.pitch)
{
generators.push(new Generator(generatorTypes.modLfoToPitch, value));
}
else
// mod lfo to volume
if (source === DLSSources.modLfo && destination === DLSDestinations.gain)
{
generators.push(new Generator(generatorTypes.modLfoToVolume, value));
}
else
// mod lfo to filter
if (source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
{
generators.push(new Generator(generatorTypes.modLfoToFilterFc, value));
}
else
// vib lfo to pitch
if (source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
{
generators.push(new Generator(generatorTypes.vibLfoToPitch, value));
}
else
// mod env to pitch
if (source === DLSSources.modEnv && destination === DLSDestinations.pitch)
{
generators.push(new Generator(generatorTypes.modEnvToPitch, value));
}
else
// mod env to filter
if (source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
{
generators.push(new Generator(generatorTypes.modEnvToFilterFc, value));
}
else
// scale tuning (key number to pitch)
if (source === DLSSources.keyNum && destination === DLSDestinations.pitch)
{
// this is just a soundfont generator, but the amount must be changed
// 12,800 means the regular scale (100)
generators.push(new Generator(generatorTypes.scaleTuning, value / 128));
}
else
// key to vol env hold
if (source === DLSSources.keyNum && destination === DLSDestinations.volEnvHold)
{
// according to viena and another strange (with modulators) rendition of gm.dls in sf2,
// it shall be divided by -128
// and a strange correction needs to be applied to the real value:
// real + (60 / 128) * scale
generators.push(new Generator(generatorTypes.keyNumToVolEnvHold, value / -128));
const correction = Math.round((60 / 128) * value);
generators.forEach(g =>
{
if (g.generatorType === generatorTypes.holdVolEnv)
{
g.generatorValue += correction;
}
});
}
else
// key to vol env decay
if (source === DLSSources.keyNum && destination === DLSDestinations.volEnvDecay)
{
// according to viena and another strange (with modulators) rendition of gm.dls in sf2,
// it shall be divided by -128
// and a strange correction needs to be applied to the real value:
// real + (60 / 128) * scale
generators.push(new Generator(generatorTypes.keyNumToVolEnvDecay, value / -128));
const correction = Math.round((60 / 128) * value);
generators.forEach(g =>
{
if (g.generatorType === generatorTypes.decayVolEnv)
{
g.generatorValue += correction;
}
});
}
else
// key to mod env hold
if (source === DLSSources.keyNum && destination === DLSDestinations.modEnvHold)
{
// according to viena and another strange (with modulators) rendition of gm.dls in sf2,
// it shall be divided by -128
// and a strange correction needs to be applied to the real value:
// real + (60 / 128) * scale
generators.push(new Generator(generatorTypes.keyNumToModEnvHold, value / -128));
const correction = Math.round((60 / 128) * value);
generators.forEach(g =>
{
if (g.generatorType === generatorTypes.holdModEnv)
{
g.generatorValue += correction;
}
});
}
else
// key to mod env decay
if (source === DLSSources.keyNum && destination === DLSDestinations.modEnvDecay)
{
// according to viena and another strange (with modulators) rendition of gm.dls in sf2,
// it shall be divided by -128
// and a strange correction needs to be applied to the real value:
// real + (60 / 128) * scale
generators.push(new Generator(generatorTypes.keyNumToModEnvDecay, value / -128));
const correction = Math.round((60 / 128) * value);
generators.forEach(g =>
{
if (g.generatorType === generatorTypes.decayModEnv)
{
g.generatorValue += correction;
}
});
}
else
{
isGenerator = false;
}
}
else
{
isGenerator = false;
}
if (isGenerator === false)
{
// UNCOMMENT TO ENABLE DEBUG
// modulatorConverterDebug(source, control, destination, value, transform)
// convert it to modulator
const mod = getSF2ModulatorFromArticulator(
source,
control,
destination,
transform,
value
);
if (mod)
{
// some articulators cannot be turned into modulators, that's why this check is a thing
modulators.push(mod);
SpessaSynthInfo("%cSucceeded converting to SF2 Modulator!", consoleColors.recognized);
}
else
{
SpessaSynthWarn("Failed converting to SF2 Modulator!");
}
}
}
}
// it seems that dls 1 does not have vibrato lfo, so we shall disable it
if (disableVibrato)
{
modulators.push(
// mod to vib
Modulator.copy(DLS_1_NO_VIBRATO_MOD),
// press to vib
Modulator.copy(DLS_1_NO_VIBRATO_PRESSURE)
);
}
return { modulators: modulators, generators: generators };
}