@ugfoundation/swaralipi-js 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1066,34 +1066,72 @@ function utf8(s) {
1066
1066
  function u32be(n) {
1067
1067
  return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
1068
1068
  }
1069
+ var DRUM_NOTE = {
1070
+ kick: 36,
1071
+ snare: 38,
1072
+ hihat: 42,
1073
+ // closed hi-hat
1074
+ tom1: 48,
1075
+ tom2: 45,
1076
+ tom3: 41
1077
+ };
1078
+ function trackChunk(raw, head) {
1079
+ raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));
1080
+ const track = [...head];
1081
+ let last = 0;
1082
+ for (const e of raw) {
1083
+ const status = (e.on ? 144 : 128) | e.ch & 15;
1084
+ track.push(...vlq(e.tick - last), status, e.note & 127, e.vel & 127);
1085
+ last = e.tick;
1086
+ }
1087
+ track.push(...vlq(0), 255, 47, 0);
1088
+ return [77, 84, 114, 107, ...u32be(track.length), ...track];
1089
+ }
1069
1090
  function encodeMidi(schedule, opts) {
1070
- const raw = [];
1091
+ const melody = [];
1071
1092
  for (const ev of schedule.events) {
1072
1093
  if (ev.midi === null) continue;
1073
1094
  const start = Math.round(ev.beat * PPQ);
1074
1095
  const end = Math.max(Math.round((ev.beat + ev.durationBeats) * PPQ), start + 1);
1075
1096
  if (ev.kan !== null) {
1076
1097
  const kOn = Math.max(start - Math.round(0.12 * PPQ), 0);
1077
- raw.push({ tick: kOn, on: true, note: ev.kan, vel: 70 });
1078
- raw.push({ tick: start, on: false, note: ev.kan, vel: 0 });
1098
+ melody.push({ tick: kOn, on: true, note: ev.kan, vel: 70, ch: 0 });
1099
+ melody.push({ tick: start, on: false, note: ev.kan, vel: 0, ch: 0 });
1079
1100
  }
1080
- raw.push({ tick: start, on: true, note: ev.midi, vel: 100 });
1081
- raw.push({ tick: end, on: false, note: ev.midi, vel: 0 });
1101
+ melody.push({ tick: start, on: true, note: ev.midi, vel: 100, ch: 0 });
1102
+ melody.push({ tick: end, on: false, note: ev.midi, vel: 0, ch: 0 });
1082
1103
  }
1083
- raw.sort((a, b) => a.tick - b.tick || Number(a.on) - Number(b.on));
1084
- const track = [];
1085
- if (opts.name) track.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
1086
1104
  const usPerBeat = Math.round(6e7 / opts.bpm);
1087
- track.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
1088
- let last = 0;
1089
- for (const e of raw) {
1090
- track.push(...vlq(e.tick - last), e.on ? 144 : 128, e.note & 127, e.vel & 127);
1091
- last = e.tick;
1105
+ const head = [];
1106
+ if (opts.name) head.push(...vlq(0), 255, 3, ...vlq(utf8(opts.name).length), ...utf8(opts.name));
1107
+ head.push(...vlq(0), 255, 81, 3, usPerBeat >> 16 & 255, usPerBeat >> 8 & 255, usPerBeat & 255);
1108
+ const chunks = [trackChunk(melody, head)];
1109
+ if (opts.drums && opts.drums.length > 0) {
1110
+ const drum = [];
1111
+ const totalBeats = Math.ceil(schedule.totalBeats);
1112
+ for (let beat = 0; beat < totalBeats; beat++) {
1113
+ const note = DRUM_NOTE[opts.drums[beat % opts.drums.length]];
1114
+ const t = beat * PPQ;
1115
+ drum.push({ tick: t, on: true, note, vel: 100, ch: 9 });
1116
+ drum.push({ tick: t + Math.round(PPQ / 4), on: false, note, vel: 0, ch: 9 });
1117
+ }
1118
+ chunks.push(trackChunk(drum, [...vlq(0), 255, 3, ...vlq(5), 68, 114, 117, 109, 115]));
1092
1119
  }
1093
- track.push(...vlq(0), 255, 47, 0);
1094
- const header = [77, 84, 104, 100, ...u32be(6), 0, 0, 0, 1, PPQ >> 8 & 255, PPQ & 255];
1095
- const trackChunk = [77, 84, 114, 107, ...u32be(track.length), ...track];
1096
- return new Uint8Array([...header, ...trackChunk]);
1120
+ const format = chunks.length > 1 ? 1 : 0;
1121
+ const header = [
1122
+ 77,
1123
+ 84,
1124
+ 104,
1125
+ 100,
1126
+ ...u32be(6),
1127
+ 0,
1128
+ format,
1129
+ chunks.length >> 8 & 255,
1130
+ chunks.length & 255,
1131
+ PPQ >> 8 & 255,
1132
+ PPQ & 255
1133
+ ];
1134
+ return new Uint8Array([...header, ...chunks.flat()]);
1097
1135
  }
1098
1136
  function safeName2(title) {
1099
1137
  return (title ?? "").replace(/[\\/:*?"<>|]+/g, "").trim() || "swaralipi";
@@ -1101,7 +1139,10 @@ function safeName2(title) {
1101
1139
  function exportMidi(doc, options = {}) {
1102
1140
  const schedule = buildSchedule(doc, { tonic: options.tonic, baseOctave: options.baseOctave });
1103
1141
  const bpm = options.bpm ?? doc.meta.bpm ?? 90;
1104
- const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title });
1142
+ const taalId = doc.meta.taal?.id;
1143
+ const taal = taalId !== void 0 && taalId in TAALS ? TAALS[taalId] : { numBeats: 4, sam: 0 };
1144
+ const drums = options.drums ? drumPattern(taal) : void 0;
1145
+ const bytes = encodeMidi(schedule, { bpm, name: options.name ?? doc.meta.title, drums });
1105
1146
  return {
1106
1147
  blob: new Blob([bytes], { type: "audio/midi" }),
1107
1148
  filename: `${safeName2(doc.meta.title)}.mid`,