diff options
Diffstat (limited to 'saga/xmidi.cpp')
-rw-r--r-- | saga/xmidi.cpp | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/saga/xmidi.cpp b/saga/xmidi.cpp new file mode 100644 index 0000000000..4767a6b849 --- /dev/null +++ b/saga/xmidi.cpp @@ -0,0 +1,832 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * 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. + * + * $Header$ + * + */ +/* + Description: + + XMIDI conversion routines + + Notes: + + Code adapted from XMILoader by Keet ( fox@foxpaws.net ), (C)2000 +*/ + +#include "reinherit.h" + +#include "yslib.h" + +/* Begin module component +\*--------------------------------------------------------------------------*/ +#include "xmidi_mod.h" +#include "xmidi.h" + +namespace Saga { + +int +ConvertEventListToSMF(XMIDIEVENT_LIST * event_list, + uchar ** smf_ptr, size_t * smf_len) +/*--------------------------------------------------------------------------*\ + * Given a pointer to an event list structure, this function creates and + * returns a pointer to a Standard Midi File (SMF) image and the image's + * length in bytes. +\*--------------------------------------------------------------------------*/ +{ + YS_IGNORE_PARAM(event_list); + YS_IGNORE_PARAM(smf_ptr); + YS_IGNORE_PARAM(smf_len); + +#if 0 + SMF_HEADER_CHUNK smfh; + SMF_TRACK_CHUNK smft; + + XMIDIEVENT *event_p; + + uchar *write_p = NULL; + uchar *smf_buf = NULL; + + size_t alloc_size; + + int vlq_len; + + if ((smf_ptr == NULL) || (smf_len == NULL)) { + + return R_FAILURE; + } + + /* Allocate memory for SMF image + * \*---------------------------------------------------------------------- */ + alloc_size = event_list->smf_size + MIDI_HEADER_LEN + + MIDI_TRACK_CHUNK_LEN; + + /* SMF requires an even size */ + if (alloc_size % 2) { + alloc_size++; + } + + smf_buf = malloc(alloc_size); + if (smf_buf == NULL) { + R_printf(R_STDERR, "Memory allocation error.\n"); + + return R_FAILURE; + } + + memset(smf_ptr, 0, alloc_size); + + /* Write header chunk + * \*---------------------------------------------------------------------- */ + write_p = smf_buf; + + smfh.smf_header_len = MIDI_HEADER_CHUNK_LEN; + smfh.smf_format = 0; + smfh.smf_ntracks = 1; + smfh.time_division.ppqn = XMIDI_TIMEDIV; + + memcpy(write_p, MIDI_HEADER_TAG, 4); + write_p += 4; + + ys_write_u32_be(smfh.smf_header_len, write_p, &write_p); + ys_write_u16_be(smfh.smf_format, write_p, &write_p); + ys_write_u16_be(smfh.smf_ntracks, write_p, &write_p); + ys_write_u16_be(smfh.time_division.ppqn, write_p, &write_p); + + /* Write track chunk + * \*---------------------------------------------------------------------- */ + memcpy(write_p, MIDI_TRACK_TAG, 4); + write_p += 4; + + smft.smf_track_len = event_list->smf_size; + + ys_write_u32_be(smft.smf_track_len, write_p, &write_p); + + /* Write MIDI events + * \*---------------------------------------------------------------------- */ + event_p = event_list->head; + + while (event_p != NULL) { + + vlq_len = WriteVLQ_DW(write_p, event_p->delta_time); + write_p += vlq_len; + + /* + * R_printf( R_STDOUT, + * "Wrote %d len VLQ. (%d)\n", + * vlq_len, + * event_p->delta_time ); + */ + + switch (event_p->event) { + + case MIDI_NOTE_ON: + case MIDI_NOTE_OFF: + case MIDI_AFTERTOUCH: + case MIDI_CONTROLCHANGE: + case MIDI_PITCHWHEEL: + + *write_p++ = + (uchar) (event_p->event | (uchar) event_p-> + channel); + *write_p++ = event_p->op1; + *write_p++ = event_p->op2; + break; + + case MIDI_PROGRAMCHANGE: + case MIDI_CHANNELPRESSURE: + + *write_p = + (uchar) (event_p->event | (uchar) event_p-> + channel); + *write_p = event_p->op1; + break; + + case MIDI_SYSTEMEXCLUSIVE: + + *write_p = (uchar) MIDI_NONMIDI; + + switch (event_p->sysex_op) { + + case MIDI_SYSEX_TRACKEND: + + *write_p++ = event_p->sysex_op; + *write_p++ = (uchar) 0; + break; + + case MIDI_SYSEX_TEMPO: + + *write_p++ = event_p->sysex_op; + *write_p++ = (uchar) 3; + /* + *write_p++ = event_p->op1; + *write_p++ = event_p->op2; + *write_p++ = event_p->op3; + */ + + /* Override tempo change */ + *write_p++ = (uchar) 0x07; + *write_p++ = (uchar) 0xA1; + *write_p++ = (uchar) 0x20; + break; + + case MIDI_SYSEX_TIMESIG: + + *write_p++ = event_p->sysex_op; + *write_p++ = (uchar) 4; + *write_p++ = event_p->op1; + *write_p++ = event_p->op2; + *write_p++ = event_p->op3; + *write_p++ = event_p->op4; + break; + + default: + + R_printf(R_STDERR, + "Error, invalid sysex event type (%d): " + "Aborting.\n", event_p->sysex_op); + + return R_FAILURE; + break; + + } + break; + + default: + R_printf(R_STDERR, + "Invalid event code encountered; " "aborting.\n"); + + return R_FAILURE; + break; + } + + event_p = event_p->next_event; + } + + *smf_ptr = smf_buf; + *smf_len = alloc_size; +#endif + return R_SUCCESS; +} + +int WriteVLQ_DW(char *write_ptr, DWORD value) +{ + + int vlq_len = 1; + DWORD pack = value & 0x7F; + int x; + + while (value >>= 7) { + pack <<= 8; + pack |= ((value & 0x7F) | 0x80); + vlq_len++; + } + for (x = 0; x < sizeof(DWORD); x++) { + *write_ptr++ = ((char *)(&pack))[x]; + } + + return vlq_len; +} + +int XMIDI_Read(const uchar * XMI_img, XMIDIEVENT_LIST * event_list) +{ + /* XMI header data */ + const uchar *XMIDI_data; + uint n_tracks; + + /* XMIDI data */ + IFF_ID_CHUNK cat_chunk; + IFF_ID_CHUNK id_chunk; /* Present after categeory chunk */ + XMI_TIMB_CHUNK timbre_chunk; /* Present after id chunk */ + XMI_EVENT_CHUNK event_chunk; + + const uchar *read_p; + + const uchar *event_data; + size_t event_data_len; + + if (XMIDI_ReadXMIHeader(XMI_img, &XMIDI_data, &n_tracks) != R_SUCCESS) { + + return R_FAILURE; + } + + read_p = XMIDI_data; + + /* Read category chunk + * \*------------------------------------------------------------- */ + ReadIFF_IDChunk(&cat_chunk, read_p, &read_p); + + if (memcmp(cat_chunk.id_4cc, IFF_CATEGORY_4CC, 4) != 0) { + + R_printf(R_STDERR, "Error: Category chunk not present.\n"); + Print4CC(cat_chunk.id_4cc); + + return R_FAILURE; + } + + if (memcmp(cat_chunk.desc_4cc, XMIDI_DESC_4CC, 4) != 0) { + + R_printf(R_STDERR, + "Error: Incorrect category description field.\n"); + Print4CC(cat_chunk.desc_4cc); + + return R_FAILURE; + } + + /* Read XMIDI ID Chunk + * \*------------------------------------------------------------- */ + ReadIFF_IDChunk(&id_chunk, read_p, &read_p); + + if (memcmp(id_chunk.id_4cc, IFF_FORMAT_4CC, 4) != 0) { + + R_printf(R_STDERR, "Error: ID chunk not present.\n"); + Print4CC(id_chunk.id_4cc); + + return R_FAILURE; + } + + if (memcmp(id_chunk.desc_4cc, XMIDI_DESC_4CC, 4) != 0) { + + R_printf(R_STDERR, + "Error: XMID tag not present in ID chunk: " + "Not XMIDI data.\n"); + Print4CC(id_chunk.desc_4cc); + + return R_FAILURE; + } + + /* Read XMIDI Timbre Chunk + * \*------------------------------------------------------------- */ + ys_read_4cc(timbre_chunk.id_4cc, read_p, &read_p); + timbre_chunk.chunk_len = ys_read_u32_be(read_p, &read_p); + + if (memcmp(timbre_chunk.id_4cc, XMIDI_TIMBRE_4CC, 4) != 0) { + + R_printf(R_STDERR, "Error: Timbre chunk not present.\n"); + Print4CC(timbre_chunk.id_4cc); + + return R_FAILURE; + } + + /* Read XMIDI Event Chunk + * \*------------------------------------------------------------- */ + read_p += timbre_chunk.chunk_len; + + ys_read_4cc(event_chunk.id_4cc, read_p, &read_p); + event_chunk.chunk_len = ys_read_u32_be(read_p, &read_p); + + if (memcmp(event_chunk.id_4cc, XMIDI_EVENT_4CC, 4) != 0) { + + R_printf(R_STDERR, "Error: Event chunk not present.\n"); + Print4CC(event_chunk.id_4cc); + + return R_FAILURE; + } + + /* Read XMIDI Event data + * \*------------------------------------------------------------- */ + event_data = read_p; + event_data_len = event_chunk.chunk_len; + + R_printf(R_STDOUT, + "Converting %d bytes of event data:\n", event_data_len); + + XMIDI_ReadEvents(event_list, event_data, event_data_len, n_tracks); + + /* Process XMIDI Event data + * \*------------------------------------------------------------- */ + ProcessEventList(event_list); + + return R_SUCCESS; +} + +int +ReadIFF_IDChunk(IFF_ID_CHUNK * chunk, + const uchar * read_p, const uchar ** read_pp) +{ + const uchar *chunk_p = read_p; + + ys_read_4cc(chunk->id_4cc, chunk_p, &chunk_p); + + chunk->chunk_len = ys_read_u32_be(chunk_p, &chunk_p); + + ys_read_4cc(chunk->desc_4cc, chunk_p, &chunk_p); + + if (read_pp != NULL) { + *read_pp = chunk_p; + } + + return R_SUCCESS; +} + +int Print4CC(char *fourcc) +{ + R_printf(R_STDERR, + "FourCC: %c%c%c%c (%X %X %X %X)\n", + fourcc[0], fourcc[1], fourcc[2], fourcc[3], + fourcc[0], fourcc[1], fourcc[2], fourcc[3]); + + return R_SUCCESS; +} + +int +XMIDI_ReadXMIHeader(const uchar * XMI_img, + const uchar ** XMIDI_data, uint * n_tracks) +{ + const uchar *read_p; + + IFF_ID_CHUNK id_chunk; + XMI_INFO_CHUNK info_chunk; + + *n_tracks = 0; + *XMIDI_data = NULL; + + /* Read ID chunk + * \*------------------------------------------------------------ */ + read_p = XMI_img; + + ys_read_4cc(id_chunk.id_4cc, read_p, &read_p); + id_chunk.chunk_len = ys_read_u32_be(read_p, &read_p); + ys_read_4cc(id_chunk.desc_4cc, read_p, &read_p); + + if (memcmp(id_chunk.id_4cc, IFF_FORMAT_4CC, 4) != 0) { + R_printf(R_STDERR, "Error: ID chunk not present.\n"); + + return R_FAILURE; + } + + if (memcmp(id_chunk.desc_4cc, XMI_DESC_4CC, 4) != 0) { + R_printf(R_STDERR, + "Error: XDIR tag not present in ID chunk.\n"); + + return R_FAILURE; + } + + /* Read INFO chunk + * \*------------------------------------------------------------ */ + ys_read_4cc(info_chunk.id_4cc, read_p, &read_p); + info_chunk.chunk_len = ys_read_u32_be(read_p, &read_p); + info_chunk.n_tracks = ys_read_u16_le(read_p, &read_p); + + if (memcmp(info_chunk.id_4cc, XMI_INFO_4CC, 4) != 0) { + + R_printf(R_STDERR, "Error: INFO chunk not present.\n"); + + return R_FAILURE; + } + + *n_tracks = info_chunk.n_tracks; + + *XMIDI_data = XMI_img + + (id_chunk.chunk_len + IFF_ID_CHUNK_HEADERLEN - 4); + + return R_SUCCESS; +} + +int +XMIDI_ReadEvents(XMIDIEVENT_LIST * event_list, + const uchar * event_data, size_t event_data_len, uint n_tracks) +{ + + const uchar *event_data_ptr = event_data; + size_t event_bytes_left = event_data_len; + + ulong new_event_time = 0; + ulong event_time = 0; + ulong event_len; + + ulong vlq_len; + uint data_byte; + + int channel; + int event; + + /*int tempo = MIDI_STD_TEMPO; */ + + unsigned int sysex_op; + unsigned int op1; + unsigned int op2; + unsigned int op3; + unsigned int op4; + + /* Set initial tempo */ + /* + * AddEventToList( event_list, MIDI_SYSEX_TEMPO_LEN + GetLengthAsVLQ( 0 ), 0, MIDI_SYSTEMEXCLUSIVE, 0, MIDI_SYSEX_TEMPO, 0, ); + */ + + while (event_bytes_left > 0) { + + vlq_len = ReadVLQ2_DW((char *)event_data_ptr, + (DWORD)event_bytes_left, (DWORD *)&new_event_time); + + event_time += new_event_time; + event_data_ptr += vlq_len; + event_bytes_left -= vlq_len; + + /* + * vlq_len = GetLengthAsVLQ( new_event_time ); + * R_printf( R_STDOUT, "Count: %d len VLQ (%d)\n", vlq_len, new_event_time ); + */ + + data_byte = *event_data_ptr++; + + channel = data_byte & 0x0FU; + event = data_byte & 0xF0U; + + switch (event) { + + case MIDI_NOTE_ON: + +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_NOTE_ON event:\n"); +#endif + + op1 = *(event_data_ptr++); + op2 = *(event_data_ptr++); + + AddEventToList(event_list, + MIDI_NOTE_ON_LEN, + event_time, event, channel, 0, op1, op2, 0, 0); + + vlq_len = + ReadVLQ_DW((char *)event_data_ptr, (DWORD)event_bytes_left, + (DWORD *)&event_len); + AddEventToList(event_list, MIDI_NOTE_OFF_LEN, + event_time + event_len, MIDI_NOTE_OFF, channel, 0, + op1, MIDI_STD_VELOCITY, 0, 0); + + event_data_ptr += (vlq_len); + event_bytes_left -= (2 + vlq_len); + break; + + case MIDI_AFTERTOUCH: +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_AFTERTOUCH event:\n"); +#endif + op1 = *(event_data_ptr++); + op2 = *(event_data_ptr++); + + AddEventToList(event_list, + MIDI_AFTERTOUCH_LEN, + event_time, event, channel, 0, op1, op2, 0, 0); + + event_bytes_left -= 2; + break; + + case MIDI_CONTROLCHANGE: +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_CONTROLCHANGE event:\n"); +#endif + op1 = *(event_data_ptr++); + op2 = *(event_data_ptr++); + + AddEventToList(event_list, + MIDI_CONTROLCHANGE_LEN, + event_time, event, channel, 0, op1, op2, 0, 0); + + event_bytes_left -= 2; + break; + + case MIDI_PITCHWHEEL: +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_PITCHWHEEL event:\n"); +#endif + op1 = *(event_data_ptr++); + op2 = *(event_data_ptr++); + + AddEventToList(event_list, + MIDI_PITCHWHEEL_LEN, + event_time, event, channel, 0, op1, op2, 0, 0); + + event_bytes_left -= 2; + break; + + case MIDI_PROGRAMCHANGE: +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_PROGRAMCHANGE event:\n"); +#endif + op1 = *(event_data_ptr++); + AddEventToList(event_list, MIDI_PROGRAMCHANGE_LEN, + event_time, event, channel, 0, op1, 0, 0, 0); + + event_bytes_left--; + break; + + case MIDI_CHANNELPRESSURE: +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, "MIDI_CHANNELPRESSURE event:\n"); +#endif + op1 = *(event_data_ptr++); + AddEventToList(event_list, MIDI_CHANNELPRESSURE_LEN, + event_time, event, channel, 0, op1, 0, 0, 0); + + event_bytes_left--; + break; + + case MIDI_SYSTEMEXCLUSIVE: + + sysex_op = (BYTE) * event_data_ptr++; + event_bytes_left--; + + if (data_byte == MIDI_NONMIDI) { + + switch (sysex_op) { + + case MIDI_SYSEX_TRACKEND: + R_printf(R_STDOUT, + "Track end encountered.\n"); + AddEventToList(event_list, + MIDI_SYSEX_TRACKEND_LEN, + event_time, event, channel, + sysex_op, op1, op2, 0, 0); + event_bytes_left = 0; + break; + + case MIDI_SYSEX_TEMPO: + event_data_ptr++; /*(skip length VLQ) (always 3) */ + + op1 = (BYTE) * event_data_ptr++; + op2 = (BYTE) * event_data_ptr++; + op3 = (BYTE) * event_data_ptr++; + AddEventToList(event_list, + MIDI_SYSEX_TEMPO_LEN, event_time, + event, channel, sysex_op, op1, op2, + op3, 0); + /* + * R_printf( R_STDOUT, "Adding tempo change event. :%X %X %X\n", op1, op2, op3 ); + */ + event_bytes_left -= 4; + break; + + case MIDI_SYSEX_TIMESIG: + event_data_ptr++; /*(skip length VLQ) (always 4) */ + + op1 = (BYTE) * event_data_ptr++; + op2 = (BYTE) * event_data_ptr++; + op3 = (BYTE) * event_data_ptr++; + op4 = (BYTE) * event_data_ptr++; + AddEventToList(event_list, + MIDI_SYSEX_TIMESIG_LEN, event_time, + event, channel, sysex_op, op1, op2, + op3, op4); + + /* + * R_printf( R_STDOUT, "Adding time signature event. :%X %X %X %X\n", op1, op2, op3, op4 ); + */ + event_bytes_left -= 5; + break; + + default: + R_printf(R_STDERR, + "Unhandled sysex nonmidi event, aborting.\n"); + R_printf(R_STDERR, "%X %X %X %X", + *event_data_ptr, + *(event_data_ptr + 1), + *(event_data_ptr + 2), + *(event_data_ptr + 3)); + + event_bytes_left = 0; + break; + + } + } else { + R_printf(R_STDERR, + "Unhandled sysex event, aborting.\n"); + event_bytes_left = 0; + } + + break; + + default: + R_printf(R_STDERR, + "Invalid event code encountered; aborting.\n"); + event_bytes_left = 0; + break; + } + + } /* end while ( event_bytes_left > 0 ) */ + + return R_SUCCESS; +} + +int GetLengthAsVLQ(DWORD data) +{ + + int len = 1; + + while (data >>= 7) + len++; + return len; + +} + +DWORD ReadVLQ_DW(char *data, DWORD bytes_left, DWORD * value) +{ + BYTE byte; + DWORD vlq_len = 0; + *value = 0; + + do { + if (bytes_left <= 0) + return 0; + byte = *data++; + bytes_left--; + vlq_len++; + *value = (*value << 7) | (byte & 0x7F); + } while (byte & 0x80); + + return vlq_len; +} + +DWORD ReadVLQ2_DW(char *data, DWORD bytes_left, DWORD * value) +{ + + BYTE byte; + DWORD vlq_len = 0; + *value = 0; + + while (!((byte = *data++) & 0x80)) { + if (bytes_left <= 0) + return 0; + bytes_left--; + vlq_len++; + (*value) += byte; + } + + return vlq_len; +} + +int +AddEventToList(XMIDIEVENT_LIST * event_list, int smf_size, int time, int event, + int channel, int sysex_op, int op1, int op2, int op3, int op4) +{ + + XMIDIEVENT *new_event; + XMIDIEVENT *search_ptr = event_list->tail; + + new_event = (XMIDIEVENT *)malloc(sizeof(XMIDIEVENT)); + + if (new_event == NULL) { + R_printf(R_STDERR, + "Error: Out of memory allocating XMIDI event list entry."); + return -1; + } + + new_event->next_event = NULL; + new_event->prev_event = NULL; + + if (event_list->head == NULL) { + /* Set up new list */ + event_list->head = new_event; + event_list->tail = new_event; + } else { + /* List isn't empty */ + if ((unsigned int)time >= event_list->tail->delta_time) { + + /* If this is the most recent event, append */ + event_list->tail->next_event = new_event; + new_event->prev_event = event_list->tail; + event_list->tail = new_event; + + } else { + /* Otherwise scan list backwards and insert in proper position */ + while (search_ptr != NULL) { + + if ((unsigned int)time >= + search_ptr->delta_time) { + /* Insert entry */ + new_event->next_event = + search_ptr->next_event; + new_event->prev_event = search_ptr; + + search_ptr->next_event->prev_event = + new_event; + search_ptr->next_event = new_event; + break; + } + search_ptr = search_ptr->prev_event; + } + } + } + + new_event->smf_size = smf_size; + new_event->delta_time = time; + + new_event->sysex_op = sysex_op; + new_event->event = (BYTE) event; + new_event->channel = (BYTE) channel; + new_event->op1 = (BYTE) op1; + new_event->op2 = (BYTE) op2; + new_event->op3 = (BYTE) op3; + new_event->op4 = (BYTE) op4; + +#ifdef XMIPLAY_VERBOSE + R_printf(R_STDOUT, + "Added event: Time: %d Tempo: %d Event: %d Chan: %d Op1: %d Op2: %d\n", + new_event->delta_time, new_event->tempo, new_event->event, + new_event->channel, new_event->op1, new_event->op2); +#endif + return 0; +} + +int ProcessEventList(XMIDIEVENT_LIST * event_list) +{ + XMIDIEVENT *convert_ptr = event_list->head; + int last_time = 0; + int delta = 0; + + while (convert_ptr != NULL) { + + delta = convert_ptr->delta_time - last_time; + if (delta < 0) + R_printf(R_STDERR, + "Error: Negative delta time found."); + last_time = convert_ptr->delta_time; + convert_ptr->delta_time = delta; + + /* Update smf size count */ + event_list->smf_size += + (convert_ptr->smf_size + GetLengthAsVLQ(delta)); + convert_ptr = convert_ptr->next_event; + } + + R_printf(R_STDOUT, + "ProcessEventList(): %d bytes of SMF data processed.\n", + event_list->smf_size); + + return 0; +} + +int XMIDI_Free(XMIDIEVENT_LIST * event_list) +{ + + XMIDIEVENT *free_ptr = event_list->head; + XMIDIEVENT *temp_ptr; + + while (free_ptr != NULL) { + temp_ptr = free_ptr->next_event; + free(free_ptr); + free_ptr = temp_ptr; + } + + return 0; + +} + +} // End of namespace Saga + |