Skip to content

Commit

Permalink
Add DMOD support
Browse files Browse the repository at this point in the history
  • Loading branch information
spessasus committed Sep 23, 2024
1 parent 84057e7 commit a59313e
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 107 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SpessaSynth",
"version": "3.20.24",
"version": "3.20.25",
"type": "module",
"scripts": {
"start": "node src/website/server/server.js"
Expand Down
35 changes: 16 additions & 19 deletions src/spessasynth_lib/soundfont/basic_soundfont/basic_preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
* sampleID: number,
* }} SampleAndGenerators
*/
import { defaultModulators } from '../read_sf2/modulators.js'
import { generatorTypes } from '../read_sf2/generators.js'
import { Modulator } from '../read_sf2/modulators.js'

export class BasicPreset
{
constructor()
/**
* @param modulators {Modulator[]}
*/
constructor(modulators)
{
/**
* The preset's name
Expand Down Expand Up @@ -67,6 +70,12 @@ export class BasicPreset
* @type {number}
*/
this.morphology = 0;

/**
* Default modulators
* @type {Modulator[]}
*/
this.defaultModulators = modulators;
}

deletePreset()
Expand Down Expand Up @@ -137,21 +146,9 @@ export class BasicPreset
return [];
}

function isInRange(min, max, number) {
return number >= min && number <= max;
}

/**
* @param mod1 {Modulator}
* @param mod2 {Modulator}
* @returns {boolean}
*/
function identicalMod(mod1, mod2)
function isInRange(min, max, number)
{
return (mod1.modulatorSource === mod2.modulatorSource)
&& (mod1.modulatorDestination === mod2.modulatorDestination)
&& (mod1.modulationSecondarySrc === mod2.modulationSecondarySrc)
&& (mod1.transformType === mod2.transformType);
return number >= min && number <= max;
}

/**
Expand All @@ -169,7 +166,7 @@ export class BasicPreset
*/
function addUniqueMods(main, adder)
{
main.push(...adder.filter(m => !main.find(mm => identicalMod(m, mm))));
main.push(...adder.filter(m => !main.find(mm => Modulator.isIdentical(m, mm))));
}

/**
Expand Down Expand Up @@ -237,7 +234,7 @@ export class BasicPreset
addUniqueMods(instrumentModulators, globalInstrumentModulators);

// default mods
addUniqueMods(instrumentModulators, defaultModulators);
addUniqueMods(instrumentModulators, this.defaultModulators);

/**
* sum preset modulators to instruments (amount) sf spec page 54
Expand All @@ -247,7 +244,7 @@ export class BasicPreset
for(let i = 0; i < presetModulators.length; i++)
{
let mod = presetModulators[i];
const identicalInstrumentModulator = finalModulatorList.findIndex(m => identicalMod(mod, m));
const identicalInstrumentModulator = finalModulatorList.findIndex(m => Modulator.isIdentical(mod, m));
if(identicalInstrumentModulator !== -1)
{
// sum the amounts (this makes a new modulator because otherwise it would overwrite the one in the soundfont!!!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SpessaSynthWarn } from '../../utils/loggin.js'
import { consoleColors } from '../../utils/other.js'
import { write } from './write_sf2/write.js'
import { defaultModulators, Modulator } from '../read_sf2/modulators.js'

class BasicSoundFont
{
Expand All @@ -12,7 +13,7 @@ class BasicSoundFont
{
/**
* Soundfont's info stored as name: value. ifil and iver are stored as string representation of float (e.g. 2.1)
* @type {Object<string, string>}
* @type {Object<string, string|IndexedByteArray>}
*/
this.soundFontInfo = {};

Expand All @@ -34,6 +35,12 @@ class BasicSoundFont
*/
this.instruments = [];

/**
* Soundfont's default modulatorss
* @type {Modulator[]}
*/
this.defaultModulators = defaultModulators.map(m => Modulator.copy(m));

