mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-25 07:58:04 +03:00
290 lines
7.4 KiB
C
290 lines
7.4 KiB
C
/*
|
|
* midifile.h standard midifile parser
|
|
*
|
|
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
|
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
|
* 2024 kichikuou <KichikuouChrome@gmail.com>
|
|
*
|
|
* Based on midiplay+ by Daisuke NAGANO <breeze.nagano@nifty.ne.jp>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
/* $Id: midifile.c,v 1.4 2002/12/31 04:11:19 chikama Exp $ */
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <portmidi.h>
|
|
|
|
#include "portab.h"
|
|
#include "system.h"
|
|
#include "midifile.h"
|
|
|
|
// magic bytes
|
|
#define RIFF 0x52494646
|
|
#define MThd 0x4d546864
|
|
#define MTrk 0x4d54726b
|
|
|
|
// MIDI commands
|
|
#define MIDI_CONTROL 0xb0
|
|
|
|
#define META_END_OF_TRACK 0x2f
|
|
#define META_SET_TEMPO 0x51
|
|
|
|
struct stream {
|
|
const uint8_t *p;
|
|
const uint8_t *end;
|
|
};
|
|
|
|
static inline uint8_t read8(struct stream *input) {
|
|
return *input->p++;
|
|
}
|
|
|
|
static inline uint16_t read16_be(struct stream *s) {
|
|
uint16_t val = s->p[0] << 8 | s->p[1];
|
|
s->p += 2;
|
|
return val;
|
|
}
|
|
|
|
static inline uint32_t read32_be(struct stream *s) {
|
|
uint32_t val = s->p[0] << 24 | s->p[1] << 16 | s->p[2] << 8 | s->p[3];
|
|
s->p += 4;
|
|
return val;
|
|
}
|
|
|
|
static int read_vlq(struct stream *input) {
|
|
int c, value = 0;
|
|
do {
|
|
c = read8(input);
|
|
value = (value << 7) | (c & 0x7f);
|
|
} while (c & 0x80);
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* System 3.x defines custom (non-standard) MIDI commands using Registered
|
|
* Parameter Numbers (RPN). A custom command is represented by the following
|
|
* sequence of MIDI events:
|
|
*
|
|
* b0 65 64 ; set RPN MSB to 100
|
|
* b0 64 XX ; set RPN LSB to XX
|
|
* b0 06 YY ; set data MSB to YY
|
|
* b0 26 ZZ ; set data LSB to ZZ
|
|
* b0 65 7f ; unset RPN MSB
|
|
* b0 64 7f ; unset RPN LSB
|
|
*
|
|
* XX represents the command number, and YY and ZZ are data values. The
|
|
* following commands are defined:
|
|
*
|
|
* 00: Label definition. Defines a label #YY at the location of this event.
|
|
* 01: Label jump. Jumps to the label #YY.
|
|
* 02: Flag set. Sets the flag #YY to the value ZZ.
|
|
* 03: Flag jump. If the flag #YY is set to 1, jumps to the label #YY.
|
|
* 04: Variable set. Sets the variable #YY to the value ZZ.
|
|
* 05: Variable jump. Decrements the variable #YY by 1. If it reaches 0, jumps
|
|
* to the label #ZZ.
|
|
*
|
|
* Flags and variables can be retrieved or set from scenario scripts, using the
|
|
* SG command.
|
|
*/
|
|
|
|
#define CC_DATA_ENTRY_MSB 0x06
|
|
#define CC_DATA_ENTRY_LSB 0x26
|
|
#define CC_RPN_LSB 0x64
|
|
#define CC_RPN_MSB 0x65
|
|
#define SYS35_RPN_MSB 0x64
|
|
#define RPN_NULL 0x7f
|
|
|
|
static bool check_sys35cmd(int32_t msg, int *state) {
|
|
if ((Pm_MessageStatus(msg) & 0xf0) != MIDI_CONTROL) {
|
|
*state = 0;
|
|
return false;
|
|
}
|
|
|
|
int data1 = Pm_MessageData1(msg);
|
|
int data2 = Pm_MessageData2(msg);
|
|
(*state)++;
|
|
|
|
switch (*state) {
|
|
case 1:
|
|
if (data1 == CC_RPN_MSB && data2 == SYS35_RPN_MSB)
|
|
return false;
|
|
break;
|
|
case 2:
|
|
if (data1 == CC_RPN_LSB)
|
|
return false;
|
|
break;
|
|
case 3:
|
|
if (data1 == CC_DATA_ENTRY_MSB)
|
|
return false;
|
|
break;
|
|
case 4:
|
|
if (data1 == CC_DATA_ENTRY_LSB)
|
|
return false;
|
|
break;
|
|
case 5:
|
|
if (data1 == CC_RPN_MSB && data2 == RPN_NULL)
|
|
return false;
|
|
break;
|
|
case 6:
|
|
if (data1 == CC_RPN_LSB && data2 == RPN_NULL) {
|
|
*state = 0;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
*state = 0;
|
|
return false;
|
|
}
|
|
|
|
static void add_event(struct midiinfo *midi, int *event_buffer_size, enum midi_event_type type, int curtime, int32_t data) {
|
|
if (midi->nr_events >= *event_buffer_size) {
|
|
*event_buffer_size = *event_buffer_size ? *event_buffer_size * 3 / 2 : 4096;
|
|
midi->event = realloc(midi->event, *event_buffer_size * sizeof(struct midievent));
|
|
}
|
|
midi->event[midi->nr_events].type = type;
|
|
midi->event[midi->nr_events].ctime = curtime;
|
|
midi->event[midi->nr_events].data = data;
|
|
midi->nr_events++;
|
|
}
|
|
|
|
static void read_events(struct midiinfo *midi, struct stream *input) {
|
|
/* This array is indexed by the high half of a status byte. It's */
|
|
/* value is either the number of bytes needed (1 or 2) for a channel */
|
|
/* message, or 0 (meaning it's not a channel message). */
|
|
const int chantype[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */
|
|
2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */
|
|
};
|
|
|
|
int curtime = 0;
|
|
int event_buffer_size = 0;
|
|
|
|
int status = 0; /* status value (e.g. 0x90==note-on) */
|
|
bool running = false;
|
|
int sys35cmd_state = 0;
|
|
while (input->p < input->end) {
|
|
int delta = read_vlq(input);
|
|
curtime += delta; /* delta time for midi event */
|
|
|
|
int c = read8(input);
|
|
|
|
if (!(c & 0x80)) {
|
|
running = true;
|
|
} else {
|
|
status = c;
|
|
running = false;
|
|
}
|
|
|
|
int needed = chantype[(status >> 4) & 0x0f];
|
|
|
|
if (needed) { /* ie. is it a channel message? */
|
|
int c1 = running ? c : read8(input);
|
|
int c2 = needed > 1 ? read8(input) : 0;
|
|
int32_t msg = Pm_Message(status, c1, c2);
|
|
add_event(midi, &event_buffer_size, MIDI_EVENT_NORMAL, curtime, msg);
|
|
|
|
if (check_sys35cmd(msg, &sys35cmd_state)) {
|
|
int command_top = midi->nr_events - MIDI_SYS35_EVENT_SIZE;
|
|
midi->event[command_top].type = MIDI_EVENT_SYS35;
|
|
int cmd = Pm_MessageData2(midi->event[command_top + 1].data);
|
|
int val1 = Pm_MessageData2(midi->event[command_top + 2].data);
|
|
if (cmd == MIDI_SYS35_LABEL_DEFINITION)
|
|
midi->sys35_label[val1] = command_top;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
sys35cmd_state = 0;
|
|
|
|
int type, len;
|
|
switch (c) {
|
|
case 0xFF: /* meta event */
|
|
type = read8(input);
|
|
len = read_vlq(input);
|
|
switch(type) {
|
|
case META_END_OF_TRACK:
|
|
return;
|
|
case META_SET_TEMPO:
|
|
add_event(midi, &event_buffer_size, MIDI_EVENT_TEMPO, curtime, input->p[0] << 16 | input->p[1] << 8 | input->p[2]);
|
|
break;
|
|
}
|
|
input->p += len;
|
|
break;
|
|
|
|
case 0xF0: /* system exclusive */
|
|
input->p += read_vlq(input);
|
|
break;
|
|
|
|
case 0xF7: /* system exclusive continuation or arbitrary stuff */
|
|
input->p += read_vlq(input);
|
|
break;
|
|
|
|
default:
|
|
WARNING("SMF: Unknown Event 0x%x", c);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool read_header(struct midiinfo *midi, struct stream *input) {
|
|
int magic = read32_be(input);
|
|
if (magic == RIFF) {
|
|
input->p += 16;
|
|
magic = read32_be(input);
|
|
}
|
|
if (magic != MThd)
|
|
return false;
|
|
|
|
if (read32_be(input) != 6) // header size
|
|
return false;
|
|
if (read16_be(input) != 0) // format
|
|
return false;
|
|
if (read16_be(input) != 1) // number of tracks
|
|
return false;
|
|
midi->division = read16_be(input);
|
|
|
|
magic = read32_be(input);
|
|
if (magic != MTrk)
|
|
return false;
|
|
|
|
int tracklen = read32_be(input);
|
|
if (input->p + tracklen < input->end)
|
|
input->end = input->p + tracklen;
|
|
|
|
return true;
|
|
}
|
|
|
|
struct midiinfo *mf_read_midifile(const uint8_t *data, size_t len) {
|
|
struct stream input = { .p = data, .end = data + len };
|
|
struct midiinfo *midi = calloc(1, sizeof(struct midiinfo));
|
|
|
|
if (!read_header(midi, &input)) {
|
|
free(midi);
|
|
return NULL;
|
|
}
|
|
|
|
read_events(midi, &input);
|
|
|
|
return midi;
|
|
}
|
|
|
|
void mf_remove_midifile(struct midiinfo *m) {
|
|
free(m->event);
|
|
free(m);
|
|
}
|