Add commands to API
This commit is contained in:
@ -270,8 +270,8 @@
|
||||
</div>
|
||||
<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">
|
||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Channels to scripts</label>
|
||||
</div>
|
||||
<label for="default-checkbox" class="ml-2 text-sm font-medium text-dark">Route channels to scripts</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Information card -->
|
||||
|
||||
129
src/API.ts
129
src/API.ts
@ -1,5 +1,5 @@
|
||||
import { seededRandom } from "zifferjs";
|
||||
import { MidiConnection } from "./IO/MidiConnection";
|
||||
import { MidiCCEvent, MidiConnection, MidiNoteEvent } from "./IO/MidiConnection";
|
||||
import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
||||
import { DrunkWalk } from "./Utils/Drunk";
|
||||
import { Editor } from "./main";
|
||||
@ -411,6 +411,133 @@ export class UserAPI {
|
||||
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
|
||||
// =============================================================
|
||||
|
||||
@ -1,6 +1,20 @@
|
||||
import { UserAPI } from "../API";
|
||||
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 {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Midi output */
|
||||
private api: UserAPI;
|
||||
private settings: AppSettings;
|
||||
private midiAccess: MIDIAccess | null = null;
|
||||
public midiOutputs: MIDIOutput[] = [];
|
||||
public midiInputs: MIDIInput[] = [];
|
||||
public midiOutputs: MIDIOutput[] = [];
|
||||
private currentOutputIndex: number = 0;
|
||||
private currentInputIndex: number|undefined = undefined;
|
||||
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 */
|
||||
private midiClockInputIndex: number|undefined = undefined;
|
||||
private midiClockInput?: MIDIInput|undefined = undefined;
|
||||
@ -264,23 +286,147 @@ export class MidiConnection {
|
||||
}
|
||||
/* 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(all_note_ons.indexOf(message.data[0]) !== -1) {
|
||||
const channel = all_note_ons.indexOf(message.data[0])+1;
|
||||
if(message.data[0] >= 0x90 && message.data[0] <= 0x9F) {
|
||||
const channel = message.data[0] - 0x90 + 1;
|
||||
const note = message.data[1];
|
||||
const velocity = message.data[2];
|
||||
this.api.variable(`channel_${channel}_note`, note);
|
||||
this.api.variable(`channel_${channel}_velocity`, velocity);
|
||||
|
||||
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 {
|
||||
/**
|
||||
* Unregister midi input listener
|
||||
|
||||
Reference in New Issue
Block a user