diff --git a/index.html b/index.html
index cc42a433..681f4135 100644
--- a/index.html
+++ b/index.html
@@ -189,7 +189,7 @@
SpessaSynth: Online Demo
}
else
{
- title = midiFiles[i].name;
+ title = midiFiles[i].name.replace(".mid", "");
}
titles.push(title);
}
diff --git a/src/spessasynth_lib/synthetizer/buffer_voice/voice.js b/src/spessasynth_lib/synthetizer/buffer_voice/voice.js
index c7afdb48..2dc62ada 100644
--- a/src/spessasynth_lib/synthetizer/buffer_voice/voice.js
+++ b/src/spessasynth_lib/synthetizer/buffer_voice/voice.js
@@ -1,5 +1,9 @@
+/**
+ * @typedef {{}}
+ */
+
import {Preset} from "../../soundfont/chunk/presets.js";
-import { SynthesisModel } from './synthesis_model.js'
+import { SynthesisModel } from './synthesis_model.js';
export class Voice
{
@@ -64,6 +68,11 @@ export class Voice
});
}
+ /**
+ * @type {[]}
+ */
+ static cachedVoices = [];
+
/**
* @param debug {boolean}
diff --git a/src/spessasynth_lib/synthetizer/synthetizer.js b/src/spessasynth_lib/synthetizer/synthetizer.js
index 636bbd37..4ad466e7 100644
--- a/src/spessasynth_lib/synthetizer/synthetizer.js
+++ b/src/spessasynth_lib/synthetizer/synthetizer.js
@@ -4,9 +4,10 @@ import {ShiftableByteArray} from "../utils/shiftable_array.js";
import { arrayToHexString, consoleColors } from '../utils/other.js';
import { midiControllers } from '../midi_parser/midi_message.js'
import { WorkletChannel } from './worklet_channel/worklet_channel.js'
+import { EventHandler } from '../utils/event_handler.js'
// i mean come on
-const VOICES_CAP = 2137;
+const VOICES_CAP = 800;
export const DEFAULT_GAIN = 0.5;
export const DEFAULT_PERCUSSION = 9;
@@ -23,6 +24,8 @@ export class Synthetizer {
this.soundFont = soundFont;
this.context = targetNode.context;
+ this.eventHandler = new EventHandler();
+
this.volumeController = new GainNode(targetNode.context, {
gain: DEFAULT_GAIN
});
@@ -92,9 +95,13 @@ export class Synthetizer {
let chan = this.midiChannels[channel];
chan.playNote(midiNote, velocity, enableDebugging);
- if(this.onNoteOn.length) {
- this.callEvent(this.onNoteOn, [midiNote, channel, velocity, chan.channelVolume, chan.channelExpression]);
- }
+ this.eventHandler.callEvent("noteon", {
+ midiNote: midiNote,
+ channel: channel,
+ velocity: velocity,
+ channelVolume: chan.channelVolume,
+ channelExpression: chan.channelExpression,
+ });
}
/*
@@ -116,9 +123,10 @@ export class Synthetizer {
console.warn(`Received a noteOn for note`, midiNote, "Ignoring.");
return;
}
- if(this.onNoteOff) {
- this.callEvent(this.onNoteOff, [midiNote, channel]);
- }
+ this.eventHandler.callEvent("noteoff", {
+ midiNote: midiNote,
+ channel: channel
+ });
if(this.highPerformanceMode)
{
this.midiChannels[channel].stopNote(midiNote, true);
@@ -127,34 +135,6 @@ export class Synthetizer {
this.midiChannels[channel].stopNote(midiNote);
}
- /**
- * @param event {function[]}
- * @param args {number[]}
- */
- callEvent(event, args)
- {
- event.forEach(f => f(...args))
- }
-
- /**
- * Plays when the midi note goes on
- * @type {function[]}
- * @param midiNote {number} 0-127
- * @param channel {number} 0-15
- * @param velocity {number} 0-127
- * @param volume {number} 0-1
- * @param expression {number} 0-1
- */
- onNoteOn = [];
-
- /**
- * Plays when the midi note goes off
- * @type {function[]}
- * @param midiNote {number} 0-127
- * @param channel {number} 0-15
- */
- onNoteOff = [];
-
/**
* Stops all notes
* @param force {boolean} if we should instantly kill the note, defaults to false
@@ -162,12 +142,12 @@ export class Synthetizer {
stopAll(force=false) {
console.log("%cStop all received!", consoleColors.info);
for (let channel of this.midiChannels) {
- if(this.onNoteOff)
+ for(const note of channel.notes)
{
- for(const note of channel.notes)
- {
- this.callEvent(this.onNoteOff, [note, channel.channelNumber - 1]);
- }
+ this.eventHandler.callEvent("noteoff", {
+ midiNote: note,
+ channel: channel.channelNumber - 1
+ });
}
channel.stopAll(force);
}
@@ -216,8 +196,16 @@ export class Synthetizer {
if(this.midiChannels[channel].bank === 127)
{
this.midiChannels[channel].percussionChannel = true;
+ this.eventHandler.callEvent("drumchange",{
+ channel: channel,
+ isDrumChannel: true
+ });
}
this.midiChannels[channel].bank = controllerValue;
+ this.eventHandler.callEvent("drumchange",{
+ channel: channel,
+ isDrumChannel: false
+ });
}
break;
@@ -226,10 +214,11 @@ export class Synthetizer {
this.midiChannels[channel].controllerChange(controllerNumber, controllerValue);
break;
}
- if(this.onControllerChange)
- {
- this.callEvent(this.onControllerChange, [channel, controllerNumber, controllerValue]);
- }
+ this.eventHandler.callEvent("controllerchange", {
+ channel: channel,
+ controllerNumber: controllerNumber,
+ controllerValue: controllerValue
+ });
}
/**
@@ -242,33 +231,37 @@ export class Synthetizer {
{
// reset
ch.resetControllers();
- ch.percussionChannel = false;
ch.bank = 0;
- ch.setPreset(this.defaultPreset);
+ if(ch.channelNumber - 1 === DEFAULT_PERCUSSION) {
+ ch.setPreset(this.percussionPreset);
+ ch.percussionChannel = true;
+ this.eventHandler.callEvent("drumchange",{
+ channel: ch.channelNumber - 1,
+ isDrumChannel: true
+ });
+ }
+ else
+ {
+ ch.percussionChannel = false;
+ ch.setPreset(this.defaultPreset);
+ this.eventHandler.callEvent("drumchange",{
+ channel: ch.channelNumber - 1,
+ isDrumChannel: false
+ });
+ }
// call all the event listeners
const chNr = ch.channelNumber - 1;
- if(this.onProgramChange.length)
- {
- this.callEvent(this.onProgramChange, [chNr, chNr === DEFAULT_PERCUSSION ? this.percussionPreset : this.defaultPreset]);
- }
- if(this.onControllerChange.length)
- {
- this.callEvent(this.onControllerChange, [chNr, midiControllers.mainVolume, 100]);
- this.callEvent(this.onControllerChange, [chNr, midiControllers.pan, 64]);
- this.callEvent(this.onControllerChange, [chNr, midiControllers.expressionController, 127]);
- this.callEvent(this.onControllerChange, [chNr, midiControllers.modulationWheel, 0]);
- this.callEvent(this.onControllerChange, [chNr, midiControllers.effects3Depth, 0]);
+ this.eventHandler.callEvent("programchange", {channel: chNr, preset: ch.preset})
- }
- if(this.onPitchWheel.length)
- {
- this.callEvent(this.onPitchWheel, [chNr, 64, 0]);
- }
- }
+ this.eventHandler.callEvent("controllerchange", {channel: chNr, controllerNumber: midiControllers.mainVolume, controllerValue: 100});
+ this.eventHandler.callEvent("controllerchange", {channel: chNr, controllerNumber: midiControllers.pan, controllerValue: 64});
+ this.eventHandler.callEvent("controllerchange", {channel: chNr, controllerNumber: midiControllers.expressionController, controllerValue: 127});
+ this.eventHandler.callEvent("controllerchange", {channel: chNr, controllerNumber: midiControllers.modulationWheel, controllerValue: 0});
+ this.eventHandler.callEvent("controllerchange", {channel: chNr, controllerNumber: midiControllers.effects3Depth, controllerValue: 0});
- this.midiChannels[DEFAULT_PERCUSSION].percussionChannel = true;
- this.midiChannels[DEFAULT_PERCUSSION].setPreset(this.percussionPreset);
+ this.eventHandler.callEvent("pitchwheel", {channel: chNr, MSB: 64, LSB: 0})
+ }
this.system = "gm2";
this.volumeController.gain.value = DEFAULT_GAIN;
this.panController.pan.value = 0;
@@ -283,10 +276,11 @@ export class Synthetizer {
pitchWheel(channel, MSB, LSB)
{
this.midiChannels[channel].setPitchBend(MSB, LSB);
- if(this.onPitchWheel.length)
- {
- this.callEvent(this.onPitchWheel, [channel, MSB, LSB]);
- }
+ this.eventHandler.callEvent("pitchwheel", {
+ channel: channel,
+ MSB: MSB,
+ LSB: LSB
+ });
}
/**
@@ -307,27 +301,6 @@ export class Synthetizer {
this.volumeController.gain.value = volume * DEFAULT_GAIN;
}
- /**
- * Calls on program change(channel number, preset)
- * @type {function(number, Preset)[]}
- */
- onProgramChange = [];
-
- /**
- * Calls on controller change(channel number, cc, controller value)
- * @param channel {number} 0-16
- * @param controllerNumber {number} 0-127
- * @param controllerValue {number} 0-127
- * @type {function[]}
- */
- onControllerChange = [];
-
- /**
- * Calls on pitch wheel change (channel, msb, lsb)
- * @type {function(number, number, number)[]}
- */
- onPitchWheel = [];
-
/**
* Changes the patch for a given channel
* @param channel {number} 0-15 the channel to change
@@ -342,11 +315,10 @@ export class Synthetizer {
// find the preset
let preset = this.soundFont.getPreset(bank, programNumber);
channelObj.setPreset(preset);
- // console.log("changing channel", channel, "to bank:", channelObj.bank,
- // "preset:", programNumber, preset.presetName);
- if(this.onProgramChange) {
- this.callEvent(this.onProgramChange, [channel, preset]);
- }
+ this.eventHandler.callEvent("programchange", {
+ channel: channel,
+ preset: preset
+ });
}
/**
@@ -452,6 +424,11 @@ export class Synthetizer {
consoleColors.recognized,
consoleColors.info,
consoleColors.value);
+
+ this.eventHandler.callEvent("drumchange",{
+ channel: channel,
+ isDrumChannel: this.midiChannels[channel].percussionChannel
+ });
}
else
if(messageData[4] === 0x40 && messageData[6] === 0x06 && messageData[5] === 0x00)
diff --git a/src/spessasynth_lib/utils/event_handler.js b/src/spessasynth_lib/utils/event_handler.js
new file mode 100644
index 00000000..451d24c2
--- /dev/null
+++ b/src/spessasynth_lib/utils/event_handler.js
@@ -0,0 +1,62 @@
+/**
+ * @typedef {
+ * "noteon"|
+ * "noteoff"|
+ * "pitchwheel"|
+ * "controllerchange"|
+ * "programchange"|
+ * "drumchange"} EventTypes
+ */
+export class EventHandler
+{
+ /**
+ * A new synthesizer event handler
+ */
+ constructor() {
+ /**
+ * The main list of events
+ * @type {Object}
+ */
+ this.events = {};
+ }
+
+ /**
+ * Adds a new event listener
+ * @param name {EventTypes}
+ * @param callback {function(Object)}
+ */
+ addEvent(name, callback)
+ {
+ if(this.events[name])
+ {
+ this.events[name].push(callback);
+ }
+ else
+ {
+ this.events[name] = [callback];
+ }
+ }
+
+ /**
+ * Removes an event listener
+ * @param name {EventTypes}
+ * @param callback {function(Object)}
+ */
+ removeEvent(name, callback)
+ {
+ this.events[name].splice(this.events[name].findIndex(c => c === callback), 1);
+ }
+
+ /**
+ * Calls the given event
+ * @param name {EventTypes}
+ * @param eventData {Object}
+ */
+ callEvent(name, eventData)
+ {
+ if(this.events[name])
+ {
+ this.events[name].forEach(ev => ev(eventData));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/website/css/keyboard.css b/src/website/css/keyboard.css
index f4eb304b..bc3e876c 100644
--- a/src/website/css/keyboard.css
+++ b/src/website/css/keyboard.css
@@ -23,6 +23,10 @@
--flat-half-translate: 18%;
}
+#keyboard .flat_dark_key{
+ background: linear-gradient(262deg, #111, #000);
+}
+
#keyboard .sharp_key{
--sharp-transform: scale(1, 0.7);
transform: var(--sharp-transform);
diff --git a/src/website/css/keyboard_ui.css b/src/website/css/keyboard_ui.css
index aa2267cb..f99e1506 100644
--- a/src/website/css/keyboard_ui.css
+++ b/src/website/css/keyboard_ui.css
@@ -17,4 +17,25 @@
background: var(--top-color);
text-align: center;
margin: auto;
-}
\ No newline at end of file
+}
+
+.kebui_button
+{
+ background: #000;
+ border: 1px #333 solid;
+ min-height: 2em;
+ border-radius: 5px;
+ font-size: 16px;
+ display: block;
+ white-space: nowrap;
+ color: var(--font-color);
+ margin: auto;
+}
+
+.kebui_button:hover
+{
+ cursor: pointer;
+ background: #111;
+ color: #fff;
+ text-shadow: 0 0 5px white;
+}
diff --git a/src/website/css/sequencer_ui.css b/src/website/css/sequencer_ui.css
index 5c7880d7..f0570c1f 100644
--- a/src/website/css/sequencer_ui.css
+++ b/src/website/css/sequencer_ui.css
@@ -23,7 +23,7 @@
z-index: 5;
font-size: var(--progress-bar-height);
text-align: center;
- color: #ccc;
+ color: var(--font-color);
margin: auto;
width: 100%;
}
diff --git a/src/website/css/style.css b/src/website/css/style.css
index 5cfd2be7..8dd2629b 100644
--- a/src/website/css/style.css
+++ b/src/website/css/style.css
@@ -4,8 +4,9 @@
}
* {
+ --font-color: #ccc;
font-family: "Sans", serif;
- color: #ccc;
+ color: var(--font-color);
text-align: center;
margin: 0;
}
@@ -66,6 +67,7 @@ input[type="file"] {
line-height: 100%;
font-weight: lighter;
font-size: 2.1em;
+ text-shadow: 0 0 5px var(--font-color);
}
#progress_bar
diff --git a/src/website/css/synthesizer_ui.css b/src/website/css/synthesizer_ui.css
index 6568f4fa..b1d6a0eb 100644
--- a/src/website/css/synthesizer_ui.css
+++ b/src/website/css/synthesizer_ui.css
@@ -127,6 +127,7 @@
.mute_button:hover{
cursor: pointer;
+
}
.voice_reset{
@@ -165,12 +166,12 @@
display: block;
min-height: var(--voice-meter-height);
white-space: nowrap;
- color: #ccc;
- padding: 9px;
+ color: var(--font-color);
}
.synthui_button:hover
{
cursor: pointer;
background: #111;
color: #fff;
+ text-shadow: 0 0 5px white;
}
diff --git a/src/website/main.js b/src/website/main.js
index 4939369f..0b289306 100644
--- a/src/website/main.js
+++ b/src/website/main.js
@@ -110,7 +110,7 @@ async function startMidi(midiFiles)
}
else
{
- title = midiFiles[i].name;
+ title = midiFiles[i].name.replace(".mid", "");
}
titles.push(title);
}
@@ -192,7 +192,9 @@ document.body.onclick = () =>
{
// user has clicked, we can create the ui
if(!window.audioContextMain) {
- window.audioContextMain = new AudioContext({sampleRate: 44100});
+ navigator.mediaSession.playbackState = "playing";
+ window.audioContextMain = new AudioContext({sampleRate: 44100,
+ latencyHint: "playback"});
if(window.soundFontParser) {
titleMessage.innerText = TITLE;
// prepare midi interface
diff --git a/src/website/manager.js b/src/website/manager.js
index d043703c..20bf7239 100644
--- a/src/website/manager.js
+++ b/src/website/manager.js
@@ -38,15 +38,15 @@ export class Manager {
constructor(context, soundFont) {
this.context = context;
this.initializeContext(context, soundFont).then();
-
}
async initializeContext(context, soundFont) {
- try {
- await context.audioWorklet.addModule("/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js");
- }
- catch (e) {
- await context.audioWorklet.addModule("src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js");
+ if(context.audioWorklet) {
+ try {
+ await context.audioWorklet.addModule("/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js");
+ } catch (e) {
+ await context.audioWorklet.addModule("src/spessasynth_lib/synthetizer/worklet_channel/channel_processor.js");
+ }
}
// set up soundfont
this.soundFont = soundFont;
diff --git a/src/website/sequence_recorder/sequence_recorder.js b/src/website/sequence_recorder/sequence_recorder.js
index 0da3d71c..8868969c 100644
--- a/src/website/sequence_recorder/sequence_recorder.js
+++ b/src/website/sequence_recorder/sequence_recorder.js
@@ -25,30 +25,49 @@ export class SequenceRecorder
{
this.targetChannel = desiredChannel;
// connect to synth
- this.synth.onNoteOn.push(this.noteOn.bind(this));
- this.nOnI = this.synth.onNoteOn.length - 1; // note on index
- this.synth.onNoteOff.push(this.noteOff.bind(this));
- this.nOffI = this.synth.onNoteOn.length - 1; // note off index
- this.synth.onControllerChange.push(this.controllerChange.bind(this));
- this.cCI = this.synth.onNoteOn.length - 1; // controller change index
- this.synth.onProgramChange.push(this.programChange.bind(this));
- this.pCI = this.synth.onNoteOn.length - 1; // program change index
- this.synth.onPitchWheel.push(this.pitchWheel.bind(this));
- this.pWI = this.synth.onNoteOn.length - 1; // pitch wheel index
+ this.synth.eventHandler.addEvent("noteon", e => {
+ this.noteOn(e.midiNote, e.channel, e.velocity);
+ });
+ this.synth.eventHandler.addEvent("noteoff", e => {
+ this.noteOff(e.midiNote, e.channel);
+ });
+ this.synth.eventHandler.addEvent("controllerchange", e => {
+ this.controllerChange(e.channel, e.controllerNumber, e.controllerValue);
+ });
+ this.synth.eventHandler.addEvent("programchange", e => {
+ this.programChange(e.channel, e.preset);
+ });
+ this.synth.eventHandler.addEvent("controllerchange", e => {
+ this.controllerChange(e.channel, e.controllerNumber, e.controllerValue);
+ });
+ //this.synth.onNoteOn.push(this.noteOn.bind(this));
+ //this.nOnI = this.synth.onNoteOn.length - 1; // note on index
+ //this.synth.onNoteOff.push(this.noteOff.bind(this));
+ // this.nOffI = this.synth.onNoteOn.length - 1; // note off index
+ // this.synth.onControllerChange.push(this.controllerChange.bind(this));
+ // this.cCI = this.synth.onNoteOn.length - 1; // controller change index
+ // this.synth.onProgramChange.push(this.programChange.bind(this));
+ // this.pCI = this.synth.onNoteOn.length - 1; // program change index
+ // this.synth.onPitchWheel.push(this.pitchWheel.bind(this));
+ // this.pWI = this.synth.onNoteOn.length - 1; // pitch wheel index
}
stopRecording()
{
- this.synth.onNoteOff.splice(this.nOffI, 1);
- this.synth.onNoteOn.splice(this.nOnI, 1);
- this.synth.onControllerChange.splice(this.cCI, 1);
- this.synth.onProgramChange.splice(this.pCI, 1);
- this.synth.onPitchWheel.splice(this.pWI, 1);
- // this.synth.onNoteOff = this.synth.onNoteOff.filter(e => e !== this.noteOff.bind(this));
- // this.synth.onNoteOn = this.synth.onNoteOn.filter(e => e !== this.noteOn.bind(this));
- // this.synth.onControllerChange = this.synth.onControllerChange.filter(e => e !== this.controllerChange.bind(this));
- // this.synth.onProgramChange = this.synth.onProgramChange.filter(e => e !== this.programChange.bind(this));
- // this.synth.onPitchWheel = this.synth.onPitchWheel.filter(e => e !== this.pitchWheel.bind(this));
+ this.synth.eventHandler.removeEvent("noteon", e => {
+ this.noteOn(e.midiNote, e.channel, e.velocity);
+ });
+ this.synth.eventHandler.removeEvent("noteoff", e => {
+ this.noteOff(e.midiNote, e.channel);
+ });
+ this.synth.eventHandler.removeEvent("controllerchange", e => {
+ this.controllerChange(e.channel, e.controllerNumber, e.controllerValue);
+ });
+ // this.synth.onNoteOff.splice(this.nOffI, 1);
+ // this.synth.onNoteOn.splice(this.nOnI, 1);
+ // this.synth.onControllerChange.splice(this.cCI, 1);
+ // this.synth.onProgramChange.splice(this.pCI, 1);
+ // this.synth.onPitchWheel.splice(this.pWI, 1);
}
getTime()
diff --git a/src/website/ui/midi_keyboard.js b/src/website/ui/midi_keyboard.js
index a8373ccd..5d52de24 100644
--- a/src/website/ui/midi_keyboard.js
+++ b/src/website/ui/midi_keyboard.js
@@ -3,6 +3,7 @@ import { MIDIDeviceHandler } from '../../spessasynth_lib/midi_handler/midi_handl
import { midiControllers } from '../../spessasynth_lib/midi_parser/midi_message.js'
const KEYBOARD_VELOCITY = 126;
+const GLOW_PX = 50;
export class MidiKeyboard
{
@@ -15,6 +16,10 @@ export class MidiKeyboard
constructor(channelColors, synth, handler) {
this.mouseHeld = false;
this.heldKeys = [];
+ /**
+ * @type {"light"|"dark"}
+ */
+ this.mode = "light";
document.onmousedown = () => this.mouseHeld = true;
document.onmouseup = () => {
@@ -22,7 +27,7 @@ export class MidiKeyboard
for(let key of this.heldKeys)
{
// user note off
- this.releaseNote(key);
+ this.releaseNote(key, this.channel);
this.synth.noteOff(this.channel, key);
}
}
@@ -98,7 +103,7 @@ export class MidiKeyboard
keyElement.onmouseout = () => {
// user note off
this.heldKeys.splice(this.heldKeys.indexOf(midiNote), 1);
- this.releaseNote(midiNote);
+ this.releaseNote(midiNote, this.channel);
this.synth.noteOff(this.channel, midiNote);
};
keyElement.onmouseleave = keyElement.onmouseup;
@@ -142,8 +147,16 @@ export class MidiKeyboard
this.keys.push(keyElement);
}
- // channel selector
this.selectorMenu = document.getElementById("keyboard_selector");
+ // dark mode toggle
+ const modeToggler = document.createElement("button");
+ modeToggler.innerText = "Toggle Dark Keyboard";
+ modeToggler.classList.add("kebui_button");
+ modeToggler.onclick = this.toggleMode.bind(this);
+
+ this.selectorMenu.appendChild(modeToggler);
+
+ // channel selector
const channelSelector = document.createElement("select");
let channelNumber = 0;
@@ -193,8 +206,32 @@ export class MidiKeyboard
});
// connect the synth to keyboard
- this.synth.onNoteOn.push((note, chan, vel, vol, exp) => this.pressNote(note, chan, vel, vol, exp));
- this.synth.onNoteOff.push(note => this.releaseNote(note));
+ this.synth.eventHandler.addEvent("noteon", e => {
+ this.pressNote(e.midiNote, e.channel, e.velocity, e.channelVolume, e.channelExpression);
+ });
+ this.synth.eventHandler.addEvent("noteoff", e => {
+ this.releaseNote(e.midiNote, e.channel);
+ })
+ //this.synth.onNoteOn.push((note, chan, vel, vol, exp) => this.pressNote(note, chan, vel, vol, exp));
+ //this.synth.onNoteOff.push((note, chan) => this.releaseNote(note, chan));
+ }
+
+ toggleMode()
+ {
+ if(this.mode === "light")
+ {
+ this.mode = "dark";
+ }
+ else
+ {
+ this.mode = "light";
+ }
+ this.keys.forEach(k => {
+ if(k.classList.contains("flat_key"))
+ {
+ k.classList.toggle("flat_dark_key");
+ }
+ })
}
createMIDIOutputSelector(seq)
@@ -252,12 +289,13 @@ export class MidiKeyboard
let rgbaValues = this.channelColors[channel].match(/\d+(\.\d+)?/g).map(parseFloat);
// multiply the rgb values by brightness
- if (!isSharp) {
+ let color;
+ if (!isSharp && this.mode === "light") {
// multiply the rgb values
let newRGBValues = rgbaValues.slice(0, 3).map(value => 255 - (255 - value) * brightness);
// create the new color
- key.style.background = `rgba(${newRGBValues.join(", ")}, ${rgbaValues[3]})`;
+ color = `rgba(${newRGBValues.join(", ")}, ${rgbaValues[3]})`;
}
else
{
@@ -265,7 +303,12 @@ export class MidiKeyboard
let newRGBValues = rgbaValues.slice(0, 3).map(value => value * brightness);
// create the new color
- key.style.background = `rgba(${newRGBValues.join(", ")}, ${rgbaValues[3]})`;
+ color = `rgba(${newRGBValues.join(", ")}, ${rgbaValues[3]})`;
+ }
+ key.style.background = color;
+ if(this.mode === "dark")
+ {
+ key.style.boxShadow = `0px 0px ${GLOW_PX * brightness}px ${color}`;
}
/**
* @type {string[]}
@@ -273,7 +316,11 @@ export class MidiKeyboard
this.keyColors[midiNote].push(this.channelColors[channel]);
}
- releaseNote(midiNote)
+ /**
+ * @param midiNote {number} 0-127
+ * @param channel {number} 0-15
+ */
+ releaseNote(midiNote, channel)
{
if(midiNote > 127 || midiNote < 0)
{
@@ -290,13 +337,18 @@ export class MidiKeyboard
return;
}
if(pressedColors.length > 1) {
- pressedColors.pop();
+ pressedColors.splice(pressedColors.findLastIndex(v => v === this.channelColors[channel]), 1);
key.style.background = pressedColors[pressedColors.length - 1];
+ if(this.mode === "dark")
+ {
+ key.style.boxShadow = `0px 0px ${GLOW_PX}px ${pressedColors[pressedColors.length - 1]}`;
+ }
}
if(pressedColors.length === 1)
{
key.classList.remove("pressed");
key.style.background = "";
+ key.style.boxShadow = "";
}
}
}
\ No newline at end of file
diff --git a/src/website/ui/sequencer_ui/sequencer_ui.js b/src/website/ui/sequencer_ui/sequencer_ui.js
index 9cadc741..38823b02 100644
--- a/src/website/ui/sequencer_ui/sequencer_ui.js
+++ b/src/website/ui/sequencer_ui/sequencer_ui.js
@@ -1,4 +1,4 @@
-import {Sequencer} from "../../../spessasynth_lib/sequencer/sequencer.js";
+import { Sequencer } from '../../../spessasynth_lib/sequencer/sequencer.js'
import { formatTime, supportedEncodings } from '../../../spessasynth_lib/utils/other.js'
import { getBackwardSvg, getForwardSvg, getLoopSvg, getPauseSvg, getPlaySvg, getTextSvg } from '../icons.js'
import { messageTypes } from '../../../spessasynth_lib/midi_parser/midi_message.js'
@@ -23,9 +23,74 @@ export class SequencerUI{
this.text = "";
this.rawText = [];
this.titles = [""];
- this.titleIndex = 0;
}
+ createNavigatorHandler()
+ {
+
+ navigator.mediaSession.metadata = new MediaMetadata({
+ title: this.titles[this.seq.songIndex],
+ artist: "SpessaSynth"
+ });
+
+ navigator.mediaSession.setActionHandler("play", () => {
+ this.seqPlay();
+ });
+ navigator.mediaSession.setActionHandler("pause", () => {
+ this.seqPause();
+ });
+ navigator.mediaSession.setActionHandler("stop", () => {
+ this.seq.currentTime = 0;
+ this.seqPause();
+ });
+ navigator.mediaSession.setActionHandler("seekbackward", e => {
+ this.seq.currentTime -= e.seekOffset || 10;
+ });
+ navigator.mediaSession.setActionHandler("seekforward", e => {
+ this.seq.currentTime += e.seekOffset || 10;
+ });
+ navigator.mediaSession.setActionHandler("seekto", e => {
+ this.seq.currentTime = e.seekTime
+ });
+ navigator.mediaSession.setActionHandler("previoustrack", () => {
+ this.switchToPreviousSong();
+ });
+ navigator.mediaSession.setActionHandler("nexttrack", () => {
+ this.switchToNextSong();
+ });
+
+ navigator.mediaSession.playbackState = "playing";
+ }
+
+ seqPlay()
+ {
+ this.seq.play();
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
+ navigator.mediaSession.playbackState = "playing";
+ }
+
+ seqPause()
+ {
+ this.seq.pause();
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
+ navigator.mediaSession.playbackState = "paused";
+ }
+
+ switchToNextSong()
+ {
+ this.seq.nextSong();
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
+ }
+
+ switchToPreviousSong()
+ {
+ this.seq.previousSong();
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
+ }
/**
* @param songTitles {string[]}
@@ -33,7 +98,8 @@ export class SequencerUI{
setSongTitles(songTitles)
{
this.titles = songTitles;
- this.titleIndex = 0;
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
}
/**
@@ -45,6 +111,8 @@ export class SequencerUI{
this.seq = sequencer;
this.createControls();
this.setSliderInterval();
+ this.createNavigatorHandler();
+ this.updateTitleAndMediaStatus();
this.seq.onTextEvent = (data, type) => {
let end = "";
@@ -113,20 +181,15 @@ export class SequencerUI{
* }}
*/
this.lyricsElement = {};
-
// main div
const mainLyricsDiv = document.createElement("div");
mainLyricsDiv.classList.add("lyrics");
-
-
// title
const lyricsTitle = document.createElement("h2");
lyricsTitle.innerText = "Decoded Text";
lyricsTitle.classList.add("lyrics_title");
mainLyricsDiv.appendChild(lyricsTitle);
this.lyricsElement.title = lyricsTitle;
-
-
// encoding selector
const encodingSelector = document.createElement("select");
supportedEncodings.forEach(encoding => {
@@ -139,8 +202,6 @@ export class SequencerUI{
encodingSelector.onchange = () => this.changeEncoding(encodingSelector.value);
encodingSelector.classList.add("lyrics_selector");
mainLyricsDiv.appendChild(encodingSelector);
-
-
// the actual text
const text = document.createElement("p");
text.classList.add("lyrics_text");
@@ -175,12 +236,12 @@ export class SequencerUI{
const togglePlayback = () => {
if(this.seq.paused)
{
- this.seq.play();
playPauseButton.innerHTML = getPauseSvg(ICON_SIZE);
+ this.seqPlay();
}
else
{
- this.seq.pause();
+ this.seqPause()
playPauseButton.innerHTML = getPlaySvg(ICON_SIZE);
}
}
@@ -190,12 +251,12 @@ export class SequencerUI{
// previous song button
const previousSongButton = getSeqUIButton("Previous song",
getBackwardSvg(ICON_SIZE));
- previousSongButton.onclick = () => this.seq.previousSong();
+ previousSongButton.onclick = () => this.switchToPreviousSong();
// next song button
const nextSongButton = getSeqUIButton("Next song",
getForwardSvg(ICON_SIZE));
- nextSongButton.onclick = () => this.seq.nextSong();
+ nextSongButton.onclick = () => this.switchToNextSong();
// loop button
const loopButton = getSeqUIButton("Loop this",
@@ -282,11 +343,11 @@ export class SequencerUI{
break;
case "[":
- this.seq.previousSong();
+ this.switchToPreviousSong();
break;
case "]":
- this.seq.nextSong();
+ this.switchToNextSong();
break;
default:
@@ -305,17 +366,24 @@ export class SequencerUI{
})
}
+ updateTitleAndMediaStatus()
+ {
+ document.getElementById("title").innerText = this.titles[this.seq.songIndex];
+
+ navigator.mediaSession.setPositionState({
+ duration: this.seq.duration,
+ playbackRate: this.seq.playbackRate,
+ position: this.seq.currentTime
+ });
+ }
+
setSliderInterval(){
setInterval(() => {
+ //this.updateTitleAndMediaStatus();
this.progressBar.style.width = `${(this.seq.currentTime / this.seq.duration) * 100}%`;
const time = formatTime(this.seq.currentTime);
const total = formatTime(this.seq.duration);
this.progressTime.innerText = `${time.time} / ${total.time}`;
- if(this.seq.songIndex !== this.titleIndex)
- {
- document.getElementById("title").innerText = this.titles[this.seq.songIndex];
- this.titleIndex = this.seq.songIndex;
- }
}, 100);
}
}
\ 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 236a204b..e52c0bb1 100644
--- a/src/website/ui/synthesizer_ui/synthetizer_ui.js
+++ b/src/website/ui/synthesizer_ui/synthetizer_ui.js
@@ -8,7 +8,6 @@ import { midiControllers } from '../../../spessasynth_lib/midi_parser/midi_messa
import { SequencePlayer } from '../../sequence_recorder/sequence_player.js'
import { SequenceRecorder } from '../../sequence_recorder/sequence_recorder.js'
-const MAX_VOICE_METER = 400;
export class SynthetizerUI
{
/**
@@ -35,7 +34,7 @@ export class SynthetizerUI
* Voice meter
* @type {Meter}
*/
- this.voiceMeter = new Meter("#206", "Voices: ", 0, MAX_VOICE_METER);
+ this.voiceMeter = new Meter("#206", "Voices: ", 0, this.synth.voiceCap);
this.voiceMeter.bar.classList.add("voice_meter_bar_smooth");
/**
@@ -185,15 +184,20 @@ export class SynthetizerUI
num++;
}
- this.synth.onProgramChange.push((channel, p) => {
- if(this.synth.midiChannels[channel].lockPreset)
+ // add event listeners
+ this.synth.eventHandler.addEvent("programchange", e =>
+ {
+ if(this.synth.midiChannels[e.channel].lockPreset)
{
return;
}
- this.controllers[channel].preset.value = JSON.stringify([p.bank, p.program]);
+ this.controllers[e.channel].preset.value = JSON.stringify([e.preset.bank, e.preset.program]);
});
- this.synth.onControllerChange.push((channel, controller, value) => {
+ this.synth.eventHandler.addEvent("controllerchange", e => {
+ const controller = e.controllerNumber;
+ const channel = e.channel;
+ const value = e.controllerValue;
switch (controller)
{
default:
@@ -226,10 +230,18 @@ export class SynthetizerUI
}
});
- this.synth.onPitchWheel.push((channel, MSB, LSB) => {
- const val = (MSB << 7) | LSB;
+ this.synth.eventHandler.addEvent("pitchwheel", e => {
+ const val = (e.MSB << 7) | e.LSB;
// pitch wheel
- this.controllers[channel].pitchWheel.update(val - 8192);
+ this.controllers[e.channel].pitchWheel.update(val - 8192);
+ });
+
+ this.synth.eventHandler.addEvent("drumchange", e => {
+ if(this.synth.midiChannels[e.channel].lockPreset)
+ {
+ return;
+ }
+ this.reloadSelector(this.controllers[e.channel].preset, e.isDrumChannel ? this.percussionList : this.instrumentList);
});
setInterval(this.updateVoicesAmount.bind(this), 100);