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 */