Skip to content

Commit

Permalink
feat: cleaner types & smart message values
Browse files Browse the repository at this point in the history
  • Loading branch information
GriffinSauce committed Aug 21, 2022
1 parent 07b5425 commit b8c44d7
Show file tree
Hide file tree
Showing 17 changed files with 140 additions and 95 deletions.
1 change: 0 additions & 1 deletion src/index.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export * from './types';
export { PirateMidiDevice } from './PirateMidiDevice';
export { ValidationError } from './ValidationError';
export * from './midiMessage';
export * from './midiMessage/types';
export { getMockDevice } from './mock';

/**
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export * from './types';
export { PirateMidiDevice } from './PirateMidiDevice';
export { ValidationError } from './ValidationError';
export * from './midiMessage';
export * from './midiMessage/types';
export { getMockDevice } from './mock';

/**
Expand Down
23 changes: 13 additions & 10 deletions src/midiMessage/guards.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { BaseMessage, ExpMessage, SmartMessage } from '../types/Messages';
import { convertStatusByteToType } from './midiHexHelpers';
import { MidiMessageType } from './types';
import {
RawMessage,
RawExpMessage,
RawSmartMessage,
MidiMessageType,
} from '../types';

export const isMidiMessageType = (type: string): type is MidiMessageType =>
Object.values(MidiMessageType).includes(type as MidiMessageType);

export const isExpressionMidiMessage = (
message: BaseMessage | ExpMessage | SmartMessage
): message is ExpMessage => !!(message as ExpMessage).sweep;
message: RawMessage | RawExpMessage | RawSmartMessage
): message is RawExpMessage => !!(message as RawExpMessage).sweep;

export const isSmartMidiMessage = (
message: BaseMessage | ExpMessage | SmartMessage
): message is SmartMessage => {
const type = convertStatusByteToType(message.statusByte);
return type === MidiMessageType.SmartMessage;
};
message: RawMessage | RawExpMessage | RawSmartMessage
): message is RawSmartMessage => message.statusByte === '70';
16 changes: 11 additions & 5 deletions src/midiMessage/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { decodeMidiMessage, encodeMidiMessage } from '.';
import { SmartMessageType } from '../types/Messages';
import { MidiMessageType } from './types';
import { SmartSwitchMessage } from './types/SmartMessage';
import {
MidiMessageType,
RawMessage,
SmartMessageType,
SmartSwitchMessage,
} from '../types';

const encodedMessages = {
programChange: {
statusByte: 'c0',
dataByte1: '00',
dataByte2: '00',
outputs: {
midi0: true,
flexi1: true,
Expand Down Expand Up @@ -132,7 +136,7 @@ const decodedMessages = {
smartSwitchToggle: {
type: 'SmartMessage',
smartType: 'switchToggle',
side: 'primary',
side: 'Primary',
switchIndex: 4,
},
};
Expand Down Expand Up @@ -166,7 +170,9 @@ describe('MidiMessage', () => {
});
describe('smart messages', () => {
it('should throw for invalid data', () => {
expect(() => decodeMidiMessage({ statusByte: '70' })).toThrow();
expect(() =>
decodeMidiMessage({ statusByte: '70' } as RawMessage)
).toThrow();
});
it('should decode a switch toggle message', () => {
expect(decodeMidiMessage(encodedMessages.smartSwitchToggle)).toEqual(
Expand Down
62 changes: 39 additions & 23 deletions src/midiMessage/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
BaseMessage,
ExpMessage,
SmartMessage,
RawMessage,
RawExpMessage,
RawSmartMessage,
SmartMessageType,
} from '../types/Messages';
import {
MidiMessageType,
ParsedExpMessage,
ParsedMessage,
ParsedSmartMessage,
} from './types';
SwitchSide,
FlexiPart,
} from '../types';
import {
convertTypeAndChannelToStatusByte,
convertStatusByteToType,
Expand All @@ -25,11 +25,14 @@ import {
} from './midiHexHelpers';
import { isExpressionMidiMessage, isSmartMidiMessage } from './guards';

export function decodeMidiMessage(input: BaseMessage): ParsedMessage;
export function decodeMidiMessage(input: ExpMessage): ParsedExpMessage;
export function decodeMidiMessage(input: SmartMessage): ParsedSmartMessage;
/**
* Decodes a raw message from the device into a more usable format with human/device readable properties
*/
export function decodeMidiMessage(input: RawMessage): ParsedMessage;
export function decodeMidiMessage(input: RawExpMessage): ParsedExpMessage;
export function decodeMidiMessage(input: RawSmartMessage): ParsedSmartMessage;
export function decodeMidiMessage(
message: BaseMessage | ExpMessage | SmartMessage
message: RawMessage | RawExpMessage | RawSmartMessage
): ParsedMessage | ParsedExpMessage | ParsedSmartMessage {
if (isSmartMidiMessage(message)) {
return decodeSmartMessage(message);
Expand All @@ -45,7 +48,7 @@ export function decodeMidiMessage(
}

const decodeBaseMessage = (
message: BaseMessage | ExpMessage
message: RawMessage | RawExpMessage
): ParsedMessage => {
const type = convertStatusByteToType(message.statusByte);
const channel = convertStatusByteToChannel(message.statusByte);
Expand Down Expand Up @@ -97,7 +100,7 @@ const decodeBaseMessage = (
}
};

const decodeSmartMessage = (message: SmartMessage): ParsedSmartMessage => {
const decodeSmartMessage = (message: RawSmartMessage): ParsedSmartMessage => {
const { dataByte1, dataByte2 } = message;

// TODO: complete validation
Expand All @@ -112,7 +115,9 @@ const decodeSmartMessage = (message: SmartMessage): ParsedSmartMessage => {
type: MidiMessageType.SmartMessage,
smartType: message.smartType,
switchIndex: convertDataByteToNumber(dataByte1),
side: ['primary', 'secondary'][convertDataByteToNumber(dataByte2)],
side: [SwitchSide.Primary, SwitchSide.Secondary][
convertDataByteToNumber(dataByte2)
],
};
case SmartMessageType.SequentialResetStep:
case SmartMessageType.SequentialIncrementStep:
Expand Down Expand Up @@ -172,21 +177,27 @@ const decodeSmartMessage = (message: SmartMessage): ParsedSmartMessage => {
type: MidiMessageType.SmartMessage,
smartType: message.smartType,
flexiPort: convertDataByteToNumber(dataByte1),
part: ['None', 'Tip', 'Ring', 'TipRing'][
convertDataByteToNumber(dataByte2)
],
part: [
FlexiPart.None,
FlexiPart.Tip,
FlexiPart.Ring,
FlexiPart.TipRing,
][convertDataByteToNumber(dataByte2)],
};
default:
throw new Error('unhandled smart type');
}
};

export function encodeMidiMessage(input: ParsedMessage): BaseMessage;
export function encodeMidiMessage(input: ParsedExpMessage): ExpMessage;
export function encodeMidiMessage(input: ParsedSmartMessage): SmartMessage;
/**
* Encodes the easy-to-use format back into a raw device message
*/
export function encodeMidiMessage(input: ParsedMessage): RawMessage;
export function encodeMidiMessage(input: ParsedExpMessage): RawExpMessage;
export function encodeMidiMessage(input: ParsedSmartMessage): RawSmartMessage;
export function encodeMidiMessage(
message: ParsedMessage | ParsedExpMessage | ParsedSmartMessage
): BaseMessage | ExpMessage | SmartMessage {
): RawMessage | RawExpMessage | RawSmartMessage {
switch (message.type) {
case MidiMessageType.ProgramChange:
case MidiMessageType.ChannelPressure: {
Expand Down Expand Up @@ -234,7 +245,7 @@ export function encodeMidiMessage(
}
}

const encodeSmartMessage = (message: ParsedSmartMessage): SmartMessage => {
const encodeSmartMessage = (message: ParsedSmartMessage): RawSmartMessage => {
const common = { smartType: message.smartType, statusByte: '70' };

switch (message.smartType) {
Expand All @@ -245,7 +256,7 @@ const encodeSmartMessage = (message: ParsedSmartMessage): SmartMessage => {
...common,
dataByte1: convertNumberToDataByte(message.switchIndex),
dataByte2: convertNumberToDataByte(
{ primary: 0, secondary: 1 }[message.side]
{ [SwitchSide.Primary]: 0, [SwitchSide.Secondary]: 1 }[message.side]
),
};
case SmartMessageType.SequentialResetStep:
Expand Down Expand Up @@ -301,7 +312,12 @@ const encodeSmartMessage = (message: ParsedSmartMessage): SmartMessage => {
...common,
dataByte1: convertNumberToDataByte(message.flexiPort),
dataByte2: convertNumberToDataByte(
{ None: 0, Tip: 1, Ring: 2, TipRing: 3 }[message.part]
{
[FlexiPart.None]: 0,
[FlexiPart.Tip]: 1,
[FlexiPart.Ring]: 2,
[FlexiPart.TipRing]: 3,
}[message.part]
),
};
}
Expand Down
5 changes: 4 additions & 1 deletion src/midiMessage/midiHexHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* Massive thanks to Pirate MIDI and Kurtis Simpson for sharing their conversion methods
*/

import { MidiMessageType } from './types';
import { MidiMessageType } from '../types';
import { isMidiMessageType } from './guards';

// Two arrays allow us to map key<->value in either direction
const types = [
Expand Down Expand Up @@ -36,6 +37,8 @@ export function convertTypeAndChannelToStatusByte(
type: string,
channel: number
): string {
if (!isMidiMessageType(type)) throw new Error(`type "${type}" is not valid`);

const firstHex: string = typeByteValues[types.indexOf(type)];
const secondHex: string = (channel - 1).toString(16);

Expand Down
3 changes: 0 additions & 3 deletions src/midiMessage/types/index.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/types/BankSettings.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { AuxMessages, ExpMessage, MessageStack } from './Messages';
import { RawExpMessage } from './RawMessage';
import { AuxMessages, MessageStack } from './MessageStack';

// Extend Record to help infer the runCommand type
export interface BankSettings extends Record<string, unknown> {
bankName: string;
bankId: string; // 32-bit
bankMessages: MessageStack;
expMessages: MessageStack<ExpMessage>[];
expMessages: MessageStack<RawExpMessage>[];
switchGroups: Array<SwitchGroup[]>;
auxMessages: AuxMessages[];
footswitches: Footswitch[];
Expand Down
5 changes: 3 additions & 2 deletions src/types/GlobalSettings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EnabledMidiOuts } from './EnabledMidiOuts';
import { AuxMessages, ExpMessage, MessageStack } from './Messages';
import { RawExpMessage } from './RawMessage';
import { MessageStack, AuxMessages } from './MessageStack';

// Extend Record to help infer the runCommand type
export interface GlobalSettings extends Record<string, unknown> {
Expand All @@ -21,7 +22,7 @@ export interface GlobalSettings extends Record<string, unknown> {
flexi2ThruHandles: EnabledMidiOuts;
midi0ThruHandles: EnabledMidiOuts;
usbThruHandles: EnabledMidiOuts;
expMessages: MessageStack<ExpMessage>[];
expMessages: MessageStack<RawExpMessage>[];
auxMessages: AuxMessages[];
}

Expand Down
4 changes: 2 additions & 2 deletions src/midiMessage/types/Message.ts → src/types/Message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EnabledMidiOuts } from '../../types/EnabledMidiOuts';
import { ExpressionParameters } from '../../types/Messages';
import { EnabledMidiOuts } from './EnabledMidiOuts';
import { ExpressionParameters } from './RawMessage';
import { MidiMessageType } from './MidiMessageType';

interface Base {
Expand Down
17 changes: 17 additions & 0 deletions src/types/MessageStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RawMessage } from './RawMessage';

export interface MessageStack<Message = RawMessage> {
numMessages: number;
messages: Message[];
}

export interface AuxMessages {
tip: AuxMessageStacks;
ring: AuxMessageStacks;
tipRing: AuxMessageStacks;
}

export interface AuxMessageStacks {
pressMessages: MessageStack;
holdMessages: MessageStack;
}
File renamed without changes.
24 changes: 24 additions & 0 deletions src/types/RawMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EnabledMidiOuts } from './EnabledMidiOuts';
import { SmartMessageType } from './SmartMessageType';

export interface ExpressionParameters {
minLimit: number;
maxLimit: number;
sweep: 'linear' | 'log' | 'reverseLog';
}

export interface RawMessage {
statusByte: string; // 8-bit hex
dataByte1: string; // 8-bit hex
dataByte2?: string; // 8-bit hex
outputs: EnabledMidiOuts;
}

export interface RawSmartMessage {
statusByte: string; // 8-bit hex
dataByte1: string; // 8-bit hex
dataByte2?: string; // 8-bit hex
smartType: SmartMessageType;
}

export interface RawExpMessage extends RawMessage, ExpressionParameters {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { MidiMessageType } from './MidiMessageType';
import { SmartMessageType } from '../../types/Messages';
import { SmartMessageType } from './SmartMessageType';

export enum SwitchSide {
Primary = 'Primary',
Secondary = 'Secondary',
}

export enum FlexiPart {
None = 'None',
Tip = 'Tip',
Ring = 'Ring',
TipRing = 'TipRing',
}

export interface SmartSwitchMessage {
type: MidiMessageType.SmartMessage;
Expand All @@ -8,7 +20,7 @@ export interface SmartSwitchMessage {
| SmartMessageType.SwitchOff
| SmartMessageType.SwitchToggle;
switchIndex: number;
side: 'primary' | 'secondary';
side: SwitchSide;
}

export interface SmartSequentialMessage {
Expand Down Expand Up @@ -66,7 +78,7 @@ export interface SmartTrsMessage {
type: MidiMessageType.SmartMessage;
smartType: SmartMessageType.TrsSwitchOut | SmartMessageType.TrsPulseOut;
flexiPort: number;
part: 'None' | 'Tip' | 'Ring' | 'TipRing';
part: FlexiPart;
}

export type ParsedSmartMessage =
Expand Down
Loading

0 comments on commit b8c44d7

Please sign in to comment.