Skip to content

Commit

Permalink
feat: use program change instead of control change for sketch switch (#8
Browse files Browse the repository at this point in the history
)
  • Loading branch information
pahund authored Nov 9, 2024
1 parent 8b313ad commit 74cd33e
Show file tree
Hide file tree
Showing 18 changed files with 98 additions and 130 deletions.
2 changes: 0 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export const configFileName = "8tlr-router.config.yaml";

export const sketchSwitchControlChangeNumber = 119;

export const frontendUpdateInterval = 100;
2 changes: 1 addition & 1 deletion src/frontend/updateSketch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function updateSketch(uiUpdate: UiUpdate) {
const { type, track, sketch } = uiUpdate;

if (type !== "sketch") {
if (type !== "pgm") {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
declare const MAIN_WINDOW_VITE_NAME: string;

type TrackName = "fhyd" | "tang" | "duri" | "poml" | "tiff" | "coco" | "plum" | "flam";
type MidiMessageType = "note-on" | "note-off" | "cc" | "at" | "pb" | "sketch";
type MidiMessageType = "note-on" | "note-off" | "cc" | "at" | "pb" | "pgm";
type Sketch = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;

interface UiUpdate {
Expand Down
35 changes: 16 additions & 19 deletions src/midi/createMidiMessageRouter.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { MidiMessage, Output } from "@julusian/midi";
import { createMidiMessageRouter, loggers } from "./createMidiMessageRouter";
import { sketchSwitchControlChangeNumber } from "../constants";

const outputs: Output[] = new Array(4).fill(null).map(() => ({
sendMessage: vi.fn(),
Expand All @@ -17,8 +16,8 @@ const outputs: Output[] = new Array(4).fill(null).map(() => ({

const NOTE_ON_CH1_C2_V100: MidiMessage = [0x90, 0x30, 0x64];
const NOTE_ON_CH9_C2_V100: MidiMessage = [0x98, 0x30, 0x64];
const SKETCH_SWITCH_2: MidiMessage = [0xb0, sketchSwitchControlChangeNumber, 0x10];
const SKETCH_SWITCH_3: MidiMessage = [0xb0, sketchSwitchControlChangeNumber, 0x20];
const PROGRAM_CHANGE_2: MidiMessage = [0xc0, 0x01];
const PROGRAM_CHANGE_3: MidiMessage = [0xc0, 0x02];

vi.mock("debug", () => ({ default: () => vi.fn() }));

Expand All @@ -30,7 +29,7 @@ describe("The router function created by createMidiMessageRouter", () => {
midiMessageRouter = createMidiMessageRouter({ outputs });
});

describe("when no sketch switch MIDI message has been received previously", () => {
describe("when no program change MIDI message has been received previously", () => {
describe("and it receives a MIDI message", () => {
beforeEach(() => {
result = midiMessageRouter(0, NOTE_ON_CH1_C2_V100);
Expand Down Expand Up @@ -73,21 +72,20 @@ describe("The router function created by createMidiMessageRouter", () => {
});
});

describe("when it receives a sketch switch control change for sketch 2", () => {
describe("when it receives a program change for sketch 2", () => {
beforeEach(() => {
result = midiMessageRouter(0, SKETCH_SWITCH_2);
result = midiMessageRouter(0, PROGRAM_CHANGE_2);
});
it(`Logs a debug message that indicates that MIDI messages arriving on channel 1
will now be routed to channel 9 on the same output port`, () => {
expect(loggers.sketch).toHaveBeenCalledWith(" [SK] ch: 1 | skt: 2 | val: 16");
it(`Logs a debug message that indicates that a program change message
with value 1 was received (pgm is zero-based, so 1 is sketch 2)`, () => {
expect(loggers.pgm).toHaveBeenCalledWith(" [PG] ch: 1 | | val: 1");
});
it("returns the correct result", () => {
expect(result).toMatchInlineSnapshot(`
{
"outputMidiMessage": [
184,
119,
16,
200,
1,
],
"outputPortIndex": 0,
}
Expand Down Expand Up @@ -121,19 +119,18 @@ describe("The router function created by createMidiMessageRouter", () => {

describe("when it receives a sketch switch control change for sketch 3", () => {
beforeEach(() => {
result = midiMessageRouter(0, SKETCH_SWITCH_3);
result = midiMessageRouter(0, PROGRAM_CHANGE_3);
});
it(`Logs a debug message that indicates that MIDI messages arriving on channel 1
will now be routed to channel 1 on output port 2`, () => {
expect(loggers.sketch).toHaveBeenCalledWith(" [SK] ch: 1 | skt: 3 | val: 32");
it(`Logs a debug message that indicates that a program change message
with value 1 was received (pgm is zero-based, so 2 is sketch 3)`, () => {
expect(loggers.pgm).toHaveBeenCalledWith(" [PG] ch: 1 | | val: 2");
});
it("returns the correct result", () => {
expect(result).toMatchInlineSnapshot(`
{
"outputMidiMessage": [
176,
119,
32,
192,
2,
],
"outputPortIndex": 1,
}
Expand Down
39 changes: 30 additions & 9 deletions src/midi/createMidiMessageRouter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { MidiMessage, Output } from "@julusian/midi";
import { getMidiChannel } from "./getMidiChannel";
import createDebug from "debug";
import { isSketchSwitch } from "./isSketchSwitch";
import { isProgramChange } from "./isProgramChange";
import { formatMidiMessage } from "../utils";
import { getSketchIndex } from "./getSketchIndex";
import { getMidiMessageType } from "./getMidiMessageType";

export const loggers = {
Expand All @@ -12,7 +11,7 @@ export const loggers = {
cc: createDebug("8tlr-router:midi:router:cc"),
at: createDebug("8tlr-router:midi:router:at"),
pb: createDebug("8tlr-router:midi:router:pb"),
sketch: createDebug("8tlr-router:midi:router:sketch"),
pgm: createDebug("8tlr-router:midi:router:pgm"),
other: createDebug("8tlr-router:midi:router:other"),
};

Expand All @@ -22,7 +21,7 @@ const leftPaddings = {
cc: 6,
at: 6,
pb: 6,
sketch: 2,
pgm: 5,
other: 3,
};

Expand All @@ -42,17 +41,39 @@ export function createMidiMessageRouter({ outputs }: Args): MidiMessageRouter {
return null;
}

const isSketchSwitchMessage = isSketchSwitch(inputMidiMessage);
if (isSketchSwitchMessage) {
const sketchIndex = getSketchIndex(inputMidiMessage);
const isProgramChangeMessage = isProgramChange(inputMidiMessage);
if (isProgramChangeMessage) {
const sketchIndex = inputMidiMessage[1];

/*
* 0 => 0
* 1 => 0
* 2 => 1
* 3 => 1
* 4 => 2
* 5 => 2
* 6 => 3
* 7 => 3
*/
selectedOutputIndices[inputChannel] = Math.floor(sketchIndex / 2);

/*
* 0 => false
* 1 => true
* 2 => false
* 3 => true
* 4 => false
* 5 => true
* 6 => false
* 7 => true
*/
shiftChannel[inputChannel] = sketchIndex % 2 !== 0;
}
if (shiftChannel[inputChannel]) {
outputMidiMessage[0] += 8;
}
const outputPortIndex = selectedOutputIndices[inputChannel];
if (!isSketchSwitchMessage) {
if (!isProgramChangeMessage) {
const midiMessageType = getMidiMessageType(inputMidiMessage);
const logger = midiMessageType === null ? loggers.other : loggers[midiMessageType];
const leftPadding = midiMessageType === null ? leftPaddings.other : leftPaddings[midiMessageType];
Expand All @@ -61,7 +82,7 @@ export function createMidiMessageRouter({ outputs }: Args): MidiMessageRouter {
`${" ".repeat(leftPadding)}${formatMidiMessage(inputMidiMessage, "pretty")} >>> ${formatMidiMessage(outputMidiMessage, "pretty")} | port: ${outputPortIndex + 1}`,
);
} else {
loggers.sketch(` ${formatMidiMessage(inputMidiMessage, "pretty")}`);
loggers.pgm(` ${formatMidiMessage(inputMidiMessage, "pretty")}`);
}

return {
Expand Down
28 changes: 14 additions & 14 deletions src/midi/getMidiMessageType.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import type { MidiMessage } from "@julusian/midi";

import { isSketchSwitch } from "./isSketchSwitch";
import { isNoteOn } from "./isNoteOn";
import { isNoteOff } from "./isNoteOff";
import { isControlChange } from "./isControlChange";
import { isAfterTouch } from "./isAfterTouch";
import { isControlChange } from "./isControlChange";
import { isNoteOff } from "./isNoteOff";
import { isNoteOn } from "./isNoteOn";
import { isPitchBend } from "./isPitchBend";
import { isProgramChange } from "./isProgramChange";

export function getMidiMessageType(midiMessage: MidiMessage): MidiMessageType | null {
if (isSketchSwitch(midiMessage)) {
return "sketch";
if (isAfterTouch(midiMessage)) {
return "at";
}
if (isNoteOn(midiMessage)) {
return "note-on";
if (isControlChange(midiMessage)) {
return "cc";
}
if (isNoteOff(midiMessage)) {
return "note-off";
}
if (isControlChange(midiMessage)) {
return "cc";
}
if (isAfterTouch(midiMessage)) {
return "at";
if (isNoteOn(midiMessage)) {
return "note-on";
}
if (isPitchBend(midiMessage)) {
return "sketch";
return "pb";
}
if (isProgramChange(midiMessage)) {
return "pgm";
}
return null;
}
26 changes: 13 additions & 13 deletions src/midi/getMidiMessageTypeName.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import type { MidiMessage } from "@julusian/midi";

import { isSketchSwitch } from "./isSketchSwitch";
import { isNoteOn } from "./isNoteOn";
import { isNoteOff } from "./isNoteOff";
import { isControlChange } from "./isControlChange";
import { isAfterTouch } from "./isAfterTouch";
import { isControlChange } from "./isControlChange";
import { isNoteOff } from "./isNoteOff";
import { isNoteOn } from "./isNoteOn";
import { isPitchBend } from "./isPitchBend";
import { isProgramChange } from "./isProgramChange";

export function getMidiMessageTypeName(midiMessage: MidiMessage, verbose = false) {
if (isSketchSwitch(midiMessage)) {
return verbose ? "sketch switch" : "SK";
if (isAfterTouch(midiMessage)) {
return verbose ? "aftertouch" : "AT";
}
if (isNoteOn(midiMessage)) {
return verbose ? "note on" : "NO";
if (isControlChange(midiMessage)) {
return verbose ? "control change" : "CC";
}
if (isNoteOff(midiMessage)) {
return verbose ? "note off" : "NF";
}
if (isControlChange(midiMessage)) {
return verbose ? "control change" : "CC";
}
if (isAfterTouch(midiMessage)) {
return verbose ? "aftertouch" : "AT";
if (isNoteOn(midiMessage)) {
return verbose ? "note on" : "NO";
}
if (isPitchBend(midiMessage)) {
return verbose ? "pitch bend" : "PB";
}
if (isProgramChange(midiMessage)) {
return verbose ? "program change" : "PG";
}
return null;
}
15 changes: 0 additions & 15 deletions src/midi/getSketchIndex.test.ts

This file was deleted.

6 changes: 0 additions & 6 deletions src/midi/getSketchIndex.ts

This file was deleted.

13 changes: 6 additions & 7 deletions src/midi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ export { createExitHandler } from "./createExitHandler";
export { createMidiMessageHandler } from "./createMidiMessageHandler";
export { createMidiMessageRouter } from "./createMidiMessageRouter";
export { getMidiChannel } from "./getMidiChannel";
export { getPortIndex } from "./getPortIndex";
export { getSketchIndex } from "./getSketchIndex";
export { getMidiMessageType } from "./getMidiMessageType";
export { getMidiMessageTypeName } from "./getMidiMessageTypeName";
export { getPitchBendValue } from "./getPitchBendValue";
export { getPortIndex } from "./getPortIndex";
export { initPort } from "./initPort";
export { isControlChange } from "./isControlChange";
export { isSketchSwitch } from "./isSketchSwitch";
export { sendAllNotesOff } from "./sendAllNotesOff";
export { isAfterTouch } from "./isAfterTouch";
export { isNoteOn } from "./isNoteOn";
export { isControlChange } from "./isControlChange";
export { isNoteOff } from "./isNoteOff";
export { isNoteOn } from "./isNoteOn";
export { isPitchBend } from "./isPitchBend";
export { getPitchBendValue } from "./getPitchBendValue";
export { isProgramChange } from "./isProgramChange";
export { sendAllNotesOff } from "./sendAllNotesOff";
2 changes: 1 addition & 1 deletion src/midi/isControlChange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe("The isControlChange function", () => {
describe.each`
midiMessage | value
${[0xb0, 0x01, 0x64]} | ${true}
${[0xb4, 0x77, 0x24]} | ${false}
${[0xb4, 0x77, 0x24]} | ${true}
${[0x90, 0x3c, 0x40]} | ${false}
`("when it receives MIDI message $midiMessage", ({ midiMessage, value }) => {
it(`returns ${value}`, () => {
Expand Down
5 changes: 2 additions & 3 deletions src/midi/isControlChange.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { MidiMessage } from "@julusian/midi";
import { sketchSwitchControlChangeNumber } from "../constants";

export function isControlChange(message: MidiMessage) {
const [statusByte, controlNumber] = message;
return (statusByte & 0xf0) === 0xb0 && controlNumber !== sketchSwitchControlChangeNumber;
const [statusByte] = message;
return (statusByte & 0xf0) === 0xb0;
}
6 changes: 6 additions & 0 deletions src/midi/isProgramChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { MidiMessage } from "@julusian/midi";

export function isProgramChange(message: MidiMessage) {
const [statusByte] = message;
return (statusByte & 0xf0) === 0xc0;
}
7 changes: 0 additions & 7 deletions src/midi/isSketchSwitch.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/ui/createUiUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { BrowserWindow } from "electron";
import type { MidiMessage } from "@julusian/midi";
import { getTrackName } from "./getTrackName";
import { getType } from "./getType";
import { getMidiMessageType } from "../midi";
import { getSketch } from "./getSketch";

export function createUiUpdater(browserWindow: BrowserWindow) {
return (midiMessage: MidiMessage, portIndex: number) => {
const type = getType(midiMessage);
const type = getMidiMessageType(midiMessage);
if (!type) {
// we only update the UI for note, control change, aftertouch (poly and channel),
// pitch bend and sketch change MIDI messages, everything else is irrelevant
Expand Down
24 changes: 0 additions & 24 deletions src/ui/getType.ts

This file was deleted.

Loading

0 comments on commit 74cd33e

Please sign in to comment.