src/controller/tracks.js
- import MidiWriter from "../../assets/vendor/midi-writer-js.esm.js";
- import { noteNames } from "../model/theory.js";
- export function generateMidi(parts, voiceConfig, tempo) {
- if (parts.length === 0) {
- return null;
- }
- const { voices, voice } = voiceConfig;
- const writeB64 = (tracks) =>
- new MidiWriter.Writer(tracks).dataUri().split(",")[1];
- const allTracks = [];
- const voicesB64 = [];
- for (let s = 0; s < voices; s++) {
- allTracks.push(makeVoiceTrack(s, parts, voice[s].instrument, tempo));
- voicesB64.push(
- writeB64([makeVoiceTrack(s, parts, voice[s].instrument, tempo)]),
- );
- }
- return { combinedB64: writeB64(allTracks), voicesB64 };
- }
- function makeVoiceTrack(s, parts, instrument, tempo) {
- const track = new MidiWriter.Track();
- const p0 = parts[0];
-
- const MELODIC_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
-
-
-
-
- const ch = MELODIC_CHANNELS[s % MELODIC_CHANNELS.length];
- track.setTempo(tempo);
- track.setTimeSignature(p0.numerator, p0.denominator);
- track.setKeySignature(noteNames()[p0.key] + (p0.mode === "minor" ? "m" : ""));
- track.addEvent(
- new MidiWriter.ProgramChangeEvent({ instrument, channel: ch }),
- );
- const ctx = {
- track,
- grid: buildGrid(parts, s),
- queuedParts: [],
- channel: ch,
- };
- let pendingWait = 0;
- let i = 0;
- while (i < ctx.grid.length) {
- const cell = ctx.grid[i];
- if (cell.newPart) {
- ctx.queuedParts.push(cell.newPart);
- }
- if (
- cell.val === -1 ||
- cell.val === -2 ||
- cell.val === undefined ||
- cell.val === null
- ) {
- pendingWait += cell.ticks;
- i++;
- continue;
- }
- flushQueuedParts(ctx);
- i += emitNoteFrom(ctx, i, pendingWait);
- pendingWait = 0;
- }
- return track;
- }
- function buildGrid(parts, s) {
- const grid = [];
- for (let m = 0; m < parts.length; m++) {
- const p = parts[m];
- const ticks = Math.round(512 / (p.denominator * p.subdivision));
- for (let t = 0; t < p.numSubBeats; t++) {
- grid.push({
- val: p.solution[`note_${s}_${t}`],
- ticks,
- keyIndex: p.key,
- newPart: t === 0 && m > 0 ? p : null,
- });
- }
- }
- return grid;
- }
- function flushQueuedParts(ctx) {
- while (ctx.queuedParts.length > 0) {
- const p = ctx.queuedParts.shift();
- ctx.track.addEvent(
- new MidiWriter.TimeSignatureEvent(p.numerator, p.denominator),
- );
- ctx.track.addEvent(
- new MidiWriter.KeySignatureEvent(
- noteNames()[p.key] + (p.mode === "minor" ? "m" : ""),
- ),
- );
- }
- }
- function emitNoteFrom(ctx, i, pendingWait) {
- const { track, grid, queuedParts } = ctx;
- const cell = grid[i];
- let durTicks = cell.ticks;
- let scan = 1;
- while (
- i + scan < grid.length &&
- (grid[i + scan].val === -2 ||
- grid[i + scan].val === undefined ||
- grid[i + scan].val === null)
- ) {
- if (grid[i + scan].newPart) {
- queuedParts.push(grid[i + scan].newPart);
- }
- durTicks += grid[i + scan].ticks;
- scan++;
- }
- const names = noteNames(cell.keyIndex);
- const opts = {
- pitch: [`${names[cell.val % 12]}${Math.floor(cell.val / 12) - 1}`],
- duration: `T${durTicks}`,
- channel: ctx.channel + 1,
- };
- if (pendingWait > 0) {
- opts.wait = `T${pendingWait}`;
- }
- track.addEvent(new MidiWriter.NoteEvent(opts));
- return scan;
- }