if(data?.presets)
{
this.presets.push(...data.presets);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export function getIMOD()
ibag.modulatorZoneStartIndex = imodIndex;
for (const mod of ibag.modulators)
{
writeWord(imoddata, mod.modulatorSource);
writeWord(imoddata, mod.sourceEnum);
writeWord(imoddata, mod.modulatorDestination);
writeWord(imoddata, mod.transformAmount);
writeWord(imoddata, mod.modulationSecondarySrc);
writeWord(imoddata, mod.secondarySourceEnum);
writeWord(imoddata, mod.transformType);
imodIndex++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export function getPMOD()
pbag.modulatorZoneStartIndex = pmodIndex;
for (const mod of pbag.modulators)
{
writeWord(pmoddata, mod.modulatorSource);
writeWord(pmoddata, mod.sourceEnum);
writeWord(pmoddata, mod.modulatorDestination);
writeWord(pmoddata, mod.transformAmount);
writeWord(pmoddata, mod.modulationSecondarySrc);
writeWord(pmoddata, mod.secondarySourceEnum);
writeWord(pmoddata, mod.transformType);
pmodIndex++;
}
Expand Down
10 changes: 10 additions & 0 deletions src/spessasynth_lib/soundfont/basic_soundfont/write_sf2/write.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
{
this.soundFontInfo["ifil"] = "3.0"; // set version to 3
}

for (const [type, data] of Object.entries(this.soundFontInfo))
{
if(type === "ifil" || type === "iver")
Expand All @@ -84,6 +85,15 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
)));
}
else
if(type === "DMOD")
{
infoArrays.push(writeRIFFChunk(new RiffChunk(
type,
data.length,
data
)));
}
else
{
const arr = new IndexedByteArray(data.length);
writeStringAsBytes(arr, data);
Expand Down
4 changes: 3 additions & 1 deletion src/spessasynth_lib/soundfont/dls/dls_preset.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BasicPreset } from '../basic_soundfont/basic_preset.js'
import { BasicPresetZone } from '../basic_soundfont/basic_zones.js'
import { BasicInstrument } from '../basic_soundfont/basic_instrument.js'
import { defaultModulators } from '../read_sf2/modulators.js'

