Implement structured parsing and typing for pattern events (Note, Fader, MIDI, Unknown)
This commit is contained in:
@@ -5,8 +5,11 @@ import {
|
||||
Pad,
|
||||
PadCode,
|
||||
Pattern,
|
||||
PatternEvent,
|
||||
PatternEventKind,
|
||||
ProjectSettings,
|
||||
Scene,
|
||||
SceneGroupPatternData,
|
||||
ScenesSettings,
|
||||
Sound,
|
||||
} from '../types/types';
|
||||
@@ -28,10 +31,17 @@ type IntermediateScenes = Record<
|
||||
number,
|
||||
{
|
||||
name: string;
|
||||
groups: Record<string, { bars: number; notes: Record<number, Note[]> }>;
|
||||
groups: Record<string, ParsedPatternData>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ParsedPatternData = {
|
||||
bars: number;
|
||||
recordCount: number;
|
||||
events: PatternEvent[];
|
||||
notes: Record<number, Note[]>;
|
||||
};
|
||||
|
||||
const defaultProjectSettings = {
|
||||
bpm: 120,
|
||||
scale: 0,
|
||||
@@ -195,32 +205,104 @@ export function collectPads(files: TarFile[], sounds: Sound[]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function parsePatterns(data: Uint8Array, startOffset: number = 4) {
|
||||
const chunks = chunkArray(data, 8, startOffset);
|
||||
export function parsePatternEvents(
|
||||
data: Uint8Array,
|
||||
startOffset: number = 4,
|
||||
): ParsedPatternData {
|
||||
const recordCount = data.length >= 4 ? data[2] | (data[3] << 8) : 0;
|
||||
const availableRecordCount = Math.max(0, Math.floor((data.length - startOffset) / 8));
|
||||
const chunks = chunkArray(data, 8, startOffset).slice(
|
||||
0,
|
||||
Math.min(recordCount, availableRecordCount),
|
||||
);
|
||||
const events: PatternEvent[] = [];
|
||||
const notes: Record<number, Note[]> = {};
|
||||
|
||||
chunks.forEach((chunk) => {
|
||||
// I discovered there could be some weird chunks with pad numbers not multiple of 8
|
||||
// since I don't know what they are, just skip them
|
||||
if (chunk[2] % 8 !== 0) {
|
||||
const kind = chunk[2] & 0x07;
|
||||
const source = chunk[2] >> 3;
|
||||
const position = chunk[0] | (chunk[1] << 8);
|
||||
const flags = chunk[7];
|
||||
const rawData = chunk.slice();
|
||||
|
||||
if (kind === PatternEventKind.Note) {
|
||||
const duration = chunk[5] | (chunk[6] << 8);
|
||||
const noteEvent: PatternEvent = {
|
||||
type: 'note',
|
||||
kind: PatternEventKind.Note,
|
||||
position,
|
||||
source,
|
||||
flags,
|
||||
rawData,
|
||||
pad: source,
|
||||
note: chunk[3],
|
||||
velocity: chunk[4],
|
||||
duration,
|
||||
};
|
||||
|
||||
events.push(noteEvent);
|
||||
|
||||
if (!notes[source]) {
|
||||
notes[source] = [];
|
||||
}
|
||||
|
||||
notes[source].push({
|
||||
note: noteEvent.note,
|
||||
position: noteEvent.position,
|
||||
duration: noteEvent.duration,
|
||||
velocity: noteEvent.velocity,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const pad = chunk[2] / 8;
|
||||
if (!notes[pad]) {
|
||||
notes[pad] = [];
|
||||
if (kind === PatternEventKind.Fader) {
|
||||
const rawValue = chunk[5] | (chunk[6] << 8);
|
||||
|
||||
events.push({
|
||||
type: 'fader',
|
||||
kind: PatternEventKind.Fader,
|
||||
position,
|
||||
source,
|
||||
flags,
|
||||
rawData,
|
||||
parameter: chunk[3],
|
||||
reserved: chunk[4],
|
||||
rawValue,
|
||||
value: rawValue / 32767,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
notes[pad].push({
|
||||
note: chunk[3],
|
||||
position: (chunk[1] << 8) + chunk[0],
|
||||
duration: (chunk[6] << 8) + chunk[5],
|
||||
velocity: chunk[4],
|
||||
const eventData = chunk.slice(3, 7);
|
||||
|
||||
if (kind === PatternEventKind.Midi) {
|
||||
events.push({
|
||||
type: 'midi',
|
||||
kind: PatternEventKind.Midi,
|
||||
position,
|
||||
source,
|
||||
flags,
|
||||
rawData,
|
||||
data: eventData,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
events.push({
|
||||
type: 'unknown',
|
||||
kind,
|
||||
position,
|
||||
source,
|
||||
flags,
|
||||
rawData,
|
||||
data: eventData,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
bars: data[1],
|
||||
recordCount,
|
||||
events,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
@@ -248,6 +330,30 @@ function getPatternsForScene(scenesIntermediate: IntermediateScenes, scenePatter
|
||||
return patterns;
|
||||
}
|
||||
|
||||
function getPatternEventsForScene(
|
||||
scenesIntermediate: IntermediateScenes,
|
||||
scenePatternUsage: SceneMeta,
|
||||
) {
|
||||
const patternEvents: Partial<Record<Group, SceneGroupPatternData>> = {};
|
||||
|
||||
for (const group of ['a', 'b', 'c', 'd'] as const) {
|
||||
const patternNumber = scenePatternUsage[group];
|
||||
const groupData = scenesIntermediate[patternNumber]?.groups[group];
|
||||
|
||||
if (groupData) {
|
||||
patternEvents[group] = {
|
||||
group,
|
||||
patternNumber,
|
||||
bars: groupData.bars,
|
||||
recordCount: groupData.recordCount,
|
||||
events: groupData.events,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return patternEvents;
|
||||
}
|
||||
|
||||
export function collectScenesAndPatterns(files: TarFile[], sku: string) {
|
||||
const scenesIntermediateData: IntermediateScenes = {};
|
||||
const scenes: Record<string, Scene> = {};
|
||||
@@ -306,7 +412,7 @@ export function collectScenesAndPatterns(files: TarFile[], sku: string) {
|
||||
...scenesIntermediateData[sceneIndex],
|
||||
groups: {
|
||||
...scenesIntermediateData[sceneIndex].groups,
|
||||
[group]: parsePatterns(file.data, sku === SKU_EP40 ? 6 : 4), // EP-40 has 6-byte header
|
||||
[group]: parsePatternEvents(file.data, sku === SKU_EP40 ? 6 : 4), // EP-40 has 6-byte header
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -324,6 +430,10 @@ export function collectScenesAndPatterns(files: TarFile[], sku: string) {
|
||||
scenes[sceneIndex] = {
|
||||
name: sceneData.name,
|
||||
patterns,
|
||||
patternEvents: getPatternEventsForScene(
|
||||
scenesIntermediateData,
|
||||
scenePatternUsage[sceneIndex],
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -74,9 +74,67 @@ export type Pattern = {
|
||||
bars: number;
|
||||
};
|
||||
|
||||
export enum PatternEventKind {
|
||||
Note = 0,
|
||||
Fader = 1,
|
||||
Midi = 2,
|
||||
}
|
||||
|
||||
export type PatternEventBase = {
|
||||
position: number;
|
||||
source: number;
|
||||
flags: number;
|
||||
rawData: Uint8Array;
|
||||
};
|
||||
|
||||
export type NotePatternEvent = PatternEventBase & {
|
||||
type: 'note';
|
||||
kind: PatternEventKind.Note;
|
||||
pad: number;
|
||||
note: number;
|
||||
velocity: number;
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export type FaderAutomationEvent = PatternEventBase & {
|
||||
type: 'fader';
|
||||
kind: PatternEventKind.Fader;
|
||||
parameter: number;
|
||||
reserved: number;
|
||||
rawValue: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type MidiPatternEvent = PatternEventBase & {
|
||||
type: 'midi';
|
||||
kind: PatternEventKind.Midi;
|
||||
data: Uint8Array;
|
||||
};
|
||||
|
||||
export type UnknownPatternEvent = PatternEventBase & {
|
||||
type: 'unknown';
|
||||
kind: number;
|
||||
data: Uint8Array;
|
||||
};
|
||||
|
||||
export type PatternEvent =
|
||||
| NotePatternEvent
|
||||
| FaderAutomationEvent
|
||||
| MidiPatternEvent
|
||||
| UnknownPatternEvent;
|
||||
|
||||
export type SceneGroupPatternData = {
|
||||
group: Group;
|
||||
patternNumber: number;
|
||||
bars: number;
|
||||
recordCount: number;
|
||||
events: PatternEvent[];
|
||||
};
|
||||
|
||||
export type Scene = {
|
||||
name: string;
|
||||
patterns: Pattern[];
|
||||
patternEvents: Partial<Record<Group, SceneGroupPatternData>>;
|
||||
};
|
||||
|
||||
export type Note = {
|
||||
|
||||
Reference in New Issue
Block a user