Skip to content

Commit

Permalink
Add new Default Modulator, velocity override and optimize modulators
Browse files Browse the repository at this point in the history
- added filterQ modulator
- added velocity override
- optimized modulators by only computing the necessary one
shortened minimum note length
  • Loading branch information
spessasus committed Oct 5, 2024
1 parent ae821ad commit 01a85de
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 87 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.37",
"version": "3.20.40",
"type": "module",
"scripts": {
"start": "node src/website/server/server.js"
Expand Down
15 changes: 15 additions & 0 deletions src/spessasynth_lib/soundfont/read_sf2/modulators.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export class Modulator{
//this.precomputeModulatorTransform();
}

/**
* The current computed value of this modulator
* @type {number}
*/
currentValue = 0;

/**
* @param modulator {Modulator}
* @returns {Modulator}
Expand Down Expand Up @@ -258,6 +264,15 @@ export const defaultModulators = [
amt: 4000,
secSrcEnum: 0x0, // no controller
transform: 0
}),

// cc 71 (filter q) to filterq
new Modulator({
srcEnum: getModSourceEnum(modulatorCurveTypes.linear, 1, 0 , 1, midiControllers.timbreHarmonicContent), // linear forwards bipolar cc 74
dest: generatorTypes.initialFilterQ,
amt: 250,
secSrcEnum: 0x0, // no controller
transform: 0
})
];

Expand Down
17 changes: 17 additions & 0 deletions src/spessasynth_lib/synthetizer/synthetizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
import { DEFAULT_EFFECTS_CONFIG } from './audio_effects/effects_config.js'
import { SoundfontManager } from './synth_soundfont_manager.js'
import { channelConfiguration } from './worklet_system/worklet_utilities/worklet_processor_channel.js'


/**
Expand Down Expand Up @@ -484,6 +485,7 @@ export class Synthetizer {
*/
controllerChange(channel, controllerNumber, controllerValue, force=false)
{
if(controllerNumber > 127 || controllerNumber < 0) throw new Error(`Invalid controller number: ${controllerNumber}`);
controllerValue = Math.floor(controllerValue);
controllerNumber = Math.floor(controllerNumber);
this.post({
Expand Down Expand Up @@ -631,6 +633,21 @@ export class Synthetizer {
})
}

/**
* Overrides velocity on a given channel
* @param channel {number} usually 0-15: the channel to change
* @param velocity {number} 1-127, the velocity to use.
* 0 Disables this functionality
*/
velocityOverride(channel, velocity)
{
this.post({
channelNumber: channel,
messageType: workletMessageType.ccChange,
messageData: [channelConfiguration.velocityOverride, velocity, true]
})
}

/**
* Causes the given midi channel to ignore controller messages for the given controller number
* @param channel {number} usually 0-15: the channel to lock
Expand Down
18 changes: 9 additions & 9 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 @@ -49,7 +49,9 @@ import { getWorkletVoices } from './worklet_utilities/worklet_voice.js'
* purpose: manages the synthesizer (and worklet sequencer) from the AudioWorkletGlobalScope and renders the audio data
*/

export const MIN_NOTE_LENGTH = 0.07; // if the note is released faster than that, it forced to last that long
// if the note is released faster than that, it forced to last that long
// this is used mostly for drum channels, where a lot of midis like to send instant note off after a note on
export const MIN_NOTE_LENGTH = 0.03;

export const SYNTHESIZER_GAIN = 1.0;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { consoleColors } from '../../../utils/other.js'
import { midiControllers } from '../../../midi_parser/midi_message.js'
import { dataEntryStates } from '../worklet_utilities/worklet_processor_channel.js'
import { channelConfiguration, dataEntryStates } from '../worklet_utilities/worklet_processor_channel.js'
import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
import { SYNTHESIZER_GAIN } from '../main_processor.js'
import { DEFAULT_PERCUSSION } from '../../synthetizer.js'

/**
* @param channel {number}
* @param controllerNumber {midiControllers}
* @param controllerNumber {number}
* @param controllerValue {number}
* @param force {boolean}
* @this {SpessaSynthProcessor}
Expand All @@ -24,6 +24,19 @@ export function controllerChange(channel, controllerNumber, controllerValue, for
SpessaSynthWarn(`Trying to access channel ${channel} which does not exist... ignoring!`);
return;
}
if(controllerNumber > 127)
{
// channel configuration. force must be set to true
if(!force) return;
switch (controllerNumber)
{
default:
return;

case channelConfiguration.velocityOverride:
channelObject.velocityOverride = controllerValue;
}
}
// lsb controller values: append them as the lower nibble of the 14 bit value
// excluding bank select and data entry as it's handled separately
if(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
sentMidiNote = this.tunings[program]?.[midiNote].midiNote;
}

// velocity override
if(channelObject.velocityOverride > 0)
{
velocity = channelObject.velocityOverride;
}

// get voices
const voices = this.getWorkletVoices(
channel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { WorkletVolumeEnvelope } from '../worklet_utilities/volume_envelope.js'


const HALF_PI = Math.PI / 2;
export const PAN_SMOOTHING_FACTOR = 0.01;
export const PAN_SMOOTHING_FACTOR = 0.05;
/**
* Renders a voice to the stereo output buffer
* @param channel {WorkletProcessorChannel} the voice's channel
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
import { Modulator, modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
import { getModulatorCurveValue, MOD_PRECOMPUTED_LENGTH } from './modulator_curves.js'
import { NON_CC_INDEX_OFFSET } from './worklet_processor_channel.js'
import { generatorLimits, generatorTypes } from '../../../soundfont/read_sf2/generators.js'
Expand All @@ -21,6 +21,7 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
{
if(modulator.transformAmount === 0)
{
modulator.currentValue = 0;
return 0;
}
// mapped to 0-16384
Expand Down Expand Up @@ -95,13 +96,15 @@ export function computeWorkletModulator(controllerTable, modulator, voice)


// compute the modulator
const computedValue = sourceValue * secondSrcValue * modulator.transformAmount;
let computedValue = sourceValue * secondSrcValue * modulator.transformAmount;

if(modulator.transformType === 2)
{
// abs value
return Math.abs(computedValue);
computedValue = Math.abs(computedValue);
}

modulator.currentValue = computedValue;
return computedValue;
}

Expand All @@ -113,7 +116,9 @@ export function computeWorkletModulator(controllerTable, modulator, voice)
* @param sourceIndex {number} enum for the source
*/
export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sourceIndex = 0) {
const { modulators, generators, modulatedGenerators } = voice;
const modulators = voice.modulators;
const generators = voice.generators;
const modulatedGenerators = voice.modulatedGenerators;

// Modulation envelope is cheap to recalculate
// why here and not at the bottom?
Expand Down Expand Up @@ -158,13 +163,14 @@ export function computeModulators(voice, controllerTable, sourceUsesCC = -1, sou
{
// Reset this destination
modulatedGenerators[destination] = generators[destination];
// Compute all modulators for this destination
// compute our modulator
computeWorkletModulator(controllerTable, mod, voice);
// sum the values of all modulators for this destination
modulators.forEach(m => {
if (m.modulatorDestination === destination)
{
const limits = generatorLimits[mod.modulatorDestination];
const current = modulatedGenerators[mod.modulatorDestination];
const newValue = current + computeWorkletModulator(controllerTable, m, voice);
const newValue = modulatedGenerators[mod.modulatorDestination] + m.currentValue;
modulatedGenerators[mod.modulatorDestination] = Math.max(limits.min, Math.min(newValue, limits.max));
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { modulatorSources } from '../../../soundfont/read_sf2/modulators.js'
* @property {Int16Array} keyCentTuning - tuning of individual keys in cents
* @property {boolean} holdPedal - indicates whether the hold pedal is active
* @property {boolean} drumChannel - indicates whether the channel is a drum channel
* @property {number} velocityOverride - overrides velocity if > 0 otherwise disabled
*
* @property {dataEntryStates} dataEntryState - the current state of the data entry
* @property {number} NRPCoarse - the current coarse value of the Non-Registered Parameter
Expand Down Expand Up @@ -62,6 +63,8 @@ export function createWorkletChannel(sendEvent = false)
channelOctaveTuning: new Int8Array(12),
keyCentTuning: new Int16Array(128),
channelVibrato: {delay: 0, depth: 0, rate: 0},
velocityOverride: 0,

lockGSNRPNParams: false,
holdPedal: false,
isMuted: false,
Expand Down Expand Up @@ -92,6 +95,7 @@ resetArray[midiControllers.expressionController] = 127 << 7;
resetArray[midiControllers.pan] = 64 << 7;
resetArray[midiControllers.releaseTime] = 64 << 7;
resetArray[midiControllers.brightness] = 64 << 7;
resetArray[midiControllers.timbreHarmonicContent] = 64 << 7;
resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = 8192;
resetArray[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheelRange] = 2 << 7;

Expand Down Expand Up @@ -119,3 +123,11 @@ export const customControllers = {
export const CUSTOM_CONTROLLER_TABLE_SIZE = Object.keys(customControllers).length;
export const customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
customResetArray[customControllers.modulationMultiplier] = 1;

/**
* This is a channel configuration enum, it is internally sent from Synthetizer via controller change
* @enum {number}
*/
export const channelConfiguration = {
velocityOverride: 128, // overrides velocity for the given channel
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* purpose: prepares workletvoices from sample and generator data and manages sample dumping
* note: sample dumping means sending it over to the AudioWorkletGlobalScope
*/
import { Modulator } from '../../../soundfont/read_sf2/modulators.js'

class WorkletSample
{
Expand Down Expand Up @@ -298,7 +299,7 @@ class WorkletVoice
currentTime,
voice.targetKey,
voice.generators,
voice.modulators.slice()
voice.modulators.map(m => Modulator.copy(m))
);
}
}
Expand Down Expand Up @@ -329,7 +330,7 @@ export function getWorkletVoices(channel,
const cached = channelObject.cachedVoices[midiNote][velocity];
if(cached !== undefined)
{
workletVoices = cached.map(v => WorkletVoice.copy(v, currentTime));
return cached.map(v => WorkletVoice.copy(v, currentTime));
}
else
{
Expand Down Expand Up @@ -425,7 +426,7 @@ export function getWorkletVoices(channel,
currentTime,
targetKey,
generators,
sampleAndGenerators.modulators
sampleAndGenerators.modulators.map(m => Modulator.copy(m))
)
);
return voices;
Expand Down
Loading

0 comments on commit 01a85de

Please sign in to comment.