export class DLSPreset extends BasicPreset
{
Expand All @@ -11,7 +12,8 @@ export class DLSPreset extends BasicPreset
*/
constructor(ulBank, ulInstrument)
{
super();
// use stock default modulators, dls won't ever have DMOD chunk
super(defaultModulators);
this.program = ulInstrument & 127;
this.bank = (ulBank >> 8) & 127;
const isDrums = ulBank >> 31;
Expand Down
60 changes: 44 additions & 16 deletions src/spessasynth_lib/soundfont/read_sf2/modulators.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,21 @@ export class Modulator{
constructor(dataArray) {
if(dataArray.srcEnum)
{
this.modulatorSource = dataArray.srcEnum;
this.sourceEnum = dataArray.srcEnum;
/**
* @type {generatorTypes}
*/
this.modulatorDestination = dataArray.dest;
this.modulationSecondarySrc = dataArray.secSrcEnum;
this.secondarySourceEnum = dataArray.secSrcEnum;
this.transformAmount = dataArray.amt;
this.transformType = dataArray.transform;
}
else
{
this.modulatorSource = readLittleEndian(dataArray, 2);
this.sourceEnum = readLittleEndian(dataArray, 2);
this.modulatorDestination = readLittleEndian(dataArray, 2);
this.transformAmount = signedInt16(dataArray[dataArray.currentIndex++], dataArray[dataArray.currentIndex++]);
this.modulationSecondarySrc = readLittleEndian(dataArray, 2);
this.secondarySourceEnum = readLittleEndian(dataArray, 2);
this.transformType = readLittleEndian(dataArray, 2);
}

Expand All @@ -68,22 +68,50 @@ export class Modulator{
}

// decode the source
this.sourcePolarity = this.modulatorSource >> 9 & 1;
this.sourceDirection = this.modulatorSource >> 8 & 1;
this.sourceUsesCC = this.modulatorSource >> 7 & 1;
this.sourceIndex = this.modulatorSource & 127;
this.sourceCurveType = this.modulatorSource >> 10 & 3;
this.sourcePolarity = this.sourceEnum >> 9 & 1;
this.sourceDirection = this.sourceEnum >> 8 & 1;
this.sourceUsesCC = this.sourceEnum >> 7 & 1;
this.sourceIndex = this.sourceEnum & 127;
this.sourceCurveType = this.sourceEnum >> 10 & 3;

// decode the secondary source
this.secSrcPolarity = this.modulationSecondarySrc >> 9 & 1;
this.secSrcDirection = this.modulationSecondarySrc >> 8 & 1;
this.secSrcUsesCC = this.modulationSecondarySrc >> 7 & 1;
this.secSrcIndex = this.modulationSecondarySrc & 127;
this.secSrcCurveType = this.modulationSecondarySrc >> 10 & 3;
this.secSrcPolarity = this.secondarySourceEnum >> 9 & 1;
this.secSrcDirection = this.secondarySourceEnum >> 8 & 1;
this.secSrcUsesCC = this.secondarySourceEnum >> 7 & 1;
this.secSrcIndex = this.secondarySourceEnum & 127;
this.secSrcCurveType = this.secondarySourceEnum >> 10 & 3;

//this.precomputeModulatorTransform();
}

/**
* @param modulator {Modulator}
* @returns {Modulator}
*/
static copy(modulator)
{
return new Modulator({
srcEnum: modulator.sourceEnum,
secSrcEnum: modulator.secondarySourceEnum,
transform: modulator.transformType,
amt: modulator.transformAmount,
dest: modulator.modulatorDestination
});
}

/**
* @param mod1 {Modulator}
* @param mod2 {Modulator}
* @returns {boolean}
*/
static isIdentical(mod1, mod2)
{
return (mod1.sourceEnum === mod2.sourceEnum)
&& (mod1.modulatorDestination === mod2.modulatorDestination)
&& (mod1.secondarySourceEnum === mod2.secondarySourceEnum)
&& (mod1.transformType === mod2.transformType);
}

/**
* Sums transform and creates a NEW modulator
* @param modulator {Modulator}
Expand All @@ -92,8 +120,8 @@ export class Modulator{
sumTransform(modulator)
{
return new Modulator({
srcEnum: this.modulatorSource,
secSrcEnum: this.modulationSecondarySrc,
srcEnum: this.sourceEnum,
secSrcEnum: this.secondarySourceEnum,
dest: this.modulatorDestination,
transform: this.transformType,
amt: this.transformAmount + modulator.transformAmount
Expand Down
10 changes: 6 additions & 4 deletions src/spessasynth_lib/soundfont/read_sf2/presets.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ export class Preset extends BasicPreset
/**
* Creates a preset
* @param presetChunk {RiffChunk}
* @param defaultModulators {Modulator[]}
*/
constructor(presetChunk)
constructor(presetChunk, defaultModulators)
{
super();
super(defaultModulators);
this.presetName = readBytesAsString(presetChunk.chunkData, 20)
.trim()
.replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
Expand Down Expand Up @@ -51,17 +52,18 @@ export class Preset extends BasicPreset
* Reads the presets
* @param presetChunk {RiffChunk}
* @param presetZones {PresetZone[]}
* @param defaultModulators {Modulator[]}
* @returns {Preset[]}
*/
export function readPresets(presetChunk, presetZones)
export function readPresets(presetChunk, presetZones, defaultModulators)
{
/**
* @type {Preset[]}
*/
let presets = [];
while(presetChunk.chunkData.length > presetChunk.chunkData.currentIndex)
{
let preset = new Preset(presetChunk);
let preset = new Preset(presetChunk, defaultModulators);
if(presets.length > 0)
{
let presetZonesAmount = preset.presetZoneStartIndex - presets[presets.length - 1].presetZoneStartIndex;
Expand Down
22 changes: 19 additions & 3 deletions src/spessasynth_lib/soundfont/read_sf2/soundfont.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,42 @@ export class SoundFont2 extends BasicSoundFont
{
let chunk = readRIFFChunk(infoChunk.chunkData);
let text;
// special case: ifil
// special cases
switch (chunk.header.toLowerCase())
{
case "ifil":
case "iver":
text = `${readLittleEndian(chunk.chunkData, 2)}.${readLittleEndian(chunk.chunkData, 2)}`;
this.soundFontInfo[chunk.header] = text;
break;

case "icmt":
text = readBytesAsString(chunk.chunkData, chunk.chunkData.length, undefined, false);
this.soundFontInfo[chunk.header] = text;
break;

// dmod: default modulators
case "dmod":
const newModulators = readModulators(chunk);
newModulators.pop(); // remove the terminal record
text = `Modulators: ${newModulators.length}`;
// override default modulators
const oldDefaults = this.defaultModulators;

this.defaultModulators = newModulators;
this.defaultModulators.push(...oldDefaults.filter(m => !this.defaultModulators.find(mm => Modulator.isIdentical(m, mm))));
this.soundFontInfo[chunk.header] = chunk.chunkData;
SpessaSynthInfo("Default modulators:", this.defaultModulators.map(m => m.debugString()));
break;

default:
text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
this.soundFontInfo[chunk.header] = text;
}

SpessaSynthInfo(`%c"${chunk.header}": %c"${text}"`,
consoleColors.info,
consoleColors.recognized);
this.soundFontInfo[chunk.header] = text;
}

// SDTA
Expand Down Expand Up @@ -214,7 +230,7 @@ export class SoundFont2 extends BasicSoundFont

let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, this.instruments);

this.presets.push(...readPresets(presetHeadersChunk, presetZones));
this.presets.push(...readPresets(presetHeadersChunk, presetZones, this.defaultModulators));
this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
// preload the first preset
SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
Expand Down
16 changes: 8 additions & 8 deletions src/spessasynth_lib/synthetizer/worklet_processor.min.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function createChannelController(channelNumber)
this.locale,
[channelNumber + 1],
-8192,
8192,
8191,
true,
val => {
const meterLocked = pitchWheel.isLocked;
Expand Down
Loading

0 comments on commit a59313e

Please sign in to comment.