From d99ccee329b4b27335e6978a9f516456e1bdfb98 Mon Sep 17 00:00:00 2001
From: spessasus <95608008+spessasus@users.noreply.github.com>
Date: Thu, 21 Sep 2023 00:41:41 +0200
Subject: [PATCH] Added mute functionality and some small imrovements
---
.../synthetizer/buffer_voice/midi_channel.js | 42 +++++++----
.../synthetizer/synthetizer.js | 57 +++++++++------
.../worklet_channel/channel_processor.js | 72 +++++++++----------
.../worklet_channel/worklet_channel.js | 16 ++++-
src/website/css/synthesizer_ui.css | 14 ++++
src/website/ui/icons.js | 16 +++++
.../ui/synthesizer_ui/synthetizer_ui.js | 21 +++++-
7 files changed, 165 insertions(+), 73 deletions(-)
diff --git a/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js b/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js
index ce059edd..9094c9e6 100644
--- a/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js
+++ b/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js
@@ -30,6 +30,7 @@ export class MidiChannel {
this.outputNode = targetNode;
this.channelNumber = channelNumber
this.percussionChannel = percussionChannel;
+ this.defaultGain = CHANNEL_LOUDNESS;
this.preset = defaultPreset;
this.bank = this.preset.bank;
@@ -101,6 +102,7 @@ export class MidiChannel {
* @type {boolean}
*/
this.lockPreset = false;
+ this.lockVibrato = false;
}
/**
@@ -233,7 +235,7 @@ export class MidiChannel {
{
val = Math.min(1, val);
this.channelExpression = val;
- this.gainController.gain.value = this.getGain();
+ this.updateGain();
}
/**
@@ -242,16 +244,17 @@ export class MidiChannel {
* @param debugInfo {boolean} for debugging set to true
*/
playNote(midiNote, velocity, debugInfo = false) {
- if(!velocity)
- {
- throw "No velocity given!";
- }
if (velocity === 0) {
// stop if velocity 0
this.stopNote(midiNote);
return;
}
+ if(this.defaultGain === 0)
+ {
+ return;
+ }
+
this.notes.add(midiNote);
this.receivedNotes.add(midiNote);
let note = new Voice(midiNote, velocity, this.panner, this.preset, this.vibrato, this.channelTuningRatio, this.modulation);
@@ -330,7 +333,7 @@ export class MidiChannel {
setVolume(volume) {
volume = Math.min(127, volume);
this.channelVolume = volume / 127;
- this.gainController.gain.value = this.getGain();
+ this.updateGain();
}
setRPCoarse(value)
@@ -387,6 +390,10 @@ export class MidiChannel {
break;
case 1:
+ if(this.lockVibrato)
+ {
+ return;
+ }
switch(this.NRPFine)
{
default:
@@ -472,12 +479,20 @@ export class MidiChannel {
}
}
+ updateGain(){
+ this.gainController.gain.value = this.defaultGain * this.channelVolume * this.channelExpression;
+ }
- /**
- * @returns {number}
- */
- getGain(){
- return CHANNEL_LOUDNESS * this.channelVolume * this.channelExpression;
+ muteChannel()
+ {
+ this.defaultGain = 0;
+ this.updateGain();
+ }
+
+ unmuteChannel()
+ {
+ this.defaultGain = CHANNEL_LOUDNESS;
+ this.updateGain();
}
/**
@@ -533,6 +548,9 @@ export class MidiChannel {
{
this.stopNote(midiNote);
}
+ this.playingNotes.forEach(n => {
+ this.stopNote(n.midiNote);
+ });
if(force)
{
this.stoppingNotes.forEach(n => {
@@ -549,7 +567,7 @@ export class MidiChannel {
this.channelTuningRatio = 1;
this.channelPitchBendRange = 2;
this.holdPedal = false;
- this.gainController.gain.value = this.getGain();
+ this.updateGain();
this.chorusController.gain.value = 0;
this.panner.pan.value = 0;
this.pitchBend = 0;
diff --git a/src/spessasynth_lib/synthetizer/synthetizer.js b/src/spessasynth_lib/synthetizer/synthetizer.js
index fe9e3c9d..bc2036f1 100644
--- a/src/spessasynth_lib/synthetizer/synthetizer.js
+++ b/src/spessasynth_lib/synthetizer/synthetizer.js
@@ -13,6 +13,7 @@ export const DEFAULT_PERCUSSION = 9;
export class Synthetizer {
/**
+ * Creates a new instance of the SpessaSynth synthesizer
* @param targetNode {AudioNode}
* @param soundFont {SoundFont2}
*/
@@ -68,10 +69,10 @@ export class Synthetizer {
}
/**
- * MIDI noteOn Event
- * @param channel {number} 0-15
- * @param midiNote {number} 0-127
- * @param velocity {number} 0-127
+ * Starts playing a note
+ * @param channel {number} 0-15 the channel to play the note
+ * @param midiNote {number} 0-127 the key number of the note
+ * @param velocity {number} 0-127 the velocity of the note (generally controls loudness)
* @param enableDebugging {boolean} set to true to log stuff to console
*/
noteOn(channel, midiNote, velocity, enableDebugging = false) {
@@ -103,10 +104,18 @@ export class Synthetizer {
}
}
+ /*
+ * Prevents any further changes to the vibrato via NRPN messages
+ */
+ lockChannelVibrato()
+ {
+ this.midiChannels.forEach(c => c.lockVibrato = true);
+ }
+
/**
- * MIDI noteOff event
- * @param channel {number} 0-15
- * @param midiNote {number} 0-127
+ * Stops playing a note
+ * @param channel {number} 0-15 the channel of the note
+ * @param midiNote {number} 0-127 the key number of the note
*/
noteOff(channel, midiNote) {
if(midiNote > 127 || midiNote < 0)
@@ -161,9 +170,9 @@ export class Synthetizer {
/**
* Changes the given controller
- * @param channel {number} 0-15
- * @param controllerNumber {number} 0-127
- * @param controllerValue {number} 0-127
+ * @param channel {number} 0-15 the channel to change the controller
+ * @param controllerNumber {number} 0-127 the MIDI CC number
+ * @param controllerValue {number} 0-127 the controller value
*/
controllerChange(channel, controllerNumber, controllerValue)
{
@@ -219,7 +228,7 @@ export class Synthetizer {
}
/**
- * Resets all controllers
+ * Resets all controllers (for every channel)
*/
resetControllers()
{
@@ -260,10 +269,10 @@ export class Synthetizer {
}
/**
- * Sets the pitch
- * @param channel {number} 0-16
- * @param MSB {number} SECOND byte
- * @param LSB {number} FIRST byte
+ * Sets the pitch of the given channel
+ * @param channel {number} 0-16 the channel to change pitch
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message
*/
pitchWheel(channel, MSB, LSB)
{
@@ -276,7 +285,7 @@ export class Synthetizer {
/**
* Transposes the synthetizer's pitch by given semitones amount (percussion channels do not get affected)
- * @param semitones {number}
+ * @param semitones {number} the semitones to transpose by. Can be a floating point number for more precision
*/
transpose(semitones)
{
@@ -285,7 +294,7 @@ export class Synthetizer {
/**
* Sets the main volume
- * @param volume {number} 0-1
+ * @param volume {number} 0-1 the volume
*/
setMainVolume(volume)
{
@@ -311,8 +320,9 @@ export class Synthetizer {
onPitchWheel;
/**
- * @param channel {number} 0-15
- * @param programNumber {number} 0-127
+ * Changes the patch for a given channel
+ * @param channel {number} 0-15 the channel to change
+ * @param programNumber {number} 0-127 the MIDI patch number
*/
programChange(channel, programNumber)
{
@@ -330,6 +340,9 @@ export class Synthetizer {
}
}
+ /**
+ * Call after replacing synth.soundFont
+ */
reloadSoundFont()
{
this.defaultPreset = this.soundFont.getPreset(0, 0);
@@ -346,8 +359,8 @@ export class Synthetizer {
}
/**
- * Sends a sysex
- * @param messageData {ShiftableByteArray} the message's data (after F0)
+ * Sends a MIDI Sysex message
+ * @param messageData {ShiftableByteArray} the message's data (excluding the F0 byte, but including the F7 at the end)
*/
systemExclusive(messageData)
{
@@ -493,7 +506,7 @@ export class Synthetizer {
}
/**
- * @returns {number}
+ * @returns {number} the audioContext's current time
*/
get currentTime()
{
diff --git a/src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js b/src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js
index e26e4841..4526f2f8 100644
--- a/src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js
+++ b/src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js
@@ -211,42 +211,42 @@ class ChannelProcessor extends AudioWorkletProcessor {
// LOWPASS
- const filterQ = getModulated(voice, generatorTypes.initialFilterQ, this.midiControllers) - 3.01; // polyphone????
- const filterQgain = Math.pow(10, filterQ / 20);
- const filterFcHz = absCentsToHz(getModulated(voice, generatorTypes.initialFilterFc, this.midiControllers));
- // calculate coefficients
- const theta = 2 * Math.PI * filterFcHz / sampleRate;
- let a0, a1, a2, b1, b2;
- if (filterQgain <= 0)
- {
- a0 = 1;
- a1 = 0;
- a2 = 0;
- b1 = 0;
- b2 = 0;
- }
- else
- {
- const dTmp = Math.sin(theta) / (2 * filterQgain);
- if (dTmp <= -1.0)
- {
- a0 = 1;
- a1 = 0;
- a2 = 0;
- b1 = 0;
- b2 = 0;
- }
- else
- {
- const beta = 0.5 * (1 - dTmp) / (1 + dTmp);
- const gamma = (0.5 + beta) * Math.cos(theta);
- a0 = (0.5 + beta - gamma) / 2;
- a1 = 2 * a0;
- a2 = a0;
- b1 = -2 * gamma;
- b2 = 2 * beta;
- }
- }
+ // const filterQ = getModulated(voice, generatorTypes.initialFilterQ, this.midiControllers) - 3.01; // polyphone????
+ // const filterQgain = Math.pow(10, filterQ / 20);
+ // const filterFcHz = absCentsToHz(getModulated(voice, generatorTypes.initialFilterFc, this.midiControllers));
+ // // calculate coefficients
+ // const theta = 2 * Math.PI * filterFcHz / sampleRate;
+ // let a0, a1, a2, b1, b2;
+ // if (filterQgain <= 0)
+ // {
+ // a0 = 1;
+ // a1 = 0;
+ // a2 = 0;
+ // b1 = 0;
+ // b2 = 0;
+ // }
+ // else
+ // {
+ // const dTmp = Math.sin(theta) / (2 * filterQgain);
+ // if (dTmp <= -1.0)
+ // {
+ // a0 = 1;
+ // a1 = 0;
+ // a2 = 0;
+ // b1 = 0;
+ // b2 = 0;
+ // }
+ // else
+ // {
+ // const beta = 0.5 * (1 - dTmp) / (1 + dTmp);
+ // const gamma = (0.5 + beta) * Math.cos(theta);
+ // a0 = (0.5 + beta - gamma) / 2;
+ // a1 = 2 * a0;
+ // a2 = a0;
+ // b1 = -2 * gamma;
+ // b2 = 2 * beta;
+ // }
+ // }
// SYNTHESIS
let actualTime = currentTime;
diff --git a/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js b/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js
index 43cc7c32..222b4014 100644
--- a/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js
+++ b/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js
@@ -30,7 +30,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'
-
const CHANNEL_GAIN = 0.5;
export const NON_CC_INDEX_OFFSET = 128;
@@ -160,6 +159,7 @@ export class WorkletChannel {
* @type {boolean}
*/
this.lockPreset = false;
+ this.lockVibrato = false;
}
/**
@@ -170,6 +170,16 @@ export class WorkletChannel {
this.worklet.port.postMessage(data);
}
+ muteChannel()
+ {
+ this.gainController.gain.value = 0;
+ }
+
+ unmuteChannel()
+ {
+ this.gainController.gain.value = CHANNEL_GAIN;
+ }
+
/**
* @param cc {number}
* @param val {number}
@@ -493,6 +503,10 @@ export class WorkletChannel {
// vibrato rate
case 8:
+ if(this.lockVibrato)
+ {
+ return;
+ }
if(dataValue === 64)
{
return;
diff --git a/src/website/css/synthesizer_ui.css b/src/website/css/synthesizer_ui.css
index acc89798..6568f4fa 100644
--- a/src/website/css/synthesizer_ui.css
+++ b/src/website/css/synthesizer_ui.css
@@ -115,6 +115,20 @@
display: block;
}
+.mute_button
+{
+ flex: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: #777 1px solid;
+ min-width: var(--voice-meter-height);
+}
+
+.mute_button:hover{
+ cursor: pointer;
+}
+
.voice_reset{
flex: 0;
min-width: 0;
diff --git a/src/website/ui/icons.js b/src/website/ui/icons.js
index c868b725..b3887c2a 100644
--- a/src/website/ui/icons.js
+++ b/src/website/ui/icons.js
@@ -65,4 +65,20 @@ export function getBackwardSvg(size)
return ``;
+}
+
+export function getVolumeSvg(size)
+{
+ return ``;
+}
+
+export function getMuteSvg(size)
+{
+ return ``;
}
\ No newline at end of file
diff --git a/src/website/ui/synthesizer_ui/synthetizer_ui.js b/src/website/ui/synthesizer_ui/synthetizer_ui.js
index 3125b4f5..46f85928 100644
--- a/src/website/ui/synthesizer_ui/synthetizer_ui.js
+++ b/src/website/ui/synthesizer_ui/synthetizer_ui.js
@@ -1,6 +1,6 @@
import { DEFAULT_GAIN, Synthetizer } from '../../../spessasynth_lib/synthetizer/synthetizer.js'
import {MidiChannel} from "../../../spessasynth_lib/synthetizer/buffer_voice/midi_channel.js";
-import { getLoopSvg } from '../icons.js';
+import { getLoopSvg, getMuteSvg, getVolumeSvg } from '../icons.js'
import { ShiftableByteArray } from '../../../spessasynth_lib/utils/shiftable_array.js';
import { Meter } from './synthui_meter.js'
import { midiPatchNames } from '../../../spessasynth_lib/utils/other.js'
@@ -324,9 +324,26 @@ export class SynthetizerUI
this.synth.midiChannels[channelNumber].lockPreset = false;
presetSelector.classList.remove("locked_selector");
}
-
controller.appendChild(presetReset);
+ // mute button
+ const muteButton = document.createElement("div");
+ muteButton.innerHTML = getVolumeSvg(32);
+ muteButton.classList.add("controller_element");
+ muteButton.classList.add("mute_button");
+ muteButton.onclick = () => {
+ if(this.synth.midiChannels[channelNumber].gainController.gain.value === 0)
+ {
+ this.synth.midiChannels[channelNumber].unmuteChannel();
+ muteButton.innerHTML = getVolumeSvg(32);
+ }
+ else
+ {
+ this.synth.midiChannels[channelNumber].muteChannel();
+ muteButton.innerHTML = getMuteSvg(32);
+ }
+ }
+ controller.appendChild(muteButton);
return {
controller: controller,