From 9c0f231bdcada08ebe66c565cc723ed41101578b Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Thu, 12 Oct 2023 01:23:16 +0300 Subject: [PATCH] Adding show scale method to interact with lighted keyboards --- package.json | 2 +- src/API.ts | 58 +++++++++++++++++++++++++++++- src/IO/MidiConnection.ts | 60 +++++++++++++++++++++++++++++++- src/documentation/interaction.ts | 21 +++++++++++ yarn.lock | 8 ++--- 5 files changed, 142 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index a3c6903..01e6069 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.32", + "zifferjs": "^0.0.33", "zzfx": "^1.2.0" } } diff --git a/src/API.ts b/src/API.ts index c0134f2..1b9eede 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1,4 +1,4 @@ -import { seededRandom } from "zifferjs"; +import { getAllScaleNotes, seededRandom } from "zifferjs"; import { MidiCCEvent, MidiConnection, @@ -63,6 +63,7 @@ export class UserAPI { private errorTimeoutID: number = 0; private printTimeoutID: number = 0; public MidiConnection: MidiConnection; + public scale_aid: string|number|undefined = undefined; load: samples; constructor(public app: Editor) { @@ -586,6 +587,61 @@ export class UserAPI { else return this.MidiConnection.ccInputBuffer.shift(); }; + public show_scale = ( + root: number|string, + scale: number|string, + channel: number = 0, + port: number|string = (this.MidiConnection.currentOutputIndex || 0), + soundOff: boolean = false): void => { + /** + * Sends given scale to midi output for visual aid + */ + if (!this.scale_aid || scale !== this.scale_aid) { + this.hide_scale(root,scale,channel,port); + const scaleNotes = getAllScaleNotes(scale, root); + // Send each scale note to current midi out + scaleNotes.forEach(note => { + this.MidiConnection.sendMidiOn(note, channel, 1, port); + if(soundOff) this.MidiConnection.sendAllSoundOff(channel, port); + }); + + this.scale_aid = scale; + } + } + + public hide_scale = ( + // @ts-ignore + root: number|string=0, + // @ts-ignore + scale: number|string=0, + channel: number = 0, + port: number|string = (this.MidiConnection.currentOutputIndex || 0)): void => { + /** + * Hides all notes by sending all notes off to midi output + */ + const allNotes = Array.from(Array(128).keys()); + // Send each scale note to current midi out + allNotes.forEach(note => { + this.MidiConnection.sendMidiOff(note, channel, port); + }); + this.scale_aid = undefined; + + } + + midi_notes_off = (channel: number = 0, port: number|string = (this.MidiConnection.currentOutputIndex || 0)): void => { + /** + * Sends all notes off to midi output + */ + this.MidiConnection.sendAllNotesOff(channel, port); + } + + midi_sound_off = (channel: number = 0, port: number|string = (this.MidiConnection.currentOutputIndex || 0)): void => { + /** + * Sends all sound off to midi output + */ + this.MidiConnection.sendAllSoundOff(channel, port); + } + // ============================================================= // Ziffers related functions // ============================================================= diff --git a/src/IO/MidiConnection.ts b/src/IO/MidiConnection.ts index 85f40ce..fa281b1 100644 --- a/src/IO/MidiConnection.ts +++ b/src/IO/MidiConnection.ts @@ -31,7 +31,7 @@ export class MidiConnection { private settings: AppSettings; private midiAccess: MIDIAccess | null = null; public midiOutputs: MIDIOutput[] = []; - private currentOutputIndex: number = 0; + public currentOutputIndex: number = 0; private scheduledNotes: { [noteNumber: number]: number } = {}; // { noteNumber: timeoutId } /* Midi input */ @@ -625,6 +625,64 @@ export class MidiConnection { } } + public sendMidiOn(note: number, channel: number, velocity: number, port: number | string = this.currentOutputIndex) { + /** + * Sending Midi Note on message + */ + if(typeof port === "string") port = this.getMidiOutputIndex(port); + const output = this.midiOutputs[port]; + note = Math.min(Math.max(note, 0), 127); + if (output) { + const noteOnMessage = [0x90 + channel, note, velocity]; + output.send(noteOnMessage); + } else { + console.error("MIDI output not available."); + } + } + + sendMidiOff(note: number, channel: number, port: number | string = this.currentOutputIndex) { + /** + * Sending Midi Note off message + */ + if(typeof port === "string") port = this.getMidiOutputIndex(port); + const output = this.midiOutputs[port]; + note = Math.min(Math.max(note, 0), 127); + if (output) { + const noteOffMessage = [0x80 + channel, note, 0]; + output.send(noteOffMessage); + } else { + console.error("MIDI output not available."); + } + } + + sendAllNotesOff(channel: number, port: number | string = this.currentOutputIndex) { + /** + * Sending Midi Note off message + */ + if(typeof port === "string") port = this.getMidiOutputIndex(port); + const output = this.midiOutputs[port]; + if (output) { + const noteOffMessage = [0xb0 + channel, 123, 0]; + output.send(noteOffMessage); + } else { + console.error("MIDI output not available."); + } + } + + sendAllSoundOff(channel: number, port: number | string = this.currentOutputIndex) { + /** + * Sending all sound off + */ + if(typeof port === "string") port = this.getMidiOutputIndex(port); + const output = this.midiOutputs[port]; + if (output) { + const noteOffMessage = [0xb0 + channel, 120, 0]; + output.send(noteOffMessage); + } else { + console.error("MIDI output not available."); + } + } + public sendSysExMessage(message: number[]): void { /** * Sends a SysEx message to the currently selected MIDI output. diff --git a/src/documentation/interaction.ts b/src/documentation/interaction.ts index ca2aa99..5330a00 100644 --- a/src/documentation/interaction.ts +++ b/src/documentation/interaction.ts @@ -149,5 +149,26 @@ beat(.25) :: sound('sine') `, true )} + +## Scale output for lighted keys + +Topos can output scales to external keyboards lighted keys using the following functions: + +- show_scale(key: string, scale: string|int, channel?: number, port?: string|number, soundOff?: boolean): void: sends the scale as midi on messages to specified port and channel to light the keys of external keyboard. If soundOff is true, all sound off message will be sent after every note on message. This can be useful with some keyboards not supporting external channel for lightning or routing for the midi in to suppress the sound from incoming note on messages. + +${makeExample( + "Show scale on external keyboard", + `show_scale("F","aeolian",0,4)`, + true + )} + +${makeExample( + "Hide scale", + `hide_scale("F","aeolian",0,4)`, + true + )} + + ` } + diff --git a/yarn.lock b/yarn.lock index 27acbe6..f189c86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1451,10 +1451,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.32: - version "0.0.32" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.32.tgz#825ab27f890f6fc9315e350e9d6b7ea40bcdaa3e" - integrity sha512-jY7Jx1lFIa7+I606G0+SSYID5hUe8nYlzebtlLKmbuE1Fb1utReb6064auLcpUcRz+zoSBm2GDr6/JlZ4HpKJg== +zifferjs@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.33.tgz#286e3c9ccaebe88d4acaad59f99db0cff5cc1884" + integrity sha512-sYD4wOSKTip4GTkK3epMNAzlRWN3YpQlgR9sCGvyj6xdHzaaEHdU8F/PvkI092UbfYi4NNM/pVZKF1RLUlJPAw== zzfx@^1.2.0: version "1.2.0"