login

<     >

2021-01-02 21:02:41 (UTC-03:00)

Marcel Rodrigues <marcelgmr@gmail.com>

add MIDI file (SMF) front-end

diff --git a/smf.c b/smf.c
new file mode 100644
index 0000000..03d2358
--- /dev/null
+++ b/smf.c
@@ -0,0 +1,186 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "qms.h"
+#include "smf.h"
+
+Event midi_evs[NEVENTS];
+
+typedef struct TempoChange {
+    uint32_t offset;
+    uint32_t usecs_per_quarter;
+} TempoChange;
+
+#define MAX_TEMPO_CHANGES   1024
+
+static TempoChange tempo_changes[MAX_TEMPO_CHANGES];
+
+static uint8_t
+read_u8(int fd)
+{
+    uint8_t byte;
+    read(fd, &byte, 1);
+    return byte;
+}
+
+static uint16_t
+read_beu16(int fd)
+{
+    return (((uint16_t) read_u8(fd)) << 8) + read_u8(fd);
+}
+
+static uint32_t
+read_beu32(int fd)
+{
+    return (((uint32_t) read_beu16(fd)) << 16) + read_beu16(fd);
+}
+
+static uint32_t
+read_vlv(int fd)
+{
+    uint8_t byte;
+    uint32_t v = 0;
+    do {
+        read(fd, &byte, 1);
+        v <<= 7;
+        v += byte & 0x7F;
+    } while (byte & 0x80);
+    return v;
+}
+
+int
+cmp_offset(const void *a, const void *b)
+{
+    return *(uint32_t *) a - *(uint32_t *) b;
+}
+
+void
+ticks2samples(uint16_t ticks_per_quarter, int ntcs, int nevs)
+{
+    uint32_t usecs_per_quarter, offset, total_samples;
+    uint32_t delta, usecs, samples;
+    int tci, evi;
+    tci = 0;
+    usecs_per_quarter = 500000; /* MIDI default */
+    offset = 0;
+    total_samples = 0;
+    for (evi = 0; evi < nevs; evi++) {
+        while (tci < ntcs && tempo_changes[tci].offset <= offset)
+            usecs_per_quarter = tempo_changes[tci++].usecs_per_quarter;
+        delta = midi_evs[evi].offset - offset;
+        usecs = delta * usecs_per_quarter / ticks_per_quarter;
+        samples = usecs * (R/100) / 10000;
+        total_samples += samples;
+        midi_evs[evi].offset = total_samples;
+        offset += delta;
+    }
+}
+
+#define add_ev(ev)  (midi_evs[nevs++] = (Event) {offset, (ev)})
+#define add_tc(upq) (tempo_changes[ntcs++] = (TempoChange) {offset, (upq)})
+
+SMFError
+qms_smf2evs(const char *fname, int *pnevs)
+{
+    uint32_t data_length;
+    uint16_t format, ntracks, division;
+    uint16_t ticks_per_quarter;
+    uint32_t usecs_per_quarter;
+    uint32_t offset;
+    uint8_t byte, status, chan, arg, vel;
+    int smf_track;
+    int ntcs, nevs;
+    ntcs = nevs = 0;
+    int fd = open(fname, O_RDONLY);
+    if (fd == -1) return SMF_NOFILE;
+    if (read_beu32(fd) != 0x4D546864) return SMF_BADSIG;
+    data_length = read_beu32(fd); /* always 6 */
+    format = read_beu16(fd);
+    if (format > 1) return SMF_BADFMT;
+    ntracks = read_beu16(fd);
+    division = read_beu16(fd);
+    if (division & 0x8000) return SMF_BADDIV;
+    ticks_per_quarter = division;
+    status = 0; /* not meaningful, just to initialize variable */
+    for (smf_track = 0; smf_track < ntracks; smf_track++) {
+        if (read_beu32(fd) != 0x4D54726B) return SMF_BADSIG;
+        data_length = read_beu32(fd);
+        for (offset = 0;;) {
+            offset += read_vlv(fd);
+            byte = read_u8(fd);
+            if (byte == 0xFF) {
+                /* Meta Event */
+                arg = read_u8(fd);
+                if (arg == 0x2F) {
+                    /* End of Track */
+                    (void) read_u8(fd);
+                    break;
+                } else if (arg == 0x51) {
+                    /* Tempo Change */
+                    /* safely ignore a byte 0x03 meaning length */
+                    usecs_per_quarter = read_beu32(fd) & 0xFFFFFF;
+                    if (ntcs == MAX_TEMPO_CHANGES) return SMF_TOOBIG;
+                    add_tc(usecs_per_quarter);
+                } else {
+                    /* ignore any other kind of meta events */
+                    data_length = read_vlv(fd);
+                    lseek(fd, data_length, SEEK_CUR);
+                }
+            } else if (byte >= 0xF0) {
+                /* SysEx Event (ignore) */
+                data_length = read_vlv(fd);
+                lseek(fd, data_length, SEEK_CUR);
+            } else {
+                /* MIDI Event */
+                if (byte < 0x80) {
+                    /* running status */
+                    arg = byte;
+                } else {
+                    status = byte;
+                    arg = read_u8(fd);
+                }
+                chan = status & 0xF;
+                switch (status >> 4) {
+                case 0x8:   /* note off */
+                    vel = read_u8(fd);
+                    add_ev(qms_ev_vel(chan, 0, 0));
+                    break;
+                case 0x9:   /* note on */
+                    vel = read_u8(fd);
+                    add_ev(qms_ev_vel(chan, 0, vel));
+                    if (vel > 0) {
+                        add_ev(qms_ev_pitch(chan, 0, arg));
+                    }
+                    break;
+                case 0xB:   /* control change */
+                    switch (arg) {
+                    case 0x07:  /* channel volume */
+                        add_ev(qms_ev_vol(chan, read_u8(fd)));
+                        break;
+                    case 0x0A:  /* channel pan */
+                        add_ev(qms_ev_pan(chan, read_u8(fd)));
+                        break;
+                    default:    /* other control change (ignore) */
+                        (void) read_u8(fd);
+                    }
+                    break;
+                case 0xC:   /* program change */
+                    add_ev(qms_ev_pac(chan, arg % NPACS));
+                    break;
+                case 0xE:   /* pitch wheel change (ignore extra arg) */
+                    (void) read_u8(fd);
+                }
+            }
+            if (nevs == NEVENTS - 1) return SMF_TOOBIG;
+        }
+    }
+    close(fd);
+    qsort(midi_evs, nevs, sizeof(Event), cmp_offset);
+    qsort(tempo_changes, ntcs, sizeof(TempoChange), cmp_offset);
+    ticks2samples(ticks_per_quarter, ntcs, nevs);
+    if (pnevs) *pnevs = nevs;
+    return SMF_OK;
+}

diff --git a/smf.h b/smf.h
new file mode 100644
index 0000000..c03c584
--- /dev/null
+++ b/smf.h
@@ -0,0 +1,14 @@
+#ifndef SMF_H
+#define SMF_H
+
+#define NEVENTS     (1 << 14)
+
+extern Event midi_evs[NEVENTS];
+
+typedef enum SMFError {
+    SMF_OK, SMF_NOFILE, SMF_BADSIG, SMF_BADFMT, SMF_BADDIV, SMF_TOOBIG
+} SMFError;
+
+SMFError qms_smf2evs(const char *fname, int *pnevs);
+
+#endif /* SMF_H */