mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-14 16:07:39 +00:00
TWINE: removed own midi code - use MidiParser_XMIDI
This commit is contained in:
parent
d13e4b4088
commit
153bfa03aa
@ -30,8 +30,7 @@ MODULE_OBJS := \
|
||||
script_move.o \
|
||||
sound.o \
|
||||
text.o \
|
||||
twine.o \
|
||||
xmidi.o
|
||||
twine.o
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_TWINE), DYNAMIC_PLUGIN)
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "twine/hqrdepack.h"
|
||||
#include "twine/resources.h"
|
||||
#include "twine/twine.h"
|
||||
#include "twine/xmidi.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
@ -63,35 +62,46 @@ namespace TwinE {
|
||||
*/
|
||||
#define NUM_CD_TRACKS 10
|
||||
|
||||
TwinEMidiPlayer::TwinEMidiPlayer() {
|
||||
TwinEMidiPlayer::TwinEMidiPlayer(TwinEEngine* engine) : _engine(engine) {
|
||||
MidiPlayer::createDriver();
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret == 0) {
|
||||
if (_nativeMT32)
|
||||
if (_nativeMT32) {
|
||||
_driver->sendMT32Reset();
|
||||
else
|
||||
} else {
|
||||
_driver->sendGMReset();
|
||||
}
|
||||
_driver->setTimerCallback(this, &timerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void TwinEMidiPlayer::play(byte *buf, int size) {
|
||||
MidiParser *parser = MidiParser::createParser_SMF();
|
||||
if (parser->loadMusic(buf, size)) {
|
||||
parser->setTrack(0);
|
||||
parser->setMidiDriver(this);
|
||||
parser->setTimerRate(_driver->getBaseTempo());
|
||||
parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
|
||||
|
||||
_parser = parser;
|
||||
|
||||
syncVolume();
|
||||
|
||||
// All the tracks are supposed to loop
|
||||
_isLooping = true;
|
||||
_isPlaying = true;
|
||||
if (_parser == nullptr) {
|
||||
if (_engine->cfgfile.MidiType == MIDIFILE_DOS) {
|
||||
_parser = MidiParser::createParser_XMIDI();
|
||||
} else {
|
||||
_parser = MidiParser::createParser_SMF();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_parser->loadMusic(buf, size)) {
|
||||
warning("Failed to load midi music");
|
||||
return;
|
||||
}
|
||||
_parser->setTrack(0);
|
||||
_parser->setMidiDriver(this);
|
||||
_parser->setTimerRate(_driver->getBaseTempo());
|
||||
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
|
||||
|
||||
syncVolume();
|
||||
|
||||
// All the tracks are supposed to loop
|
||||
_isLooping = true;
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
Music::Music(TwinEEngine *engine) : _engine(engine), _midiPlayer(engine) {
|
||||
}
|
||||
|
||||
void Music::musicVolume(int32 volume) {
|
||||
@ -176,14 +186,6 @@ void Music::playMidiMusic(int32 midiIdx, int32 loop) {
|
||||
}
|
||||
|
||||
int32 midiSize = _engine->_hqrdepack->hqrGetallocEntry(&midiPtr, filename, midiIdx);
|
||||
|
||||
if (_engine->cfgfile.MidiType == MIDIFILE_DOS) {
|
||||
uint8 *dos_midi_ptr;
|
||||
midiSize = convert_to_midi(midiPtr, midiSize, &dos_midi_ptr);
|
||||
free(midiPtr);
|
||||
midiPtr = dos_midi_ptr;
|
||||
}
|
||||
|
||||
_midiPlayer.play(midiPtr, midiSize);
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,10 @@ namespace TwinE {
|
||||
class TwinEEngine;
|
||||
|
||||
class TwinEMidiPlayer : public Audio::MidiPlayer {
|
||||
private:
|
||||
TwinEEngine *_engine;
|
||||
public:
|
||||
TwinEMidiPlayer();
|
||||
TwinEMidiPlayer(TwinEEngine *engine);
|
||||
void play(byte *buf, int size);
|
||||
};
|
||||
|
||||
@ -48,7 +50,7 @@ private:
|
||||
uint8 *midiPtr = nullptr;
|
||||
|
||||
public:
|
||||
Music(TwinEEngine *engine) : _engine(engine) {}
|
||||
Music(TwinEEngine *engine);
|
||||
/** Track number of the current playing music */
|
||||
int32 currentMusic = 0;
|
||||
|
||||
|
@ -1,771 +0,0 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "twine/xmidi.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
#include "twine/twine.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
/**
|
||||
* Provides comprehensive information on the next event in the MIDI stream.
|
||||
* An EventInfo struct is instantiated by format-specific implementations
|
||||
* of MidiParser::parseNextEvent() each time another event is needed.
|
||||
*
|
||||
* Adapted from the ScummVM project
|
||||
*/
|
||||
struct EventInfo {
|
||||
uint8 *start; ///< Position in the MIDI stream where the event starts.
|
||||
///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta.
|
||||
uint32 delta; ///< The number of ticks after the previous event that this event should occur.
|
||||
uint8 event; ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel.
|
||||
///< For META, event == 0xFF. For SysEx, event == 0xF0.
|
||||
union {
|
||||
struct {
|
||||
uint8 param1; ///< The first parameter in a simple MIDI message.
|
||||
uint8 param2; ///< The second parameter in a simple MIDI message.
|
||||
} basic;
|
||||
struct {
|
||||
uint8 type; ///< For META events, this indicates the META type.
|
||||
uint8 *data; ///< For META and SysEx events, this points to the start of the data.
|
||||
} ext;
|
||||
};
|
||||
uint32 length; ///< For META and SysEx blocks, this indicates the length of the data.
|
||||
///< For Note On events, a non-zero value indicates that no Note Off event
|
||||
///< will occur, and the MidiParser will have to generate one itself.
|
||||
///< For all other events, this value should always be zero.
|
||||
};
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
struct XMIDI_info {
|
||||
uint8 num_tracks;
|
||||
uint8 *tracks[120]; // Maximum 120 tracks
|
||||
};
|
||||
|
||||
/* Linked list of saved note offs to be injected in the midi stream at a later
|
||||
* time. */
|
||||
struct CachedEvent {
|
||||
struct EventInfo *eventInfo;
|
||||
uint32 time;
|
||||
struct CachedEvent *next;
|
||||
};
|
||||
static struct CachedEvent *cached_events = NULL;
|
||||
|
||||
/*
|
||||
* Forward declarations
|
||||
*/
|
||||
static uint16 read2low(uint8 **data);
|
||||
static uint32 read4high(uint8 **data);
|
||||
static void write4high(uint8 **data, uint32 val);
|
||||
static void write2high(uint8 **data, uint16 val);
|
||||
static uint32 readVLQ2(uint8 **data);
|
||||
static uint32 readVLQ(uint8 **data);
|
||||
static int32 putVLQ(uint8 *dest, uint32 value);
|
||||
|
||||
/* Returns an EventInfo struct if there is a cached event that should be
|
||||
* played between current_time and current_time + delta. The cached event
|
||||
* is removed from the internal list of cached events! */
|
||||
static struct EventInfo *pop_cached_event(uint32 current_time, uint32 delta);
|
||||
static void save_event(struct EventInfo *info, uint32 current_time);
|
||||
|
||||
static int32 read_event_info(uint8 *data, struct EventInfo *info, uint32 current_time);
|
||||
static int32 put_event(uint8 *dest, struct EventInfo *info);
|
||||
static int32 convert_to_mtrk(uint8 *data, uint32 size, uint8 *dest);
|
||||
static int32 read_XMIDI_header(uint8 *data, uint32 size, struct XMIDI_info *info);
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
static uint16 read2low(uint8 **data) {
|
||||
uint8 *d = *data;
|
||||
uint16 value = (d[1] << 8) | d[0];
|
||||
*data = (d + 2);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
static uint32 read4high(uint8 **data) {
|
||||
uint8 *d = *data;
|
||||
uint16 value = (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3]);
|
||||
*data = (d + 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
static void write4high(uint8 **data, uint32 val) {
|
||||
uint8 *d = *data;
|
||||
*d++ = (val >> 24) & 0xff;
|
||||
*d++ = (val >> 16) & 0xff;
|
||||
*d++ = (val >> 8) & 0xff;
|
||||
*d++ = val & 0xff;
|
||||
*data = d;
|
||||
}
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
static void write2high(uint8 **data, uint16 val) {
|
||||
uint8 *d = *data;
|
||||
*d++ = (val >> 8) & 0xff;
|
||||
*d++ = val & 0xff;
|
||||
*data = d;
|
||||
}
|
||||
|
||||
// This is a special XMIDI variable length quantity
|
||||
//
|
||||
// Adapted from the ScummVM project
|
||||
static uint32 readVLQ2(uint8 **data) {
|
||||
uint8 *pos = *data;
|
||||
uint32 value = 0;
|
||||
while (!(pos[0] & 0x80)) {
|
||||
value += *pos++;
|
||||
}
|
||||
*data = pos;
|
||||
return value;
|
||||
}
|
||||
|
||||
// This is the conventional (i.e. SMF) variable length quantity
|
||||
//
|
||||
// Adapted from the ScummVM project
|
||||
static uint32 readVLQ(uint8 **data) {
|
||||
uint8 *d = *data;
|
||||
uint8 str;
|
||||
uint32 value = 0;
|
||||
int32 i;
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
str = *d++;
|
||||
value = (value << 7) | (str & 0x7F);
|
||||
if (!(str & 0x80))
|
||||
break;
|
||||
}
|
||||
*data = d;
|
||||
return value;
|
||||
}
|
||||
|
||||
// PutVLQ
|
||||
//
|
||||
// Write a Conventional Variable Length Quantity
|
||||
//
|
||||
// Code adapted from the Exult engine
|
||||
static int32 putVLQ(uint8 *dest, uint32 value) {
|
||||
int32 buffer;
|
||||
int32 j, i = 1;
|
||||
|
||||
buffer = value & 0x7F;
|
||||
while (value >>= 7) {
|
||||
buffer <<= 8;
|
||||
buffer |= ((value & 0x7F) | 0x80);
|
||||
i++;
|
||||
}
|
||||
if (!dest)
|
||||
return i;
|
||||
for (j = 0; j < i; j++) {
|
||||
*dest++ = buffer & 0xFF;
|
||||
buffer >>= 8;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void save_event(struct EventInfo *info, uint32 current_time) {
|
||||
uint32 delta = info->length;
|
||||
struct CachedEvent *prev, *next, *temp;
|
||||
|
||||
temp = (struct CachedEvent *)malloc(sizeof(struct CachedEvent));
|
||||
if (!temp) {
|
||||
error("Failed to allocate memory for the midi buffer");
|
||||
}
|
||||
temp->eventInfo = info;
|
||||
temp->time = current_time + delta;
|
||||
temp->next = NULL;
|
||||
|
||||
//info("Saving event to be stopped at %2X", temp->time);
|
||||
|
||||
if (!cached_events) {
|
||||
cached_events = temp;
|
||||
} else {
|
||||
prev = NULL;
|
||||
next = cached_events;
|
||||
|
||||
/* Find the proper time slot */
|
||||
while (next && next->time < current_time + delta) {
|
||||
prev = next;
|
||||
next = next->next;
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
prev->next = temp;
|
||||
} else {
|
||||
if (prev) {
|
||||
temp->next = prev->next;
|
||||
prev->next = temp;
|
||||
} else {
|
||||
temp->next = cached_events;
|
||||
cached_events = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct EventInfo *pop_cached_event(uint32 current_time, uint32 delta) {
|
||||
struct EventInfo *info = NULL;
|
||||
struct CachedEvent *old;
|
||||
|
||||
if (cached_events && cached_events->time < current_time + delta) {
|
||||
info = cached_events->eventInfo;
|
||||
info->delta = cached_events->time - current_time;
|
||||
old = cached_events;
|
||||
cached_events = cached_events->next;
|
||||
free(old);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
// Adapted from the ScummVM project
|
||||
static int32 read_event_info(uint8 *data, struct EventInfo *info, uint32 current_time) {
|
||||
struct EventInfo *injectedEvent;
|
||||
info->start = data;
|
||||
info->delta = readVLQ2(&data);
|
||||
info->event = *data++;
|
||||
|
||||
/* Advance current time here, but not yet in the main conversion loop.
|
||||
* This is so that cached events can still be injected correctly */
|
||||
current_time += info->delta;
|
||||
|
||||
//info("%02X: Parsing event %02X", current_time, info->event);
|
||||
switch (info->event >> 4) {
|
||||
case 0x9: // Note On
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = *(data++);
|
||||
info->length = readVLQ(&data);
|
||||
if (info->basic.param2 == 0) {
|
||||
info->event = (info->event & 0x0F) | 0x80;
|
||||
info->length = 0;
|
||||
} else {
|
||||
//info("Found Note On with duration %X. Saving a Note Off for later", info->length);
|
||||
injectedEvent = (struct EventInfo *)malloc(sizeof(struct EventInfo));
|
||||
if (!injectedEvent) {
|
||||
error("Could not allocate memory for event info");
|
||||
}
|
||||
injectedEvent->event = 0x80 | (info->event & 0x0f);
|
||||
injectedEvent->basic.param1 = info->basic.param1;
|
||||
injectedEvent->basic.param2 = info->basic.param2;
|
||||
injectedEvent->length = info->length;
|
||||
save_event(injectedEvent, current_time);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0x8:
|
||||
case 0xA:
|
||||
case 0xE:
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = *(data++);
|
||||
break;
|
||||
|
||||
case 0xB:
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = *(data++);
|
||||
|
||||
// This isn't a full XMIDI implementation, but it should
|
||||
// hopefully be "good enough" for most things.
|
||||
|
||||
switch (info->basic.param1) {
|
||||
// Simplified XMIDI looping.
|
||||
case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP
|
||||
#if 0 // TODO
|
||||
uint8 *pos = data;
|
||||
if (_loopCount < ARRAYSIZE(_loop) - 1)
|
||||
_loopCount++;
|
||||
/*else
|
||||
warning("XMIDI: Exceeding maximum loop count %d", ARRAYSIZE(_loop));*/
|
||||
|
||||
_loop[_loopCount].pos = pos;
|
||||
_loop[_loopCount].repeat = info->basic.param2;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK
|
||||
#if 0 // TODO
|
||||
if (_loopCount >= 0) {
|
||||
if (info->basic.param2 < 64) {
|
||||
// End the current loop.
|
||||
_loopCount--;
|
||||
} else {
|
||||
// Repeat 0 means "loop forever".
|
||||
if (_loop[_loopCount].repeat) {
|
||||
if (--_loop[_loopCount].repeat == 0)
|
||||
_loopCount--;
|
||||
else
|
||||
data = _loop[_loopCount].pos;
|
||||
} else {
|
||||
data = _loop[_loopCount].pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG
|
||||
#if 0 // TODO
|
||||
if (_callbackProc)
|
||||
_callbackProc(info->basic.param2, _callbackData);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK
|
||||
case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT
|
||||
case 0x70: // XMIDI_CONTROLLER_VOICE_PROT
|
||||
case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT
|
||||
case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE
|
||||
case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX
|
||||
case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT
|
||||
case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX
|
||||
default:
|
||||
if (info->basic.param1 >= 0x6e && info->basic.param1 <= 0x78) {
|
||||
/*warning("Unsupported XMIDI controller %d (0x%2x)",
|
||||
info->basic.param1, info->basic.param1);*/
|
||||
}
|
||||
}
|
||||
|
||||
// Should we really keep passing the XMIDI controller events to
|
||||
// the MIDI driver, or should we turn them into some kind of
|
||||
// NOP events? (Dummy meta events, perhaps?) Ah well, it has
|
||||
// worked so far, so it shouldn't cause any damage...
|
||||
|
||||
break;
|
||||
|
||||
case 0xF: // Meta or SysEx event
|
||||
switch (info->event & 0x0F) {
|
||||
case 0x2: // Song Position Pointer
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = *(data++);
|
||||
break;
|
||||
|
||||
case 0x3: // Song Select
|
||||
info->basic.param1 = *(data++);
|
||||
info->basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0x6:
|
||||
case 0x8:
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xC:
|
||||
case 0xE:
|
||||
info->basic.param1 = info->basic.param2 = 0;
|
||||
break;
|
||||
|
||||
case 0x0: // SysEx
|
||||
info->length = readVLQ(&data);
|
||||
info->ext.data = data;
|
||||
data += info->length;
|
||||
break;
|
||||
|
||||
case 0xF: // META event
|
||||
info->ext.type = *(data++);
|
||||
info->length = readVLQ(&data);
|
||||
info->ext.data = data;
|
||||
data += info->length;
|
||||
if (info->ext.type == 0x51 && info->length == 3) {
|
||||
// Tempo event. We want to make these constant 500,000.
|
||||
info->ext.data[0] = 0x07;
|
||||
info->ext.data[1] = 0xA1;
|
||||
info->ext.data[2] = 0x20;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
//warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x (delta: %X)", info->event, info->delta);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (data - info->start);
|
||||
}
|
||||
|
||||
// Code adapted from the Exult engine
|
||||
static int32 put_event(uint8 *dest, struct EventInfo *info) {
|
||||
int32 i = 0, j;
|
||||
int32 rc = 0;
|
||||
static uint8 last_event = 0;
|
||||
|
||||
rc = putVLQ(dest, info->delta);
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
|
||||
if ((info->event != last_event) || (info->event >= 0xF0)) {
|
||||
if (dest)
|
||||
*dest++ = (info->event);
|
||||
i++;
|
||||
}
|
||||
|
||||
last_event = info->event;
|
||||
|
||||
switch (info->event >> 4) {
|
||||
// 2 bytes data
|
||||
// Note off, Note on, Aftertouch, Controller and Pitch Wheel
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xE:
|
||||
if (dest) {
|
||||
*dest++ = (info->basic.param1);
|
||||
*dest++ = (info->basic.param2);
|
||||
}
|
||||
i += 2;
|
||||
break;
|
||||
|
||||
// 1 bytes data
|
||||
// Program Change and Channel Pressure
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
if (dest)
|
||||
*dest++ = (info->basic.param1);
|
||||
i++;
|
||||
break;
|
||||
|
||||
// Variable length
|
||||
// SysEx
|
||||
case 0xF:
|
||||
if (info->event == 0xFF) {
|
||||
if (dest)
|
||||
*dest++ = (info->basic.param1);
|
||||
i++;
|
||||
}
|
||||
|
||||
rc = putVLQ(dest, info->length);
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
|
||||
if (info->length) {
|
||||
for (j = 0; j < (int)info->length; j++) {
|
||||
if (dest)
|
||||
*dest++ = (info->ext.data[j]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Never occur
|
||||
default:
|
||||
//warning("Not supposed to see this");
|
||||
break;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
// Code adapted from the Exult engine
|
||||
static int32 convert_to_mtrk(uint8 *data, uint32 size, uint8 *dest) {
|
||||
int32 time = 0;
|
||||
int32 lasttime = 0;
|
||||
int32 rc;
|
||||
uint32 i = 8;
|
||||
uint8 *size_pos = NULL;
|
||||
uint8 *data_end = data + size;
|
||||
struct XMIDI_info xmidi_info;
|
||||
struct EventInfo info;
|
||||
struct EventInfo *cached_info;
|
||||
|
||||
if (dest) {
|
||||
*dest++ = ('M');
|
||||
*dest++ = ('T');
|
||||
*dest++ = ('r');
|
||||
*dest++ = ('k');
|
||||
|
||||
size_pos = dest;
|
||||
dest += 4;
|
||||
}
|
||||
|
||||
rc = read_XMIDI_header(data, size, &xmidi_info);
|
||||
if (!rc) {
|
||||
//warning("Failed to read XMIDI header");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data = xmidi_info.tracks[0];
|
||||
|
||||
while (data < data_end) {
|
||||
//info("=======================================================================");
|
||||
// We don't write the end of stream marker here, we'll do it later
|
||||
if (data[0] == 0xFF && data[1] == 0x2f) {
|
||||
//info("Got EOX");
|
||||
// lasttime = event->time;
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = read_event_info(data, &info, time);
|
||||
if (!rc) {
|
||||
//warning("Failed to read event info %ld bytes from the end!", data_end - data);
|
||||
return 0;
|
||||
}
|
||||
data += rc;
|
||||
|
||||
cached_info = pop_cached_event(time, info.delta);
|
||||
while (cached_info) {
|
||||
//info("Injecting event %2X at time %2X", cached_info->event, time);
|
||||
rc = put_event(dest, cached_info);
|
||||
if (!rc) {
|
||||
//warning("Failed to save injected event!");
|
||||
return 0;
|
||||
}
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
time += cached_info->delta;
|
||||
info.delta -= cached_info->delta;
|
||||
free(cached_info);
|
||||
cached_info = pop_cached_event(time, info.delta);
|
||||
}
|
||||
|
||||
//info("Saving event %02X", info.event);
|
||||
rc = put_event(dest, &info);
|
||||
if (!rc) {
|
||||
//warning("Failed to save event!");
|
||||
return 0;
|
||||
}
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
time += info.delta;
|
||||
if (info.event == 0xFF && info.ext.type == 0x2F) {
|
||||
//info("GOT EOX");
|
||||
data = data_end;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out end of stream marker
|
||||
if (lasttime > time) {
|
||||
rc = putVLQ(dest, lasttime - time);
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
} else {
|
||||
rc = putVLQ(dest, 0);
|
||||
if (dest)
|
||||
dest += rc;
|
||||
i += rc;
|
||||
}
|
||||
if (dest) {
|
||||
*dest++ = (0xFF);
|
||||
*dest++ = (0x2F);
|
||||
}
|
||||
rc = putVLQ(dest, 0);
|
||||
i += 2 + rc;
|
||||
|
||||
if (dest) {
|
||||
dest += rc;
|
||||
write4high(&size_pos, i - 8);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Code adapted from the ScummVM project, which originally adapted it from the
|
||||
* Exult engine */
|
||||
static int32 read_XMIDI_header(uint8 *data, uint32 size, struct XMIDI_info *info) {
|
||||
uint32 i = 0;
|
||||
uint8 *start;
|
||||
uint32 len;
|
||||
uint32 chunkLen;
|
||||
char buf[32];
|
||||
uint8 *pos = data;
|
||||
int32 tracksRead = 0;
|
||||
|
||||
if (!memcmp(pos, "FORM", 4)) {
|
||||
pos += 4;
|
||||
|
||||
// Read length of
|
||||
len = read4high(&pos);
|
||||
start = pos;
|
||||
|
||||
// XDIRless XMIDI, we can handle them here.
|
||||
if (!memcmp(pos, "XMID", 4)) {
|
||||
//warning("XMIDI doesn't have XDIR");
|
||||
pos += 4;
|
||||
info->num_tracks = 1;
|
||||
} else if (memcmp(pos, "XDIR", 4) != 0) {
|
||||
// Not an XMIDI that we recognize
|
||||
//warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
|
||||
return 0;
|
||||
} else {
|
||||
// Seems Valid
|
||||
pos += 4;
|
||||
info->num_tracks = 0;
|
||||
|
||||
for (i = 4; i < len; i++) {
|
||||
// Read 4 bytes of type
|
||||
memcpy(buf, pos, 4);
|
||||
pos += 4;
|
||||
|
||||
// Read length of chunk
|
||||
chunkLen = read4high(&pos);
|
||||
|
||||
// Add eight bytes
|
||||
i += 8;
|
||||
|
||||
if (memcmp(buf, "INFO", 4) == 0) {
|
||||
// Must be at least 2 bytes long
|
||||
if (chunkLen < 2) {
|
||||
//warning("Invalid chunk length %d for 'INFO' block", (int)chunkLen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
info->num_tracks = (uint8)read2low(&pos);
|
||||
pos += 2;
|
||||
|
||||
if (chunkLen > 2) {
|
||||
//warning("Chunk length %d is greater than 2", (int)chunkLen);
|
||||
//pos += chunkLen - 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Must align
|
||||
pos += (chunkLen + 1) & ~1;
|
||||
i += (chunkLen + 1) & ~1;
|
||||
}
|
||||
|
||||
// Didn't get to fill the header
|
||||
if (info->num_tracks == 0) {
|
||||
//warning("Didn't find a valid track count");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ok now to start part 2
|
||||
// Goto the right place
|
||||
pos = start + ((len + 1) & ~1);
|
||||
|
||||
if (memcmp(pos, "CAT ", 4) != 0) {
|
||||
// Not an XMID
|
||||
//warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
|
||||
return 0;
|
||||
}
|
||||
pos += 4;
|
||||
|
||||
// Now read length of this track
|
||||
len = read4high(&pos);
|
||||
|
||||
if (memcmp(pos, "XMID", 4) != 0) {
|
||||
// Not an XMID
|
||||
//warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
|
||||
return 0;
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// Ok it's an XMIDI.
|
||||
// We're going to identify and store the location for each track.
|
||||
if (info->num_tracks > ARRAYSIZE(info->tracks)) {
|
||||
//warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(info->tracks), (int)info->num_tracks);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (tracksRead < info->num_tracks) {
|
||||
if (!memcmp(pos, "FORM", 4)) {
|
||||
// Skip this plus the 4 bytes after it.
|
||||
pos += 8;
|
||||
} else if (!memcmp(pos, "XMID", 4)) {
|
||||
// Skip this.
|
||||
pos += 4;
|
||||
} else if (!memcmp(pos, "TIMB", 4)) {
|
||||
// Custom timbres?
|
||||
// We don't support them.
|
||||
// Read the length, skip it, and hope there was nothing there.
|
||||
pos += 4;
|
||||
len = read4high(&pos);
|
||||
pos += (len + 1) & ~1;
|
||||
} else if (!memcmp(pos, "EVNT", 4)) {
|
||||
// Ahh! What we're looking for at last.
|
||||
info->tracks[tracksRead] = pos + 8; // Skip the EVNT and length bytes
|
||||
pos += 4;
|
||||
len = read4high(&pos);
|
||||
pos += (len + 1) & ~1;
|
||||
++tracksRead;
|
||||
} else {
|
||||
//warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/********************************* Public API *********************************/
|
||||
// Code adapted from the Exult engine
|
||||
uint32 convert_to_midi(uint8 *data, uint32 size, uint8 **dest) {
|
||||
int32 len;
|
||||
uint8 *d, *start;
|
||||
|
||||
if (!dest)
|
||||
return 0;
|
||||
|
||||
/* Do a dry run first so we know how much memory to allocate */
|
||||
len = convert_to_mtrk(data, size, NULL);
|
||||
if (!len) {
|
||||
//warning("Failed dummy conversion!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//info("Allocating %d bytes of memory", len);
|
||||
d = (uint8 *)malloc(len + 14);
|
||||
if (!d) {
|
||||
perror("Could not allocate memory");
|
||||
return 0;
|
||||
}
|
||||
start = d;
|
||||
|
||||
*d++ = ('M');
|
||||
*d++ = ('T');
|
||||
*d++ = ('h');
|
||||
*d++ = ('d');
|
||||
|
||||
write4high(&d, 6);
|
||||
|
||||
write2high(&d, 0);
|
||||
write2high(&d, 1);
|
||||
write2high(&d, 60); // The PPQN
|
||||
|
||||
len = convert_to_mtrk(data, size, d);
|
||||
if (!len) {
|
||||
//warning("Failed to convert");
|
||||
free(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*dest = start;
|
||||
|
||||
return len + 14;
|
||||
}
|
||||
|
||||
} // namespace TwinE
|
@ -1,48 +0,0 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TWINE_XMIDI_H
|
||||
#define TWINE_XMIDI_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace TwinE {
|
||||
|
||||
/**
|
||||
* Credit where credit is due:
|
||||
* Most of code to convert XMIDI to MIDI is adapted from either the ScummVM
|
||||
* project or the Exult game engine.
|
||||
* //risca
|
||||
*/
|
||||
|
||||
// TODO: use MidiParser_XMIDI
|
||||
|
||||
/*
|
||||
* Takes a pointer to XMIDI data of size 'size' and converts it to MIDI. The
|
||||
* result is allocated dynamically and saved to the 'dest' pointer. Returns
|
||||
* the size of the MIDI data or 0 on error.
|
||||
*/
|
||||
uint32 convert_to_midi(uint8 *data, uint32 size, uint8 **dest);
|
||||
|
||||
} // namespace TwinE
|
||||
|
||||
#endif // XMIDI_H
|
Loading…
x
Reference in New Issue
Block a user