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