/* 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. * */ #include "common/macresman.h" #include "common/translation.h" #include "engines/engine.h" #include "gui/message.h" #include "scumm/player_mac.h" #include "scumm/resource.h" #include "scumm/scumm.h" #include "scumm/imuse/imuse.h" namespace Scumm { Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds) : _vm(scumm), _mixer(mixer), _sampleRate(_mixer->getOutputRate()), _soundPlaying(-1), _numberOfChannels(numberOfChannels), _channelMask(channelMask), _fadeNoteEnds(fadeNoteEnds) { assert(scumm); assert(mixer); } void Player_Mac::init() { _channel = new Player_Mac::Channel[_numberOfChannels]; int i; for (i = 0; i < _numberOfChannels; i++) { _channel[i]._looped = false; _channel[i]._length = 0; _channel[i]._data = NULL; _channel[i]._pos = 0; _channel[i]._pitchModifier = 0; _channel[i]._velocity = 0; _channel[i]._remaining = 0; _channel[i]._notesLeft = false; _channel[i]._instrument._data = NULL; _channel[i]._instrument._size = 0; _channel[i]._instrument._rate = 0; _channel[i]._instrument._loopStart = 0; _channel[i]._instrument._loopEnd = 0; _channel[i]._instrument._baseFreq = 0; _channel[i]._instrument._pos = 0; _channel[i]._instrument._subPos = 0; } _pitchTable[116] = 1664510; _pitchTable[117] = 1763487; _pitchTable[118] = 1868350; _pitchTable[119] = 1979447; _pitchTable[120] = 2097152; _pitchTable[121] = 2221855; _pitchTable[122] = 2353973; _pitchTable[123] = 2493948; _pitchTable[124] = 2642246; _pitchTable[125] = 2799362; _pitchTable[126] = 2965820; _pitchTable[127] = 3142177; for (i = 115; i >= 0; --i) { _pitchTable[i] = _pitchTable[i + 12] / 2; } setMusicVolume(255); if (!checkMusicAvailable()) { return; } _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); } Player_Mac::~Player_Mac() { Common::StackLock lock(_mutex); _mixer->stopHandle(_soundHandle); stopAllSounds_Internal(); delete[] _channel; } void Player_Mac::saveLoadWithSerializer(Serializer *ser) { Common::StackLock lock(_mutex); if (ser->getVersion() < VER(94)) { if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); dummyImuse->save_or_load(ser, _vm, false); delete dummyImuse; } } else { static const SaveLoadEntry musicEntries[] = { MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)), MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), MKEND() }; static const SaveLoadEntry channelEntries[] = { MKLINE(Channel, _pos, sleUint16, VER(94)), MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), MKLINE(Channel, _velocity, sleUint8, VER(94)), MKLINE(Channel, _remaining, sleUint32, VER(94)), MKLINE(Channel, _notesLeft, sleUint8, VER(94)), MKEND() }; static const SaveLoadEntry instrumentEntries[] = { MKLINE(Instrument, _pos, sleUint32, VER(94)), MKLINE(Instrument, _subPos, sleUint32, VER(94)), MKEND() }; uint32 mixerSampleRate = _sampleRate; int i; ser->saveLoadEntries(this, musicEntries); if (ser->isLoading() && _soundPlaying != -1) { const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying); assert(ptr); loadMusic(ptr); } ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); for (i = 0; i < _numberOfChannels; i++) { ser->saveLoadEntries(&_channel[i], instrumentEntries); } if (ser->isLoading()) { // If necessary, adjust the channel data to fit the // current sample rate. if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) { double mult = (double)_sampleRate / (double)mixerSampleRate; for (i = 0; i < _numberOfChannels; i++) { _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult); _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult); } } _sampleRate = mixerSampleRate; } } } void Player_Mac::setMusicVolume(int vol) { debug(5, "Player_Mac::setMusicVolume(%d)", vol); } void Player_Mac::stopAllSounds_Internal() { if (_soundPlaying != -1) { _vm->_res->unlock(rtSound, _soundPlaying); } _soundPlaying = -1; for (int i = 0; i < _numberOfChannels; i++) { // The channel data is managed by the resource manager, so // don't delete that. delete[] _channel[i]._instrument._data; _channel[i]._instrument._data = NULL; _channel[i]._remaining = 0; _channel[i]._notesLeft = false; } } void Player_Mac::stopAllSounds() { Common::StackLock lock(_mutex); debug(5, "Player_Mac::stopAllSounds()"); stopAllSounds_Internal(); } void Player_Mac::stopSound(int nr) { Common::StackLock lock(_mutex); debug(5, "Player_Mac::stopSound(%d)", nr); if (nr == _soundPlaying) { stopAllSounds(); } } void Player_Mac::startSound(int nr) { Common::StackLock lock(_mutex); debug(5, "Player_Mac::startSound(%d)", nr); stopAllSounds_Internal(); const byte *ptr = _vm->getResourceAddress(rtSound, nr); assert(ptr); if (!loadMusic(ptr)) { return; } _vm->_res->lock(rtSound, nr); _soundPlaying = nr; } bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { uint16 soundType = stream->readUint16BE(); if (soundType != 1) { warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); return false; } uint16 typeCount = stream->readUint16BE(); if (typeCount != 1) { warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount); return false; } uint16 dataType = stream->readUint16BE(); if (dataType != 5) { warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType); return false; } stream->readUint32BE(); // initialization option uint16 cmdCount = stream->readUint16BE(); if (cmdCount != 1) { warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount); return false; } uint16 command = stream->readUint16BE(); if (command != 0x8050 && command != 0x8051) { warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command); return false; } stream->readUint16BE(); // 0 uint32 soundHeaderOffset = stream->readUint32BE(); stream->seek(soundHeaderOffset); uint32 soundDataOffset = stream->readUint32BE(); uint32 size = stream->readUint32BE(); uint32 rate = stream->readUint32BE() >> 16; uint32 loopStart = stream->readUint32BE(); uint32 loopEnd = stream->readUint32BE(); byte encoding = stream->readByte(); byte baseFreq = stream->readByte(); if (encoding != 0) { warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding); return false; } stream->skip(soundDataOffset); byte *data = new byte[size]; stream->read(data, size); _instrument._data = data; _instrument._size = size; _instrument._rate = rate; _instrument._loopStart = loopStart; _instrument._loopEnd = loopEnd; _instrument._baseFreq = baseFreq; return true; } int Player_Mac::getMusicTimer() { return 0; } int Player_Mac::getSoundStatus(int nr) const { return _soundPlaying == nr; } uint32 Player_Mac::durationToSamples(uint16 duration) { // The correct formula should be: // // (duration * 473 * _sampleRate) / (4 * 480 * 480) // // But that's likely to cause integer overflow, so we do it in two // steps using bitwise operations to perform // ((duration * 473 * _sampleRate) / 4096) without overflowing, // then divide this by 225 // (note that 4 * 480 * 480 == 225 * 4096 == 225 << 12) // // The original code is a bit unclear on if it should be 473 or 437, // but since the comments indicated 473 I'm assuming 437 was a typo. uint32 samples = (duration * _sampleRate); samples = (samples >> 12) * 473 + (((samples & 4095) * 473) >> 12); samples = samples / 225; return samples; } int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { if (note > 0) { const int pitchIdx = note + 60 - instrument->_baseFreq; // I don't want to use floating-point arithmetics here, but I // ran into overflow problems with the church music in Monkey // Island. It's only once per note, so it should be ok. double mult = (double)instrument->_rate / (double)_sampleRate; return (int)(mult * _pitchTable[pitchIdx]); } else { return 0; } } int Player_Mac::readBuffer(int16 *data, const int numSamples) { Common::StackLock lock(_mutex); memset(data, 0, numSamples * 2); if (_soundPlaying == -1) { return numSamples; } bool notesLeft = false; for (int i = 0; i < _numberOfChannels; i++) { if (!(_channelMask & (1 << i))) { continue; } uint samplesLeft = numSamples; int16 *ptr = data; while (samplesLeft > 0) { int generated; if (_channel[i]._remaining == 0) { uint32 samples; int pitchModifier; byte velocity; if (getNextNote(i, samples, pitchModifier, velocity)) { _channel[i]._remaining = samples; _channel[i]._pitchModifier = pitchModifier; _channel[i]._velocity = velocity; } else { _channel[i]._pitchModifier = 0; _channel[i]._velocity = 0; _channel[i]._remaining = samplesLeft; } } generated = MIN(_channel[i]._remaining, samplesLeft); if (_channel[i]._velocity != 0) { _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds); } ptr += generated; samplesLeft -= generated; _channel[i]._remaining -= generated; } if (_channel[i]._notesLeft) { notesLeft = true; } } if (!notesLeft) { stopAllSounds_Internal(); } return numSamples; } void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) { int samplesLeft = numSamples; while (samplesLeft) { _subPos += pitchModifier; while (_subPos >= 0x10000) { _subPos -= 0x10000; _pos++; if (_pos >= _loopEnd) { _pos = _loopStart; } } int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; if (fadeNoteEnds) { // Fade out the last 100 samples on each note. Even at // low output sample rates this is just a fraction of a // second, but it gets rid of distracting "pops" at the // end when the sample would otherwise go abruptly from // something to nothing. This was particularly // noticeable on the distaff notes in Loom. // // The reason it's conditional is that Monkey Island // appears to have a "hold current note" command, and // if we fade out the current note in that case we // will actually introduce new "pops". remainingSamplesOnNote--; if (remainingSamplesOnNote < 100) { newSample = (newSample * remainingSamplesOnNote) / 100; } } int sample = *data + newSample; if (sample > 32767) { sample = 32767; } else if (sample < -32768) { sample = -32768; } *data++ = sample; samplesLeft--; } } } // End of namespace Scumm