aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKari Salminen2007-08-19 23:57:12 +0000
committerKari Salminen2007-08-19 23:57:12 +0000
commitef0bd468a2d53d3865685944c089ab260266d99f (patch)
tree07d5461d24248b9b121eb8d1d4f071e83a676feb
parent42e257b08d5bfec8a3f132135117f680dbef5683 (diff)
downloadscummvm-rg350-ef0bd468a2d53d3865685944c089ab260266d99f.tar.gz
scummvm-rg350-ef0bd468a2d53d3865685944c089ab260266d99f.tar.bz2
scummvm-rg350-ef0bd468a2d53d3865685944c089ab260266d99f.zip
Made Apple IIGS MIDI file parsing work (They aren't completely standard MIDI files). Enables playing the MIDI files (No sound! Only stepping through). Speed's probably not correct.
svn-id: r28674
-rw-r--r--engines/agi/sound.cpp156
-rw-r--r--engines/agi/sound.h14
2 files changed, 122 insertions, 48 deletions
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index 8459c86928..da5a18330c 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -58,6 +58,7 @@ AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, S
IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
_data = data; // Save the resource pointer
+ _ptr = _data + 2; // Set current position to just after the header
_len = len; // Save the resource's length
_type = READ_LE_UINT16(data); // Read sound resource's type
_isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2);
@@ -254,6 +255,10 @@ static const IIgsExeInfo IIgsExeInfos[] = {
static IIgsInstrumentHeader g_instruments[MAX_INSTRUMENTS];
static uint g_numInstruments = 0;
static int16 g_wave[SIERRASTANDARD_SIZE]; // FIXME? Should this be allocated from the heap? (Size is 128KiB)
+// Time (In milliseconds) in Apple IIGS mixing buffer time granularity
+// (i.e. in IIGS_BUFFER_SIZE / getRate() seconds granularity)
+static uint32 g_IIgsBufGranMillis = 0;
+static uint32 g_midiMillis = 0; // Time position (In milliseconds) in currently playing MIDI sound
bool SoundMgr::finalizeInstruments(Common::SeekableReadStream &uint8Wave) {
for (uint i = 0; i < g_numInstruments; i++)
@@ -361,6 +366,7 @@ void SoundMgr::startSound(int resnum, int flag) {
break;
}
case AGI_SOUND_MIDI:
+ g_IIgsBufGranMillis = g_midiMillis = 0;
#if 0
debugC(3, kDebugLevelSound, "IIGS MIDI sequence");
@@ -509,64 +515,119 @@ void SoundMgr::playNote(int i, int freq, int vol) {
}
void SoundMgr::playMidiSound() {
- uint8 *p;
+ const uint8 *p;
uint8 parm1, parm2;
static uint8 cmd, ch;
- _playing = true;
-
- if (_chn[0].timer > 0) {
- _chn[0].timer -= 2;
+ if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) {
+ warning("Error playing Apple IIGS MIDI sound resource");
+ _playing = false;
return;
}
- p = (uint8 *)_chn[0].ptr;
+ IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound];
- if (*p & 0x80) {
- cmd = *p++;
- ch = cmd & 0x0f;
- cmd >>= 4;
- }
+ _playing = true;
+ p = midiObj->getPtr();
- switch (cmd) {
- case 0x08:
- parm1 = *p++;
- parm2 = *p++;
- if (ch < NUM_CHANNELS)
- stopNote(ch);
- break;
- case 0x09:
- parm1 = *p++;
- parm2 = *p++;
- if (ch < NUM_CHANNELS)
- playNote(ch, noteToPeriod(parm1), 127);
- break;
- case 0x0b:
- parm1 = *p++;
- parm2 = *p++;
- debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x", parm1, ch, parm2);
- break;
- case 0x0c:
- parm1 = *p++;
-#if 0
- if (ch < NUM_CHANNELS) {
- _chn[ch].ins = (uint16 *)&wave[waveaddr[parm1]];
- _chn[ch].size = wavesize[parm1];
+ g_IIgsBufGranMillis += (IIGS_BUFFER_SIZE * 1000) / getRate();
+
+ while (g_midiMillis < g_IIgsBufGranMillis) {
+ uint8 readByte = *p++;
+
+ // Check for end of MIDI sequence marker (Can also be here before delta-time)
+ if (readByte == MIDI_BYTE_STOP_SEQUENCE) {
+ debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)");
+ g_IIgsBufGranMillis = g_midiMillis = 0;
+ _playing = false;
+ midiObj->rewind();
+ return;
+ } else if (readByte == MIDI_BYTE_TIMER_SYNC) {
+ debugC(3, kDebugLevelSound, "Timer sync");
+ continue;
}
- debugC(3, kDebugLevelSound, "set patch %02x (%d,%d), ch %02x",
- parm1, waveaddr[parm1], wavesize[parm1], ch);
-#endif
- break;
- }
- _chn[0].timer = *p++;
- _chn[0].ptr = p;
+ uint8 deltaTime = readByte;
+ uint32 bpm = 120; // Don't know if this is correct
+ g_midiMillis += (deltaTime * 1000) / bpm;
- if (*p >= 0xfc) {
- debugC(3, kDebugLevelSound, "end of sequence");
- _playing = false;
- return;
+ // Check for end of MIDI sequence marker (This time it after reading delta-time)
+ if (*p == MIDI_BYTE_STOP_SEQUENCE) {
+ debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)");
+ g_IIgsBufGranMillis = g_midiMillis = 0;
+ _playing = false;
+ midiObj->rewind();
+ return;
+ }
+
+ // Separate byte into command and channel if it's a command byte.
+ // Otherwise use running status (i.e. previously set command and channel).
+ if (*p & 0x80) {
+ cmd = *p++;
+ ch = cmd & 0x0f;
+ cmd >>= 4;
+ }
+
+ switch (cmd) {
+ case MIDI_CMD_NOTE_OFF:
+ parm1 = *p++;
+ parm2 = *p++;
+#if 0
+ if (ch < NUM_CHANNELS)
+ stopNote(ch);
+#endif
+ debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", ch, parm1, parm2);
+ break;
+ case MIDI_CMD_NOTE_ON:
+ parm1 = *p++;
+ parm2 = *p++;
+#if 0
+ if (ch < NUM_CHANNELS)
+ playNote(ch, noteToPeriod(parm1), 127);
+#endif
+ debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", ch, parm1, parm2);
+ break;
+ case MIDI_CMD_CONTROLLER:
+ // The tested Apple IIGS AGI MIDI resources only used
+ // controllers 0 (Bank select?), 7 (Volume) and 64 (Sustain On/Off).
+ // Controller 0's parameter was in range 94-127,
+ // controller 7's parameter was in range 0-127 and
+ // controller 64's parameter was always 0 (i.e. sustain off).
+ // TODO: Find out what controller 0 does and implement volume changes.
+ parm1 = *p++;
+ parm2 = *p++;
+ debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x", parm1, ch, parm2);
+ break;
+ case MIDI_CMD_PROGRAM_CHANGE:
+ // In all the tested Apple IIGS AGI MIDI resources
+ // program change's parameter was in range 0-43.
+ // This doesn't map directly to instrument numbers as all of
+ // the tested Apple IIGS AGI games only use 26 or 28 instruments.
+ // TODO: Find out the mapping to instruments and implement it.
+ parm1 = *p++;
+ debugC(3, kDebugLevelSound, "program change %02x, channel %02x", parm1, ch);
+#if 0
+ if (ch < NUM_CHANNELS) {
+ chn[ch].ins = (uint16 *)&wave[waveaddr[parm1]];
+ chn[ch].size = wavesize[parm1];
+ }
+ debugC(3, kDebugLevelSound, "set patch %02x (%d,%d), ch %02x",
+ parm1, waveaddr[parm1], wavesize[parm1], ch);
+#endif
+ break;
+ case MIDI_CMD_PITCH_WHEEL:
+ parm1 = *p++;
+ parm2 = *p++;
+ // In all the tested Apple IIGS AGI MIDI resources
+ // pitch wheel commands always had 0x2000 (Center position)
+ // as the combined 14-bit value for the position.
+ uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
+ debugC(3, kDebugLevelSound, "Pitch wheel position %04x (Not implemented yet)", wheelPos);
+ break;
+ }
}
+
+ midiObj->setPtr(p);
}
void SoundMgr::playSampleSound() {
@@ -628,9 +689,8 @@ void SoundMgr::playSound() {
if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
if (_playingSound != -1) {
if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) {
- /* play_midi_sound (); */
+ playMidiSound();
//warning("playSound: Trying to play an Apple IIGS MIDI sound. Not yet implemented!");
- _playing = false;
} else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) {
//debugC(3, kDebugLevelSound, "playSound: Trying to play an Apple IIGS sample");
playSampleSound();
diff --git a/engines/agi/sound.h b/engines/agi/sound.h
index 60be253d40..9ef4ebd20b 100644
--- a/engines/agi/sound.h
+++ b/engines/agi/sound.h
@@ -50,6 +50,16 @@ namespace Agi {
#define ENV_RELEASE 7500 /**< envelope release rate */
#define NUM_CHANNELS 7 /**< number of sound channels */
+// MIDI command values (Shifted right by 4 so they're in the lower nibble)
+#define MIDI_CMD_NOTE_OFF 0x08
+#define MIDI_CMD_NOTE_ON 0x09
+#define MIDI_CMD_CONTROLLER 0x0B
+#define MIDI_CMD_PROGRAM_CHANGE 0x0C
+#define MIDI_CMD_PITCH_WHEEL 0x0E
+// Whole MIDI byte values (Command and channel info together)
+#define MIDI_BYTE_STOP_SEQUENCE 0xFC
+#define MIDI_BYTE_TIMER_SYNC 0xF8
+
struct IIgsEnvelopeSegment {
uint8 bp;
uint16 inc; ///< 8b.8b fixed point, very probably little endian
@@ -281,8 +291,12 @@ public:
IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager);
~IIgsMidi() { if (_data != NULL) free(_data); }
virtual uint16 type() { return _type; }
+ virtual const uint8 *getPtr() { return _ptr; }
+ virtual void setPtr(const uint8 *ptr) { _ptr = ptr; }
+ virtual void rewind() { _ptr = _data + 2; }
protected:
uint8 *_data; ///< Raw sound resource data
+ const uint8 *_ptr; ///< Pointer to the current position in the MIDI data
uint32 _len; ///< Length of the raw sound resource
uint16 _type; ///< Sound resource type
};