diff options
author | Eugene Sandulenko | 2010-06-15 10:36:54 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2010-06-15 10:36:54 +0000 |
commit | ceb2909e0ad0ee4db8846bc2e6e9009364e24731 (patch) | |
tree | 555f051c683192e1a7e150bc4cd198fafa41c4f4 /engines | |
parent | 0fe65d3e5fcabaac354ffb67cb1970e3e0f5f270 (diff) | |
download | scummvm-rg350-ceb2909e0ad0ee4db8846bc2e6e9009364e24731.tar.gz scummvm-rg350-ceb2909e0ad0ee4db8846bc2e6e9009364e24731.tar.bz2 scummvm-rg350-ceb2909e0ad0ee4db8846bc2e6e9009364e24731.zip |
AGI: Split all sound generators into separate modules.
Now the sound subsystem of the engine finally is possible to grasp.
Also now it is obvious why CoCo3 sounds are not functioning.
svn-id: r49757
Diffstat (limited to 'engines')
-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 */ |