mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-30 07:23:05 +00:00
SCUMM: Move most of the Macintosh player code into its own class
The Monkey Island and Loom mac music is really quite similar. The data layout is a bit different, but most of the code was easy to separate into its own class. The Loom player doesn't do looped music but I don't remember off-hand if it ever should.
This commit is contained in:
parent
b1d10e6a62
commit
148a6d3347
@ -36,6 +36,7 @@ MODULE_OBJS := \
|
||||
object.o \
|
||||
palette.o \
|
||||
player_apple2.o \
|
||||
player_mac.o \
|
||||
player_mod.o \
|
||||
player_nes.o \
|
||||
player_pce.o \
|
||||
|
319
engines/scumm/player_mac.cpp
Normal file
319
engines/scumm/player_mac.cpp
Normal file
@ -0,0 +1,319 @@
|
||||
/* 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/scumm.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask)
|
||||
: _vm(scumm),
|
||||
_mixer(mixer),
|
||||
_sampleRate(_mixer->getOutputRate()),
|
||||
_soundPlaying(-1),
|
||||
_numberOfChannels(numberOfChannels),
|
||||
_channelMask(channelMask) {
|
||||
|
||||
assert(scumm);
|
||||
assert(mixer);
|
||||
|
||||
if (checkMusicAvailable()) {
|
||||
_channel = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
_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::setMusicVolume(int vol) {
|
||||
debug(5, "Player_Mac::setMusicVolume(%d)", vol);
|
||||
}
|
||||
|
||||
void Player_Mac::stopAllSounds_Internal() {
|
||||
_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);
|
||||
|
||||
const byte *ptr = _vm->getResourceAddress(rtSound, nr);
|
||||
assert(ptr);
|
||||
|
||||
if (!loadMusic(ptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_soundPlaying = nr;
|
||||
}
|
||||
|
||||
bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) {
|
||||
// Load the sound
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
uint16 duration;
|
||||
byte note, velocity;
|
||||
if (getNextNote(i, duration, note, velocity)) {
|
||||
if (note > 1) {
|
||||
const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
|
||||
assert(pitchIdx >= 0);
|
||||
// I don't want to use floating-point arithmetics here,
|
||||
// but I ran into overflow problems with the church
|
||||
// music. It's only once per note, so it should be ok.
|
||||
double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
|
||||
_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
|
||||
_channel[i]._velocity = velocity;
|
||||
} else {
|
||||
_channel[i]._pitchModifier = 0;
|
||||
_channel[i]._velocity = 0;
|
||||
}
|
||||
|
||||
// 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 and hope that the rounding
|
||||
// error won't be noticeable.
|
||||
//
|
||||
// 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.
|
||||
_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
|
||||
_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
|
||||
} 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);
|
||||
}
|
||||
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 samplesLeft = numSamples;
|
||||
while (samplesLeft) {
|
||||
_subPos += pitchModifier;
|
||||
while (_subPos >= 0x10000) {
|
||||
_subPos -= 0x10000;
|
||||
_pos++;
|
||||
if (_pos >= _loopEnd) {
|
||||
_pos = _loopStart;
|
||||
}
|
||||
}
|
||||
|
||||
int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
|
||||
if (sample > 32767) {
|
||||
sample = 32767;
|
||||
} else if (sample < -32768) {
|
||||
sample = -32768;
|
||||
}
|
||||
|
||||
*data++ = sample; // (_data[_pos] * 127) / 100;
|
||||
samplesLeft--;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
124
engines/scumm/player_mac.h
Normal file
124
engines/scumm/player_mac.h
Normal file
@ -0,0 +1,124 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCUMM_PLAYER_MAC_H
|
||||
#define SCUMM_PLAYER_MAC_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/util.h"
|
||||
#include "common/mutex.h"
|
||||
#include "scumm/music.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
#define RES_SND MKTAG('s', 'n', 'd', ' ')
|
||||
|
||||
class Mixer;
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class ScummEngine;
|
||||
|
||||
/**
|
||||
* Scumm Macintosh music driver, base class.
|
||||
*/
|
||||
class Player_Mac : public Audio::AudioStream, public MusicEngine {
|
||||
public:
|
||||
Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask);
|
||||
virtual ~Player_Mac();
|
||||
|
||||
// MusicEngine API
|
||||
virtual void setMusicVolume(int vol);
|
||||
virtual void startSound(int sound);
|
||||
virtual void stopSound(int sound);
|
||||
virtual void stopAllSounds();
|
||||
virtual int getMusicTimer();
|
||||
virtual int getSoundStatus(int sound) const;
|
||||
|
||||
// AudioStream API
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
virtual bool isStereo() const { return false; }
|
||||
virtual bool endOfData() const { return false; }
|
||||
virtual int getRate() const { return _sampleRate; }
|
||||
|
||||
private:
|
||||
Common::Mutex _mutex;
|
||||
Audio::Mixer *const _mixer;
|
||||
Audio::SoundHandle _soundHandle;
|
||||
const uint32 _sampleRate;
|
||||
int _soundPlaying;
|
||||
|
||||
void stopAllSounds_Internal();
|
||||
|
||||
struct Instrument {
|
||||
byte *_data;
|
||||
uint32 _size;
|
||||
uint32 _rate;
|
||||
uint32 _loopStart;
|
||||
uint32 _loopEnd;
|
||||
byte _baseFreq;
|
||||
|
||||
uint _pos;
|
||||
uint _subPos;
|
||||
|
||||
void newNote() {
|
||||
_pos = 0;
|
||||
_subPos = 0;
|
||||
}
|
||||
|
||||
void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
|
||||
};
|
||||
|
||||
int _pitchTable[128];
|
||||
int _numberOfChannels;
|
||||
int _channelMask;
|
||||
|
||||
virtual bool checkMusicAvailable() { return false; }
|
||||
virtual bool loadMusic(const byte *ptr) { return false; }
|
||||
virtual bool getNextNote(int ch, uint16 &duration, byte &value, byte &velocity) { return false; }
|
||||
|
||||
protected:
|
||||
struct Channel {
|
||||
virtual ~Channel() {}
|
||||
|
||||
Instrument _instrument;
|
||||
bool _looped;
|
||||
uint32 _length;
|
||||
const byte *_data;
|
||||
|
||||
uint _pos;
|
||||
int _pitchModifier;
|
||||
byte _velocity;
|
||||
uint32 _remaining;
|
||||
|
||||
bool _notesLeft;
|
||||
|
||||
bool loadInstrument(Common::SeekableReadStream *stream);
|
||||
};
|
||||
|
||||
ScummEngine *const _vm;
|
||||
Channel *_channel;
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
@ -94,59 +94,18 @@
|
||||
#include "scumm/player_v3m.h"
|
||||
#include "scumm/scumm.h"
|
||||
|
||||
#define RES_SND MKTAG('s', 'n', 'd', ' ')
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
|
||||
: _vm(scumm),
|
||||
_mixer(mixer),
|
||||
_sampleRate(_mixer->getOutputRate()),
|
||||
_soundPlaying(-1) {
|
||||
|
||||
assert(scumm);
|
||||
assert(mixer);
|
||||
: Player_Mac(scumm, mixer, 5, 0x1E) {
|
||||
assert(_vm->_game.id == GID_LOOM);
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 5; 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);
|
||||
// Channel 0 seems to be what was played on low-end macs, that couldn't
|
||||
// handle multi-channel music and play the game at the same time. I'm
|
||||
// not sure if stream 4 is ever used, but let's use it just in case.
|
||||
}
|
||||
|
||||
bool Player_V3M::checkMusicAvailable() {
|
||||
Common::MacResManager resource;
|
||||
// \252 is a trademark glyph. I don't think we should assume that it
|
||||
// will be preserved when copying from the Mac disk.
|
||||
@ -155,72 +114,28 @@ Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer)
|
||||
"Could not find the 'Loom' Macintosh executable to read the\n"
|
||||
"instruments from. Music will be disabled."), _("OK"));
|
||||
dialog.runModal();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
Player_V3M::~Player_V3M() {
|
||||
Common::StackLock lock(_mutex);
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
void Player_V3M::setMusicVolume(int vol) {
|
||||
debug(5, "Player_V3M::setMusicVolume(%d)", vol);
|
||||
}
|
||||
|
||||
void Player_V3M::stopAllSounds_Internal() {
|
||||
_soundPlaying = -1;
|
||||
for (int i = 0; i < 3; 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_V3M::stopAllSounds() {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V3M::stopAllSounds()");
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
void Player_V3M::stopSound(int nr) {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V3M::stopSound(%d)", nr);
|
||||
|
||||
if (nr == _soundPlaying) {
|
||||
stopAllSounds();
|
||||
}
|
||||
}
|
||||
|
||||
void Player_V3M::startSound(int nr) {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V3M::startSound(%d)", nr);
|
||||
|
||||
bool Player_V3M::loadMusic(const byte *ptr) {
|
||||
Common::MacResManager resource;
|
||||
if (!resource.open("Loom\252") && !resource.open("Loom")) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const byte *ptr = _vm->getResourceAddress(rtSound, nr);
|
||||
assert(ptr);
|
||||
assert(ptr[4] == 's' && ptr[5] == 'o');
|
||||
|
||||
const byte *src = ptr;
|
||||
uint i;
|
||||
|
||||
assert(src[4] == 's' && src[5] == 'o');
|
||||
for (i = 0; i < 5; i++) {
|
||||
int instrument = READ_BE_UINT16(src + 20 + 2 * i);
|
||||
int offset = READ_BE_UINT16(src + 30 + 2 * i);
|
||||
int instrument = READ_BE_UINT16(ptr + 20 + 2 * i);
|
||||
int offset = READ_BE_UINT16(ptr + 30 + 2 * i);
|
||||
|
||||
_channel[i]._looped = false;
|
||||
_channel[i]._length = READ_BE_UINT16(src + offset + 4) * 3;
|
||||
_channel[i]._data = src + offset + 6;
|
||||
_channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3;
|
||||
_channel[i]._data = ptr + offset + 6;
|
||||
_channel[i]._pos = 0;
|
||||
_channel[i]._pitchModifier = 0;
|
||||
_channel[i]._velocity = 0;
|
||||
@ -229,205 +144,31 @@ void Player_V3M::startSound(int nr) {
|
||||
|
||||
Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument);
|
||||
if (_channel[i].loadInstrument(stream)) {
|
||||
debug(6, "Player_V3M::startSound: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
|
||||
debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str());
|
||||
} else {
|
||||
resource.close();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
resource.close();
|
||||
_soundPlaying = nr;
|
||||
}
|
||||
|
||||
bool Player_V3M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
|
||||
// Load the sound
|
||||
uint16 soundType = stream->readUint16BE();
|
||||
if (soundType != 1) {
|
||||
warning("Player_V3M::loadInstrument: Unsupported sound type %d", soundType);
|
||||
return false;
|
||||
}
|
||||
uint16 typeCount = stream->readUint16BE();
|
||||
if (typeCount != 1) {
|
||||
warning("Player_V3M::loadInstrument: Unsupported data type count %d", typeCount);
|
||||
return false;
|
||||
}
|
||||
uint16 dataType = stream->readUint16BE();
|
||||
if (dataType != 5) {
|
||||
warning("Player_V3M::loadInstrument: Unsupported data type %d", dataType);
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->readUint32BE(); // initialization option
|
||||
|
||||
uint16 cmdCount = stream->readUint16BE();
|
||||
if (cmdCount != 1) {
|
||||
warning("Player_V3M::loadInstrument: Unsupported command count %d", cmdCount);
|
||||
return false;
|
||||
}
|
||||
uint16 command = stream->readUint16BE();
|
||||
if (command != 0x8050 && command != 0x8051) {
|
||||
warning("Player_V3M::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_V3M::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_V3M::getMusicTimer() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Player_V3M::getSoundStatus(int nr) const {
|
||||
return _soundPlaying == nr;
|
||||
}
|
||||
|
||||
int Player_V3M::readBuffer(int16 *data, const int numSamples) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
memset(data, 0, numSamples * 2);
|
||||
if (_soundPlaying == -1) {
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
bool notesLeft = false;
|
||||
|
||||
// Channel 0 seems to be what was played on low-end macs, that couldn't
|
||||
// handle multi-channel music and play the game at the same time. I'm
|
||||
// not sure if stream 4 is ever used, but let's use it just in case.
|
||||
|
||||
for (int i = 1; i < 5; i++) {
|
||||
uint samplesLeft = numSamples;
|
||||
int16 *ptr = data;
|
||||
|
||||
while (samplesLeft > 0) {
|
||||
int generated;
|
||||
if (_channel[i]._remaining == 0) {
|
||||
uint16 duration;
|
||||
byte note, velocity;
|
||||
if (_channel[i].getNextNote(duration, note, velocity)) {
|
||||
if (note > 0) {
|
||||
const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
|
||||
assert(pitchIdx >= 0);
|
||||
// I don't want to use floating-point arithmetics here,
|
||||
// but I ran into overflow problems with the church
|
||||
// music. It's only once per note, so it should be ok.
|
||||
double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
|
||||
_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
|
||||
_channel[i]._velocity = velocity;
|
||||
} else {
|
||||
_channel[i]._pitchModifier = 0;
|
||||
_channel[i]._velocity = 0;
|
||||
}
|
||||
|
||||
// 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 and hope that the rounding
|
||||
// error won't be noticeable.
|
||||
//
|
||||
// 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.
|
||||
_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
|
||||
_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
|
||||
} 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);
|
||||
}
|
||||
ptr += generated;
|
||||
samplesLeft -= generated;
|
||||
_channel[i]._remaining -= generated;
|
||||
}
|
||||
|
||||
if (_channel[i]._notesLeft) {
|
||||
notesLeft = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!notesLeft) {
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
bool Player_V3M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) {
|
||||
_instrument.newNote();
|
||||
if (_pos >= _length) {
|
||||
if (!_looped) {
|
||||
_notesLeft = false;
|
||||
bool Player_V3M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) {
|
||||
_channel[ch]._instrument.newNote();
|
||||
if (_channel[ch]._pos >= _channel[ch]._length) {
|
||||
if (!_channel[ch]._looped) {
|
||||
_channel[ch]._notesLeft = false;
|
||||
return false;
|
||||
}
|
||||
_pos = 0;
|
||||
_channel[ch]._pos = 0;
|
||||
}
|
||||
duration = READ_BE_UINT16(&_data[_pos]);
|
||||
note = _data[_pos + 2];
|
||||
duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
|
||||
note = _channel[ch]._data[_channel[ch]._pos + 2];
|
||||
velocity = 127;
|
||||
_pos += 3;
|
||||
_channel[ch]._pos += 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player_V3M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
|
||||
int samplesLeft = numSamples;
|
||||
while (samplesLeft) {
|
||||
_subPos += pitchModifier;
|
||||
while (_subPos >= 0x10000) {
|
||||
_subPos -= 0x10000;
|
||||
_pos++;
|
||||
if (_pos >= _loopEnd) {
|
||||
_pos = _loopStart;
|
||||
}
|
||||
}
|
||||
|
||||
int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
|
||||
if (sample > 32767) {
|
||||
sample = 32767;
|
||||
} else if (sample < -32768) {
|
||||
sample = -32768;
|
||||
}
|
||||
|
||||
*data++ = sample; // (_data[_pos] * 127) / 100;
|
||||
samplesLeft--;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "common/util.h"
|
||||
#include "common/mutex.h"
|
||||
#include "scumm/music.h"
|
||||
#include "scumm/player_mac.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
@ -39,73 +40,13 @@ class ScummEngine;
|
||||
/**
|
||||
* Scumm V3 Macintosh music driver.
|
||||
*/
|
||||
class Player_V3M : public Audio::AudioStream, public MusicEngine {
|
||||
class Player_V3M : public Player_Mac {
|
||||
public:
|
||||
Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer);
|
||||
virtual ~Player_V3M();
|
||||
|
||||
// MusicEngine API
|
||||
virtual void setMusicVolume(int vol);
|
||||
virtual void startSound(int sound);
|
||||
virtual void stopSound(int sound);
|
||||
virtual void stopAllSounds();
|
||||
virtual int getMusicTimer();
|
||||
virtual int getSoundStatus(int sound) const;
|
||||
|
||||
// AudioStream API
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
virtual bool isStereo() const { return false; }
|
||||
virtual bool endOfData() const { return false; }
|
||||
virtual int getRate() const { return _sampleRate; }
|
||||
|
||||
private:
|
||||
ScummEngine *const _vm;
|
||||
Common::Mutex _mutex;
|
||||
Audio::Mixer *const _mixer;
|
||||
Audio::SoundHandle _soundHandle;
|
||||
const uint32 _sampleRate;
|
||||
int _soundPlaying;
|
||||
|
||||
void stopAllSounds_Internal();
|
||||
|
||||
struct Instrument {
|
||||
byte *_data;
|
||||
uint32 _size;
|
||||
uint32 _rate;
|
||||
uint32 _loopStart;
|
||||
uint32 _loopEnd;
|
||||
byte _baseFreq;
|
||||
|
||||
uint _pos;
|
||||
uint _subPos;
|
||||
|
||||
void newNote() {
|
||||
_pos = 0;
|
||||
_subPos = 0;
|
||||
}
|
||||
|
||||
void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
Instrument _instrument;
|
||||
bool _looped;
|
||||
uint32 _length;
|
||||
const byte *_data;
|
||||
|
||||
uint _pos;
|
||||
int _pitchModifier;
|
||||
byte _velocity;
|
||||
uint32 _remaining;
|
||||
|
||||
bool _notesLeft;
|
||||
|
||||
bool loadInstrument(Common::SeekableReadStream *stream);
|
||||
bool getNextNote(uint16 &duration, byte &value, byte &velocity);
|
||||
};
|
||||
|
||||
Channel _channel[5];
|
||||
int _pitchTable[128];
|
||||
virtual bool checkMusicAvailable();
|
||||
virtual bool loadMusic(const byte *ptr);
|
||||
virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity);
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
@ -79,139 +79,49 @@
|
||||
#include "scumm/player_v5m.h"
|
||||
#include "scumm/scumm.h"
|
||||
|
||||
#define RES_SND MKTAG('s', 'n', 'd', ' ')
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer)
|
||||
: _vm(scumm),
|
||||
_mixer(mixer),
|
||||
_sampleRate(_mixer->getOutputRate()),
|
||||
_soundPlaying(-1) {
|
||||
|
||||
assert(scumm);
|
||||
assert(mixer);
|
||||
: Player_Mac(scumm, mixer, 3, 0x07) {
|
||||
assert(_vm->_game.id == GID_MONKEY);
|
||||
}
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; 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);
|
||||
|
||||
bool Player_V5M::checkMusicAvailable() {
|
||||
Common::MacResManager resource;
|
||||
if (!resource.exists("Monkey Island")) {
|
||||
GUI::MessageDialog dialog(_(
|
||||
"Could not find the 'Monkey Island' Macintosh executable to read the\n"
|
||||
"instruments from. Music will be disabled."), _("OK"));
|
||||
dialog.runModal();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
Player_V5M::~Player_V5M() {
|
||||
Common::StackLock lock(_mutex);
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
void Player_V5M::setMusicVolume(int vol) {
|
||||
debug(5, "Player_V5M::setMusicVolume(%d)", vol);
|
||||
}
|
||||
|
||||
void Player_V5M::stopAllSounds_Internal() {
|
||||
_soundPlaying = -1;
|
||||
for (int i = 0; i < 3; 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_V5M::stopAllSounds() {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V5M::stopAllSounds()");
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
void Player_V5M::stopSound(int nr) {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V5M::stopSound(%d)", nr);
|
||||
|
||||
if (nr == _soundPlaying) {
|
||||
stopAllSounds();
|
||||
}
|
||||
}
|
||||
|
||||
void Player_V5M::startSound(int nr) {
|
||||
Common::StackLock lock(_mutex);
|
||||
debug(5, "Player_V5M::startSound(%d)", nr);
|
||||
|
||||
bool Player_V5M::loadMusic(const byte *ptr) {
|
||||
Common::MacResManager resource;
|
||||
if (!resource.open("Monkey Island")) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const byte *ptr = _vm->getResourceAddress(rtSound, nr);
|
||||
assert(ptr);
|
||||
|
||||
const byte *src = ptr;
|
||||
uint i;
|
||||
|
||||
src += 8;
|
||||
ptr += 8;
|
||||
// TODO: Decipher the unknown bytes in the header. For now, skip 'em
|
||||
src += 28;
|
||||
ptr += 28;
|
||||
|
||||
Common::MacResIDArray idArray = resource.getResIDArray(RES_SND);
|
||||
|
||||
// Load the three channels and their instruments
|
||||
for (i = 0; i < 3; i++) {
|
||||
assert(READ_BE_UINT32(src) == MKTAG('C', 'h', 'a', 'n'));
|
||||
uint32 len = READ_BE_UINT32(src + 4);
|
||||
uint32 instrument = READ_BE_UINT32(src + 8);
|
||||
assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n'));
|
||||
uint32 len = READ_BE_UINT32(ptr + 4);
|
||||
uint32 instrument = READ_BE_UINT32(ptr + 8);
|
||||
|
||||
_channel[i]._length = len - 24;
|
||||
_channel[i]._data = src + 12;
|
||||
_channel[i]._looped = (READ_BE_UINT32(src + len - 8) == MKTAG('L', 'o', 'o', 'p'));
|
||||
_channel[i]._data = ptr + 12;
|
||||
_channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p'));
|
||||
_channel[i]._pos = 0;
|
||||
_channel[i]._pitchModifier = 0;
|
||||
_channel[i]._velocity = 0;
|
||||
@ -221,213 +131,43 @@ void Player_V5M::startSound(int nr) {
|
||||
for (uint j = 0; j < idArray.size(); j++) {
|
||||
Common::String name = resource.getResName(RES_SND, idArray[j]);
|
||||
if (instrument == READ_BE_UINT32(name.c_str())) {
|
||||
debug(6, "Channel %d: Loading instrument '%s'", i, name.c_str());
|
||||
debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str());
|
||||
Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]);
|
||||
|
||||
if (!_channel[i].loadInstrument(stream)) {
|
||||
resource.close();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
src += len;
|
||||
ptr += len;
|
||||
}
|
||||
|
||||
resource.close();
|
||||
_soundPlaying = nr;
|
||||
}
|
||||
|
||||
bool Player_V5M::Channel::loadInstrument(Common::SeekableReadStream *stream) {
|
||||
// Load the sound
|
||||
uint16 soundType = stream->readUint16BE();
|
||||
if (soundType != 1) {
|
||||
warning("Player_V5M::loadInstrument: Unsupported sound type %d", soundType);
|
||||
return false;
|
||||
}
|
||||
uint16 typeCount = stream->readUint16BE();
|
||||
if (typeCount != 1) {
|
||||
warning("Player_V5M::loadInstrument: Unsupported data type count %d", typeCount);
|
||||
return false;
|
||||
}
|
||||
uint16 dataType = stream->readUint16BE();
|
||||
if (dataType != 5) {
|
||||
warning("Player_V5M::loadInstrument: Unsupported data type %d", dataType);
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->readUint32BE(); // initialization option
|
||||
|
||||
uint16 cmdCount = stream->readUint16BE();
|
||||
if (cmdCount != 1) {
|
||||
warning("Player_V5M::loadInstrument: Unsupported command count %d", cmdCount);
|
||||
return false;
|
||||
}
|
||||
uint16 command = stream->readUint16BE();
|
||||
if (command != 0x8050 && command != 0x8051) {
|
||||
warning("Player_V5M::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_V5M::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_V5M::getMusicTimer() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Player_V5M::getSoundStatus(int nr) const {
|
||||
return _soundPlaying == nr;
|
||||
}
|
||||
|
||||
int Player_V5M::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 < 3; i++) {
|
||||
uint samplesLeft = numSamples;
|
||||
int16 *ptr = data;
|
||||
|
||||
while (samplesLeft > 0) {
|
||||
int generated;
|
||||
if (_channel[i]._remaining == 0) {
|
||||
uint16 duration;
|
||||
byte note, velocity;
|
||||
if (_channel[i].getNextNote(duration, note, velocity)) {
|
||||
if (note > 1) {
|
||||
const int pitchIdx = note + 60 - _channel[i]._instrument._baseFreq;
|
||||
assert(pitchIdx >= 0);
|
||||
// I don't want to use floating-point arithmetics here,
|
||||
// but I ran into overflow problems with the church
|
||||
// music. It's only once per note, so it should be ok.
|
||||
double mult = (double)(_channel[i]._instrument._rate) / (double)_sampleRate;
|
||||
_channel[i]._pitchModifier = mult * _pitchTable[pitchIdx];
|
||||
_channel[i]._velocity = velocity;
|
||||
} else {
|
||||
_channel[i]._pitchModifier = 0;
|
||||
_channel[i]._velocity = 0;
|
||||
}
|
||||
|
||||
// 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 and hope that the rounding
|
||||
// error won't be noticeable.
|
||||
//
|
||||
// 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.
|
||||
_channel[i]._remaining = (duration * _sampleRate) / (4 * 480);
|
||||
_channel[i]._remaining = (_channel[i]._remaining * 473) / 480;
|
||||
} 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);
|
||||
}
|
||||
ptr += generated;
|
||||
samplesLeft -= generated;
|
||||
_channel[i]._remaining -= generated;
|
||||
}
|
||||
|
||||
if (_channel[i]._notesLeft) {
|
||||
notesLeft = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!notesLeft) {
|
||||
stopAllSounds_Internal();
|
||||
}
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
bool Player_V5M::Channel::getNextNote(uint16 &duration, byte ¬e, byte &velocity) {
|
||||
_instrument.newNote();
|
||||
if (_pos >= _length) {
|
||||
if (!_looped) {
|
||||
_notesLeft = false;
|
||||
bool Player_V5M::getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity) {
|
||||
_channel[ch]._instrument.newNote();
|
||||
if (_channel[ch]._pos >= _channel[ch]._length) {
|
||||
if (!_channel[ch]._looped) {
|
||||
_channel[ch]._notesLeft = false;
|
||||
return false;
|
||||
}
|
||||
// FIXME: Jamieson630: The jump seems to be happening
|
||||
// too quickly! There should maybe be a pause after
|
||||
// the last Note Off? But I couldn't find one in the
|
||||
// MI1 Lookout music, where I was hearing problems.
|
||||
_pos = 0;
|
||||
_channel[ch]._pos = 0;
|
||||
}
|
||||
duration = READ_BE_UINT16(&_data[_pos]);
|
||||
note = _data[_pos + 2];
|
||||
velocity = _data[_pos + 3];
|
||||
_pos += 4;
|
||||
duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]);
|
||||
note = _channel[ch]._data[_channel[ch]._pos + 2];
|
||||
velocity = _channel[ch]._data[_channel[ch]._pos + 3];
|
||||
_channel[ch]._pos += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player_V5M::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples) {
|
||||
int samplesLeft = numSamples;
|
||||
while (samplesLeft) {
|
||||
_subPos += pitchModifier;
|
||||
while (_subPos >= 0x10000) {
|
||||
_subPos -= 0x10000;
|
||||
_pos++;
|
||||
if (_pos >= _loopEnd) {
|
||||
_pos = _loopStart;
|
||||
}
|
||||
}
|
||||
|
||||
int sample = *data + ((_data[_pos] - 129) * 128 * volume) / 255;
|
||||
if (sample > 32767) {
|
||||
sample = 32767;
|
||||
} else if (sample < -32768) {
|
||||
sample = -32768;
|
||||
}
|
||||
|
||||
*data++ = sample; // (_data[_pos] * 127) / 100;
|
||||
samplesLeft--;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "common/util.h"
|
||||
#include "common/mutex.h"
|
||||
#include "scumm/music.h"
|
||||
#include "scumm/player_mac.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
@ -39,73 +40,13 @@ class ScummEngine;
|
||||
/**
|
||||
* Scumm V5 Macintosh music driver.
|
||||
*/
|
||||
class Player_V5M : public Audio::AudioStream, public MusicEngine {
|
||||
class Player_V5M : public Player_Mac {
|
||||
public:
|
||||
Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer);
|
||||
virtual ~Player_V5M();
|
||||
|
||||
// MusicEngine API
|
||||
virtual void setMusicVolume(int vol);
|
||||
virtual void startSound(int sound);
|
||||
virtual void stopSound(int sound);
|
||||
virtual void stopAllSounds();
|
||||
virtual int getMusicTimer();
|
||||
virtual int getSoundStatus(int sound) const;
|
||||
|
||||
// AudioStream API
|
||||
virtual int readBuffer(int16 *buffer, const int numSamples);
|
||||
virtual bool isStereo() const { return false; }
|
||||
virtual bool endOfData() const { return false; }
|
||||
virtual int getRate() const { return _sampleRate; }
|
||||
|
||||
private:
|
||||
ScummEngine *const _vm;
|
||||
Common::Mutex _mutex;
|
||||
Audio::Mixer *const _mixer;
|
||||
Audio::SoundHandle _soundHandle;
|
||||
const uint32 _sampleRate;
|
||||
int _soundPlaying;
|
||||
|
||||
void stopAllSounds_Internal();
|
||||
|
||||
struct Instrument {
|
||||
byte *_data;
|
||||
uint32 _size;
|
||||
uint32 _rate;
|
||||
uint32 _loopStart;
|
||||
uint32 _loopEnd;
|
||||
byte _baseFreq;
|
||||
|
||||
uint _pos;
|
||||
uint _subPos;
|
||||
|
||||
void newNote() {
|
||||
_pos = 0;
|
||||
_subPos = 0;
|
||||
}
|
||||
|
||||
void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples);
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
Instrument _instrument;
|
||||
bool _looped;
|
||||
uint32 _length;
|
||||
const byte *_data;
|
||||
|
||||
uint _pos;
|
||||
int _pitchModifier;
|
||||
byte _velocity;
|
||||
uint32 _remaining;
|
||||
|
||||
bool _notesLeft;
|
||||
|
||||
bool loadInstrument(Common::SeekableReadStream *stream);
|
||||
bool getNextNote(uint16 &duration, byte &value, byte &velocity);
|
||||
};
|
||||
|
||||
Channel _channel[3];
|
||||
int _pitchTable[128];
|
||||
virtual bool checkMusicAvailable();
|
||||
virtual bool loadMusic(const byte *ptr);
|
||||
virtual bool getNextNote(int ch, uint16 &duration, byte ¬e, byte &velocity);
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
Loading…
x
Reference in New Issue
Block a user