aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/agi/module.mk2
-rw-r--r--engines/agi/sound.cpp553
-rw-r--r--engines/agi/sound.h141
-rw-r--r--engines/agi/sound_2gs.cpp259
-rw-r--r--engines/agi/sound_2gs.h58
-rw-r--r--engines/agi/sound_coco3.cpp80
-rwxr-xr-xengines/agi/sound_coco3.h73
-rwxr-xr-xengines/agi/sound_midi.cpp41
-rwxr-xr-xengines/agi/sound_midi.h13
-rwxr-xr-xengines/agi/sound_pcjr.cpp23
-rwxr-xr-xengines/agi/sound_pcjr.h25
-rw-r--r--engines/agi/sound_sarien.cpp357
-rwxr-xr-xengines/agi/sound_sarien.h120
13 files changed, 1036 insertions, 709 deletions
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index db640c02b5..2339d1019f 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -31,8 +31,10 @@ MODULE_OBJS := \
saveload.o \
sound.o \
sound_2gs.o \
+ sound_coco3.o \
sound_midi.o \
sound_pcjr.o \
+ sound_sarien.o \
sprite.o \
text.o \
view.o \
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index 0a71bcabca..2ca2706259 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -23,6 +23,15 @@
*
*/
+#include "agi/agi.h"
+
+#include "agi/sound_2gs.h"
+#include "agi/sound_coco3.h"
+#include "agi/sound_midi.h"
+#include "agi/sound_sarien.h"
+#include "agi/sound_pcjr.h"
+
+#if 0
#include "common/md5.h"
#include "common/config-manager.h"
#include "common/fs.h"
@@ -31,16 +40,11 @@
#include "sound/mididrv.h"
-#include "agi/agi.h"
-#include "agi/sound_2gs.h"
-#include "agi/sound_midi.h"
-#include "agi/sound_pcjr.h"
+#endif
namespace Agi {
-#define USE_INTERPOLATION
-
//
// TODO: add support for variable sampling rate in the output device
//
@@ -84,59 +88,17 @@ const uint8 *PCjrSound::getVoicePointer(uint voiceNum) {
return _data + voiceStartOffset;
}
-static const int16 waveformRamp[WAVEFORM_SIZE] = {
- 0, 8, 16, 24, 32, 40, 48, 56,
- 64, 72, 80, 88, 96, 104, 112, 120,
- 128, 136, 144, 152, 160, 168, 176, 184,
- 192, 200, 208, 216, 224, 232, 240, 255,
- 0, -248, -240, -232, -224, -216, -208, -200,
- -192, -184, -176, -168, -160, -152, -144, -136,
- -128, -120, -112, -104, -96, -88, -80, -72,
- -64, -56, -48, -40, -32, -24, -16, -8 // Ramp up
-};
-
-static const int16 waveformSquare[WAVEFORM_SIZE] = {
- 255, 230, 220, 220, 220, 220, 220, 220,
- 220, 220, 220, 220, 220, 220, 220, 220,
- 220, 220, 220, 220, 220, 220, 220, 220,
- 220, 220, 220, 220, 220, 220, 220, 110,
- -255, -230, -220, -220, -220, -220, -220, -220,
- -220, -220, -220, -220, -220, -220, -220, -220,
- -220, -220, -220, -220, -220, -220, -220, -220,
- -220, -220, -220, -110, 0, 0, 0, 0 // Square
-};
-
-static const int16 waveformMac[WAVEFORM_SIZE] = {
- 45, 110, 135, 161, 167, 173, 175, 176,
- 156, 137, 123, 110, 91, 72, 35, -2,
- -60, -118, -142, -165, -170, -176, -177, -179,
- -177, -176, -164, -152, -117, -82, -17, 47,
- 92, 137, 151, 166, 170, 173, 171, 169,
- 151, 133, 116, 100, 72, 43, -7, -57,
- -99, -141, -156, -170, -174, -177, -178, -179,
- -175, -172, -165, -159, -137, -114, -67, -19
-};
-
+#if 0
static const uint16 period[] = {
1024, 1085, 1149, 1218, 1290, 1367,
1448, 1534, 1625, 1722, 1825, 1933
};
-#if 0
static int noteToPeriod(int note) {
return 10 * (period[note % 12] >> (note / 12 - 3));
}
#endif
-int SoundMgr::readBuffer(int16 *buffer, const int numSamples) {
- if (_vm->_soundemu == SOUND_EMU_PCJR)
- _soundGen->premixerCall(buffer, numSamples);
- else
- premixerCall(buffer, numSamples / 2);
-
- return numSamples;
-}
-
void SoundMgr::unloadSound(int resnum) {
if (_vm->_game.dirSound[resnum].flags & RES_LOADED) {
if (_vm->_game.sounds[resnum]->isPlaying()) {
@@ -151,7 +113,6 @@ void SoundMgr::unloadSound(int resnum) {
}
void SoundMgr::startSound(int resnum, int flag) {
- int i;
AgiSoundEmuType type;
if (_vm->_game.sounds[resnum] != NULL && _vm->_game.sounds[resnum]->isPlaying())
@@ -172,46 +133,8 @@ void SoundMgr::startSound(int resnum, int flag) {
debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d) type = %d", resnum, flag, type);
- switch (type) {
- case AGI_SOUND_SAMPLE: {
- IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound];
- _gsSound->playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
- break;
- }
- case AGI_SOUND_MIDI:
- ((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind();
- break;
- case AGI_SOUND_4CHN:
- if (_vm->_soundemu == SOUND_EMU_MIDI) {
- _musicPlayer->playMIDI((MIDISound *)_vm->_game.sounds[resnum]);
- } else if (_vm->_soundemu == SOUND_EMU_PCJR) {
- _soundGen->play(resnum, flag);
- } else {
-
- PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum];
-
- // Initialize channel info
- for (i = 0; i < NUM_CHANNELS; i++) {
- _chn[i].type = type;
- _chn[i].flags = AGI_SOUND_LOOP;
-
- if (_env) {
- _chn[i].flags |= AGI_SOUND_ENVELOPE;
- _chn[i].adsr = AGI_SOUND_ENV_ATTACK;
- }
-
- _chn[i].ins = _waveform;
- _chn[i].size = WAVEFORM_SIZE;
- _chn[i].ptr = pcjrSound->getVoicePointer(i % 4);
- _chn[i].timer = 0;
- _chn[i].vol = 0;
- _chn[i].end = 0;
- }
- }
- break;
- }
+ _soundGen->play(resnum);
- memset(_sndBuffer, 0, BUFFER_SIZE << 1);
_endflag = flag;
// Nat Budin reports that the flag should be reset when sound starts
@@ -219,32 +142,15 @@ void SoundMgr::startSound(int resnum, int flag) {
}
void SoundMgr::stopSound() {
- int i;
-
debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
_endflag = -1;
- if (_vm->_soundemu != SOUND_EMU_APPLE2GS && _vm->_soundemu != SOUND_EMU_PCJR) {
- for (i = 0; i < NUM_CHANNELS; i++)
- stopNote(i);
- }
-
if (_playingSound != -1) {
if (_vm->_game.sounds[_playingSound]) // sanity checking
_vm->_game.sounds[_playingSound]->stop();
- if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- _gsSound->stopSounds();
- }
-
- if (_vm->_soundemu == SOUND_EMU_MIDI) {
- _musicPlayer->stop();
- }
-
- if (_vm->_soundemu == SOUND_EMU_PCJR) {
- _soundGen->stop();
- }
+ _soundGen->stop();
_playingSound = -1;
}
@@ -254,432 +160,57 @@ void SoundMgr::stopSound() {
}
int SoundMgr::initSound() {
- int r = -1;
+ return -1;
+}
- memset(_sndBuffer, 0, BUFFER_SIZE << 1);
- _env = false;
+void SoundMgr::deinitSound() {
+ stopSound();
+
+ delete _soundGen;
+}
+
+void SoundMgr::soundIsFinished() {
+ if (_endflag != -1)
+ _vm->setflag(_endflag, true);
+
+ if (_playingSound != -1)
+ _vm->_game.sounds[_playingSound]->stop();
+ _playingSound = -1;
+ _endflag = -1;
+}
+
+SoundMgr::SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer) {
+ _vm = agi;
+ _endflag = -1;
+ _playingSound = -1;
switch (_vm->_soundemu) {
case SOUND_EMU_NONE:
- _waveform = waveformRamp;
- _env = true;
- break;
case SOUND_EMU_AMIGA:
case SOUND_EMU_PC:
- _waveform = waveformSquare;
- break;
case SOUND_EMU_MAC:
- _waveform = waveformMac;
+ _soundGen = new SoundGenSarien(_vm, pMixer);
+ break;
+ case SOUND_EMU_PCJR:
+ _soundGen = new SoundGenPCJr(_vm, pMixer);
break;
case SOUND_EMU_APPLE2GS:
- _disabledMidi = !loadInstruments();
+ _soundGen = new SoundGen2GS(_vm, pMixer);
break;
case SOUND_EMU_COCO3:
+ _soundGen = new SoundGenCoCo3(_vm, pMixer);
break;
case SOUND_EMU_MIDI:
+ _soundGen = new SoundGenMIDI(_vm, pMixer);
break;
- case SOUND_EMU_PCJR:
- _soundGen = new SoundGenPCJr(_vm);
- break;
- }
-
- report("Initializing sound:\n");
-
- report("sound: envelopes ");
- if (_env) {
- report("enabled (decay=%d, sustain=%d)\n", ENV_DECAY, ENV_SUSTAIN);
- } else {
- report("disabled\n");
- }
-
- if (_vm->_soundemu != SOUND_EMU_MIDI)
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
-
- return r;
-}
-
-void SoundMgr::deinitSound() {
- debugC(3, kDebugLevelSound, "()");
-
- stopSound();
- _mixer->stopHandle(_soundHandle);
-}
-
-void SoundMgr::stopNote(int i) {
- _chn[i].adsr = AGI_SOUND_ENV_RELEASE;
-
- if (_useChorus) {
- // Stop chorus ;)
- if (_chn[i].type == AGI_SOUND_4CHN &&
- _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
- stopNote(i + 4);
- }
- }
-}
-
-void SoundMgr::playNote(int i, int freq, int vol) {
- if (!_vm->getflag(fSoundOn))
- vol = 0;
- else if (vol && _vm->_soundemu == SOUND_EMU_PC)
- vol = 160;
-
- _chn[i].phase = 0;
- _chn[i].freq = freq;
- _chn[i].vol = vol;
- _chn[i].env = 0x10000;
- _chn[i].adsr = AGI_SOUND_ENV_ATTACK;
-
- if (_useChorus) {
- // Add chorus ;)
- if (_chn[i].type == AGI_SOUND_4CHN &&
- _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
-
- int newfreq = freq * 1007 / 1000;
-
- if (freq == newfreq)
- newfreq++;
-
- playNote(i + 4, newfreq, vol * 2 / 3);
- }
- }
-}
-
-static int cocoFrequencies[] = {
- 130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246,
- 261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493,
- 523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987,
- 1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567, 1661, 1760, 1864, 1975,
- 2093, 2217, 2349, 2489, 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951
-};
-
-void SoundMgr::playCoCoSound() {
- int i = 0;
- CoCoNote note;
-
- do {
- note.read(_chn[i].ptr);
-
- if (note.freq != 0xff) {
- playNote(0, cocoFrequencies[note.freq], note.volume);
-
- uint32 start_time = _vm->_system->getMillis();
-
- while (_vm->_system->getMillis() < start_time + note.duration) {
- _vm->_system->updateScreen();
-
- _vm->_system->delayMillis(10);
- }
- }
- } while (note.freq != 0xff);
-}
-
-void SoundMgr::playAgiSound() {
- int i;
- AgiNote note;
-
- _playing = false;
- for (i = 0; i < (_vm->_soundemu == SOUND_EMU_PC ? 1 : 4); i++) {
- _playing |= !_chn[i].end;
- note.read(_chn[i].ptr); // Read a single note (Doesn't advance the pointer)
-
- if (_chn[i].end)
- continue;
-
- if ((--_chn[i].timer) <= 0) {
- stopNote(i);
-
- if (note.freqDiv != 0) {
- int volume = (note.attenuation == 0x0F) ? 0 : (0xFF - note.attenuation * 2);
- playNote(i, note.freqDiv * 10, volume);
- }
-
- _chn[i].timer = note.duration;
-
- if (_chn[i].timer == 0xffff) {
- _chn[i].end = 1;
- _chn[i].vol = 0;
- _chn[i].env = 0;
-
- if (_useChorus) {
- // chorus
- if (_chn[i].type == AGI_SOUND_4CHN && _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
- _chn[i + 4].vol = 0;
- _chn[i + 4].env = 0;
- }
- }
- }
- _chn[i].ptr += 5; // Advance the pointer to the next note data (5 bytes per note)
- }
- }
-}
-
-void SoundMgr::playSound() {
- int i;
-
- if (_endflag == -1)
- return;
-
- if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- if (_playingSound != -1) {
- 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();
- }
- }
- } else if (_vm->_soundemu == SOUND_EMU_COCO3) {
- playCoCoSound();
- } else {
- //debugC(3, kDebugLevelSound, "playSound: Trying to play a PCjr 4-channel sound");
- playAgiSound();
- }
-
- if (!_playing) {
- if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
- for (i = 0; i < NUM_CHANNELS; _chn[i++].vol = 0)
- ;
- }
-
- if (_endflag != -1)
- _vm->setflag(_endflag, true);
-
- if (_playingSound != -1)
- _vm->_game.sounds[_playingSound]->stop();
- _playingSound = -1;
- _endflag = -1;
}
}
-uint32 SoundMgr::mixSound() {
- register int i, p;
- const int16 *src;
- int c, b, m;
-
- memset(_sndBuffer, 0, BUFFER_SIZE << 1);
-
- 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
- if (_vm->_soundemu == SOUND_EMU_APPLE2GS) {
- for (uint midiChan = 0; midiChan < _gsSound->_midiChannels.size(); midiChan++) {
- for (uint gsChan = 0; gsChan < _gsSound->_midiChannels[midiChan]._gsChannels.size(); gsChan++) {
- IIgsChannelInfo &channel = _gsSound->_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;
- }
- }
- }
-
- 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;
- }
- }
- }
- }
- }
- }
- _gsSound->removeStoppedSounds();
- return IIGS_BUFFER_SIZE;
- } // else ...
-
- // Handle PCjr 4-channel sound mixing here
- for (c = 0; c < NUM_CHANNELS; c++) {
- if (!_chn[c].vol)
- continue;
-
- m = _chn[c].flags & AGI_SOUND_ENVELOPE ?
- _chn[c].vol * _chn[c].env >> 16 : _chn[c].vol;
-
- if (_chn[c].type != AGI_SOUND_4CHN || c != 3) {
- src = _chn[c].ins;
-
- p = _chn[c].phase;
- for (i = 0; i < BUFFER_SIZE; i++) {
- b = src[p >> 8];
-#ifdef USE_INTERPOLATION
- b += ((src[((p >> 8) + 1) % _chn[c].size] - src[p >> 8]) * (p & 0xff)) >> 8;
-#endif
- _sndBuffer[i] += (b * m) >> 4;
-
- p += (uint32) 118600 *4 / _chn[c].freq;
-
- // FIXME: Fingolfin asks: why is there a FIXME here? Please either clarify what
- // needs fixing, or remove it!
- // FIXME
- if (_chn[c].flags & AGI_SOUND_LOOP) {
- p %= _chn[c].size << 8;
- } else {
- if (p >= _chn[c].size << 8) {
- p = _chn[c].vol = 0;
- _chn[c].end = 1;
- break;
- }
- }
-
- }
- _chn[c].phase = p;
- } else {
- // Add white noise
- for (i = 0; i < BUFFER_SIZE; i++) {
- b = _vm->_rnd->getRandomNumber(255) - 128;
- _sndBuffer[i] += (b * m) >> 4;
- }
- }
-
- switch (_chn[c].adsr) {
- case AGI_SOUND_ENV_ATTACK:
- // not implemented
- _chn[c].adsr = AGI_SOUND_ENV_DECAY;
- break;
- case AGI_SOUND_ENV_DECAY:
- if (_chn[c].env > _chn[c].vol * ENV_SUSTAIN + ENV_DECAY) {
- _chn[c].env -= ENV_DECAY;
- } else {
- _chn[c].env = _chn[c].vol * ENV_SUSTAIN;
- _chn[c].adsr = AGI_SOUND_ENV_SUSTAIN;
- }
- break;
- case AGI_SOUND_ENV_SUSTAIN:
- break;
- case AGI_SOUND_ENV_RELEASE:
- if (_chn[c].env >= ENV_RELEASE) {
- _chn[c].env -= ENV_RELEASE;
- } else {
- _chn[c].env = 0;
- }
- }
- }
-
- return BUFFER_SIZE;
-}
-
-/**
- * Convert sample from 8-bit unsigned to 8-bit signed format.
- * @param source Source stream containing the 8-bit unsigned sample data.
- * @param dest Destination buffer for the 8-bit signed sample data.
- * @param length Length of the sample data to be converted.
- */
-bool SoundMgr::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);
- return !(source.eos() || source.err());
-}
-
-void SoundMgr::fillAudio(void *udata, int16 *stream, uint len) {
- SoundMgr *soundMgr = (SoundMgr *)udata;
- 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, %p, %d)", (void *)udata, (void *)stream, len);
-
- while (len > data_available) {
- memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, data_available);
- p += data_available;
- len -= data_available;
-
- soundMgr->playSound();
- data_available = soundMgr->mixSound() << 1;
- data_offset = 0;
- }
-
- memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len);
- data_offset += len;
- data_available -= len;
-}
-
-SoundMgr::SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer) : _chn() {
- _vm = agi;
- _mixer = pMixer;
- _sampleRate = pMixer->getOutputRate();
- _endflag = -1;
- _playingSound = -1;
- _env = false;
- _playing = false;
- _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE);
- _waveform = 0;
- _disabledMidi = false;
- _useChorus = true; // FIXME: Currently always true?
- _midiDriver = 0;
- _musicPlayer = 0;
- _soundGen = 0;
-
- _gsSound = new IIgsSoundMgr;
-
- if (_vm->_soundemu == SOUND_EMU_MIDI) {
- MidiDriverType midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB);
-
- _midiDriver = MidiDriver::createMidi(midiDriver);
- _musicPlayer = new MusicPlayer(_midiDriver, this);
- }
-}
-
-void SoundMgr::premixerCall(int16 *data, uint len) {
- if (_vm->_soundemu != SOUND_EMU_MIDI)
- fillAudio(this, data, len);
-}
-
void SoundMgr::setVolume(uint8 volume) {
// TODO
}
SoundMgr::~SoundMgr() {
- free(_sndBuffer);
- delete _gsSound;
-
- delete _soundGen;
- delete _musicPlayer;
- delete _midiDriver;
}
} // End of namespace Agi
diff --git a/engines/agi/sound.h b/engines/agi/sound.h
index 001b3e35c2..63b36e017c 100644
--- a/engines/agi/sound.h
+++ b/engines/agi/sound.h
@@ -26,15 +26,10 @@
#ifndef AGI_SOUND_H
#define AGI_SOUND_H
-#include "sound/audiostream.h"
#include "sound/mixer.h"
-class MidiDriver;
-
namespace Agi {
-#define BUFFER_SIZE 410
-
#define SOUND_EMU_NONE 0
#define SOUND_EMU_PC 1
#define SOUND_EMU_PCJR 2
@@ -44,13 +39,6 @@ namespace Agi {
#define SOUND_EMU_COCO3 6
#define SOUND_EMU_MIDI 7
-#define WAVEFORM_SIZE 64
-#define ENV_ATTACK 10000 /**< envelope attack rate */
-#define ENV_DECAY 1000 /**< envelope decay rate */
-#define ENV_SUSTAIN 100 /**< envelope sustain level */
-#define ENV_RELEASE 7500 /**< envelope release rate */
-#define NUM_CHANNELS 7 /**< number of sound channels */
-
/**
* AGI sound note structure.
*/
@@ -70,19 +58,6 @@ struct AgiNote {
}
};
-struct CoCoNote {
- uint8 freq;
- uint8 volume;
- uint16 duration; ///< Note duration
-
- /** Reads a CoCoNote through the given pointer. */
- void read(const uint8 *ptr) {
- freq = *ptr;
- volume = *(ptr + 1);
- duration = READ_LE_UINT16(ptr + 2);
- }
-};
-
/**
* AGI sound resource types.
* It's probably coincidence that all the values here are powers of two
@@ -94,47 +69,26 @@ enum AgiSoundEmuType {
AGI_SOUND_MIDI = 0x0002,
AGI_SOUND_4CHN = 0x0008
};
-enum AgiSoundFlags {
- AGI_SOUND_LOOP = 0x0001,
- AGI_SOUND_ENVELOPE = 0x0002
-};
-enum AgiSoundEnv {
- AGI_SOUND_ENV_ATTACK = 3,
- AGI_SOUND_ENV_DECAY = 2,
- AGI_SOUND_ENV_SUSTAIN = 1,
- AGI_SOUND_ENV_RELEASE = 0
-};
-
-
-/**
- * AGI engine sound channel structure.
- */
-struct ChannelInfo {
- AgiSoundEmuType type;
- const uint8 *ptr; // Pointer to the AgiNote data
- const int16 *ins;
- int32 size;
- uint32 phase;
- uint32 flags; // ORs values from AgiSoundFlags
- AgiSoundEnv adsr;
- int32 timer;
- uint32 end;
- uint32 freq;
- uint32 vol;
- uint32 env;
-};
class SoundMgr;
class SoundGen {
public:
- SoundGen() {}
+ SoundGen(AgiEngine *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) {
+ _sampleRate = pMixer->getOutputRate();
+ }
+
virtual ~SoundGen() {}
- virtual void play(int resnum, int flag) = 0;
+ virtual void play(int resnum) = 0;
virtual void stop(void) = 0;
- virtual void premixerCall(int16 *stream, int len) = 0;
+ AgiEngine *_vm;
+
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+
+ uint32 _sampleRate;
};
/**
@@ -177,81 +131,30 @@ protected:
uint16 _type; ///< Sound resource type
};
-class AgiEngine;
-class IIgsSoundMgr;
-class MusicPlayer;
-
-struct IIgsExeInfo;
-
-class SoundMgr : public Audio::AudioStream {
+class SoundMgr {
public:
SoundMgr(AgiEngine *agi, Audio::Mixer *pMixer);
~SoundMgr();
- virtual void setVolume(uint8 volume);
- // AudioStream API
- int readBuffer(int16 *buffer, const int numSamples);
+ void setVolume(uint8 volume);
- bool isStereo() const {
- return false;
- }
-
- bool endOfData() const {
- return false;
- }
+ void unloadSound(int);
+ void playSound();
+ int initSound();
+ void deinitSound();
+ void startSound(int, int);
+ void stopSound();
- int getRate() const {
- // FIXME: Ideally, we should use _sampleRate.
- return 22050;
- }
+ void soundIsFinished();
+private:
int _endflag;
AgiEngine *_vm;
-private:
- Audio::Mixer *_mixer;
- Audio::SoundHandle _soundHandle;
-
- MusicPlayer *_musicPlayer;
- MidiDriver *_midiDriver;
-
- uint32 _sampleRate;
-
- bool _playing;
- ChannelInfo _chn[NUM_CHANNELS];
- IIgsSoundMgr *_gsSound;
- int _playingSound;
- uint8 _env;
- bool _disabledMidi;
-
- int16 *_sndBuffer;
- const int16 *_waveform;
-
- bool _useChorus;
-
- void premixerCall(int16 *buf, uint len);
- void fillAudio(void *udata, int16 *stream, uint len);
-
SoundGen *_soundGen;
-public:
- void unloadSound(int);
- void playSound();
- int initSound();
- void deinitSound();
- void startSound(int, int);
- void stopSound();
- void stopNote(int i);
- void playNote(int i, int freq, int vol);
- void playAgiSound();
- void playCoCoSound();
- uint32 mixSound();
- bool loadInstruments();
- void playMidiSound();
- void playSampleSound();
- const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
- static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint length);
+ int _playingSound;
};
} // End of namespace Agi
diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp
index ce4aa160a0..056eac2d5c 100644
--- a/engines/agi/sound_2gs.cpp
+++ b/engines/agi/sound_2gs.cpp
@@ -23,10 +23,9 @@
*
*/
-#include "common/md5.h"
#include "common/config-manager.h"
#include "common/fs.h"
-#include "common/random.h"
+#include "common/md5.h"
#include "common/str-array.h"
#include "agi/agi.h"
@@ -34,6 +33,194 @@
namespace Agi {
+SoundGen2GS::SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
+ _disabledMidi = !loadInstruments();
+
+ _playingSound = -1;
+ _playing = false;
+
+ _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE);
+
+ _midiChannels.resize(16); // Set the amount of available MIDI channels
+
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+SoundGen2GS::~SoundGen2GS() {
+ _mixer->stopHandle(_soundHandle);
+
+ free(_sndBuffer);
+}
+
+int SoundGen2GS::readBuffer(int16 *buffer, const int numSamples) {
+ fillAudio(buffer, numSamples / 2);
+
+ return numSamples;
+}
+
+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);
+
+ switch (type) {
+ case AGI_SOUND_SAMPLE: {
+ IIgsSample *sampleRes = (IIgsSample *) _vm->_game.sounds[_playingSound];
+ playSampleSound(sampleRes->getHeader(), sampleRes->getSample());
+ break;
+ }
+ case AGI_SOUND_MIDI:
+ ((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind();
+ break;
+ default:
+ break;
+ }
+}
+
+void SoundGen2GS::stop() {
+ _playingSound = -1;
+
+ // Stops all sounds on all MIDI channels
+ for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
+ iter->stopSounds();
+}
+
+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);
+
+ 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;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ removeStoppedSounds();
+
+ return IIGS_BUFFER_SIZE;
+}
+
+/**
+ * Convert sample from 8-bit unsigned to 8-bit signed format.
+ * @param source Source stream containing the 8-bit unsigned sample data.
+ * @param dest Destination buffer for the 8-bit signed sample data.
+ * @param length Length of the sample data to be converted.
+ */
+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);
+ return !(source.eos() || source.err());
+}
+
+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;
+ }
+
+ memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len);
+ data_offset += len;
+ data_available -= len;
+}
+
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
@@ -77,7 +264,7 @@ IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) :
_sample = new int8[_header.sampleSize];
if (_sample != NULL)
- _isValid = SoundMgr::convertWave(stream, _sample, _header.sampleSize);
+ _isValid = convertWave(stream, _sample, _header.sampleSize);
}
if (!_isValid) // Check for errors
@@ -248,23 +435,23 @@ static const IIgsExeInfo IIgsExeInfos[] = {
{GID_GOLDRUSH, "GR", 0x3003, 148268, 0x8979, instSetV2}
};
-void IIgsSoundMgr::stopSounds() {
- // Stops all sounds on all MIDI channels
- for (iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
- iter->stopSounds();
-}
-
-void SoundMgr::playSampleSound() {
+void SoundGen2GS::playSampleSound() {
if (_vm->_soundemu != SOUND_EMU_APPLE2GS) {
warning("Trying to play a sample but not using Apple IIGS sound emulation mode");
return;
}
if (_playingSound != -1)
- _playing = _gsSound->activeSounds() > 0;
+ _playing = activeSounds() > 0;
}
-bool IIgsSoundMgr::playSampleSound(const IIgsSampleHeader &sampleHeader, const int8 *sample) {
+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];
@@ -283,7 +470,7 @@ void IIgsMidiChannel::stopSounds() {
_gsChannels.clear();
}
-void SoundMgr::playMidiSound() {
+void SoundGen2GS::playMidiSound() {
if (_disabledMidi)
return;
@@ -352,28 +539,28 @@ void SoundMgr::playMidiSound() {
case MIDI_CMD_NOTE_OFF:
parm1 = *p++;
parm2 = *p++;
- _gsSound->midiNoteOff(ch, parm1, parm2);
+ midiNoteOff(ch, parm1, parm2);
break;
case MIDI_CMD_NOTE_ON:
parm1 = *p++;
parm2 = *p++;
- _gsSound->midiNoteOn(ch, parm1, parm2);
+ midiNoteOn(ch, parm1, parm2);
break;
case MIDI_CMD_CONTROLLER:
parm1 = *p++;
parm2 = *p++;
- _gsSound->midiController(ch, parm1, parm2);
+ midiController(ch, parm1, parm2);
break;
case MIDI_CMD_PROGRAM_CHANGE:
parm1 = *p++;
- _gsSound->midiProgramChange(ch, parm1);
+ midiProgramChange(ch, parm1);
break;
case MIDI_CMD_PITCH_WHEEL:
parm1 = *p++;
parm2 = *p++;
uint16 wheelPos = ((parm2 & 0x7F) << 7) | (parm1 & 0x7F); // 14-bit value
- _gsSound->midiPitchWheel(wheelPos);
+ midiPitchWheel(wheelPos);
break;
}
}
@@ -381,19 +568,19 @@ void SoundMgr::playMidiSound() {
midiObj->setPtr(p);
}
-void IIgsSoundMgr::midiNoteOff(uint8 channel, uint8 note, uint8 velocity) {
+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 IIgsSoundMgr::midiNoteOn(uint8 channel, uint8 note, uint8 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 IIgsSoundMgr::midiController(uint8 channel, uint8 controller, uint8 value) {
+void SoundGen2GS::midiController(uint8 channel, uint8 controller, uint8 value) {
IIgsMidiChannel &midiChannel = _midiChannels[channel];
// The tested Apple IIGS AGI MIDI resources only used
@@ -413,31 +600,27 @@ void IIgsSoundMgr::midiController(uint8 channel, uint8 controller, uint8 value)
debugC(3, kDebugLevelSound, "controller %02x, ch %02x, val %02x%s", controller, channel, value, unimplemented ? " (Unimplemented)" : "");
}
-void IIgsSoundMgr::midiProgramChange(uint8 channel, uint8 program) {
+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 IIgsSoundMgr::midiPitchWheel(uint8 wheelPos) {
+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);
}
-IIgsSoundMgr::IIgsSoundMgr() {
- _midiChannels.resize(16); // Set the amount of available MIDI channels
-}
-
-const IIgsInstrumentHeader* IIgsSoundMgr::getInstrument(uint8 program) const {
+const IIgsInstrumentHeader* SoundGen2GS::getInstrument(uint8 program) const {
return &_instruments[_midiProgToInst->map(program)];
}
-void IIgsSoundMgr::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) {
+void SoundGen2GS::setProgramChangeMapping(const MidiProgramChangeMapping *mapping) {
_midiProgToInst = mapping;
}
-void IIgsSoundMgr::removeStoppedSounds() {
+void SoundGen2GS::removeStoppedSounds() {
for (Common::Array<IIgsMidiChannel>::iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
iter->removeStoppedSounds();
}
@@ -448,7 +631,7 @@ void IIgsMidiChannel::removeStoppedSounds() {
_gsChannels.remove_at(i);
}
-uint IIgsSoundMgr::activeSounds() const {
+uint SoundGen2GS::activeSounds() const {
uint result = 0;
for (Common::Array<IIgsMidiChannel>::const_iterator iter = _midiChannels.begin(); iter != _midiChannels.end(); ++iter)
@@ -561,14 +744,14 @@ bool IIgsChannelInfo::playing() {
* Finds information about an Apple IIGS AGI executable based on the game ID.
* @return A non-null IIgsExeInfo pointer if successful, otherwise NULL.
*/
-const IIgsExeInfo *SoundMgr::getIIgsExeInfo(enum AgiGameID gameid) const {
+const IIgsExeInfo *SoundGen2GS::getIIgsExeInfo(enum AgiGameID gameid) const {
for (int i = 0; i < ARRAYSIZE(IIgsExeInfos); i++)
if (IIgsExeInfos[i].gameid == gameid)
return &IIgsExeInfos[i];
return NULL;
}
-bool IIgsSoundMgr::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) {
+bool SoundGen2GS::loadInstrumentHeaders(const Common::FSNode &exePath, const IIgsExeInfo &exeInfo) {
bool loadedOk = false; // Was loading successful?
Common::File file;
@@ -628,7 +811,7 @@ bool IIgsSoundMgr::loadInstrumentHeaders(const Common::FSNode &exePath, const II
return loadedOk;
}
-bool IIgsSoundMgr::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) {
+bool SoundGen2GS::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInfo &exeInfo) {
Common::File file;
// Open the wave file and read it into memory
@@ -650,7 +833,7 @@ bool IIgsSoundMgr::loadWaveFile(const Common::FSNode &wavePath, const IIgsExeInf
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 SoundMgr::convertWave(*uint8Wave, _wave.begin(), 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;
@@ -676,7 +859,7 @@ private:
Common::StringArray _str;
};
-bool SoundMgr::loadInstruments() {
+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");
@@ -729,8 +912,8 @@ bool SoundMgr::loadInstruments() {
// 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.
- _gsSound->setProgramChangeMapping(&exeInfo->instSet.progToInst);
- return _gsSound->loadWaveFile(*waveFsnode, *exeInfo) && _gsSound->loadInstrumentHeaders(*exeFsnode, *exeInfo);
+ setProgramChangeMapping(&exeInfo->instSet.progToInst);
+ return loadWaveFile(*waveFsnode, *exeInfo) && loadInstrumentHeaders(*exeFsnode, *exeInfo);
}
} // End of namespace Agi
diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h
index 3669d29955..12dede0b69 100644
--- a/engines/agi/sound_2gs.h
+++ b/engines/agi/sound_2gs.h
@@ -27,9 +27,12 @@
#define AGI_SOUND_2GS_H
#include "common/frac.h"
+#include "sound/audiostream.h"
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
@@ -268,39 +271,78 @@ protected:
uint8 _volume; ///< MIDI controller number 7 (Volume)
};
+class SoundGen2GS : public SoundGen, public Audio::AudioStream {
+public:
+ SoundGen2GS(AgiEngine *vm, Audio::Mixer *pMixer);
+ ~SoundGen2GS();
+
+ 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;
+ }
+
+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
*/
-class IIgsSoundMgr {
-public:
+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
-public:
- // For initializing
- IIgsSoundMgr();
+
+ bool loadInstruments();
+ const IIgsExeInfo *getIIgsExeInfo(enum AgiGameID gameid) const;
+
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:
+ //protected:
const IIgsInstrumentHeader* getInstrument(uint8 program) const;
-public:
+ //public:
Common::Array<IIgsMidiChannel> _midiChannels; ///< Information about each MIDI channel
-protected:
+ //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
diff --git a/engines/agi/sound_coco3.cpp b/engines/agi/sound_coco3.cpp
new file mode 100644
index 0000000000..f054be0682
--- /dev/null
+++ b/engines/agi/sound_coco3.cpp
@@ -0,0 +1,80 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "agi/agi.h"
+
+#include "agi/sound_coco3.h"
+
+namespace Agi {
+
+static int cocoFrequencies[] = {
+ 130, 138, 146, 155, 164, 174, 184, 195, 207, 220, 233, 246,
+ 261, 277, 293, 311, 329, 349, 369, 391, 415, 440, 466, 493,
+ 523, 554, 587, 622, 659, 698, 739, 783, 830, 880, 932, 987,
+ 1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567, 1661, 1760, 1864, 1975,
+ 2093, 2217, 2349, 2489, 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951
+};
+
+SoundGenCoCo3::SoundGenCoCo3(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
+}
+
+SoundGenCoCo3::~SoundGenCoCo3() {
+}
+
+void SoundGenCoCo3::play(int resnum) {
+ int i = cocoFrequencies[0]; // Silence warning
+
+ i = i + 1;
+
+#if 0
+ int i = 0;
+ CoCoNote note;
+
+ do {
+ note.read(_chn[i].ptr);
+
+ if (note.freq != 0xff) {
+ playNote(0, cocoFrequencies[note.freq], note.volume);
+
+ uint32 start_time = _vm->_system->getMillis();
+
+ while (_vm->_system->getMillis() < start_time + note.duration) {
+ _vm->_system->updateScreen();
+
+ _vm->_system->delayMillis(10);
+ }
+ }
+ } while (note.freq != 0xff);
+#endif
+}
+
+void SoundGenCoCo3::stop() {
+}
+
+int SoundGenCoCo3::readBuffer(int16 *buffer, const int numSamples) {
+ return numSamples;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/sound_coco3.h b/engines/agi/sound_coco3.h
new file mode 100755
index 0000000000..b60f1937cd
--- /dev/null
+++ b/engines/agi/sound_coco3.h
@@ -0,0 +1,73 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef AGI_SOUND_COCO3_H
+#define AGI_SOUND_COCO3_H
+
+#include "sound/audiostream.h"
+
+namespace Agi {
+
+struct CoCoNote {
+ uint8 freq;
+ uint8 volume;
+ uint16 duration; ///< Note duration
+
+ /** Reads a CoCoNote through the given pointer. */
+ void read(const uint8 *ptr) {
+ freq = *ptr;
+ volume = *(ptr + 1);
+ duration = READ_LE_UINT16(ptr + 2);
+ }
+};
+
+class SoundGenCoCo3 : public SoundGen, public Audio::AudioStream {
+public:
+ SoundGenCoCo3(AgiEngine *vm, Audio::Mixer *pMixer);
+ ~SoundGenCoCo3();
+
+ 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;
+ }
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_SOUND_COCO3_H */
diff --git a/engines/agi/sound_midi.cpp b/engines/agi/sound_midi.cpp
index b31a4228fb..551df527cc 100755
--- a/engines/agi/sound_midi.cpp
+++ b/engines/agi/sound_midi.cpp
@@ -70,7 +70,10 @@ MIDISound::MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : A
warning("Error creating MIDI sound from resource %d (Type %d, length %d)", resnum, _type, len);
}
-MusicPlayer::MusicPlayer(MidiDriver *driver, SoundMgr *manager) : _parser(0), _driver(driver), _isPlaying(false), _passThrough(false), _isGM(false), _manager(manager) {
+SoundGenMIDI::SoundGenMIDI(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer), _parser(0), _isPlaying(false), _passThrough(false), _isGM(false) {
+ MidiDriverType midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB);
+ _driver = MidiDriver::createMidi(midiDriver);
+
memset(_channel, 0, sizeof(_channel));
memset(_channelVolume, 255, sizeof(_channelVolume));
_masterVolume = 0;
@@ -79,7 +82,7 @@ MusicPlayer::MusicPlayer(MidiDriver *driver, SoundMgr *manager) : _parser(0), _d
_midiMusicData = NULL;
}
-MusicPlayer::~MusicPlayer() {
+SoundGenMIDI::~SoundGenMIDI() {
_driver->setTimerCallback(NULL, NULL);
stop();
this->close();
@@ -88,12 +91,12 @@ MusicPlayer::~MusicPlayer() {
delete[] _midiMusicData;
}
-void MusicPlayer::setChannelVolume(int channel) {
+void SoundGenMIDI::setChannelVolume(int channel) {
int newVolume = _channelVolume[channel] * _masterVolume / 255;
_channel[channel]->volume(newVolume);
}
-void MusicPlayer::setVolume(int volume) {
+void SoundGenMIDI::setVolume(int volume) {
Common::StackLock lock(_mutex);
volume = CLIP(volume, 0, 255);
@@ -108,7 +111,7 @@ void MusicPlayer::setVolume(int volume) {
}
}
-int MusicPlayer::open() {
+int SoundGenMIDI::open() {
// Don't ever call open without first setting the output driver!
if (!_driver)
return 255;
@@ -121,14 +124,14 @@ int MusicPlayer::open() {
return 0;
}
-void MusicPlayer::close() {
+void SoundGenMIDI::close() {
stop();
if (_driver)
_driver->close();
_driver = 0;
}
-void MusicPlayer::send(uint32 b) {
+void SoundGenMIDI::send(uint32 b) {
if (_passThrough) {
_driver->send(b);
return;
@@ -163,11 +166,12 @@ void MusicPlayer::send(uint32 b) {
_channel[channel]->send(b);
}
-void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+void SoundGenMIDI::metaEvent(byte type, byte *data, uint16 length) {
switch (type) {
case 0x2F: // End of Track
stop();
+ _vm->_sound->soundIsFinished();
break;
default:
//warning("Unhandled meta event: %02x", type);
@@ -175,19 +179,23 @@ void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
}
}
-void MusicPlayer::onTimer(void *refCon) {
- MusicPlayer *music = (MusicPlayer *)refCon;
+void SoundGenMIDI::onTimer(void *refCon) {
+ SoundGenMIDI *music = (SoundGenMIDI *)refCon;
Common::StackLock lock(music->_mutex);
if (music->_parser)
music->_parser->onTimer();
}
-void MusicPlayer::playMIDI(MIDISound *track) {
+void SoundGenMIDI::play(int resnum) {
+ MIDISound *track;
+
stop();
_isGM = true;
+ track = (MIDISound *)_vm->_game.sounds[resnum];
+
// Convert AGI Sound data to MIDI
int midiMusicSize = convertSND2MIDI(track->_data, &_midiMusicData);
@@ -206,7 +214,7 @@ void MusicPlayer::playMIDI(MIDISound *track) {
}
}
-void MusicPlayer::stop() {
+void SoundGenMIDI::stop() {
Common::StackLock lock(_mutex);
if (!_isPlaying)
@@ -217,22 +225,19 @@ void MusicPlayer::stop() {
_parser->unloadMusic();
_parser = NULL;
}
-
- if (_manager->_endflag != -1)
- _manager->_vm->setflag(_manager->_endflag, true);
}
-void MusicPlayer::pause() {
+void SoundGenMIDI::pause() {
setVolume(-1);
_isPlaying = false;
}
-void MusicPlayer::resume() {
+void SoundGenMIDI::resume() {
syncVolume();
_isPlaying = true;
}
-void MusicPlayer::syncVolume() {
+void SoundGenMIDI::syncVolume() {
int volume = ConfMan.getInt("music_volume");
if (ConfMan.getBool("mute")) {
volume = -1;
diff --git a/engines/agi/sound_midi.h b/engines/agi/sound_midi.h
index 48216ceb12..26b75e0d70 100755
--- a/engines/agi/sound_midi.h
+++ b/engines/agi/sound_midi.h
@@ -46,10 +46,13 @@ protected:
uint16 _type; ///< Sound resource type
};
-class MusicPlayer : public MidiDriver {
+class SoundGenMIDI : public SoundGen, public MidiDriver {
public:
- MusicPlayer(MidiDriver *driver, SoundMgr *manager);
- ~MusicPlayer();
+ SoundGenMIDI(AgiEngine *vm, Audio::Mixer *pMixer);
+ ~SoundGenMIDI();
+
+ void play(int resnum);
+ void stop();
bool isPlaying() { return _isPlaying; }
void setPlaying(bool playing) { _isPlaying = playing; }
@@ -60,8 +63,6 @@ public:
void setNativeMT32(bool b) { _nativeMT32 = b; }
bool hasNativeMT32() { return _nativeMT32; }
- void playMIDI(MIDISound *track);
- void stop();
void pause();
void resume();
void setLoop(bool loop) { _looping = loop; }
@@ -86,7 +87,7 @@ public:
MidiParser *_parser;
Common::Mutex _mutex;
-protected:
+private:
static void onTimer(void *data);
void setChannelVolume(int channel);
diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp
index f00c4424bc..b9d701d7f7 100755
--- a/engines/agi/sound_pcjr.cpp
+++ b/engines/agi/sound_pcjr.cpp
@@ -105,7 +105,7 @@ const int8 dissolveDataV3[] = {
};
-SoundGenPCJr::SoundGenPCJr(AgiEngine *vm) : _vm(vm) {
+SoundGenPCJr::SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
_chanAllocated = 10240; // preallocate something which will most likely fit
_chanData = (int16 *)malloc(_chanAllocated << 1);
@@ -122,13 +122,19 @@ SoundGenPCJr::SoundGenPCJr(AgiEngine *vm) : _vm(vm) {
_dissolveMethod = 2;
else
_dissolveMethod = 0;
+
+ _dissolveMethod = 3;
+
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
SoundGenPCJr::~SoundGenPCJr() {
free(_chanData);
+
+ _mixer->stopHandle(_soundHandle);
}
-void SoundGenPCJr::play(int resnum, int flag) {
+void SoundGenPCJr::play(int resnum) {
PCjrSound *pcjrSound = (PCjrSound *)_vm->_game.sounds[resnum];
for (int i = 0; i < CHAN_MAX; i++) {
@@ -467,7 +473,7 @@ int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) {
return len;
}
-void SoundGenPCJr::premixerCall(int16 *stream, int len) {
+int SoundGenPCJr::readBuffer(int16 *stream, const int len) {
int streamCount;
int16 *sPtr, *cPtr;
@@ -480,6 +486,8 @@ void SoundGenPCJr::premixerCall(int16 *stream, int len) {
assert(stream);
+ bool finished = true;
+
for (int i = 0; i < CHAN_MAX; i++) {
// get channel data(chan.userdata)
if (chanGen(i, _chanData, len) == 0) {
@@ -487,11 +495,18 @@ void SoundGenPCJr::premixerCall(int16 *stream, int len) {
streamCount = len;
sPtr = stream;
cPtr = _chanData;
-
+
while (streamCount--)
*(sPtr++) += *(cPtr++) / CHAN_MAX;
+
+ finished = false;
}
}
+
+ if (finished)
+ _vm->_sound->soundIsFinished();
+
+ return len;
}
} // End of namespace Agi
diff --git a/engines/agi/sound_pcjr.h b/engines/agi/sound_pcjr.h
index 9ef9aa2385..fe0e762f4e 100755
--- a/engines/agi/sound_pcjr.h
+++ b/engines/agi/sound_pcjr.h
@@ -26,6 +26,8 @@
#ifndef AGI_SOUND_PCJR_H
#define AGI_SOUND_PCJR_H
+#include "sound/audiostream.h"
+
namespace Agi {
#define CHAN_MAX 4
@@ -78,15 +80,29 @@ struct Tone {
GenType type;
};
-class SoundGenPCJr : public SoundGen {
+class SoundGenPCJr : public SoundGen, public Audio::AudioStream {
public:
- SoundGenPCJr(AgiEngine *vm);
+ SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer);
~SoundGenPCJr();
- void play(int resnum, int flag);
+ void play(int resnum);
void stop(void);
- void premixerCall(int16 *stream, int len);
+ // 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;
+ }
private:
int getNextNote(int ch, Tone *tone);
@@ -98,7 +114,6 @@ private:
int fillSquare(ToneChan *t, int16 *buf, int len);
private:
- AgiEngine *_vm;
SndGenChan _channel[CHAN_MAX];
ToneChan _tchannel[CHAN_MAX];
int16 *_chanData;
diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp
new file mode 100644
index 0000000000..08bdd47497
--- /dev/null
+++ b/engines/agi/sound_sarien.cpp
@@ -0,0 +1,357 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/md5.h"
+#include "common/config-manager.h"
+#include "common/fs.h"
+#include "common/random.h"
+#include "common/str-array.h"
+
+#include "sound/mididrv.h"
+
+#include "agi/agi.h"
+
+#include "agi/sound_sarien.h"
+
+namespace Agi {
+
+#define USE_INTERPOLATION
+
+static const int16 waveformRamp[WAVEFORM_SIZE] = {
+ 0, 8, 16, 24, 32, 40, 48, 56,
+ 64, 72, 80, 88, 96, 104, 112, 120,
+ 128, 136, 144, 152, 160, 168, 176, 184,
+ 192, 200, 208, 216, 224, 232, 240, 255,
+ 0, -248, -240, -232, -224, -216, -208, -200,
+ -192, -184, -176, -168, -160, -152, -144, -136,
+ -128, -120, -112, -104, -96, -88, -80, -72,
+ -64, -56, -48, -40, -32, -24, -16, -8 // Ramp up
+};
+
+static const int16 waveformSquare[WAVEFORM_SIZE] = {
+ 255, 230, 220, 220, 220, 220, 220, 220,
+ 220, 220, 220, 220, 220, 220, 220, 220,
+ 220, 220, 220, 220, 220, 220, 220, 220,
+ 220, 220, 220, 220, 220, 220, 220, 110,
+ -255, -230, -220, -220, -220, -220, -220, -220,
+ -220, -220, -220, -220, -220, -220, -220, -220,
+ -220, -220, -220, -220, -220, -220, -220, -220,
+ -220, -220, -220, -110, 0, 0, 0, 0 // Square
+};
+
+static const int16 waveformMac[WAVEFORM_SIZE] = {
+ 45, 110, 135, 161, 167, 173, 175, 176,
+ 156, 137, 123, 110, 91, 72, 35, -2,
+ -60, -118, -142, -165, -170, -176, -177, -179,
+ -177, -176, -164, -152, -117, -82, -17, 47,
+ 92, 137, 151, 166, 170, 173, 171, 169,
+ 151, 133, 116, 100, 72, 43, -7, -57,
+ -99, -141, -156, -170, -174, -177, -178, -179,
+ -175, -172, -165, -159, -137, -114, -67, -19
+};
+
+SoundGenSarien::SoundGenSarien(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer), _chn() {
+ _sndBuffer = (int16 *)calloc(2, BUFFER_SIZE);
+
+ memset(_sndBuffer, 0, BUFFER_SIZE << 1);
+ _env = false;
+ _playingSound = -1;
+ _playing = false;
+ _useChorus = true; // FIXME: Currently always true?
+
+ switch (_vm->_soundemu) {
+ case SOUND_EMU_NONE:
+ _waveform = waveformRamp;
+ _env = true;
+ break;
+ case SOUND_EMU_AMIGA:
+ case SOUND_EMU_PC:
+ _waveform = waveformSquare;
+ break;
+ case SOUND_EMU_MAC:
+ _waveform = waveformMac;
+ break;
+ }
+
+ report("Initializing sound:\n");
+
+ report("sound: envelopes ");
+ if (_env) {
+ report("enabled (decay=%d, sustain=%d)\n", ENV_DECAY, ENV_SUSTAIN);
+ } else {
+ report("disabled\n");
+ }
+
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+}
+
+SoundGenSarien::~SoundGenSarien() {
+ _mixer->stopHandle(_soundHandle);
+
+ free(_sndBuffer);
+}
+
+int SoundGenSarien::readBuffer(int16 *buffer, const int numSamples) {
+ fillAudio(buffer, numSamples / 2);
+
+ return numSamples;
+}
+
+void SoundGenSarien::play(int resnum) {
+ AgiSoundEmuType type;
+
+ type = (AgiSoundEmuType)_vm->_game.sounds[resnum]->type();
+
+ assert(type == AGI_SOUND_4CHN);
+
+ _playingSound = resnum;
+
+ PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum];
+
+ // Initialize channel info
+ for (int i = 0; i < NUM_CHANNELS; i++) {
+ _chn[i].type = type;
+ _chn[i].flags = AGI_SOUND_LOOP;
+
+ if (_env) {
+ _chn[i].flags |= AGI_SOUND_ENVELOPE;
+ _chn[i].adsr = AGI_SOUND_ENV_ATTACK;
+ }
+
+ _chn[i].ins = _waveform;
+ _chn[i].size = WAVEFORM_SIZE;
+ _chn[i].ptr = pcjrSound->getVoicePointer(i % 4);
+ _chn[i].timer = 0;
+ _chn[i].vol = 0;
+ _chn[i].end = 0;
+ }
+
+ memset(_sndBuffer, 0, BUFFER_SIZE << 1);
+}
+
+void SoundGenSarien::stop() {
+ _playingSound = -1;
+
+ for (int i = 0; i < NUM_CHANNELS; i++)
+ stopNote(i);
+}
+
+void SoundGenSarien::stopNote(int i) {
+ _chn[i].adsr = AGI_SOUND_ENV_RELEASE;
+
+ if (_useChorus) {
+ // Stop chorus ;)
+ if (_chn[i].type == AGI_SOUND_4CHN &&
+ _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
+ stopNote(i + 4);
+ }
+ }
+}
+
+void SoundGenSarien::playNote(int i, int freq, int vol) {
+ if (!_vm->getflag(fSoundOn))
+ vol = 0;
+ else if (vol && _vm->_soundemu == SOUND_EMU_PC)
+ vol = 160;
+
+ _chn[i].phase = 0;
+ _chn[i].freq = freq;
+ _chn[i].vol = vol;
+ _chn[i].env = 0x10000;
+ _chn[i].adsr = AGI_SOUND_ENV_ATTACK;
+
+ if (_useChorus) {
+ // Add chorus ;)
+ if (_chn[i].type == AGI_SOUND_4CHN &&
+ _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
+
+ int newfreq = freq * 1007 / 1000;
+
+ if (freq == newfreq)
+ newfreq++;
+
+ playNote(i + 4, newfreq, vol * 2 / 3);
+ }
+ }
+}
+
+void SoundGenSarien::playSound() {
+ int i;
+ AgiNote note;
+
+ if (_playingSound == -1)
+ return;
+
+ _playing = false;
+ for (i = 0; i < (_vm->_soundemu == SOUND_EMU_PC ? 1 : 4); i++) {
+ _playing |= !_chn[i].end;
+ note.read(_chn[i].ptr); // Read a single note (Doesn't advance the pointer)
+
+ if (_chn[i].end)
+ continue;
+
+ if ((--_chn[i].timer) <= 0) {
+ stopNote(i);
+
+ if (note.freqDiv != 0) {
+ int volume = (note.attenuation == 0x0F) ? 0 : (0xFF - note.attenuation * 2);
+ playNote(i, note.freqDiv * 10, volume);
+ }
+
+ _chn[i].timer = note.duration;
+
+ if (_chn[i].timer == 0xffff) {
+ _chn[i].end = 1;
+ _chn[i].vol = 0;
+ _chn[i].env = 0;
+
+ if (_useChorus) {
+ // chorus
+ if (_chn[i].type == AGI_SOUND_4CHN && _vm->_soundemu == SOUND_EMU_NONE && i < 3) {
+ _chn[i + 4].vol = 0;
+ _chn[i + 4].env = 0;
+ }
+ }
+ }
+ _chn[i].ptr += 5; // Advance the pointer to the next note data (5 bytes per note)
+ }
+ }
+
+ if (!_playing) {
+ _vm->_sound->soundIsFinished();
+
+ _playingSound = -1;
+ }
+}
+
+uint32 SoundGenSarien::mixSound() {
+ register int i, p;
+ const int16 *src;
+ int c, b, m;
+
+ memset(_sndBuffer, 0, BUFFER_SIZE << 1);
+
+ if (!_playing || _playingSound == -1)
+ return BUFFER_SIZE;
+
+ // Handle PCjr 4-channel sound mixing here
+ for (c = 0; c < NUM_CHANNELS; c++) {
+ if (!_chn[c].vol)
+ continue;
+
+ m = _chn[c].flags & AGI_SOUND_ENVELOPE ?
+ _chn[c].vol * _chn[c].env >> 16 : _chn[c].vol;
+
+ if (_chn[c].type != AGI_SOUND_4CHN || c != 3) {
+ src = _chn[c].ins;
+
+ p = _chn[c].phase;
+ for (i = 0; i < BUFFER_SIZE; i++) {
+ b = src[p >> 8];
+#ifdef USE_INTERPOLATION
+ b += ((src[((p >> 8) + 1) % _chn[c].size] - src[p >> 8]) * (p & 0xff)) >> 8;
+#endif
+ _sndBuffer[i] += (b * m) >> 4;
+
+ p += (uint32) 118600 *4 / _chn[c].freq;
+
+ // FIXME: Fingolfin asks: why is there a FIXME here? Please either clarify what
+ // needs fixing, or remove it!
+ // FIXME
+ if (_chn[c].flags & AGI_SOUND_LOOP) {
+ p %= _chn[c].size << 8;
+ } else {
+ if (p >= _chn[c].size << 8) {
+ p = _chn[c].vol = 0;
+ _chn[c].end = 1;
+ break;
+ }
+ }
+
+ }
+ _chn[c].phase = p;
+ } else {
+ // Add white noise
+ for (i = 0; i < BUFFER_SIZE; i++) {
+ b = _vm->_rnd->getRandomNumber(255) - 128;
+ _sndBuffer[i] += (b * m) >> 4;
+ }
+ }
+
+ switch (_chn[c].adsr) {
+ case AGI_SOUND_ENV_ATTACK:
+ // not implemented
+ _chn[c].adsr = AGI_SOUND_ENV_DECAY;
+ break;
+ case AGI_SOUND_ENV_DECAY:
+ if (_chn[c].env > _chn[c].vol * ENV_SUSTAIN + ENV_DECAY) {
+ _chn[c].env -= ENV_DECAY;
+ } else {
+ _chn[c].env = _chn[c].vol * ENV_SUSTAIN;
+ _chn[c].adsr = AGI_SOUND_ENV_SUSTAIN;
+ }
+ break;
+ case AGI_SOUND_ENV_SUSTAIN:
+ break;
+ case AGI_SOUND_ENV_RELEASE:
+ if (_chn[c].env >= ENV_RELEASE) {
+ _chn[c].env -= ENV_RELEASE;
+ } else {
+ _chn[c].env = 0;
+ }
+ }
+ }
+
+ return BUFFER_SIZE;
+}
+
+void SoundGenSarien::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;
+ }
+
+ memcpy((uint8 *)stream + p, (uint8*)_sndBuffer + data_offset, len);
+ data_offset += len;
+ data_available -= len;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/sound_sarien.h b/engines/agi/sound_sarien.h
new file mode 100755
index 0000000000..54222ba624
--- /dev/null
+++ b/engines/agi/sound_sarien.h
@@ -0,0 +1,120 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef AGI_SOUND_SARIEN_H
+#define AGI_SOUND_SARIEN_H
+
+#include "sound/audiostream.h"
+
+namespace Agi {
+
+#define BUFFER_SIZE 410
+
+#define WAVEFORM_SIZE 64
+#define ENV_ATTACK 10000 /**< envelope attack rate */
+#define ENV_DECAY 1000 /**< envelope decay rate */
+#define ENV_SUSTAIN 100 /**< envelope sustain level */
+#define ENV_RELEASE 7500 /**< envelope release rate */
+#define NUM_CHANNELS 7 /**< number of sound channels */
+
+enum AgiSoundFlags {
+ AGI_SOUND_LOOP = 0x0001,
+ AGI_SOUND_ENVELOPE = 0x0002
+};
+enum AgiSoundEnv {
+ AGI_SOUND_ENV_ATTACK = 3,
+ AGI_SOUND_ENV_DECAY = 2,
+ AGI_SOUND_ENV_SUSTAIN = 1,
+ AGI_SOUND_ENV_RELEASE = 0
+};
+
+
+/**
+ * AGI engine sound channel structure.
+ */
+struct ChannelInfo {
+ AgiSoundEmuType type;
+ const uint8 *ptr; // Pointer to the AgiNote data
+ const int16 *ins;
+ int32 size;
+ uint32 phase;
+ uint32 flags; // ORs values from AgiSoundFlags
+ AgiSoundEnv adsr;
+ int32 timer;
+ uint32 end;
+ uint32 freq;
+ uint32 vol;
+ uint32 env;
+};
+
+class SoundGenSarien : public SoundGen, public Audio::AudioStream {
+public:
+ SoundGenSarien(AgiEngine *vm, Audio::Mixer *pMixer);
+ ~SoundGenSarien();
+
+ 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;
+ }
+
+private:
+ ChannelInfo _chn[NUM_CHANNELS];
+ uint8 _env;
+
+ int16 *_sndBuffer;
+ const int16 *_waveform;
+
+ bool _useChorus;
+
+ bool _playing;
+ int _playingSound;
+
+private:
+ void playSound();
+ uint32 mixSound();
+ void fillAudio(int16 *stream, uint len);
+
+ void stopNote(int i);
+ void playNote(int i, int freq, int vol);
+
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_SOUND_SARIEN_H */