Add commands to API
This commit is contained in:
@ -270,7 +270,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-4 ml-20">
|
<div class="flex items-center mb-4 ml-20">
|
||||||
<input id="midi-channels-scripts" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
<input id="midi-channels-scripts" type="checkbox" value="" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Channels to scripts</label>
|
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Route channels to scripts</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
129
src/API.ts
129
src/API.ts
@ -1,5 +1,5 @@
|
|||||||
import { seededRandom } from "zifferjs";
|
import { seededRandom } from "zifferjs";
|
||||||
import { MidiConnection } from "./IO/MidiConnection";
|
import { MidiCCEvent, MidiConnection, MidiNoteEvent } from "./IO/MidiConnection";
|
||||||
import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
||||||
import { DrunkWalk } from "./Utils/Drunk";
|
import { DrunkWalk } from "./Utils/Drunk";
|
||||||
import { Editor } from "./main";
|
import { Editor } from "./main";
|
||||||
@ -411,6 +411,133 @@ export class UserAPI {
|
|||||||
this.MidiConnection.panic();
|
this.MidiConnection.panic();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public active_note_events = (channel?: number): MidiNoteEvent[]|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns A list of currently active MIDI notes
|
||||||
|
*/
|
||||||
|
let events;
|
||||||
|
if(channel) {
|
||||||
|
events = this.MidiConnection.activeNotesFromChannel(channel);
|
||||||
|
} else {
|
||||||
|
events = this.MidiConnection.activeNotes;
|
||||||
|
}
|
||||||
|
if(events.length>0) return events
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public transmission(): boolean {
|
||||||
|
/**
|
||||||
|
* Returns true if there are active notes
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.activeNotes.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public active_notes = (channel?: number): number[]|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns A list of currently active MIDI notes
|
||||||
|
*/
|
||||||
|
let notes;
|
||||||
|
if(channel) {
|
||||||
|
notes = this.MidiConnection.activeNotesFromChannel(channel).map((note) => note.note);
|
||||||
|
} else {
|
||||||
|
notes = this.MidiConnection.activeNotes.map((note) => note.note);
|
||||||
|
}
|
||||||
|
if(notes.length > 0) return notes;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill_active_notes = (): void => {
|
||||||
|
/**
|
||||||
|
* Clears all active notes
|
||||||
|
*/
|
||||||
|
this.MidiConnection.activeNotes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sticky_notes = (channel?: number): number[]|undefined => {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
let notes;
|
||||||
|
if(channel) notes = this.MidiConnection.stickyNotesFromChannel(channel);
|
||||||
|
else notes = this.MidiConnection.stickyNotes;
|
||||||
|
if(notes.length > 0) return notes.map((e) => e.note);
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill_sticky_notes = (): void => {
|
||||||
|
/**
|
||||||
|
* Clears all sticky notes
|
||||||
|
*/
|
||||||
|
this.MidiConnection.stickyNotes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public last_event = (): MidiNoteEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns latest unlistened note event
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.popNoteFromBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public last_note = (): number|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns latest received note
|
||||||
|
*/
|
||||||
|
const note = this.MidiConnection.popNoteFromBuffer();
|
||||||
|
return note ? note.note : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public first_event = (): MidiNoteEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first unlistened note event
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.shiftNoteFromBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public first_note = (): number|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first received note
|
||||||
|
*/
|
||||||
|
const note = this.MidiConnection.shiftNoteFromBuffer();
|
||||||
|
return note ? note.note : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public last_channel_note = (channel: number): MidiNoteEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first unlistened note event on a specific channel
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.findNoteFromBufferInChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public find_channel_note = (channel: number): MidiNoteEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first unlistened note event on a specific channel
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.findNoteFromBufferInChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public first_cc = (): MidiCCEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first unlistened cc event
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.popCCFromBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public last_cc = (): MidiCCEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns latest unlistened cc event
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.shiftCCFromBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public find_channel_cc = (channel: number): MidiCCEvent|undefined => {
|
||||||
|
/**
|
||||||
|
* @returns Returns first unlistened cc event on a specific channel
|
||||||
|
*/
|
||||||
|
return this.MidiConnection.findCCFromBufferInChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Ziffers related functions
|
// Ziffers related functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
import { UserAPI } from "../API";
|
import { UserAPI } from "../API";
|
||||||
import { AppSettings } from "../AppSettings";
|
import { AppSettings } from "../AppSettings";
|
||||||
|
|
||||||
|
export type MidiNoteEvent = {
|
||||||
|
note: number;
|
||||||
|
velocity: number;
|
||||||
|
channel: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MidiCCEvent = {
|
||||||
|
control: number;
|
||||||
|
value: number;
|
||||||
|
channel: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class MidiConnection {
|
export class MidiConnection {
|
||||||
/**
|
/**
|
||||||
* Wrapper class for Web MIDI API. Provides methods for sending MIDI messages.
|
* Wrapper class for Web MIDI API. Provides methods for sending MIDI messages.
|
||||||
@ -12,15 +26,23 @@ export class MidiConnection {
|
|||||||
* @param scheduledNotes - Object containing scheduled notes. Keys are note numbers and values are timeout IDs.
|
* @param scheduledNotes - Object containing scheduled notes. Keys are note numbers and values are timeout IDs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Midi output */
|
||||||
private api: UserAPI;
|
private api: UserAPI;
|
||||||
private settings: AppSettings;
|
private settings: AppSettings;
|
||||||
private midiAccess: MIDIAccess | null = null;
|
private midiAccess: MIDIAccess | null = null;
|
||||||
public midiOutputs: MIDIOutput[] = [];
|
public midiOutputs: MIDIOutput[] = [];
|
||||||
public midiInputs: MIDIInput[] = [];
|
|
||||||
private currentOutputIndex: number = 0;
|
private currentOutputIndex: number = 0;
|
||||||
private currentInputIndex: number|undefined = undefined;
|
|
||||||
private scheduledNotes: { [noteNumber: number]: number } = {}; // { noteNumber: timeoutId }
|
private scheduledNotes: { [noteNumber: number]: number } = {}; // { noteNumber: timeoutId }
|
||||||
|
|
||||||
|
/* Midi input */
|
||||||
|
public midiInputs: MIDIInput[] = [];
|
||||||
|
private currentInputIndex: number|undefined = undefined;
|
||||||
|
public bufferLength: number = 512; // 32*16
|
||||||
|
public noteInputBuffer: MidiNoteEvent[] = [];
|
||||||
|
public ccInputBuffer: MidiCCEvent[] = [];
|
||||||
|
public activeNotes: MidiNoteEvent[] = [];
|
||||||
|
public stickyNotes: MidiNoteEvent[] = [];
|
||||||
|
|
||||||
/* MIDI clock stuff */
|
/* MIDI clock stuff */
|
||||||
private midiClockInputIndex: number|undefined = undefined;
|
private midiClockInputIndex: number|undefined = undefined;
|
||||||
private midiClockInput?: MIDIInput|undefined = undefined;
|
private midiClockInput?: MIDIInput|undefined = undefined;
|
||||||
@ -264,21 +286,145 @@ export class MidiConnection {
|
|||||||
}
|
}
|
||||||
/* DEFAULT MIDI INPUT */
|
/* DEFAULT MIDI INPUT */
|
||||||
if(input.name === this.settings.default_midi_input) {
|
if(input.name === this.settings.default_midi_input) {
|
||||||
// List of all note_on messages from channels 1-16
|
|
||||||
const all_note_ons = [0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,0x9E, 0x9F];
|
|
||||||
// If message is one of note ons
|
// If message is one of note ons
|
||||||
if(all_note_ons.indexOf(message.data[0]) !== -1) {
|
if(message.data[0] >= 0x90 && message.data[0] <= 0x9F) {
|
||||||
const channel = all_note_ons.indexOf(message.data[0])+1;
|
const channel = message.data[0] - 0x90 + 1;
|
||||||
const note = message.data[1];
|
const note = message.data[1];
|
||||||
const velocity = message.data[2];
|
const velocity = message.data[2];
|
||||||
this.api.variable(`channel_${channel}_note`, note);
|
this.api.variable(`channel_${channel}_note`, note);
|
||||||
this.api.variable(`channel_${channel}_velocity`, velocity);
|
this.api.variable(`channel_${channel}_velocity`, velocity);
|
||||||
|
|
||||||
if(this.settings.midi_channels_scripts) this.api.script(channel);
|
if(this.settings.midi_channels_scripts) this.api.script(channel);
|
||||||
|
|
||||||
|
//console.log(`NOTE: ${note} VELOCITY: ${velocity} CHANNEL: ${channel}`);
|
||||||
|
|
||||||
|
this.pushToMidiInputBuffer({note, velocity, channel, timestamp: event.timeStamp});
|
||||||
|
this.activeNotes.push({note, velocity, channel, timestamp: event.timeStamp});
|
||||||
|
|
||||||
|
const sticky = this.removeFromStickyNotes(note, channel);
|
||||||
|
if(!sticky) this.stickyNotes.push({note, velocity, channel, timestamp: event.timeStamp});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If note off
|
||||||
|
if(message.data[0] >= 0x80 && message.data[0] <= 0x8F) {
|
||||||
|
const channel = message.data[0] - 0x80 + 1;
|
||||||
|
const note = message.data[1];
|
||||||
|
this.removeFromActiveNotes(note, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If message is one of CCs
|
||||||
|
if(message.data[0]>=0xB0 && message.data[0]<=0xBF) {
|
||||||
|
const channel = message.data[0] - 0xB0 + 1;
|
||||||
|
const control = message.data[1];
|
||||||
|
const value = message.data[2];
|
||||||
|
this.api.variable(`channel_${channel}_control`, control);
|
||||||
|
this.api.variable(`channel_${channel}_value`, value);
|
||||||
|
|
||||||
|
//console.log(`CC: ${control} VALUE: ${value} CHANNEL: ${channel}`);
|
||||||
|
|
||||||
|
this.pushToMidiCCBuffer({control, value, channel, timestamp: event.timeStamp});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Methods for handling active midi notes */
|
||||||
|
|
||||||
|
public removeFromActiveNotes(note: number, channel: number): void {
|
||||||
|
const index = this.activeNotes.findIndex((e) => e.note===note && e.channel===channel);
|
||||||
|
if(index>=0) this.activeNotes.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromStickyNotes(note: number, channel: number): boolean {
|
||||||
|
const index = this.stickyNotes.findIndex((e) => e.note===note && e.channel===channel);
|
||||||
|
if(index>=0) {
|
||||||
|
this.stickyNotes.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
} else { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public stickyNotesFromChannel(channel: number): MidiNoteEvent[] {
|
||||||
|
return this.stickyNotes.filter((e) => e.channel===channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public activeNotesFromChannel(channel: number): MidiNoteEvent[] {
|
||||||
|
return this.activeNotes.filter((e) => e.channel===channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public killActiveNotes(): void {
|
||||||
|
this.activeNotes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public killActiveNotesFromChannel(channel: number): void {
|
||||||
|
this.activeNotes = this.activeNotes.filter((e) => e.channel!==channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Methods for handling midi input buffers */
|
||||||
|
|
||||||
|
private pushToMidiInputBuffer(event: MidiNoteEvent): void {
|
||||||
|
this.noteInputBuffer.push(event);
|
||||||
|
if(this.noteInputBuffer.length>this.bufferLength) {
|
||||||
|
this.noteInputBuffer.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private pushToMidiCCBuffer(event: MidiCCEvent): void {
|
||||||
|
this.ccInputBuffer.push(event);
|
||||||
|
if(this.ccInputBuffer.length>this.bufferLength) {
|
||||||
|
this.ccInputBuffer.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public shiftNoteFromBuffer(): MidiNoteEvent|undefined {
|
||||||
|
const event = this.noteInputBuffer.shift();
|
||||||
|
if(event) return event;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public popNoteFromBuffer(): MidiNoteEvent|undefined {
|
||||||
|
const event = this.noteInputBuffer.pop();
|
||||||
|
if(event) return event;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public popCCFromBuffer(): MidiCCEvent|undefined {
|
||||||
|
const event = this.ccInputBuffer.pop();
|
||||||
|
if(event) return event;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public shiftCCFromBuffer(): MidiCCEvent|undefined {
|
||||||
|
const event = this.ccInputBuffer.shift();
|
||||||
|
if(event) return event;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public findNoteFromBufferInChannel(channel: number|undefined) {
|
||||||
|
const index = this.noteInputBuffer.findIndex((e) => e.channel===channel);
|
||||||
|
if(index>=0) {
|
||||||
|
const event = this.noteInputBuffer[index];
|
||||||
|
this.noteInputBuffer.splice(index, 1);
|
||||||
|
return event;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public findCCFromBufferInChannel(channel: number|undefined) {
|
||||||
|
const index = this.ccInputBuffer.findIndex((e) => e.channel===channel);
|
||||||
|
if(index>=0) {
|
||||||
|
const event = this.ccInputBuffer[index];
|
||||||
|
this.ccInputBuffer.splice(index, 1);
|
||||||
|
return event;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unregisterMidiInputListener(inputIndex: number): void {
|
public unregisterMidiInputListener(inputIndex: number): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user