From ea00d0c249b9d9029532294ec174d7cd7e3d0cf6 Mon Sep 17 00:00:00 2001 From: spessasus Date: Sat, 30 Mar 2024 16:24:47 +0100 Subject: [PATCH] New chorus - sf2 compliant chorus (respects the generators) - maintaining legacy system - the synth will now use the legacy system if the website is served over insecure context - update README accordingly - finally added the event system to the documentation --- README.md | 7 +- .../soundfont/chunk/modulators.js | 3 + src/spessasynth_lib/synthetizer/chorus.js | 79 ------------------- .../synthetizer/fancy_chorus.js | 41 ++++++++++ .../synthetizer/native_system/README.md | 30 +++---- .../synthetizer/native_system/midi_channel.js | 33 ++++++-- .../synthetizer/synthetizer.js | 73 +++++++++++++++-- .../worklet_system/channel_processor.js | 10 ++- .../worklet_system/worklet_channel.js | 29 ++++--- .../worklet_utilities/stereo_panner.js | 37 ++++++--- src/website/ui/midi_keyboard.js | 12 ++- 11 files changed, 213 insertions(+), 141 deletions(-) delete mode 100644 src/spessasynth_lib/synthetizer/chorus.js create mode 100644 src/spessasynth_lib/synthetizer/fancy_chorus.js diff --git a/README.md b/README.md index 339a32b7..0726face 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ SoundFont2 based realtime synthetizer and MIDI player written in JavaScript usin - Reverb and chorus support - A few custom modulators to support some additional controllers (see `modulators.js`) - Written using AudioWorklets (Firefox and Chrome both work perfectly) +- Legacy system that doesn't use AudioWorklets (Available to use over HTTP and will switch automatically) - Can load really large soundfonts (4GB!) (but only on Firefox, Chromium has a memory limit) - Multi-port MIDIs support (more than 16 channels) - MIDI Controller Support (Default supported controllers can be found [here](../../wiki/Synthetizer-Class#supported-controllers)) @@ -37,6 +38,8 @@ SoundFont2 based realtime synthetizer and MIDI player written in JavaScript usin ### Limitations - It might not sound as good as other synthetizers (e.g. FluidSynth or BASSMIDI) +- The modulation envelope needs some work +- the modulators need some work ## Installation ***When you're loading a large (>4GB) SoundFont, use Firefox because chromium has a 4GB memory limit*** @@ -77,6 +80,8 @@ The program is divided into parts: #### todo - make the worklet system perform good - port the worklet system to emscripten (maybe) -- +- fix modenv +- fix rare clicking in volenv +- fix attenuation modulators ### License Copyright © 2024 Spessasus. Licensed under the MIT License. diff --git a/src/spessasynth_lib/soundfont/chunk/modulators.js b/src/spessasynth_lib/soundfont/chunk/modulators.js index 51800788..9c773df8 100644 --- a/src/spessasynth_lib/soundfont/chunk/modulators.js +++ b/src/spessasynth_lib/soundfont/chunk/modulators.js @@ -122,6 +122,9 @@ export const defaultModulators = [ // reverb effects to send new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 500, secSrcEnum: 0x0, transform: 0}), + // chorus effects to send + new Modulator({srcEnum: 0x00DD, dest: generatorTypes.chorusEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}), + // custom modulators heck yeah // cc 92 (tremolo) to modLFO volume new Modulator({ diff --git a/src/spessasynth_lib/synthetizer/chorus.js b/src/spessasynth_lib/synthetizer/chorus.js deleted file mode 100644 index f41e960b..00000000 --- a/src/spessasynth_lib/synthetizer/chorus.js +++ /dev/null @@ -1,79 +0,0 @@ -const OSC_GAIN = 0.001; -const OSC_FREQ = 1.766; -const DELAY_TIME = 0.017; -export class Chorus -{ - /** - * Creates a new chorus effect - * @param input {AudioNode} - * @param output {AudioNode} - * @param defaultGain {number} - */ - constructor(input, output, defaultGain) { - this.input = input; - this.output = output; - this.context = input.context; - - this.delayLine = new DelayNode(this.context, { - delayTime: DELAY_TIME - }); - this.wetController = new GainNode(this.context, { - gain: defaultGain - }); - this.delayOscillator = new OscillatorNode(this.context, { - type: "triangle", - frequency: OSC_FREQ - }); - this.delayGain = new GainNode(this.context, { - gain: OSC_GAIN - }); - this.delayOscillator.start(); - - /* +----------------------------------------------+ - | \|/ - +-----+ +--------------+ +----------+ +------+ - |input|--->|wet controller|--->|delay line|--->|output| - +-----+ +--------------+ +----------+ +------+ - /|\ delay time - +----------+ +----------+ | - |oscillator|--->|delay gain| -------+ - +----------+ +----------+ - */ - - this.delayOscillator.connect(this.delayGain); - this.delayGain.connect(this.delayLine.delayTime); - - this.input.connect(this.wetController); - this.wetController.connect(this.delayLine); - this.delayLine.connect(this.output); - - this.input.connect(this.output); - } - - /** - * @param level {number} 0-127 - */ - setChorusLevel(level) - { - this.wetController.gain.value = level / 127; - } - - /** - * returns the chorus level - * @returns {number} 0-127 - */ - getChorusLevel() - { - return this.wetController.gain.value * 127; - } - - /** - * Disconnect the chorus from the nodes - */ - disconnectChorus() - { - this.input.disconnect(this.wetController); - this.delayLine.disconnect(this.output); - this.input.disconnect(this.output); - } -} \ No newline at end of file diff --git a/src/spessasynth_lib/synthetizer/fancy_chorus.js b/src/spessasynth_lib/synthetizer/fancy_chorus.js new file mode 100644 index 00000000..964684ec --- /dev/null +++ b/src/spessasynth_lib/synthetizer/fancy_chorus.js @@ -0,0 +1,41 @@ +const DEFAULT_DELAY = 0.02; +const DELAY_VARIATION = 0.015; + +const OSC_FREQ = 1.5; +const OSC_GAIN = 0.001; +export class FancyChorus +{ + /** + * @param output {AudioNode} + */ + constructor(output) { + const context = output.context; + + this.input = new GainNode(context, + { + gain: 1 + }); + + const osc = new OscillatorNode(context, { + type: 'sine', + frequency: OSC_FREQ + }); + + const oscGain = new GainNode(context, { + gain: OSC_GAIN + }); + osc.connect(oscGain); + osc.start(); + const delayOne = new DelayNode(context, { + delayTime: DEFAULT_DELAY + }); + const delayTwo = new DelayNode(context, { + delayTime: DEFAULT_DELAY + DELAY_VARIATION + }); + oscGain.connect(delayOne.delayTime); + this.input.connect(delayOne); + this.input.connect(delayTwo); + delayTwo.connect(output); + delayOne.connect(output); + } +} \ No newline at end of file diff --git a/src/spessasynth_lib/synthetizer/native_system/README.md b/src/spessasynth_lib/synthetizer/native_system/README.md index 30a40656..dac65873 100644 --- a/src/spessasynth_lib/synthetizer/native_system/README.md +++ b/src/spessasynth_lib/synthetizer/native_system/README.md @@ -1,25 +1,15 @@ # This is the old synthesis system. -It's not maintained anymore, but it's here for the HTTP websites which can't use AudioWorklets. -To use this system, edit `synthetizer.js`: +It's not updated anymore, but it's here for the HTTP websites which can't use AudioWorklets. +To use this system, change the `synthesisMode` to `"legacy"`: + ```js -import { MidiChannel } from './native_system/midi_channel.js'; +synth.synthesisMode = "legacy"; ``` -and change the class from `WorkletChannel` to `MidiChannel` +it should automatically create the legacy channels. +If you want to automatically use this mode, then ```js -/** - * create 16 channels - * @type {WorkletChannel[]|MidiChannel[]} - */ -this.midiChannels = [...Array(DEFAULT_CHANNEL_COUNT).keys()].map(j => new MidiChannel(this.volumeController, this.defaultPreset, j + 1, false)); - -// ... +export const DEFAULT_SYNTHESIS_MODE = "legacy"; +``` -/** - * Adds a new channel to the synthesizer - */ -addNewChannel() -{ - this.midiChannels.push(new MidiChannel(this.volumeController, this.defaultPreset, this.midiChannels.length + 1, false)); - this.eventHandler.callEvent("newchannel", this.midiChannels[this.midiChannels.length - 1]); -} -``` \ No newline at end of file +Note: +the synth will try to detect if the worklets are available. if not, then it should switch to legacy diff --git a/src/spessasynth_lib/synthetizer/native_system/midi_channel.js b/src/spessasynth_lib/synthetizer/native_system/midi_channel.js index 1a9bf132..2e6efca7 100644 --- a/src/spessasynth_lib/synthetizer/native_system/midi_channel.js +++ b/src/spessasynth_lib/synthetizer/native_system/midi_channel.js @@ -2,7 +2,6 @@ import {VoiceGroup} from "./voice_group.js"; import {Preset} from "../../soundfont/chunk/presets.js"; import { consoleColors } from '../../utils/other.js' import { midiControllers } from '../../midi_parser/midi_message.js' -import { Chorus } from '../chorus.js' const CHANNEL_LOUDNESS = 0.3; @@ -21,11 +20,12 @@ export class MidiChannel { * creates a midi channel * @param targetNode {AudioNode} * @param targetReverbNode {AudioNode} + * @param targetChorusNode {AudioNode} * @param defaultPreset {Preset} * @param channelNumber {number} * @param percussionChannel {boolean} */ - constructor(targetNode, targetReverbNode, defaultPreset, channelNumber = -1, percussionChannel = false) { + constructor(targetNode, targetReverbNode, targetChorusNode, defaultPreset, channelNumber = -1, percussionChannel = false) { this.ctx = targetNode.context; this.outputNode = targetNode; this.channelNumber = channelNumber @@ -65,12 +65,17 @@ export class MidiChannel { // note -> panner -> chorus -> gain -> reverb + passthrough) -> out //const dummy = new GainNode(this.ctx, {gain: 1}); - this.chorus = new Chorus(this.panner, this.gainController, 0); + this.chorus = new GainNode(this.ctx, { + gain: 0 + }); + this.chorus.connect(targetChorusNode) //this.reverb.input.connect(dummy); //this.reverb.connectOutput(this.gainController); + this.panner.connect(this.gainController); this.gainController.connect(this.outputNode); this.gainController.connect(this.reverb); + this.gainController.connect(this.chorus); this.resetControllers(); @@ -100,6 +105,18 @@ export class MidiChannel { this.lockVibrato = false; } + /** + * Kills the channel, disconnecting everything + */ + killChannel() + { + this.gainController.disconnect(); + this.stopAll(true); + this.muteChannel(); + this.playingNotes = []; + this.stoppingNotes = []; + } + /** * @param number {number} * @param value @@ -335,7 +352,7 @@ export class MidiChannel { */ setChorus(chorus) { - this.chorus.setChorusLevel(chorus); + this.chorus.gain.value = chorus / 127; } setReverb(reverbLevel) @@ -578,11 +595,17 @@ export class MidiChannel { resetControllers() { + /** + * @type {number} + */ this.channelVolume = 100 / 127; // per midi spec + /** + * @type {number} + */ this.channelExpression = 1; this.channelTuningRatio = 1; this.channelPitchBendRange = 2; - this.chorus.setChorusLevel(0); + this.chorus.gain.value = 0; this.holdPedal = false; this.updateGain(); this.panner.pan.value = 0; diff --git a/src/spessasynth_lib/synthetizer/synthetizer.js b/src/spessasynth_lib/synthetizer/synthetizer.js index 109dc134..4c51d6cb 100644 --- a/src/spessasynth_lib/synthetizer/synthetizer.js +++ b/src/spessasynth_lib/synthetizer/synthetizer.js @@ -5,6 +5,7 @@ import { arrayToHexString, consoleColors } from '../utils/other.js'; import { getEvent, messageTypes, midiControllers } from '../midi_parser/midi_message.js' import { WorkletChannel } from './worklet_system/worklet_channel.js' import { EventHandler } from '../utils/synth_event_handler.js' +import { FancyChorus } from './fancy_chorus.js' const VOICES_CAP = 450; @@ -12,6 +13,8 @@ export const DEFAULT_GAIN = 1; export const DEFAULT_PERCUSSION = 9; export const DEFAULT_CHANNEL_COUNT = 16; export const REVERB_TIME_S = 2; +export const DEFAULT_SYNTH_MODE = "gm2"; +export const DEFAULT_SYNTHESIS_MODE = "worklet"; export class Synthetizer { /** @@ -66,6 +69,7 @@ export class Synthetizer { this.reverbProcessor.connect(this.volumeController); this.volumeController.connect(this.panController); this.panController.connect(targetNode); + this.chorusProcessor = new FancyChorus(this.volumeController); /** * For Black MIDI's - forces release time to 50ms @@ -77,24 +81,59 @@ export class Synthetizer { * Controls the system * @type {"gm"|"gm2"|"gs"|"xg"} */ - this.system = "gm2"; + this.system = DEFAULT_SYNTH_MODE; + /** + * the system that synth uses + * @type {"worklet"|"legacy"} + * @private + */ + this._synthesisMode = DEFAULT_SYNTHESIS_MODE; + if(window.isSecureContext === false) + { + this._synthesisMode = "legacy"; + console.log("%cDetected insecure context. Worklet system is unavailable, switching to legacy instead.", consoleColors.warn); + } this.defaultPreset = this.soundFont.getPreset(0, 0); this.percussionPreset = this.soundFont.getPreset(128, 0); + this.createDefaultChannels(); + console.log("%cSpessaSynth is ready!", consoleColors.recognized); + } + + createDefaultChannels() + { /** * create 16 channels * @type {WorkletChannel[]|MidiChannel[]} */ - this.midiChannels = [...Array(DEFAULT_CHANNEL_COUNT).keys()].map(j => new WorkletChannel(this.volumeController, this.reverbProcessor, this.defaultPreset, j + 1, false)); + this.midiChannels = []; + + for (let i = 0; i < DEFAULT_CHANNEL_COUNT; i++) { + this._addChannelInternal(); + } + // change percussion channel to the percussion preset this.midiChannels[DEFAULT_PERCUSSION].percussionChannel = true; this.midiChannels[DEFAULT_PERCUSSION].setPreset(this.percussionPreset); this.midiChannels[DEFAULT_PERCUSSION].bank = 128; + } - console.log("%cSpessaSynth is ready!", consoleColors.recognized); + /** + * @private + */ + _addChannelInternal() + { + if(this._synthesisMode === "worklet") + { + this.midiChannels.push(new WorkletChannel(this.volumeController, this.reverbProcessor, this.chorusProcessor.input, this.defaultPreset, this.midiChannels.length + 1, false)); + } + else + { + this.midiChannels.push(new MidiChannel(this.volumeController, this.reverbProcessor, this.chorusProcessor.input, this.defaultPreset, this.midiChannels.length + 1, false)); + } } /** @@ -102,7 +141,7 @@ export class Synthetizer { */ addNewChannel() { - this.midiChannels.push(new WorkletChannel(this.volumeController, this.reverbProcessor, this.defaultPreset, this.midiChannels.length + 1, false)); + this._addChannelInternal(); this.eventHandler.callEvent("newchannel", this.midiChannels[this.midiChannels.length - 1]); } @@ -148,8 +187,6 @@ export class Synthetizer { midiNote: midiNote, channel: channel, velocity: velocity, - channelVolume: chan.channelVolume, - channelExpression: chan.channelExpression, }); } @@ -638,6 +675,30 @@ export class Synthetizer { return this.midiChannels.reduce((amt, chan) => amt + chan.voicesAmount, 0); } + get synthesisMode() + { + return this._synthesisMode; + } + + /** + * @param value {"worklet"|"legacy"} + */ + set synthesisMode(value) + { + if (value !== "worklet" && value !== "legacy") + { + throw TypeError("invalid type!"); + } + this._synthesisMode = value; + const channelsAmount = this.midiChannels.length; + for (let i = 0; i < channelsAmount; i++) { + this.midiChannels[i].stopAll(true); + this.midiChannels[i].killChannel(); + delete this.midiChannels[i]; + } + this.createDefaultChannels(); + } + reverbateEverythingBecauseWhyNot() { for (let i = 0; i < this.midiChannels.length; i++) { diff --git a/src/spessasynth_lib/synthetizer/worklet_system/channel_processor.js b/src/spessasynth_lib/synthetizer/worklet_system/channel_processor.js index 1ba782d0..c735c751 100644 --- a/src/spessasynth_lib/synthetizer/worklet_system/channel_processor.js +++ b/src/spessasynth_lib/synthetizer/worklet_system/channel_processor.js @@ -229,10 +229,11 @@ class ChannelProcessor extends AudioWorkletProcessor { } const channels = outputs[0]; const reverbChannels = outputs[1]; + const chorusChannels = outputs[2]; const tempV = this.voices; this.voices = []; tempV.forEach(v => { - this.renderVoice(v, channels, reverbChannels); + this.renderVoice(v, channels, reverbChannels, chorusChannels); if(!v.finished) { this.voices.push(v); @@ -251,8 +252,9 @@ class ChannelProcessor extends AudioWorkletProcessor { * @param voice {WorkletVoice} the voice to render * @param output {Float32Array[]} the output buffer * @param reverbOutput {Float32Array[]} output for reverb + * @param chorusOutput {Float32Array[]} output for chorus */ - renderVoice(voice, output, reverbOutput) + renderVoice(voice, output, reverbOutput, chorusOutput) { if(!this.samples[voice.sample.sampleID]) { @@ -351,7 +353,9 @@ class ChannelProcessor extends AudioWorkletProcessor { applyVolumeEnvelope(voice, bufferOut, currentTime, modLfoCentibels, this.sampleTime); // pan the voice and write out - panVoice(pan, bufferOut, output, reverbOutput, voice.modulatedGenerators[generatorTypes.reverbEffectsSend]); + panVoice(pan, bufferOut, output, + reverbOutput, voice.modulatedGenerators[generatorTypes.reverbEffectsSend], + chorusOutput, voice.modulatedGenerators[generatorTypes.chorusEffectsSend]); } /** diff --git a/src/spessasynth_lib/synthetizer/worklet_system/worklet_channel.js b/src/spessasynth_lib/synthetizer/worklet_system/worklet_channel.js index f19ca3f0..a5cd5592 100644 --- a/src/spessasynth_lib/synthetizer/worklet_system/worklet_channel.js +++ b/src/spessasynth_lib/synthetizer/worklet_system/worklet_channel.js @@ -61,7 +61,6 @@ import { consoleColors } from '../../utils/other.js' import { modulatorSources } from '../../soundfont/chunk/modulators.js' import { midiControllers } from '../../midi_parser/midi_message.js' import { addAndClampGenerator, generatorTypes } from '../../soundfont/chunk/generators.js' -import { Chorus } from '../chorus.js' const CHANNEL_GAIN = 0.5; export const NON_CC_INDEX_OFFSET = 128; @@ -119,11 +118,12 @@ export class WorkletChannel { * creates a midi channel * @param targetNode {AudioNode} * @param reverbNode {AudioNode} + * @param chorusNode {AudioNode} * @param defaultPreset {Preset} * @param channelNumber {number} * @param percussionChannel {boolean} */ - constructor(targetNode, reverbNode, defaultPreset, channelNumber = -1, percussionChannel = false) { + constructor(targetNode, reverbNode, chorusNode, defaultPreset, channelNumber = -1, percussionChannel = false) { this.ctx = targetNode.context; this.outputNode = targetNode; this.channelNumber = channelNumber @@ -146,8 +146,8 @@ export class WorkletChannel { this.channelTuningSemitones = 0; this.worklet = new AudioWorkletNode(this.ctx, "worklet-channel-processor", { - outputChannelCount: [2, 2], - numberOfOutputs: 2 + outputChannelCount: [2, 2, 2], + numberOfOutputs: 3 }); this.reportedVoicesAmount = 0; @@ -164,10 +164,10 @@ export class WorkletChannel { */ this.dumpedSamples = new Set(); - this.chorus = new Chorus(this.worklet, this.gainController, 0); - this.gainController.connect(this.outputNode); + this.worklet.connect(this.gainController, 0); this.worklet.connect(reverbNode, 1); + this.worklet.connect(chorusNode, 2); this.resetControllers(); @@ -187,6 +187,18 @@ export class WorkletChannel { this.lockVibrato = false; } + /** + * Kills the channel, disconnecting everything + */ + killChannel() + { + this.stopAll(true); + this.muteChannel(); + this.worklet.disconnect(); + this.gainController.disconnect(); + delete this.cachedWorkletVoices; + } + /** * Kills the given amount of notes * @param amount {number} @@ -266,10 +278,6 @@ export class WorkletChannel { this.setNRPFine(val); break; - case midiControllers.effects3Depth: - this.chorus.setChorusLevel(val); - break; - case midiControllers.dataEntryMsb: this.dataEntryCoarse(val); } @@ -713,7 +721,6 @@ export class WorkletChannel { resetControllers() { - this.chorus.setChorusLevel(0); this._vibrato = {depth: 0, rate: 0, delay: 0}; diff --git a/src/spessasynth_lib/synthetizer/worklet_system/worklet_utilities/stereo_panner.js b/src/spessasynth_lib/synthetizer/worklet_system/worklet_utilities/stereo_panner.js index 455479d2..3adc7776 100644 --- a/src/spessasynth_lib/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +++ b/src/spessasynth_lib/synthetizer/worklet_system/worklet_utilities/stereo_panner.js @@ -7,21 +7,18 @@ import { HALF_PI } from './unit_converter.js' * @param output {Float32Array[]} stereo output buffer * @param reverb {Float32Array[]} stereo reverb input * @param reverbLevel {number} 0 to 1000, the level of reverb to send + * @param chorus {Float32Array[]} stereo chorus buttfer + * @param chorusLevel {number} 0 to 1000, the level of chorus to send */ -export function panVoice(pan, inputBuffer, output, reverb, reverbLevel) +export function panVoice(pan, inputBuffer, output, reverb, reverbLevel, chorus, chorusLevel) { if(isNaN(inputBuffer[0])) { return; } - const leftChannel = output[0]; - const rightChannel = output[1]; - const panLeft = Math.cos(HALF_PI * pan); - const panRight = Math.sin(HALF_PI * pan); - for (let i = 0; i < inputBuffer.length; i++) { - leftChannel[i] += panLeft * inputBuffer[i]; - rightChannel[i] += panRight * inputBuffer[i]; - } + let panLeft = Math.cos(HALF_PI * pan); + let panRight = Math.sin(HALF_PI * pan); + if(reverbLevel > 0) { const reverbLeft = reverb[0]; @@ -34,4 +31,26 @@ export function panVoice(pan, inputBuffer, output, reverb, reverbLevel) reverbRight[i] += reverbRightGain * inputBuffer[i]; } } + + if(chorusLevel > 0) + { + const chorusLeft = chorus[0]; + const chorusRight = chorus[1]; + const chorusGain = chorusLevel / 1000; + const chorusLeftGain = panLeft * chorusGain; + const chorusRightGain = panRight * chorusGain; + for (let i = 0; i < inputBuffer.length; i++) { + chorusLeft[i] += chorusLeftGain * inputBuffer[i]; + chorusRight[i] += chorusRightGain * inputBuffer[i]; + } + } + + const leftChannel = output[0]; + const rightChannel = output[1]; + // panLeft *= dryGain; + // panRight *= dryGain; + for (let i = 0; i < inputBuffer.length; i++) { + leftChannel[i] += panLeft * inputBuffer[i]; + rightChannel[i] += panRight * inputBuffer[i]; + } } \ No newline at end of file diff --git a/src/website/ui/midi_keyboard.js b/src/website/ui/midi_keyboard.js index 9d21fef4..5b50d38a 100644 --- a/src/website/ui/midi_keyboard.js +++ b/src/website/ui/midi_keyboard.js @@ -86,7 +86,7 @@ export class MidiKeyboard // user note on this.heldKeys.push(midiNote); - this.pressNote(midiNote, this.channel, KEYBOARD_VELOCITY, 1, 1); + this.pressNote(midiNote, this.channel, KEYBOARD_VELOCITY); this.synth.noteOn(this.channel, midiNote, KEYBOARD_VELOCITY, true); } @@ -94,7 +94,7 @@ export class MidiKeyboard { // user note on this.heldKeys.push(midiNote); - this.pressNote(midiNote, this.channel, KEYBOARD_VELOCITY, 1, 1); + this.pressNote(midiNote, this.channel, KEYBOARD_VELOCITY); this.synth.noteOn(this.channel, midiNote, KEYBOARD_VELOCITY, true); } @@ -147,7 +147,7 @@ export class MidiKeyboard // connect the synth to keyboard this.synth.eventHandler.addEvent("noteon", "keyboard-note-on", e => { - this.pressNote(e.midiNote, e.channel, e.velocity, e.channelVolume, e.channelExpression); + this.pressNote(e.midiNote, e.channel, e.velocity); }); this.synth.eventHandler.addEvent("noteoff", "keyboard-note-off", e => { @@ -190,17 +190,15 @@ export class MidiKeyboard * presses a midi note visually * @param midiNote {number} 0-127 * @param channel {number} 0-15 * @param volume {number} 0-1 - * @param expression {number} 0-1 - * @param volume {number} 0-1 * @param velocity {number} 0-127 */ - pressNote(midiNote, channel, velocity, volume, expression) + pressNote(midiNote, channel, velocity) { let key = this.keys[midiNote]; key.classList.add("pressed"); let isSharp = key.classList.contains("sharp_key"); - let brightness = expression * volume * (velocity / 127); + let brightness = velocity / 127; let rgbaValues = this.channelColors[channel % 16].match(/\d+(\.\d+)?/g).map(parseFloat); // multiply the rgb values by brightness