diff options
-rw-r--r-- | engines/agi/module.mk | 2 | ||||
-rw-r--r-- | engines/agi/sound.cpp | 553 | ||||
-rw-r--r-- | engines/agi/sound.h | 141 | ||||
-rw-r--r-- | engines/agi/sound_2gs.cpp | 259 | ||||
-rw-r--r-- | engines/agi/sound_2gs.h | 58 | ||||
-rw-r--r-- | engines/agi/sound_coco3.cpp | 80 | ||||
-rwxr-xr-x | engines/agi/sound_coco3.h | 73 | ||||
-rwxr-xr-x | engines/agi/sound_midi.cpp | 41 | ||||
-rwxr-xr-x | engines/agi/sound_midi.h | 13 | ||||
-rwxr-xr-x | engines/agi/sound_pcjr.cpp | 23 | ||||
-rwxr-xr-x | engines/agi/sound_pcjr.h | 25 | ||||
-rw-r--r-- | engines/agi/sound_sarien.cpp | 357 | ||||
-rwxr-xr-x | engines/agi/sound_sarien.h | 120 |
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 */ |