aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sandulenko2011-05-25 10:00:26 -0700
committerEugene Sandulenko2011-05-25 10:00:26 -0700
commited9768fde3ff673b4cd5981eee4b926b2f0107ab (patch)
tree2667328ff0f164bc6339976a6247db9ecd513a55
parent9539017ee35ce280758f22e589aa52c3baf9aaf3 (diff)
parent40b0d468e3112518d5a83d2360c978e72b92f01c (diff)
downloadscummvm-rg350-ed9768fde3ff673b4cd5981eee4b926b2f0107ab.tar.gz
scummvm-rg350-ed9768fde3ff673b4cd5981eee4b926b2f0107ab.tar.bz2
scummvm-rg350-ed9768fde3ff673b4cd5981eee4b926b2f0107ab.zip
Merge pull request #9 from tiqpit/a2gs
AGI: Fix //gs output
-rw-r--r--engines/agi/sound.cpp34
-rw-r--r--engines/agi/sound_2gs.cpp1014
-rw-r--r--engines/agi/sound_2gs.h344
3 files changed, 597 insertions, 795 deletions
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index 746d4e9070..aa338db0f2 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -99,51 +99,53 @@ void SoundMgr::unloadSound(int resnum) {
}
}
+/**
+ * Start playing a sound resource. The logic here is that when the sound is
+ * finished we set the given flag to be true. This way the condition can be
+ * detected by the game. On the other hand, if the game wishes to start
+ * playing a new sound before the current one is finished, we also let it
+ * do that.
+ * @param resnum the sound resource number
+ * @param flag the flag that is wished to be set true when finished
+ */
void SoundMgr::startSound(int resnum, int flag) {
- AgiSoundEmuType type;
-
- if (_vm->_game.sounds[resnum] != NULL && _vm->_game.sounds[resnum]->isPlaying())
- return;
-
- stopSound();
+ debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d)", resnum, flag);
if (_vm->_game.sounds[resnum] == NULL) // Is this needed at all?
return;
- type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type();
+ stopSound();
+ AgiSoundEmuType type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type();
if (type != AGI_SOUND_SAMPLE && type != AGI_SOUND_MIDI && type != AGI_SOUND_4CHN)
return;
+ debugC(3, kDebugLevelSound, " type = %d", type);
_vm->_game.sounds[resnum]->play();
_playingSound = resnum;
-
- debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d) type = %d", resnum, flag, type);
-
_soundGen->play(resnum);
+ // Reset the flag
_endflag = flag;
-
- // Nat Budin reports that the flag should be reset when sound starts
_vm->setflag(_endflag, false);
}
void SoundMgr::stopSound() {
debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
- _endflag = -1;
-
if (_playingSound != -1) {
if (_vm->_game.sounds[_playingSound]) // sanity checking
_vm->_game.sounds[_playingSound]->stop();
-
_soundGen->stop();
-
_playingSound = -1;
}
+ // This is probably not needed most of the time, but there also should
+ // not be any harm doing it, so do it anyway.
if (_endflag != -1)
_vm->setflag(_endflag, true);
+
+ _endflag = -1;
}
int SoundMgr::initSound() {
diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp
index 88feadd084..af7214f749 100644
--- a/engines/agi/sound_2gs.cpp
+++ b/engines/agi/sound_2gs.cpp
@@ -22,6 +22,7 @@
#include "common/config-manager.h"
#include "common/fs.h"
+#include "common/archive.h"
#include "common/md5.h"
#include "common/memstream.h"
#include "common/str-array.h"
@@ -33,47 +34,91 @@
namespace Agi {
SoundGen2GS::SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
- _disabledMidi = !loadInstruments();
+ // Allocate memory for the wavetable
+ _wavetable = new int8[SIERRASTANDARD_SIZE];
+ // Apple IIGS AGI MIDI player advances 60 ticks per second. Strategy
+ // here is to first generate audio for a 1/60th of a second and then
+ // advance the MIDI player by one tick. Thus, make the output buffer
+ // to be a 1/60th of a second in length.
+ _outSize = _sampleRate / 60;
+ _out = new int16[2 * _outSize]; // stereo
+
+ // Initialize player variables
+ _nextGen = 0;
+ _ticks = 0;
+
+ // Not playing anything yet
_playingSound = -1;
_playing = false;
- _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE);
-
- _midiChannels.resize(16); // Set the amount of available MIDI channels
+ // Load instruments
+ _disableMidi = !loadInstruments();
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
SoundGen2GS::~SoundGen2GS() {
_mixer->stopHandle(_soundHandle);
-
- free(_sndBuffer);
+ delete[] _wavetable;
+ delete[] _out;
}
int SoundGen2GS::readBuffer(int16 *buffer, const int numSamples) {
- fillAudio(buffer, numSamples / 2);
-
+ static uint data_available = 0;
+ static uint data_offset = 0;
+ uint n = numSamples << 1;
+ uint8 *p = (uint8*)buffer;
+
+ while (n > data_available) {
+ memcpy(p, (uint8*)_out + data_offset, data_available);
+ p += data_available;
+ n -= data_available;
+
+ advancePlayer();
+
+ data_available = generateOutput() << 1;
+ data_offset = 0;
+ }
+
+ memcpy(p, (uint8*)_out + data_offset, n);
+ data_offset += n;
+ data_available -= n;
+
return numSamples;
}
+/**
+ * Initiate the playing of a sound resource.
+ * @param resnum Resource number
+ */
void SoundGen2GS::play(int resnum) {
AgiSoundEmuType type;
_playingSound = resnum;
type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type();
-
assert (type == AGI_SOUND_SAMPLE || type == AGI_SOUND_MIDI);
+ if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
+ warning("Trying to play sample or MIDI resource but not using Apple IIGS sound emulation mode");
+ return;
+ }
+
+ haltGenerators();
+
switch (type) {
case AGI_SOUND_SAMPLE: {
IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound];
- playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
+ const IIgsSampleHeader &header = sampleRes->getHeader();
+ _channels[kSfxMidiChannel].setInstrument(&header.instrument);
+ _channels[kSfxMidiChannel].setVolume(header.volume);
+ midiNoteOn(kSfxMidiChannel, header.pitch, 127);
break;
}
case AGI_SOUND_MIDI:
((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind();
+ _ticks = 0;
break;
default:
break;
@@ -81,213 +126,178 @@ void SoundGen2GS::play(int resnum) {
}
void SoundGen2GS::stop() {
+ haltGenerators();
_playingSound = -1;
-
- // Stops all sounds on all MIDI channels
- for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->stopSounds();
+ _playing = 0;
}
-void SoundGen2GS::playSound() {
- if (_playingSound == -1)
- return;
-
- if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) {
- playMidiSound();
- //warning("playSound: Trying to play an Apple IIGS MIDI sound. Not yet implemented");
- } else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) {
- //debugC(3, kDebugLevelSound, "playSound: Trying to play an Apple IIGS sample");
- playSampleSound();
- }
-
- if (!_playing) {
- _vm->_sound->soundIsFinished();
-
- _playingSound = -1;
- }
-}
-
-uint32 SoundGen2GS::mixSound() {
- int i, b;
-
- memset(_sndBuffer, 0, BUFFER_SIZE << 1);
+/**
+ * Fill output buffer by advancing the generators for a 1/60th of a second.
+ * @return Number of generated samples
+ */
+uint32 SoundGen2GS::generateOutput() {
+ memset(_out, 0, _outSize * 2 * 2);
if (!_playing || _playingSound == -1)
- return BUFFER_SIZE;
-
- // Handle Apple IIGS sound mixing here
- // TODO: Implement playing both waves in an oscillator
- // TODO: Implement swap-mode in an oscillator
- for (uint midiChan = 0; midiChan < _midiChannels.size(); midiChan++) {
- for (uint gsChan = 0; gsChan < _midiChannels[midiChan]._gsChannels.size(); gsChan++) {
- IIgsChannelInfo &channel = _midiChannels[midiChan]._gsChannels[gsChan];
- if (channel.playing()) { // Only mix in actively playing channels
- // Frequency multiplier was 1076.0 based on tests made with MESS 0.117.
- // Tests made with KEGS32 averaged the multiplier to around 1045.
- // So this is a guess but maybe it's 1046.5... i.e. C6's frequency?
- double hertz = C6_FREQ * pow(SEMITONE, fracToDouble(channel.note));
- channel.posAdd = doubleToFrac(hertz / getRate());
- channel.vol = doubleToFrac(fracToDouble(channel.envVol) * fracToDouble(channel.chanVol) / 127.0);
- double tempVol = fracToDouble(channel.vol)/127.0;
- for (i = 0; i < IIGS_BUFFER_SIZE; i++) {
- b = channel.relocatedSample[fracToInt(channel.pos)];
- // TODO: Find out what volume/amplification setting is loud enough
- // but still doesn't clip when playing many channels on it.
- _sndBuffer[i] += (int16) (b * tempVol * 256/4);
- channel.pos += channel.posAdd;
-
- if (channel.pos >= intToFrac(channel.size)) {
- if (channel.loop) {
- // Don't divide by zero on zero length samples
- channel.pos %= intToFrac(channel.size + (channel.size == 0));
- // Probably we should loop the envelope too
- channel.envSeg = 0;
- channel.envVol = channel.startEnvVol;
- } else {
- channel.pos = channel.chanVol = 0;
- channel.end = true;
- break;
- }
+ return _outSize * 2;
+
+ int16 *p = _out;
+ int n = _outSize;
+ while (n--) {
+ int outl = 0;
+ int outr = 0;
+ for (int k = 0; k < MAX_GENERATORS; k++) {
+ IIgsGenerator *g = &_generators[k];
+ if (!g->ins)
+ continue;
+ const IIgsInstrumentHeader *i = g->ins;
+
+ // Advance envelope
+ int vol = fracToInt(g->a);
+ if (g->a <= i->env[g->seg].bp) {
+ g->a += i->env[g->seg].inc * ENVELOPE_COEF;
+ if (g->a > i->env[g->seg].bp) {
+ g->a = i->env[g->seg].bp;
+ g->seg++;
+ }
+ } else {
+ g->a -= i->env[g->seg].inc * ENVELOPE_COEF;
+ if (g->a < i->env[g->seg].bp) {
+ g->a = i->env[g->seg].bp;
+ g->seg++;
+ }
+ }
+
+ // TODO: Advance vibrato here. The Apple IIGS uses a LFO with
+ // triangle wave to modulate the frequency of both oscillators.
+ // In Apple IIGS the vibrato and the envelope are updated at the
+ // same time, so the vibrato speed depends on ENVELOPE_COEF.
+
+ // Advance oscillators
+ int s0 = 0;
+ int s1 = 0;
+ if (!g->osc[0].halt) {
+ s0 = g->osc[0].base[fracToInt(g->osc[0].p)];
+ g->osc[0].p += g->osc[0].pd;
+ if ((uint)fracToInt(g->osc[0].p) >= g->osc[0].size) {
+ g->osc[0].p -= intToFrac(g->osc[0].size);
+ if (!g->osc[0].loop)
+ g->osc[0].halt = 1;
+ if (g->osc[0].swap) {
+ g->osc[0].halt = 1;
+ g->osc[1].halt = 0;
}
}
-
- if (channel.envSeg < ENVELOPE_SEGMENT_COUNT) {
- const IIgsEnvelopeSegment &seg = channel.ins->env.seg[channel.envSeg];
- // I currently assume enveloping works with the same speed as the MIDI
- // (i.e. with 1/60ths of a second ticks).
- // TODO: Check if enveloping really works with the same speed as MIDI
- frac_t envVolDelta = doubleToFrac(seg.inc/256.0);
- if (intToFrac(seg.bp) >= channel.envVol) {
- channel.envVol += envVolDelta;
- if (channel.envVol >= intToFrac(seg.bp)) {
- channel.envVol = intToFrac(seg.bp);
- channel.envSeg += 1;
- }
- } else {
- channel.envVol -= envVolDelta;
- if (channel.envVol <= intToFrac(seg.bp)) {
- channel.envVol = intToFrac(seg.bp);
- channel.envSeg += 1;
- }
+ }
+ if (!g->osc[1].halt) {
+ s1 = g->osc[1].base[fracToInt(g->osc[1].p)];
+ g->osc[1].p += g->osc[1].pd;
+ if ((uint)fracToInt(g->osc[1].p) >= g->osc[1].size) {
+ g->osc[1].p -= intToFrac(g->osc[1].size);
+ if (!g->osc[1].loop)
+ g->osc[1].halt = 1;
+ if (g->osc[1].swap) {
+ g->osc[0].halt = 0;
+ g->osc[1].halt = 1;
}
}
}
+
+ // Take envelope and MIDI volume information into account.
+ // Also amplify.
+ s0 *= vol * g->vel / 127 * 80 / 256;
+ s1 *= vol * g->vel / 127 * 80 / 256;
+
+ // Select output channel.
+ if (g->osc[0].chn)
+ outl += s0;
+ else
+ outr += s0;
+
+ if (g->osc[1].chn)
+ outl += s1;
+ else
+ outr += s1;
}
- }
-
- removeStoppedSounds();
- return IIGS_BUFFER_SIZE;
-}
-
-void SoundGen2GS::fillAudio(int16 *stream, uint len) {
- uint32 p = 0;
-
- // current number of audio bytes in _sndBuffer
- static uint32 data_available = 0;
- // offset of start of audio bytes in _sndBuffer
- static uint32 data_offset = 0;
-
- len <<= 2;
-
- debugC(5, kDebugLevelSound, "(%p, %d)", (void *)stream, len);
-
- while (len > data_available) {
- memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available);
- p += data_available;
- len -= data_available;
-
- playSound();
- data_available = mixSound() << 1;
- data_offset = 0;
+ if (outl > 32768)
+ outl = 32768;
+ if (outl < -32767)
+ outl = -32767;
+ if (outr > 32768)
+ outr = 32768;
+ if (outr < -32767)
+ outr = -32767;
+
+ *p++ = outl;
+ *p++ = outr;
}
- memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len);
- data_offset += len;
- data_available -= len;
+ return _outSize * 2;
}
-void SoundGen2GS::playSampleSound() {
- if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
- warning("Trying to play a sample but not using Apple IIGS sound emulation mode");
+void SoundGen2GS::advancePlayer() {
+ if (_playingSound == -1)
return;
- }
-
- if (_playingSound != -1)
- _playing = activeSounds() > 0;
-}
-void SoundGen2GS::stopSounds() {
- // Stops all sounds on all MIDI channels
- for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->stopSounds();
-}
-
-bool SoundGen2GS::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) {
- stopSounds();
- IIgsMidiChannel &channel = _midiChannels[kSfxMidiChannel];
-
- channel.setInstrument(&sampleHeader.instrument, sample);
- channel.setVolume(sampleHeader.volume);
- channel.noteOn(sampleHeader.pitch, 64); // Use default velocity (i.e. 64)
+ if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_MIDI) {
+ advanceMidiPlayer();
+ } else if (_vm->_game.sounds[_playingSound]->type() == AGI_SOUND_SAMPLE) {
+ _playing = activeGenerators() > 0;
+ }
- return true;
+ if (!_playing) {
+ _vm->_sound->soundIsFinished();
+ _playingSound = -1;
+ }
}
-void SoundGen2GS::playMidiSound() {
- if (_disabledMidi)
+void SoundGen2GS::advanceMidiPlayer() {
+ if (_disableMidi)
return;
const uint8 *p;
uint8 parm1, parm2;
- static uint8 cmd, ch;
+ static uint8 cmd, chn;
if (_playingSound == -1 || _vm->_game.sounds[_playingSound] == NULL) {
warning("Error playing Apple IIGS MIDI sound resource");
_playing = false;
-
return;
}
IIgsMidi *midiObj = (IIgsMidi *) _vm->_game.sounds[_playingSound];
+ _ticks++;
_playing = true;
p = midiObj->getPtr();
- midiObj->_soundBufTicks++;
-
while (true) {
- uint8 readByte = *p;
-
// Check for end of MIDI sequence marker (Can also be here before delta-time)
- if (readByte == MIDI_BYTE_STOP_SEQUENCE) {
+ if (*p == MIDI_STOP_SEQUENCE) {
debugC(3, kDebugLevelSound, "End of MIDI sequence (Before reading delta-time)");
_playing = false;
-
midiObj->rewind();
-
return;
- } else if (readByte == MIDI_BYTE_TIMER_SYNC) {
+ }
+ if (*p == MIDI_TIMER_SYNC) {
debugC(3, kDebugLevelSound, "Timer sync");
p++; // Jump over the timer sync byte as it's not needed
-
continue;
}
- uint8 deltaTime = readByte;
- if (midiObj->_midiTicks + deltaTime > midiObj->_soundBufTicks) {
+ // Check for delta time
+ uint8 delta = *p;
+ if (midiObj->_ticks + delta > _ticks)
break;
- }
- midiObj->_midiTicks += deltaTime;
- p++; // Jump over the delta-time byte as it was already taken care of
+ midiObj->_ticks += delta;
+ p++;
// Check for end of MIDI sequence marker (This time it after reading delta-time)
- if (*p == MIDI_BYTE_STOP_SEQUENCE) {
+ if (*p == MIDI_STOP_SEQUENCE) {
debugC(3, kDebugLevelSound, "End of MIDI sequence (After reading delta-time)");
_playing = false;
-
midiObj->rewind();
-
return;
}
@@ -295,36 +305,51 @@ void SoundGen2GS::playMidiSound() {
// Otherwise use running status (i.e. previously set command and channel).
if (*p & 0x80) {
cmd = *p++;
- ch = cmd & 0x0f;
+ chn = cmd & 0x0f;
cmd >>= 4;
}
switch (cmd) {
- case MIDI_CMD_NOTE_OFF:
+ case MIDI_NOTE_OFF:
parm1 = *p++;
parm2 = *p++;
- midiNoteOff(ch, parm1, parm2);
+ debugC(3, kDebugLevelSound, "channel %X: note off (key = %d, velocity = %d)", chn, parm1, parm2);
+ midiNoteOff(chn, parm1, parm2);
break;
- case MIDI_CMD_NOTE_ON:
+ case MIDI_NOTE_ON:
parm1 = *p++;
parm2 = *p++;
- midiNoteOn(ch, parm1, parm2);
+ debugC(3, kDebugLevelSound, "channel %X: note on (key = %d, velocity = %d)", chn, parm1, parm2);
+ midiNoteOn(chn, parm1, parm2);
break;
- case MIDI_CMD_CONTROLLER:
+ case MIDI_CONTROLLER:
parm1 = *p++;
parm2 = *p++;
- midiController(ch, parm1, parm2);
+ debugC(3, kDebugLevelSound, "channel %X: controller %02X = %02X", chn, parm1, parm2);
+ // 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).
+ switch (parm1) {
+ case 7:
+ _channels[chn].setVolume(parm2);
+ break;
+ }
break;
- case MIDI_CMD_PROGRAM_CHANGE:
+ case MIDI_PROGRAM_CHANGE:
parm1 = *p++;
- midiProgramChange(ch, parm1);
+ debugC(3, kDebugLevelSound, "channel %X: program change %02X", chn, parm1);
+ _channels[chn].setInstrument(getInstrument(parm1));
break;
- case MIDI_CMD_PITCH_WHEEL:
+ case MIDI_PITCH_WHEEL:
parm1 = *p++;
parm2 = *p++;
+ debugC(3, kDebugLevelSound, "channel %X: pitch wheel (unimplemented)", chn);
+ break;
- uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
- midiPitchWheel(wheelPos);
+ default:
+ debugC(3, kDebugLevelSound, "channel %X: unimplemented command %02X", chn, cmd);
break;
}
}
@@ -332,78 +357,102 @@ void SoundGen2GS::playMidiSound() {
midiObj->setPtr(p);
}
-void SoundGen2GS::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) {
- _midiChannels[channel].noteOff(note, velocity);
- debugC(3, kDebugLevelSound, "note off, channel %02x, note %02x, velocity %02x", channel, note, velocity);
-}
-
-void SoundGen2GS::midiNoteOn(uint8 channel, uint8 note, uint8 velocity) {
- _midiChannels[channel].noteOn(note, velocity);
- debugC(3, kDebugLevelSound, "note on, channel %02x, note %02x, velocity %02x", channel, note, velocity);
-}
-
-// TODO: Check if controllers behave differently on different MIDI channels
-// TODO: Doublecheck what other controllers than the volume controller do
-void SoundGen2GS::midiController(uint8 channel, uint8 controller, uint8 value) {
- IIgsMidiChannel &midiChannel = _midiChannels[channel];
-
- // 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).
- bool unimplemented = false;
- switch (controller) {
- case 7: // Volume
- midiChannel.setVolume(value);
- break;
- default:
- unimplemented = true;
- break;
+void SoundGen2GS::midiNoteOff(int channel, int note, int velocity) {
+ // Release keys within the given MIDI channel
+ for (int i = 0; i < MAX_GENERATORS; i++) {
+ if (_generators[i].chn == channel && _generators[i].key == note)
+ _generators[i].seg = _generators[i].ins->seg;
}
- debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : "");
-}
-
-void SoundGen2GS::midiProgramChange(uint8 channel, uint8 program) {
- _midiChannels[channel].setInstrument(getInstrument(program), _wave.begin());
- debugC(3, kDebugLevelSound, "program change %02x, channel %02x", program, channel);
-}
-
-void SoundGen2GS::midiPitchWheel(uint8 wheelPos) {
- // In all the tested Apple IIGS AGI MIDI resources
- // pitch wheel commands always used 0x2000 (Center position).
- // Therefore it should be quite safe to ignore this command.
- debugC(3, kDebugLevelSound, "pitch wheel position %04x (Unimplemented)", wheelPos);
}
-const IIgsInstrumentHeader* SoundGen2GS::getInstrument(uint8 program) const {
- return &_instruments[_midiProgToInst->map(program)];
-}
+void SoundGen2GS::midiNoteOn(int channel, int note, int velocity) {
+ if (!_channels[channel].getInstrument()) {
+ debugC(3, kDebugLevelSound, "midiNoteOn(): no instrument specified for channel %d", channel);
+ return;
+ }
-void SoundGen2GS::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) {
- _midiProgToInst = mapping;
+ // Allocate a generator for the note.
+ IIgsGenerator* g = allocateGenerator();
+ g->ins = _channels[channel].getInstrument();
+ const IIgsInstrumentHeader* i = g->ins;
+
+ // Pass information from the MIDI channel to the generator. Take
+ // velocity into account, although simplistically.
+ velocity *= 5 / 3;
+ if (velocity > 127)
+ velocity = 127;
+
+ g->key = note;
+ g->vel = velocity * _channels[channel].getVolume() / 127;
+ g->chn = channel;
+
+ // Instruments can define different samples to be used based on
+ // what the key is. Find the correct samples for our key.
+ int wa = 0;
+ int wb = 0;
+ while (wa < i->waveCount[0] - 1 && note > i->wave[0][wa].key)
+ wa++;
+ while (wb < i->waveCount[1] - 1 && note > i->wave[1][wb].key)
+ wb++;
+
+ // Prepare the generator.
+ g->osc[0].base = i->wave[0][wa].base;
+ g->osc[0].size = i->wave[0][wa].size;
+ g->osc[0].pd = doubleToFrac(midiKeyToFreq(note, (double)i->wave[0][wa].tune / 256.0) / (double)_sampleRate);
+ g->osc[0].p = 0;
+ g->osc[0].halt = i->wave[0][wa].halt;
+ g->osc[0].loop = i->wave[0][wa].loop;
+ g->osc[0].swap = i->wave[0][wa].swap;
+ g->osc[0].chn = i->wave[0][wa].chn;
+
+ g->osc[1].base = i->wave[1][wb].base;
+ g->osc[1].size = i->wave[1][wb].size;
+ g->osc[1].pd = doubleToFrac(midiKeyToFreq(note, (double)i->wave[1][wb].tune / 256.0) / (double)_sampleRate);
+ g->osc[1].p = 0;
+ g->osc[1].halt = i->wave[1][wb].halt;
+ g->osc[1].loop = i->wave[1][wb].loop;
+ g->osc[1].swap = i->wave[1][wb].swap;
+ g->osc[1].chn = i->wave[1][wb].chn;
+
+ g->seg = 0;
+ g->a = 0;
+
+ // Print debug messages for instruments with swap mode or vibrato enabled
+ if (g->osc[0].swap || g->osc[1].swap)
+ debugC(2, kDebugLevelSound, "Detected swap mode in a playing instrument. This is rare and is not tested well...");
+ if (i->vibDepth > 0)
+ debugC(2, kDebugLevelSound, "Detected vibrato in a playing instrument. Vibrato is not implemented, playing without...");
+}
+
+double SoundGen2GS::midiKeyToFreq(int key, double finetune) {
+ return 440.0 * pow(2.0, (15.0 + (double)key + finetune) / 12.0);
+}
+
+void SoundGen2GS::haltGenerators() {
+ for (int i = 0; i < MAX_GENERATORS; i++) {
+ _generators[i].osc[0].halt = true;
+ _generators[i].osc[1].halt = true;
+ }
}
-void SoundGen2GS::removeStoppedSounds() {
- for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->removeStoppedSounds();
+uint SoundGen2GS::activeGenerators() {
+ int n = 0;
+ for (int i = 0; i < MAX_GENERATORS; i++)
+ if (!_generators[i].osc[0].halt || !_generators[i].osc[1].halt)
+ n++;
+ return n;
}
-uint SoundGen2GS::activeSounds() const {
- uint result = 0;
-
- for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- result += iter->activeSounds();
-
- return result;
+void SoundGen2GS::setProgramChangeMapping(const IIgsMidiProgramMapping *mapping) {
+ _progToInst = mapping;
}
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
+ _len = len; // Save the resource's length
_type = READ_LE_UINT16(data); // Read sound resource's type
- _midiTicks = _soundBufTicks = 0;
+ _ticks = 0;
_isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2);
if (!_isValid) // Check for errors
@@ -419,7 +468,7 @@ IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : Agi
static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length) {
// Convert the wave from 8-bit unsigned to 8-bit signed format
for (uint i = 0; i < length; i++)
- dest[i] = (int8) ((int) source.readByte() - 128);
+ dest[i] = (int8) ((int) source.readByte() - ZERO_OFFSET);
return !(source.eos() || source.err());
}
@@ -446,330 +495,137 @@ IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) :
_header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too
}
- // Finalize the header info using the 8-bit unsigned sample data
- _header.finalize(stream);
-
// Convert sample data from 8-bit unsigned to 8-bit signed format
stream.seek(sampleStartPos);
_sample = new int8[_header.sampleSize];
- if (_sample != NULL)
+ if (_sample != NULL) {
_isValid = convertWave(stream, _sample, _header.sampleSize);
+ // Finalize header info using sample data
+ _header.finalize(_sample);
+ }
}
if (!_isValid) // Check for errors
warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len);
}
-/** Reads an Apple IIGS envelope from then given stream. */
-bool IIgsEnvelope::read(Common::SeekableReadStream &stream) {
- for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) {
- seg[segNum].bp = stream.readByte();
- seg[segNum].inc = stream.readUint16LE();
- }
-
- return !(stream.eos() || stream.err());
-}
-
-/** Reads an Apple IIGS wave information structure from the given stream. */
-bool IIgsWaveInfo::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
- top = stream.readByte();
- addr = stream.readByte() * 256;
- size = (1 << (stream.readByte() & 7)) * 256;
-
- // Read packed mode byte and parse it into parts
- byte packedModeByte = stream.readByte();
- channel = (packedModeByte >> 4) & 1; // Bit 4
- mode = (packedModeByte >> 1) & 3; // Bits 1-2
- halt = (packedModeByte & 1) != 0; // Bit 0 (Converted to boolean)
-
- relPitch = stream.readSint16LE();
- // Zero the wave address if we want to ignore the wave address info
- if (ignoreAddr)
- addr = 0;
-
- return !(stream.eos() || stream.err());
-}
-
-bool IIgsWaveInfo::finalize(Common::SeekableReadStream &uint8Wave) {
- uint32 startPos = uint8Wave.pos(); // Save stream's starting position
- uint8Wave.seek(addr, SEEK_CUR); // Seek to wave's address
-
- // Calculate the true sample size (A zero ends the sample prematurely)
- uint trueSize = size; // Set a default value for the result
- for (uint i = 0; i < size; i++) {
- if (uint8Wave.readByte() == 0) {
- trueSize = i;
- // A zero in the sample stream turns off looping
- // (At least that's what MESS 0.117 and KEGS32 0.91 seem to do)
- if (mode == OSC_MODE_LOOP)
- mode = OSC_MODE_ONESHOT;
- break;
- }
+bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
+ for (int i = 0; i < ENVELOPE_SEGMENT_COUNT; i++) {
+ env[i].bp = intToFrac(stream.readByte());
+ env[i].inc = intToFrac(stream.readUint16LE()) >> 8;
}
- size = trueSize; // Set the true sample size
-
- uint8Wave.seek(startPos); // Seek back to the stream's starting position
-
- return true;
-}
+ seg = stream.readByte();
+ /*priority =*/ stream.readByte(); // Not needed. 32 in all tested data.
+ bend = stream.readByte();
+ vibDepth = stream.readByte();
+ vibSpeed = stream.readByte();
+ stream.readByte(); // Not needed? 0 in all tested data.
+
+ waveCount[0] = stream.readByte();
+ waveCount[1] = stream.readByte();
+ for (int i = 0; i < 2; i++)
+ for (int k = 0; k < waveCount[i]; k++) {
+ wave[i][k].key = stream.readByte();
+ wave[i][k].base = (int8*)(stream.readByte() << 8);
+ wave[i][k].size = 0x100 << (stream.readByte() & 7);
+ uint8 b = stream.readByte();
+ wave[i][k].tune = stream.readUint16LE();
+
+ // For sample resources we ignore the address.
+ if (ignoreAddr)
+ wave[i][k].base = 0;
+
+ // Check for samples that extend out of the wavetable.
+ if ((int)wave[i][k].base + wave[i][k].size >= SIERRASTANDARD_SIZE) {
+ warning("Invalid data detected in the instrument set of Apple IIGS AGI. Continuing anyway...");
+ wave[i][k].size = SIERRASTANDARD_SIZE - (int)wave[i][k].base;
+ }
-bool IIgsOscillator::finalize(Common::SeekableReadStream &uint8Wave) {
- for (uint i = 0; i < WAVES_PER_OSCILLATOR; i++)
- if (!waves[i].finalize(uint8Wave))
- return false;
+ // Parse the generator mode byte to separate fields.
+ wave[i][k].halt = b & 0x1; // Bit 0 = HALT
+ wave[i][k].loop = !(b & 0x2); // Bit 1 =!LOOP
+ wave[i][k].swap = (b & 0x6) == 0x6; // Bit 1&2 = SWAP
+ wave[k][k].chn = (b >> 4) > 0; // Output channel (left or right)
+ }
- return true;
+ return !(stream.eos() || stream.err());
}
-bool IIgsOscillatorList::read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr) {
- // First read the A waves and then the B waves for the oscillators
- for (uint waveNum = 0; waveNum < WAVES_PER_OSCILLATOR; waveNum++)
- for (uint oscNum = 0; oscNum < oscillatorCount; oscNum++)
- if (!osc[oscNum].waves[waveNum].read(stream, ignoreAddr))
- return false;
-
- count = oscillatorCount; // Set the oscillator count
-
- return true;
-}
+bool IIgsInstrumentHeader::finalize(int8 *wavetable) {
+ // Calculate final pointers to sample data and detect true sample size
+ // in case the sample ends prematurely.
+ for (int i = 0; i < 2; i++)
+ for (int k = 0; k < waveCount[i]; k++) {
+ wave[i][k].base += (uint)wavetable;
-bool IIgsOscillatorList::finalize(Common::SeekableReadStream &uint8Wave) {
- for (uint i = 0; i < count; i++)
- if (!osc[i].finalize(uint8Wave))
- return false;
+ int8 *p = wave[i][k].base;
+ uint trueSize;
+ for (trueSize = 0; trueSize < wave[i][k].size; trueSize++)
+ if (p[trueSize] == -ZERO_OFFSET)
+ break;
+ wave[i][k].size = trueSize;
+ }
return true;
}
-bool IIgsInstrumentHeader::read(Common::SeekableReadStream &stream, bool ignoreAddr) {
- env.read(stream);
- relseg = stream.readByte();
- /*byte priority =*/ stream.readByte(); // Not needed? 32 in all tested data.
- bendrange = stream.readByte();
- vibdepth = stream.readByte();
- vibspeed = stream.readByte();
- /*byte spare =*/ stream.readByte(); // Not needed? 0 in all tested data.
- byte wac = stream.readByte(); // Read A wave count
- byte wbc = stream.readByte(); // Read B wave count
- oscList.read(stream, wac, ignoreAddr); // Read the oscillators
- return (wac == wbc) && !(stream.eos() || stream.err()); // A and B wave counts must match
-}
-
-bool IIgsInstrumentHeader::finalize(Common::SeekableReadStream &uint8Wave) {
- return oscList.finalize(uint8Wave);
-}
-
bool IIgsSampleHeader::read(Common::SeekableReadStream &stream) {
- type = stream.readUint16LE();
- pitch = stream.readByte();
- unknownByte_Ofs3 = stream.readByte();
- volume = stream.readByte();
- unknownByte_Ofs5 = stream.readByte();
- instrumentSize = stream.readUint16LE();
- sampleSize = stream.readUint16LE();
+ type = stream.readUint16LE();
+ pitch = stream.readByte();
+ unknownByte_Ofs3 = stream.readByte();
+ volume = stream.readByte();
+ unknownByte_Ofs5 = stream.readByte();
+ instrumentSize = stream.readUint16LE();
+ sampleSize = stream.readUint16LE();
// Read the instrument header *ignoring* its wave address info
-
return instrument.read(stream, true);
}
-bool IIgsSampleHeader::finalize(Common::SeekableReadStream &uint8Wave) {
- return instrument.finalize(uint8Wave);
-}
-
-void IIgsMidiChannel::stopSounds() {
- // Stops all sounds on this single MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->stop();
-
- _gsChannels.clear();
-}
-
-void IIgsMidiChannel::removeStoppedSounds() {
- for (int i = _gsChannels.size() - 1; i >= 0; i--)
- if (!_gsChannels[i].playing())
- _gsChannels.remove_at(i);
-}
-
-uint IIgsMidiChannel::activeSounds() const {
- uint result = 0;
-
- for (const_iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- if (!iter->end)
- result++;
-
- return result;
+bool IIgsSampleHeader::finalize(int8 *sample) {
+ return instrument.finalize(sample);
}
-void IIgsMidiChannel::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
- _instrument = instrument;
- _sample = sample;
-
- // Set program on each Apple IIGS channel playing on this MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->setInstrument(instrument, sample);
-}
-
-void IIgsMidiChannel::setVolume(uint8 volume) {
- _volume = volume;
-
- // Set volume on each Apple IIGS channel playing on this MIDI channel
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- iter->setChannelVolume(volume);
-}
-
-void IIgsMidiChannel::noteOff(uint8 note, uint8 velocity) {
- // Go through all the notes playing on this MIDI channel
- // and turn off the ones that are playing the given note
- for (iterator iter = _gsChannels.begin(); iter != _gsChannels.end(); ++iter)
- if (iter->origNote == note)
- iter->noteOff(velocity);
-}
-
-void IIgsMidiChannel::noteOn(uint8 note, uint8 velocity) {
- IIgsChannelInfo channel;
-
- // Use the default channel volume and instrument
- channel.setChannelVolume(_volume);
- channel.setInstrument(_instrument, _sample);
-
- // Set the note on and save the channel
- channel.noteOn(note, velocity);
- _gsChannels.push_back(channel);
-}
-
-void IIgsChannelInfo::rewind() {
- this->envVol = this->startEnvVol;
- this->envSeg = 0;
- this->pos = intToFrac(0);
-}
-
-void IIgsChannelInfo::setChannelVolume(uint8 volume) {
- this->chanVol = intToFrac(volume);
-}
-
-void IIgsChannelInfo::setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample) {
- assert(instrument != NULL && sample != NULL);
- this->ins = instrument;
- this->unrelocatedSample = sample;
-}
-
-// TODO/FIXME: Implement correctly and fully (Take velocity into account etc)
-void IIgsChannelInfo::noteOn(uint8 noteParam, uint8 velocity) {
- this->origNote = noteParam;
- this->startEnvVol = intToFrac(0);
- rewind();
-
- const IIgsWaveInfo *waveInfo = NULL;
-
- for (uint i = 0; i < ins->oscList.count; i++)
- if (ins->oscList(i).waves[0].top >= noteParam)
- waveInfo = &ins->oscList(i).waves[0];
-
- assert(waveInfo != NULL);
-
- this->relocatedSample = this->unrelocatedSample + waveInfo->addr;
- this->posAdd = intToFrac(0);
- this->note = intToFrac(noteParam) + doubleToFrac(waveInfo->relPitch/256.0);
- this->vol = doubleToFrac(fracToDouble(this->envVol) * fracToDouble(this->chanVol) / 127.0);
- this->loop = (waveInfo->mode == OSC_MODE_LOOP);
- this->size = waveInfo->size - waveInfo->addr;
- this->end = waveInfo->halt;
-}
-
-// TODO/FIXME: Implement correctly and fully (Take release time and velocity into account etc)
-void IIgsChannelInfo::noteOff(uint8 velocity) {
- this->loop = false;
- this->envSeg = ins->relseg;
-}
-
-void IIgsChannelInfo::stop() {
- this->end = true;
-}
-
-bool IIgsChannelInfo::playing() {
- return !this->end;
-}
-
-/**
- * A function object (i.e. a functor) for testing if a Common::FSNode
- * object's name is equal (Ignoring case) to a string or to at least
- * one of the strings in a list of strings. Can be used e.g. with find_if().
- */
-struct fsnodeNameEqualsIgnoreCase : public Common::UnaryFunction<const Common::FSNode&, bool> {
-// FIXME: This should be replaced; use SearchMan instead
- fsnodeNameEqualsIgnoreCase(const Common::StringArray &str) : _str(str) {}
- fsnodeNameEqualsIgnoreCase(const Common::String str) { _str.push_back(str); }
- bool operator()(const Common::FSNode &param) const {
- for (Common::StringArray::const_iterator iter = _str.begin(); iter != _str.end(); ++iter)
- if (param.getName().equalsIgnoreCase(*iter))
- return true;
- return false;
- }
-private:
- Common::StringArray _str;
-};
+//###
+//### LOADER METHODS
+//###
bool SoundGen2GS::loadInstruments() {
- // Check that the platform is Apple IIGS, as only it uses custom instruments
- if (_vm->getPlatform() != Common::kPlatformApple2GS) {
- debugC(3, kDebugLevelSound, "Platform isn't Apple IIGS so not loading any instruments");
- return true;
- }
-
// Get info on the particular Apple IIGS AGI game's executable
- const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID) _vm->getGameID());
+ const IIgsExeInfo *exeInfo = getIIgsExeInfo((enum AgiGameID)_vm->getGameID());
if (exeInfo == NULL) {
warning("Unsupported Apple IIGS game, not loading instruments");
return false;
}
- // List files in the game path
- Common::FSList fslist;
- Common::FSNode dir(ConfMan.get("path"));
- if (!dir.getChildren(fslist, Common::FSNode::kListFilesOnly)) {
- warning("Invalid game path (\"%s\"), not loading Apple IIGS instruments", dir.getPath().c_str());
- return false;
- }
-
- // Populate executable filenames list (Long filename and short filename) for searching
- Common::StringArray exeNames;
- exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS16");
- exeNames.push_back(Common::String(exeInfo->exePrefix) + ".SYS");
+ // Find the executable file and the wavetable file
+ Common::ArchiveMemberList exeNames, waveNames;
+ SearchMan.listMatchingMembers(exeNames, "*.SYS16");
+ SearchMan.listMatchingMembers(exeNames, "*.SYS");
+ SearchMan.listMatchingMembers(waveNames, "SIERRASTANDARD");
+ SearchMan.listMatchingMembers(waveNames, "SIERRAST");
- // Populate wave filenames list (Long filename and short filename) for searching
- Common::StringArray waveNames;
- waveNames.push_back("SIERRASTANDARD");
- waveNames.push_back("SIERRAST");
-
- // Search for the executable file and the wave file (i.e. check if any of the filenames match)
- Common::FSList::const_iterator exeFsnode, waveFsnode;
- exeFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(exeNames));
- waveFsnode = Common::find_if(fslist.begin(), fslist.end(), fsnodeNameEqualsIgnoreCase(waveNames));
-
- // Make sure that we found the executable file
- if (exeFsnode == fslist.end()) {
- warning("Couldn't find Apple IIGS game executable (%s), not loading instruments", exeNames.begin()->c_str());
+ if (exeNames.empty()) {
+ warning("Couldn't find Apple IIGS game executable (*.SYS16 or *.SYS), not loading instruments");
return false;
}
-
- // Make sure that we found the wave file
- if (waveFsnode == fslist.end()) {
- warning("Couldn't find Apple IIGS wave file (%s), not loading instruments", waveNames.begin()->c_str());
+ if (waveNames.empty()) {
+ warning("Couldn't find Apple IIGS wave file (SIERRASTANDARD or SIERRAST), not loading instruments");
return false;
}
+ Common::String exeName = exeNames.front()->getName();
+ Common::String waveName = waveNames.front()->getName();
+
// Set the MIDI program change to instrument number mapping and
// load the instrument headers and their sample data.
- // None of the tested SIERRASTANDARD-files have zeroes in them so
- // there's no need to check for prematurely ending samples here.
setProgramChangeMapping(exeInfo->instSet->progToInst);
- return loadWaveFile(*waveFsnode, *exeInfo) && loadInstrumentHeaders(*exeFsnode, *exeInfo);
+ return loadWaveFile(waveName, *exeInfo) && loadInstrumentHeaders(exeName, *exeInfo);
}
/** Older Apple IIGS AGI MIDI program change to instrument number mapping. */
-static const MidiProgramChangeMapping progToInstMappingV1 = {
+static const IIgsMidiProgramMapping progToInstMappingV1 = {
{19, 20, 22, 23, 21, 24, 5, 5, 5, 5,
6, 7, 10, 9, 11, 9, 15, 8, 5, 5,
17, 16, 18, 12, 14, 5, 5, 5, 5, 5,
@@ -778,8 +634,9 @@ static const MidiProgramChangeMapping progToInstMappingV1 = {
5
};
-/** Newer Apple IIGS AGI MIDI program change to instrument number mapping. */
-static const MidiProgramChangeMapping progToInstMappingV2 = {
+/** Newer Apple IIGS AGI MIDI program change to instrument number mapping.
+ FIXME: Some instrument choices sound wrong. */
+static const IIgsMidiProgramMapping progToInstMappingV2 = {
{21, 22, 24, 25, 23, 26, 6, 6, 6, 6,
7, 9, 12, 8, 13, 11, 17, 10, 6, 6,
19, 18, 20, 14, 16, 6, 6, 6, 6, 6,
@@ -788,13 +645,39 @@ static const MidiProgramChangeMapping progToInstMappingV2 = {
6
};
-/** Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002). */
-static const InstrumentSetInfo instSetV1 = {
+// Older Apple IIGS AGI instrument set. Used only by Space Quest I (AGI v1.002).
+//
+// Instrument 0 uses vibrato.
+// Instrument 1 uses vibrato.
+// Instrument 3 uses vibrato.
+// Instrument 5 has swap mode enabled for the first oscillator.
+// Instruemnt 9 uses vibrato.
+// Instrument 10 uses vibrato.
+// Instrument 12 uses vibrato.
+// Instrument 15 uses vibrato.
+// Instrument 16 uses vibrato.
+// Instrument 18 uses vibrato.
+static const IIgsInstrumentSetInfo instSetV1 = {
1192, 26, "7ee16bbc135171ffd6b9120cc7ff1af2", "edd3bf8905d9c238e02832b732fb2e18", &progToInstMappingV1
};
-/** Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I. */
-static const InstrumentSetInfo instSetV2 = {
+// Newer Apple IIGS AGI instrument set (AGI v1.003+). Used by all others than Space Quest I.
+//
+// Instrument 0 uses vibrato.
+// Instrument 1 uses vibrato.
+// Instrument 3 uses vibrato.
+// Instrument 6 has swap mode enabled for the first oscillator.
+// Instrument 11 uses vibrato.
+// Instrument 12 uses vibrato.
+// Instrument 14 uses vibrato.
+// Instrument 17 uses vibrato.
+// Instrument 18 uses vibrato.
+// Instrument 20 uses vibrato.
+//
+// In KQ1 intro and in LSL intro one (and the same, or at least similar)
+// instrument is using vibrato. In PQ intro there is also one instrument
+// using vibrato.
+static const IIgsInstrumentSetInfo instSetV2 = {
1292, 28, "b7d428955bb90721996de1cbca25e768", "c05fb0b0e11deefab58bc68fbd2a3d07", &progToInstMappingV2
};
@@ -826,15 +709,14 @@ const IIgsExeInfo *SoundGen2GS::getIIgsExeInfo(enum AgiGameID gameid) const {
return NULL;
}
-bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) {
- bool loadedOk = false; // Was loading successful?
+bool SoundGen2GS::loadInstrumentHeaders(Common::String &exePath, const IIgsExeInfo &exeInfo) {
Common::File file;
// Open the executable file and check that it has correct size
file.open(exePath);
if (file.size() != (int32)exeInfo.exeSize) {
debugC(3, kDebugLevelSound, "Apple IIGS executable (%s) has wrong size (Is %d, should be %d)",
- exePath.getPath().c_str(), file.size(), exeInfo.exeSize);
+ exePath.c_str(), file.size(), exeInfo.exeSize);
}
// Read the whole executable file into memory
@@ -842,50 +724,49 @@ bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIg
file.close();
// Check that we got enough data to be able to parse the instruments
- if (data && data->size() >= (int32)(exeInfo.instSetStart + exeInfo.instSet->byteCount)) {
- // Check instrument set's length (The info's saved in the executable)
- data->seek(exeInfo.instSetStart - 4);
- uint16 instSetByteCount = data->readUint16LE();
- if (instSetByteCount != exeInfo.instSet->byteCount) {
- debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)",
- instSetByteCount, exeInfo.instSet->byteCount, exePath.getPath().c_str());
- }
+ if (!data || data->size() < (int32)(exeInfo.instSetStart + exeInfo.instSet->byteCount)) {
+ warning("Error loading instruments from Apple IIGS executable (%s)", exePath.c_str());
+ return false;
+ }
- // Check instrument set's md5sum
- data->seek(exeInfo.instSetStart);
+ // Check instrument set's length (The info's saved in the executable)
+ data->seek(exeInfo.instSetStart - 4);
+ uint16 instSetByteCount = data->readUint16LE();
+ if (instSetByteCount != exeInfo.instSet->byteCount) {
+ debugC(3, kDebugLevelSound, "Wrong instrument set size (Is %d, should be %d) in Apple IIGS executable (%s)",
+ instSetByteCount, exeInfo.instSet->byteCount, exePath.c_str());
+ }
- Common::String md5str = Common::computeStreamMD5AsString(*data, exeInfo.instSet->byteCount);
- if (md5str != exeInfo.instSet->md5) {
- warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless",
- md5str.c_str(), exePath.getPath().c_str());
- }
+ // Check instrument set's md5sum
+ data->seek(exeInfo.instSetStart);
+ Common::String md5str = Common::computeStreamMD5AsString(*data, exeInfo.instSet->byteCount);
+ if (md5str != exeInfo.instSet->md5) {
+ warning("Unknown Apple IIGS instrument set (md5: %s) in %s, trying to use it nonetheless",
+ md5str.c_str(), exePath.c_str());
+ }
- // Read in the instrument set one instrument at a time
- data->seek(exeInfo.instSetStart);
+ // Read in the instrument set one instrument at a time
+ data->seek(exeInfo.instSetStart);
- // Load the instruments
- _instruments.clear();
- _instruments.reserve(exeInfo.instSet->instCount);
+ _instruments.clear();
+ _instruments.reserve(exeInfo.instSet->instCount);
- IIgsInstrumentHeader instrument;
- for (uint i = 0; i < exeInfo.instSet->instCount; i++) {
- if (!instrument.read(*data)) {
- warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments",
- i + 1, exeInfo.instSet->instCount, exePath.getPath().c_str());
- break;
- }
- _instruments.push_back(instrument); // Add the successfully loaded instrument to the instruments array
+ IIgsInstrumentHeader instrument;
+ for (uint i = 0; i < exeInfo.instSet->instCount; i++) {
+ if (!instrument.read(*data)) {
+ warning("Error loading Apple IIGS instrument (%d. of %d) from %s, not loading more instruments",
+ i + 1, exeInfo.instSet->instCount, exePath.c_str());
+ break;
}
+ instrument.finalize(_wavetable);
+ _instruments.push_back(instrument);
+ }
- // Loading was successful only if all instruments were loaded successfully
- loadedOk = (_instruments.size() == exeInfo.instSet->instCount);
- } else // Couldn't read enough data from the executable file
- warning("Error loading instruments from Apple IIGS executable (%s)", exePath.getPath().c_str());
-
- return loadedOk;
+ // Loading was successful only if all instruments were loaded successfully
+ return (_instruments.size() == exeInfo.instSet->instCount);
}
-bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) {
+bool SoundGen2GS::loadWaveFile(Common::String &wavePath, const IIgsExeInfo &exeInfo) {
Common::File file;
// Open the wave file and read it into memory
@@ -894,23 +775,22 @@ bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo
file.close();
// Check that we got the whole wave file
- if (uint8Wave && uint8Wave->size() == SIERRASTANDARD_SIZE) {
- // Check wave file's md5sum
- Common::String md5str = Common::computeStreamMD5AsString(*uint8Wave, SIERRASTANDARD_SIZE);
- if (md5str != exeInfo.instSet->waveFileMd5) {
- warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \
+ if (!uint8Wave || (uint8Wave->size() != SIERRASTANDARD_SIZE)) {
+ warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.c_str());
+ return false;
+ }
+
+ // Check wave file's md5sum
+ Common::String md5str = Common::computeStreamMD5AsString(*uint8Wave, SIERRASTANDARD_SIZE);
+ if (md5str != exeInfo.instSet->waveFileMd5) {
+ warning("Unknown Apple IIGS wave file (md5: %s, game: %s).\n" \
"Please report the information on the previous line to the ScummVM team.\n" \
"Using the wave file as it is - music may sound weird", md5str.c_str(), exeInfo.exePrefix);
- }
-
- uint8Wave->seek(0); // Seek wave to its start
- // Convert the wave file from 8-bit unsigned to 8-bit signed and save the result
- _wave.resize(uint8Wave->size());
- return convertWave(*uint8Wave, _wave.begin(), uint8Wave->size());
- } else { // Couldn't read the wave file or it had incorrect size
- warning("Error loading Apple IIGS wave file (%s), not loading instruments", wavePath.getPath().c_str());
- return false;
}
+
+ // Convert the wave file to 8-bit signed and save the result
+ uint8Wave->seek(0);
+ return convertWave(*uint8Wave, _wavetable, SIERRASTANDARD_SIZE);
}
} // End of namespace Agi
diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h
index d9c7b8d2f1..732d3cd12b 100644
--- a/engines/agi/sound_2gs.h
+++ b/engines/agi/sound_2gs.h
@@ -28,45 +28,30 @@
namespace Agi {
-#define BUFFER_SIZE 410
-
-// Apple IIGS MIDI uses 60 ticks per second (Based on tests with Apple IIGS
-// KQ1 and SQ1 under MESS 0.124a). So we make the audio buffer size to be a
-// 1/60th of a second in length. That should be getSampleRate() / 60 samples
-// in length but as getSampleRate() is always 22050 at the moment we just use
-// the hardcoded value of 368 (22050/60 = 367.5 which rounds up to 368).
-// FIXME: Use getSampleRate() / 60 rather than a hardcoded value
-#define IIGS_BUFFER_SIZE 368
-
-// 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
-};
-
-#define ENVELOPE_SEGMENT_COUNT 8
-struct IIgsEnvelope {
- IIgsEnvelopeSegment seg[ENVELOPE_SEGMENT_COUNT];
-
- /** Reads an Apple IIGS envelope from then given stream. */
- bool read(Common::SeekableReadStream &stream);
-};
-
-// 2**(1/12) i.e. the 12th root of 2
-#define SEMITONE 1.059463094359295
-
-// C6's frequency is A4's (440 Hz) frequency but one full octave and three semitones higher
-// i.e. C6_FREQ = 440 * pow(2.0, 15/12.0)
-#define C6_FREQ 1046.502261202395
+// Sample data in SIERRASTANDARD files is in unsigned 8-bit format. A zero
+// occurring in the sample data causes the ES5503 wavetable sound chip in
+// Apple IIGS to halt the corresponding oscillator immediately. We preprocess
+// the sample data by converting it to signed values and the instruments by
+// detecting prematurely stopping samples beforehand.
+//
+// Note: None of the tested SIERRASTANDARD files have zeroes in them. So in
+// practice there is no need to check for them. However, they still do exist
+// in the sample resources.
+#define ZERO_OFFSET 0x80
+
+// Apple IIGS envelope update frequency defaults to 100Hz. It can be changed,
+// so there might be differences per game, for example.
+#define ENVELOPE_COEF 100 / _sampleRate
+
+// MIDI player commands
+#define MIDI_NOTE_OFF 0x8
+#define MIDI_NOTE_ON 0x9
+#define MIDI_CONTROLLER 0xB
+#define MIDI_PROGRAM_CHANGE 0xC
+#define MIDI_PITCH_WHEEL 0xE
+
+#define MIDI_STOP_SEQUENCE 0xFC
+#define MIDI_TIMER_SYNC 0xF8
// Size of the SIERRASTANDARD file (i.e. the wave file i.e. the sample data used by the instruments).
#define SIERRASTANDARD_SIZE 65536
@@ -75,63 +60,34 @@ struct IIgsEnvelope {
// Chosen empirically based on Apple IIGS AGI game data, increase if needed.
#define MAX_INSTRUMENTS 28
-struct IIgsWaveInfo {
- uint8 top;
- uint addr;
- uint size;
-// Oscillator channel
-#define OSC_CHANNEL_RIGHT 0
-#define OSC_CHANNEL_LEFT 1
- uint channel;
-// Oscillator mode
-#define OSC_MODE_LOOP 0
-#define OSC_MODE_ONESHOT 1
-#define OSC_MODE_SYNC_AM 2
-#define OSC_MODE_SWAP 3
- uint mode;
- bool halt;
- int16 relPitch; ///< Relative pitch in semitones (Signed 8b.8b fixed point)
-
- /** Reads an Apple IIGS wave information structure from the given stream. */
- bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false);
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
-
-// Number of waves per Apple IIGS sound oscillator
-#define WAVES_PER_OSCILLATOR 2
-
-/** An Apple IIGS sound oscillator. Consists always of two waves. */
-struct IIgsOscillator {
- IIgsWaveInfo waves[WAVES_PER_OSCILLATOR];
-
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
-
-// Maximum number of oscillators in an Apple IIGS instrument.
-// Chosen empirically based on Apple IIGS AGI game data, increase if needed.
-#define MAX_OSCILLATORS 4
-
-/** An Apple IIGS sound oscillator list. */
-struct IIgsOscillatorList {
- uint count; ///< Oscillator count
- IIgsOscillator osc[MAX_OSCILLATORS]; ///< The oscillators
-
- /** Indexing operators for easier access to the oscillators. */
- const IIgsOscillator &operator()(uint index) const { return osc[index]; }
- IIgsOscillator &operator()(uint index) { return osc[index]; }
+// The MIDI player allocates one generator for each note it starts to play.
+// Here the maximum number of generators is defined. Feel free to increase
+// this if it does not seem to be enough.
+#define MAX_GENERATORS 16
- /** Reads an Apple IIGS oscillator list from the given stream. */
- bool read(Common::SeekableReadStream &stream, uint oscillatorCount, bool ignoreAddr = false);
- bool finalize(Common::SeekableReadStream &uint8Wave);
-};
+#define ENVELOPE_SEGMENT_COUNT 8
+#define MAX_OSCILLATOR_WAVES 127 // Maximum is one for every MIDI key
struct IIgsInstrumentHeader {
- IIgsEnvelope env;
- uint8 relseg;
- uint8 bendrange;
- uint8 vibdepth;
- uint8 vibspeed;
- IIgsOscillatorList oscList;
+ struct {
+ frac_t bp; ///< Envelope segment breakpoint
+ frac_t inc; ///< Envelope segment velocity
+ } env[ENVELOPE_SEGMENT_COUNT];
+ uint8 seg; ///< Envelope release segment
+ uint8 bend; ///< Maximum range for pitch bend
+ uint8 vibDepth; ///< Vibrato depth
+ uint8 vibSpeed; ///< Vibrato speed
+ uint8 waveCount[2]; ///< Wave count for both generators
+ struct {
+ uint8 key; ///< Highest MIDI key to use this wave
+ int8* base; ///< Pointer to wave data
+ uint size; ///< Wave size
+ bool halt; ///< Oscillator halted?
+ bool loop; ///< Loop mode?
+ bool swap; ///< Swap mode?
+ bool chn; ///< Output channel (left / right)
+ int16 tune; ///< Fine tune in semitones (8.8 fixed point)
+ } wave[2][MAX_OSCILLATOR_WAVES];
/**
* Read an Apple IIGS instrument header from the given stream.
@@ -140,7 +96,7 @@ struct IIgsInstrumentHeader {
* @return True if successful, false otherwise.
*/
bool read(Common::SeekableReadStream &stream, bool ignoreAddr = false);
- bool finalize(Common::SeekableReadStream &uint8Wave);
+ bool finalize(int8 *);
};
struct IIgsSampleHeader {
@@ -159,33 +115,29 @@ struct IIgsSampleHeader {
* @return True if successful, false otherwise.
*/
bool read(Common::SeekableReadStream &stream);
- bool finalize(Common::SeekableReadStream &uint8Wave);
+ bool finalize(int8 *sample);
};
-struct IIgsChannelInfo {
- const IIgsInstrumentHeader *ins; ///< Instrument info
- const int8 *relocatedSample; ///< Source sample data (8-bit signed format) using relocation
- const int8 *unrelocatedSample; ///< Source sample data (8-bit signed format) without relocation
- frac_t pos; ///< Current sample position
- frac_t posAdd; ///< Current sample position adder (Calculated using note, vibrato etc)
- uint8 origNote; ///< The original note without the added relative pitch
- frac_t note; ///< Note (With the added relative pitch)
- frac_t vol; ///< Current volume (Takes both channel volume and enveloping into account)
- frac_t chanVol; ///< Channel volume
- frac_t startEnvVol; ///< Starting envelope volume
- frac_t envVol; ///< Current envelope volume
- uint envSeg; ///< Current envelope segment
- uint size; ///< Sample size
- bool loop; ///< Should we loop the sample?
- bool end; ///< Has the playing ended?
-
- void rewind(); ///< Rewinds the sound playing on this channel to its start
- void setChannelVolume(uint8 volume); ///< Sets the channel volume
- void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample); ///< Sets the instrument to be used on this channel
- void noteOn(uint8 noteParam, uint8 velocity); ///< Starts playing a note on this channel
- void noteOff(uint8 velocity); ///< Releases the note on this channel
- void stop(); ///< Stops the note playing on this channel instantly
- bool playing(); ///< Is there a note playing on this channel?
+class IIgsGenerator {
+public:
+ IIgsGenerator() : ins(NULL), key(-1), chn(-1) {}
+
+ const IIgsInstrumentHeader *ins; ///< Currently used instrument
+ int key; ///< MIDI key
+ int vel; ///< MIDI velocity (& channel volume)
+ int chn; ///< MIDI channel
+ struct {
+ int8 *base; ///< Sample base pointer
+ uint size; ///< Sample size
+ frac_t p; ///< Sample pointer
+ frac_t pd; ///< Sample pointer delta
+ bool halt; ///< Is oscillator halted?
+ bool loop; ///< Is looping enabled?
+ bool swap; ///< Is swapping enabled?
+ bool chn; ///< Output channel (left / right)
+ } osc[2];
+ int seg; ///< Current envelope segment
+ frac_t a; ///< Current envelope amplitude
};
class IIgsMidi : public AgiSound {
@@ -195,15 +147,14 @@ public:
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; _midiTicks = _soundBufTicks = 0; }
+ virtual void rewind() { _ptr = _data + 2; _ticks = 0; }
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
public:
- uint _midiTicks; ///< MIDI song position in ticks (1/60ths of a second)
- uint _soundBufTicks; ///< Sound buffer position in ticks (1/60ths of a second)
+ uint _ticks; ///< MIDI song position in ticks (1/60ths of a second)
};
class IIgsSample : public AgiSound {
@@ -214,12 +165,12 @@ public:
const IIgsSampleHeader &getHeader() const { return _header; }
const int8 *getSample() const { return _sample; }
protected:
- IIgsSampleHeader _header; ///< Apple IIGS AGI sample header
- int8 *_sample; ///< Sample data (8-bit signed format)
+ IIgsSampleHeader _header; ///< Apple IIGS AGI sample header
+ int8 *_sample; ///< Sample data (8-bit signed format)
};
/** Apple IIGS MIDI program change to instrument number mapping. */
-struct MidiProgramChangeMapping {
+struct IIgsMidiProgramMapping {
byte midiProgToInst[44]; ///< Lookup table for the MIDI program number to instrument number mapping
byte undefinedInst; ///< The undefined instrument number
@@ -230,42 +181,34 @@ struct MidiProgramChangeMapping {
};
/** Apple IIGS AGI instrument set information. */
-struct InstrumentSetInfo {
- uint byteCount; ///< Length of the whole instrument set in bytes
- uint instCount; ///< Amount of instrument in the set
- const char *md5; ///< MD5 hex digest of the whole instrument set
+struct IIgsInstrumentSetInfo {
+ uint byteCount; ///< Length of the whole instrument set in bytes
+ uint instCount; ///< Amount of instrument in the set
+ const char *md5; ///< MD5 hex digest of the whole instrument set
const char *waveFileMd5; ///< MD5 hex digest of the wave file (i.e. the sample data used by the instruments)
- const MidiProgramChangeMapping *progToInst; ///< Program change to instrument number mapping
+ const IIgsMidiProgramMapping *progToInst; ///< Program change to instrument number mapping
};
/** Apple IIGS AGI executable file information. */
struct IIgsExeInfo {
- enum AgiGameID gameid; ///< Game ID
- const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc)
- uint agiVer; ///< Apple IIGS AGI version number, not strictly needed
- uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes
- uint instSetStart; ///< Starting offset of the instrument set inside the executable file
- const InstrumentSetInfo *instSet; ///< Information about the used instrument set
+ enum AgiGameID gameid; ///< Game ID
+ const char *exePrefix; ///< Prefix of the Apple IIGS AGI executable (e.g. "SQ", "PQ", "KQ4" etc)
+ uint agiVer; ///< Apple IIGS AGI version number, not strictly needed
+ uint exeSize; ///< Size of the Apple IIGS AGI executable file in bytes
+ uint instSetStart; ///< Starting offset of the instrument set inside the executable file
+ const IIgsInstrumentSetInfo *instSet; ///< Information about the used instrument set
};
class IIgsMidiChannel {
public:
- IIgsMidiChannel() : _instrument(0), _sample(0), _volume(0) {}
- uint activeSounds() const; ///< How many active sounds are playing?
- void setInstrument(const IIgsInstrumentHeader *instrument, const int8 *sample);
- void setVolume(uint8 volume);
- void noteOff(uint8 note, uint8 velocity);
- void noteOn(uint8 note, uint8 velocity);
- void stopSounds(); ///< Clears the channel of any sounds
- void removeStoppedSounds(); ///< Removes all stopped sounds from this MIDI channel
-public:
- typedef Common::Array<IIgsChannelInfo>::const_iterator const_iterator;
- typedef Common::Array<IIgsChannelInfo>::iterator iterator;
- Common::Array<IIgsChannelInfo> _gsChannels; ///< Apple IIGS channels playing on this MIDI channel
-protected:
+ IIgsMidiChannel() : _instrument(NULL), _volume(127) {}
+ void setInstrument(const IIgsInstrumentHeader *instrument) { _instrument = instrument; }
+ const IIgsInstrumentHeader* getInstrument() { return _instrument; }
+ void setVolume(int volume) { _volume = volume; }
+ int getVolume() { return _volume; }
+private:
const IIgsInstrumentHeader *_instrument; ///< Instrument used on this MIDI channel
- const int8 *_sample; ///< Sample data used on this MIDI channel
- uint8 _volume; ///< MIDI controller number 7 (Volume)
+ int _volume; ///< MIDI controller number 7 (Volume)
};
class SoundGen2GS : public SoundGen, public Audio::AudioStream {
@@ -276,73 +219,50 @@ public:
void play(int resnum);
void stop(void);
- // AudioStream API
int readBuffer(int16 *buffer, const int numSamples);
- bool isStereo() const {
- return false;
- }
-
- bool endOfData() const {
- return false;
- }
-
- int getRate() const {
- // FIXME: Ideally, we should use _sampleRate.
- return 22050;
- }
+ bool isStereo() const { return true; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _sampleRate; }
private:
- bool _disabledMidi;
- int _playingSound;
- bool _playing;
-
- int16 *_sndBuffer;
-
-/**
- * Class for managing Apple IIGS sound channels.
- * TODO: Check what instruments are used by default on the MIDI channels
- * FIXME: Some instrument choices sound wrong
- */
-private:
- typedef Common::Array<IIgsMidiChannel>::const_iterator const_iterator;
- typedef Common::Array<IIgsMidiChannel>::iterator iterator;
- static const uint kSfxMidiChannel = 0; ///< The MIDI channel used for playing sound effects
-
+ // Loader methods
bool loadInstruments();
- const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
+ bool loadInstrumentHeaders(Common::String &exePath, const IIgsExeInfo &exeInfo);
+ bool loadWaveFile(Common::String &wavePath, const IIgsExeInfo &exeInfo);
- void setProgramChangeMapping(const MidiProgramChangeMapping *mapping);
- bool loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo);
- bool loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo);
-
- // Miscellaneous methods
- void fillAudio(int16 *stream, uint len);
- uint32 mixSound();
- void playSound();
- uint activeSounds() const; ///< How many active sounds are playing?
- void stopSounds(); ///< Stops all sounds
- void removeStoppedSounds(); ///< Removes all stopped sounds from the MIDI channels
-
- // For playing Apple IIGS AGI samples (Sound effects etc)
- bool playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample);
- void playMidiSound();
- void playSampleSound();
-
- // MIDI commands
- void midiNoteOff(uint8 channel, uint8 note, uint8 velocity);
- void midiNoteOn(uint8 channel, uint8 note, uint8 velocity);
- void midiController(uint8 channel, uint8 controller, uint8 value);
- void midiProgramChange(uint8 channel, uint8 program);
- void midiPitchWheel(uint8 wheelPos);
- //protected:
- const IIgsInstrumentHeader* getInstrument(uint8 program) const;
- //public:
- Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel
- //protected:
- Common::Array<int8> _wave; ///< Sample data used by the Apple IIGS MIDI instruments
- const MidiProgramChangeMapping *_midiProgToInst; ///< MIDI program change to instrument number mapping
- Common::Array<IIgsInstrumentHeader> _instruments; ///< Instruments used by the Apple IIGS AGI
+ const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
+ void setProgramChangeMapping(const IIgsMidiProgramMapping *mapping);
+
+ // Player methods
+ void advancePlayer(); ///< Advance the player
+ void advanceMidiPlayer(); ///< Advance MIDI player
+ uint generateOutput(); ///< Fill the output buffer
+
+ void haltGenerators(); ///< Halt all generators
+ uint activeGenerators(); ///< How many generators are active?
+
+ void midiNoteOff(int channel, int note, int velocity);
+ void midiNoteOn(int channel, int note, int velocity);
+ double midiKeyToFreq(int key, double finetune);
+ IIgsInstrumentHeader* getInstrument(uint8 program) { return &_instruments[_progToInst->map(program)]; };
+ IIgsGenerator* allocateGenerator() { IIgsGenerator* g = &_generators[_nextGen++]; _nextGen %= 16; return g; }
+
+ bool _disableMidi; ///< Disable MIDI if loading instruments fail
+ int _playingSound; ///< Resource number for the currently playing sound
+ bool _playing; ///< True when the resource is still playing
+
+ IIgsGenerator _generators[MAX_GENERATORS]; ///< IIGS sound generators that are used to play single notes
+ uint _nextGen; ///< Next generator available for allocation
+ IIgsMidiChannel _channels[16]; ///< MIDI channels
+ Common::Array<IIgsInstrumentHeader> _instruments; ///< Instrument data
+ const IIgsMidiProgramMapping *_progToInst; ///< MIDI program number to instrument mapping
+ int8 *_wavetable; ///< Sample data used by the instruments
+ uint _ticks; ///< MIDI ticks (60Hz)
+ int16 *_out; ///< Output buffer
+ uint _outSize; ///< Output buffer size
+
+ static const int kSfxMidiChannel = 15; ///< MIDI channel used for playing sample resources
};
} // End of namespace Agi