Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_MAC_SOUND_LOWLEVEL_H
#define SCUMM_PLAYERS_MAC_SOUND_LOWLEVEL_H
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/array.h"
#include "common/func.h"
namespace Scumm {
class MacSoundDriver {
public:
MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, int activeChannels, bool canInterpolate, bool internal16Bit) : _mutex(mutex),
_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127) {
for (int i = 0; i < 4; ++i) {
_status[i].deviceRate = deviceRate;
_status[i].numExternalMixChannels = activeChannels;
_status[i].allowInterPolation = canInterpolate;
_status[i].flags = 0;
}
}
virtual ~MacSoundDriver() {}
virtual void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) = 0;
struct Status {
Status() : deviceRate(0), numExternalMixChannels(0), allowInterPolation(false), flags(0) {}
uint32 deviceRate;
int numExternalMixChannels;
bool allowInterPolation;
uint8 flags;
};
const Status &getStatus(Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) const { return _status[sndType]; }
enum StatusFlag : uint8 {
kStatusPlaying = 1 << 0,
kStatusOverflow = 1 << 1,
kStatusStartup = 1 << 2,
kStatusDone = 1 << 3,
kStatusDisabled = 1 << 7
};
void clearFlags(uint8 flags, Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) { _status[sndType].flags &= ~flags; }
void setFlags(uint8 flags, Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) { _status[sndType].flags |= flags; }
protected:
Common::Mutex &_mutex;
const int _smpSize;
const int16 _smpMin;
const int16 _smpMax;
Status _status[4];
};
class MacSndChannel;
class MacLowLevelPCMDriver final : public MacSoundDriver {
public:
struct PCMSound {
PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0), stereo(false), enc(0) {}
PCMSound(Common::SharedPtr<const byte> a, uint32 b, uint32 c, uint32 d, uint32 e, byte f, byte g, bool h) : data(a), len(b), rate(c), loopst(d), loopend(e), baseFreq(f), enc(g), stereo(h) {}
Common::SharedPtr<const byte> data;
uint32 len;
uint32 rate;
uint32 loopst;
uint32 loopend;
byte baseFreq;
byte enc;
bool stereo;
};
enum SynthType : byte {
kSquareWaveSynth = 1,
kWaveTableSynth = 3,
kSampledSynth = 5,
kIgnoreSynth = 0xff
};
enum ChanAttrib : byte {
kInitChanLeft = 2,
kInitChanRight = 3,
kWaveInitChannel0 = 4,
kWaveInitChannel1 = 5,
kWaveInitChannel2 = 6,
kWaveInitChannel3 = 7,
kNoInterp = 4,
kInitNoDrop = 8,
kInitMono = 0x80,
kInitStereo = 0xC0
};
enum ExecMode : byte {
kImmediate,
kEnqueue,
};
typedef int ChanHandle;
struct DoubleBuffer {
enum Flags : uint32 {
kBufferReady = 1,
kLastBufferLast = 4
};
DoubleBuffer(ChanHandle hdl, uint32 numframes) : numFrames(numframes), flags(0), data(0), chanHandle(hdl) {}
~DoubleBuffer() { delete[] data; }
uint32 numFrames;
const ChanHandle chanHandle;
uint32 flags;
byte *data;
};
class CallbackClient {
public:
virtual ~CallbackClient() {}
virtual void sndChannelCallback(uint16 arg1, const void *arg2) {}
virtual void dblBuffCallback(DoubleBuffer *dblBuffer) {}
};
typedef Common::Functor2Mem<uint16, const void*, void, CallbackClient> ChanCallback;
typedef Common::Functor1Mem<DoubleBuffer*, void, CallbackClient> DBCallback;
public:
MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool internal16Bit);
~MacLowLevelPCMDriver() override;
void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
ChanHandle createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, byte attributes, ChanCallback *callback);
void disposeChannel(ChanHandle handle);
void playSamples(ChanHandle handle, ExecMode mode, const PCMSound *snd);
void playNote(ChanHandle handle, ExecMode mode, uint8 note, uint16 duration);
void quiet(ChanHandle handle, ExecMode mode);
void flush(ChanHandle handle, ExecMode mode);
void wait(ChanHandle handle, ExecMode mode, uint16 duration);
void loadWaveTable(ChanHandle handle, ExecMode mode, const byte *data, uint16 dataSize);
void loadInstrument(ChanHandle handle, ExecMode mode, const PCMSound *snd);
void setTimbre(ChanHandle handle, ExecMode mode, uint16 timbre);
void callback(ChanHandle handle, ExecMode mode, uint16 arg1, const void *arg2);
bool playDoubleBuffer(ChanHandle handle, byte numChan, byte bitsPerSample, uint32 rate, DBCallback *callback, byte numMixChan = 1);
uint8 getChannelStatus(ChanHandle handle) const;
void clearChannelFlags(ChanHandle handle, uint8 flags);
static uint32 calcRate(uint32 outRate, uint32 factor, uint32 dataRate);
private:
void updateStatus(Audio::Mixer::SoundType sndType);
MacSndChannel *findAndCheckChannel(ChanHandle h, const char *caller, byte reqSynthType) const;
MacSndChannel *findChannel(ChanHandle h) const;
Common::Array<MacSndChannel*> _channels;
int _numInternalMixChannels[4];
int32 *_mixBuffer = 0;
uint32 _mixBufferSize;
};
class VblTaskClientDriver {
public:
virtual ~VblTaskClientDriver() {}
virtual void vblCallback() = 0;
virtual void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const = 0;
virtual const MacSoundDriver::Status &getDriverStatus(Audio::Mixer::SoundType sndType) const = 0;
};
class MacPlayerAudioStream : public Audio::AudioStream {
public:
MacPlayerAudioStream(VblTaskClientDriver *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit);
~MacPlayerAudioStream() override;
void initBuffers(uint32 feedBufferSize);
void initDrivers();
void addVolumeGroup(Audio::Mixer::SoundType type);
void scaleVolume(uint upscale, uint downscale) { _upscale = upscale; _downscale = downscale; }
typedef Common::Functor0Mem<void, VblTaskClientDriver> CallbackProc;
void setVblCallback(const CallbackProc *proc);
void clearBuffer();
void setMasterVolume(Audio::Mixer::SoundType type, uint16 vol);
// AudioStream interface
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return _isStereo; }
int getRate() const override { return _outputRate; }
bool endOfData() const override { return false; }
private:
void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType, bool expectStereo) const;
void runVblTask();
VblTaskClientDriver *_drv;
int _numGroups;
uint16 _upscale;
uint16 _downscale;
uint32 _vblSmpQty;
uint32 _vblSmpQtyRem;
uint32 _vblCountDown;
uint32 _vblCountDownRem;
const CallbackProc *_vblCbProc;
struct SmpBuffer {
SmpBuffer() : start(0), pos(0), end(0), volume(0), lastL(0), lastR(0), size(0), rateConvInt(0), rateConvFrac(0), rateConvAcc(-1), group(Audio::Mixer::kPlainSoundType) {}
int8 *start;
int8 *pos;
const int8 *end;
uint32 volume;
int32 lastL;
int32 lastR;
uint32 size;
uint32 rateConvInt;
uint32 rateConvFrac;
int32 rateConvAcc;
Audio::Mixer::SoundType group;
} *_buffers;
const uint32 _outputRate;
const uint8 _frameSize;
const bool _interp;
const int _smpInternalSize;
const bool _isStereo;
};
struct MacSndResource {
public:
// Construct from Mac resource stream
MacSndResource(uint32 id, Common::SeekableReadStream *&in, Common::String &&name);
// Construct from Mac sound data buffer
MacSndResource(uint32 id, const byte *in, uint32 size);
~MacSndResource() {}
const MacLowLevelPCMDriver::PCMSound *data() const { return &_snd; }
uint32 id() const { return _id; }
const char* name() { return _name.c_str(); }
private:
uint32 _id;
Common::String _name;
MacLowLevelPCMDriver::PCMSound _snd;
};
extern const uint8 _fourToneSynthWaveForm[256];
extern const uint32 _fourToneSynthWaveFormSize;
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_AD_H
#define SCUMM_PLAYERS_PLAYER_AD_H
#include "scumm/music.h"
#include "common/mutex.h"
#include "common/serializer.h"
namespace OPL {
class OPL;
}
namespace Scumm {
class ScummEngine;
/**
* Sound output for v3/v4 AdLib data.
*/
class Player_AD : public MusicEngine {
public:
Player_AD(ScummEngine *scumm, Common::Mutex &mutex);
~Player_AD() override;
// MusicEngine API
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getMusicTimer() override;
int getSoundStatus(int sound) const override;
void saveLoadWithSerializer(Common::Serializer &ser) override;
// Timer callback
void onTimer();
private:
ScummEngine *const _vm;
Common::Mutex &_mutex;
void setupVolume();
int _musicVolume;
int _sfxVolume;
OPL::OPL *_opl2;
int _musicResource;
int32 _engineMusicTimer;
struct SfxSlot;
struct HardwareChannel {
bool allocated;
int priority;
SfxSlot *sfxOwner;
} _hwChannels[9];
int _numHWChannels;
static const int _operatorOffsetToChannel[22];
int allocateHWChannel(int priority, SfxSlot *owner = nullptr);
void freeHWChannel(int channel);
void limitHWChannels(int newCount);
// AdLib register utilities
uint8 _registerBackUpTable[256];
void writeReg(int r, int v);
uint8 readReg(int r) const;
// Instrument setup
void setupChannel(const uint channel, const byte *instrOffset);
void setupOperator(const uint opr, const byte *&instrOffset);
static const int _operatorOffsetTable[18];
// Music handling
void startMusic();
void stopMusic();
void updateMusic();
bool parseCommand();
uint parseVLQ();
void noteOff(uint channel);
void setupFrequency(uint channel, int8 frequency);
void setupRhythm(uint rhythmInstr, uint instrOffset);
const byte *_musicData;
uint _timerLimit;
uint _musicTicks;
uint32 _musicTimer;
uint32 _internalMusicTimer;
bool _loopFlag;
uint _musicLoopStart;
uint _instrumentOffset[16];
struct VoiceChannel {
uint lastEvent;
uint frequency;
uint b0Reg;
} _voiceChannels[9];
void freeVoiceChannel(uint channel);
void musicSeekTo(const uint position);
bool _isSeeking;
uint _mdvdrState;
uint32 _curOffset;
uint32 _nextEventTimer;
static const uint _noteFrequencies[12];
static const uint _mdvdrTable[6];
static const uint _rhythmOperatorTable[6];
static const uint _rhythmChannelTable[6];
// SFX handling
enum {
kNoteStatePreInit = -1,
kNoteStateAttack = 0,
kNoteStateDecay = 1,
kNoteStateSustain = 2,
kNoteStateRelease = 3,
kNoteStateOff = 4
};
struct Note {
int state;
int playTime;
int sustainTimer;
int instrumentValue;
int bias;
int preIncrease;
int adjust;
struct Envelope {
int stepIncrease;
int step;
int stepCounter;
int timer;
} envelope;
};
enum {
kChannelStateOff = 0,
kChannelStateParse = 1,
kChannelStatePlay = 2
};
struct Channel {
int state;
const byte *currentOffset;
const byte *startOffset;
uint8 instrumentData[7];
Note notes[2];
int hardwareChannel;
};
struct SfxSlot {
int resource;
int priority;
Channel channels[3];
} _sfx[3];
SfxSlot *allocateSfxSlot(int priority);
bool startSfx(SfxSlot *sfx, const byte *resource);
void stopSfx(SfxSlot *sfx);
void updateSfx();
void clearChannel(const Channel &channel);
void updateChannel(Channel *channel);
void parseSlot(Channel *channel);
void updateSlot(Channel *channel);
void parseNote(Note *note, const Channel &channel, const byte *offset);
bool processNote(Note *note, const Channel &channel, const byte *offset);
void noteOffOn(int channel);
void writeRegisterSpecial(int channel, uint8 value, int offset);
uint8 readRegisterSpecial(int channel, uint8 defaultValue, int offset);
void setupNoteEnvelopeState(Note *note, int steps, int adjust);
bool processNoteEnvelope(Note *note);
int _sfxTimer;
uint8 _rndSeed;
uint8 getRnd();
static const uint _noteBiasTable[7];
static const uint _numStepsTable[16];
static const uint _noteAdjustScaleTable[7];
static const uint _noteAdjustTable[16];
static const bool _useOperatorTable[7];
static const uint _channelOffsetTable[11];
static const uint _channelOperatorOffsetTable[7];
static const uint _baseRegisterTable[7];
static const uint _registerMaskTable[7];
static const uint _registerShiftTable[7];
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,503 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/engine.h"
#include "scumm/players/player_apple2.h"
#include "scumm/scumm.h"
namespace Scumm {
/************************************
* Apple-II sound-resource parsers
************************************/
/*
* SoundFunction1: frequency up/down
*/
class AppleII_SoundFunction1_FreqUpDown : public AppleII_SoundFunction {
public:
void init(Player_AppleII *player, const byte *params) override {
_player = player;
_delta = params[0];
_count = params[1];
_interval = params[2];
_limit = params[3];
_decInterval = (params[4] >= 0x40);
}
bool update() override { // D085
if (_decInterval) {
do {
_update(_interval, _count);
_interval -= _delta;
} while (_interval >= _limit);
} else {
do {
_update(_interval, _count);
_interval += _delta;
} while (_interval < _limit);
}
return true;
}
private:
void _update(int interval /*a*/, int count /*y*/) { // D076
assert(interval > 0); // 0 == 256?
assert(count > 0); // 0 == 256?
for (; count >= 0; --count) {
_player->speakerToggle();
_player->generateSamples(17 + 5 * interval);
}
}
protected:
int _delta = 0;
int _count = 0;
byte _interval = 0; // must be unsigned byte ("interval < delta" possible)
int _limit = 0;
bool _decInterval = false;
};
/*
* SoundFunction2: symmetric wave (~)
*/
class AppleII_SoundFunction2_SymmetricWave : public AppleII_SoundFunction {
public:
void init(Player_AppleII *player, const byte *params) override {
_player = player;
_params = params;
_pos = 1;
}
bool update() override { // D0D6
// while (pos = 1; pos < 256; ++pos)
if (_pos < 256) {
byte interval = _params[_pos];
if (interval == 0xFF)
return true;
_update(interval, _params[0] /*, LD12F=interval*/);
++_pos;
return false;
}
return true;
}
private:
void _update(int interval /*a*/, int count) { // D0EF
if (interval == 0xFE) {
_player->wait(interval, 10);
} else {
assert(count > 0); // 0 == 256?
assert(interval > 0); // 0 == 256?
int a = (interval >> 3) + count;
for (int y = a; y > 0; --y) {
_player->generateSamples(1292 - 5*interval);
_player->speakerToggle();
_player->generateSamples(1287 - 5*interval);
_player->speakerToggle();
}
}
}
protected:
const byte *_params = 0;
int _pos = 0;
};
/*
* SoundFunction3: asymmetric wave (__-)
*/
class AppleII_SoundFunction3_AsymmetricWave : public AppleII_SoundFunction {
public:
void init(Player_AppleII *player, const byte *params) override {
_player = player;
_params = params;
_pos = 1;
}
bool update() override { // D132
// while (pos = 1; pos < 256; ++pos)
if (_pos < 256) {
byte interval = _params[_pos];
if (interval == 0xFF)
return true;
_update(interval, _params[0]);
++_pos;
return false;
}
return true;
}
private:
void _update(int interval /*a*/, int count /*LD12D*/) { // D14B
if (interval == 0xFE) {
_player->wait(interval, 70);
} else {
assert(interval > 0); // 0 == 256?
assert(count > 0); // 0 == 256?
for (int y = count; y > 0; --y) {
_player->generateSamples(1289 - 5*interval);
_player->speakerToggle();
}
}
}
protected:
const byte *_params = nullptr;
int _pos = 0;
};
/*
* SoundFunction4: polyphone (2 voices)
*/
class AppleII_SoundFunction4_Polyphone : public AppleII_SoundFunction {
public:
void init(Player_AppleII *player, const byte *params) override {
_player = player;
_params = params;
_updateRemain1 = 80;
_updateRemain2 = 10;
_count = 0;
}
bool update() override { // D170
// while (_params[0] != 0x01)
if (_params[0] != 0x01) {
if (_count == 0) // prepare next loop
nextLoop(_params[0], _params[1], _params[2]);
if (loopIteration()) // loop finished -> fetch next parameter set
_params += 3;
return false;
}
return true;
}
private:
/*
* prepare for next parameter set loop
*/
void nextLoop(byte param0, byte param1, byte param2) { // LD182
_count = (-param2 << 8) | 0x3;
_bitmask1 = 0x3;
_bitmask2 = 0x3;
_updateInterval2 = param0;
if (_updateInterval2 == 0)
_bitmask2 = 0x0;
_updateInterval1 = param1;
if (_updateInterval1 == 0) {
_bitmask1 = 0x0;
if (_bitmask2 != 0) {
_bitmask1 = _bitmask2;
_bitmask2 = 0;
_updateInterval1 = _updateInterval2;
}
}
_speakerShiftReg = 0;
}
/*
* perform one loop iteration
* Returns true if loop finished
*/
bool loopIteration() { // D1A2
--_updateRemain1;
--_updateRemain2;
if (_updateRemain2 == 0) {
_updateRemain2 = _updateInterval2;
// use only first voice's data (bitmask1) if both voices are triggered
if (_updateRemain1 != 0) {
_speakerShiftReg ^= _bitmask2;
}
}
if (_updateRemain1 == 0) {
_updateRemain1 = _updateInterval1;
_speakerShiftReg ^= _bitmask1;
}
if (_count < 0xff80) { // add a note separation towards the end of the note, otherwise, play the note
if (_speakerShiftReg & 0x1)
_player->speakerToggle();
}
_speakerShiftReg >>= 1;
_player->generateSamples(42); /* actually 42.5 */
++_count;
return (_count == 0);
}
protected:
const byte *_params = nullptr;
byte _updateRemain1 = 0;
byte _updateRemain2 = 0;
uint16 _count = 0;
byte _bitmask1 = 0;
byte _bitmask2 = 0;
byte _updateInterval1 = 0;
byte _updateInterval2 = 0;
byte _speakerShiftReg = 0;
};
/*
* SoundFunction5: periodic noise
*/
class AppleII_SoundFunction5_Noise : public AppleII_SoundFunction {
public:
void init(Player_AppleII *player, const byte *params) override {
_player = player;
_index = 0;
_param0 = params[0];
assert(_param0 > 0);
}
bool update() override { // D222
const byte noiseMask[] = {
0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F
};
// while (i = 0; i < 10; ++i)
if (_index < 10) {
int count = _param0;
do {
_update(noise() & noiseMask[_index], 1);
--count;
} while (count > 0);
++_index;
return false;
}
return true;
}
private:
void _update(int interval /*a*/, int count) { // D270
assert(count > 0); // 0 == 256?
if (interval == 0)
interval = 256;
for (int i = count; i > 0; --i) {
_player->generateSamples(10 + 5*interval);
_player->speakerToggle();
_player->generateSamples(5 + 5*interval);
_player->speakerToggle();
}
}
byte /*a*/ noise() { // D261
static int pos = 0; // initial value?
byte result = _noiseTable[pos];
pos = (pos + 1) % 256;
return result;
}
protected:
int _index = 0;
int _param0 = 0;
private:
static const byte _noiseTable[256];
};
// LD000[loc] ^ LD00A[loc]
const byte AppleII_SoundFunction5_Noise::_noiseTable[256] = {
0x65, 0x1b, 0xda, 0x11, 0x61, 0xe5, 0x77, 0x57, 0x92, 0xc8, 0x51, 0x1c, 0xd4, 0x91, 0x62, 0x63,
0x00, 0x38, 0x57, 0xd5, 0x18, 0xd8, 0xdc, 0x40, 0x03, 0x86, 0xd3, 0x2f, 0x10, 0x11, 0xd8, 0x3c,
0xbe, 0x00, 0x19, 0xc5, 0xd2, 0xc3, 0xca, 0x34, 0x00, 0x28, 0xbf, 0xb9, 0x18, 0x20, 0x01, 0xcc,
0xda, 0x08, 0xbc, 0x75, 0x7c, 0xb0, 0x8d, 0xe0, 0x09, 0x18, 0xbf, 0x5d, 0xe9, 0x8c, 0x75, 0x64,
0xe5, 0xb5, 0x5d, 0xe0, 0xb7, 0x7d, 0xe9, 0x8c, 0x55, 0x65, 0xc5, 0xb5, 0x5d, 0xd8, 0x09, 0x0d,
0x64, 0xf0, 0xf0, 0x08, 0x63, 0x03, 0x00, 0x55, 0x35, 0xc0, 0x00, 0x20, 0x74, 0xa5, 0x1e, 0xe3,
0x00, 0x06, 0x3c, 0x52, 0xd1, 0x70, 0xd0, 0x57, 0x02, 0xf0, 0x00, 0xb6, 0xfc, 0x02, 0x11, 0x9a,
0x3b, 0xc8, 0x38, 0xdf, 0x1a, 0xb0, 0xd1, 0xb8, 0xd0, 0x18, 0x8a, 0x4a, 0xea, 0x1b, 0x12, 0x5d,
0x29, 0x58, 0xd8, 0x43, 0xb8, 0x2d, 0xd2, 0x61, 0x10, 0x3c, 0x0c, 0x5d, 0x1b, 0x61, 0x10, 0x3c,
0x0a, 0x5d, 0x1d, 0x61, 0x10, 0x3c, 0x0b, 0x19, 0x88, 0x21, 0xc0, 0x21, 0x07, 0x00, 0x65, 0x62,
0x08, 0xe9, 0x36, 0x40, 0x20, 0x41, 0x06, 0x00, 0x20, 0x00, 0x00, 0xed, 0xa3, 0x00, 0x88, 0x06,
0x98, 0x01, 0x5d, 0x7f, 0x02, 0x1d, 0x78, 0x03, 0x60, 0xcb, 0x3a, 0x01, 0xbd, 0x78, 0x02, 0x5d,
0x7e, 0x03, 0x1d, 0xf5, 0xa6, 0x40, 0x81, 0xb4, 0xd0, 0x8d, 0xd3, 0xd0, 0x6d, 0xd5, 0x61, 0x48,
0x61, 0x4d, 0xd1, 0xc8, 0xb1, 0xd8, 0x69, 0xff, 0x61, 0xd9, 0xed, 0xa0, 0xfe, 0x19, 0x91, 0x37,
0x19, 0x37, 0x00, 0xf1, 0x00, 0x01, 0x1f, 0x00, 0xad, 0xc1, 0x01, 0x01, 0x2e, 0x00, 0x40, 0xc6,
0x7a, 0x9b, 0x95, 0x43, 0xfc, 0x18, 0xd2, 0x9e, 0x2a, 0x5a, 0x4b, 0x2a, 0xb6, 0x87, 0x30, 0x6c
};
/************************************
* Apple-II player
************************************/
Player_AppleII::Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer)
: _mixer(mixer), _vm(scumm), _soundFunc(nullptr) {
resetState();
setSampleRate(_mixer->getOutputRate());
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
Player_AppleII::~Player_AppleII() {
_mixer->stopHandle(_soundHandle);
delete _soundFunc;
}
void Player_AppleII::resetState() {
_soundNr = 0;
_type = 0;
_loop = 0;
_params = nullptr;
_speakerState = 0;
delete _soundFunc;
_soundFunc = nullptr;
_sampleConverter.reset();
}
void Player_AppleII::startSound(int nr) {
Common::StackLock lock(_mutex);
byte *data = _vm->getResourceAddress(rtSound, nr);
assert(data);
byte *ptr1 = data + 4;
resetState();
_soundNr = nr;
_type = ptr1[0];
_loop = ptr1[1];
_params = &ptr1[2];
switch (_type) {
case 0: // empty (nothing to play)
resetState();
return;
case 1:
_soundFunc = new AppleII_SoundFunction1_FreqUpDown();
break;
case 2:
_soundFunc = new AppleII_SoundFunction2_SymmetricWave();
break;
case 3:
_soundFunc = new AppleII_SoundFunction3_AsymmetricWave();
break;
case 4:
_soundFunc = new AppleII_SoundFunction4_Polyphone();
break;
case 5:
_soundFunc = new AppleII_SoundFunction5_Noise();
break;
default:
break;
}
_soundFunc->init(this, _params);
assert(_loop > 0);
debug(4, "startSound %d: type %d, loop %d",
nr, _type, _loop);
}
bool Player_AppleII::updateSound() {
if (!_soundFunc)
return false;
if (_soundFunc->update()) {
--_loop;
if (_loop <= 0) {
delete _soundFunc;
_soundFunc = nullptr;
} else {
// reset function state on each loop
_soundFunc->init(this, _params);
}
}
return true;
}
void Player_AppleII::stopAllSounds() {
Common::StackLock lock(_mutex);
resetState();
}
void Player_AppleII::stopSound(int nr) {
Common::StackLock lock(_mutex);
if (_soundNr == nr) {
resetState();
}
}
int Player_AppleII::getSoundStatus(int nr) const {
Common::StackLock lock(_mutex);
return (_soundNr == nr);
}
int Player_AppleII::getMusicTimer() {
/* Apple-II sounds are synchronous -> no music timer */
return 0;
}
int Player_AppleII::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
if (!_soundNr)
return 0;
int samplesLeft = numSamples;
do {
int nSamplesRead = _sampleConverter.readSamples(buffer, samplesLeft);
samplesLeft -= nSamplesRead;
buffer += nSamplesRead;
} while ((samplesLeft > 0) && updateSound());
// reset state if sound is played completely
if (!_soundFunc && (_sampleConverter.availableSize() == 0))
resetState();
return numSamples - samplesLeft;
}
/************************************
* Apple-II sound-resource helpers
************************************/
// toggle speaker on/off
void Player_AppleII::speakerToggle() {
_speakerState ^= 0x1;
}
void Player_AppleII::generateSamples(int cycles) {
_sampleConverter.addCycles(_speakerState, cycles);
}
void Player_AppleII::wait(int interval, int count /*y*/) {
assert(count > 0); // 0 == 256?
assert(interval > 0); // 0 == 256?
generateSamples(11 + count*(8 + 5 * interval));
}
} // End of namespace Scumm

View File

@@ -0,0 +1,292 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_APPLEII_H
#define SCUMM_PLAYERS_PLAYER_APPLEII_H
#include "common/mutex.h"
#include "common/scummsys.h"
#include "scumm/music.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Scumm {
class ScummEngine;
/*
* Optimized for use with periodical read/write phases when the buffer
* is filled in a write phase and completely read in a read phase.
* The growing strategy is optimized for repeated small (e.g. 2 bytes)
* single writes resulting in large buffers
* (avg.: 4KB, max: 18KB @ 16bit/22.050kHz (MM sound21)).
*/
class SampleBuffer {
public:
SampleBuffer() : _data(0) {
clear();
}
~SampleBuffer() {
free(_data);
}
void clear() {
free(_data);
_data = 0;
_capacity = 0;
_writePos = 0;
_readPos = 0;
}
void ensureFree(uint32 needed) {
// if data was read completely, reset read/write pos to front
if ((_writePos != 0) && (_writePos == _readPos)) {
_writePos = 0;
_readPos = 0;
}
// check for enough space at end of buffer
uint32 freeEndCnt = _capacity - _writePos;
if (needed <= freeEndCnt)
return;
uint32 avail = availableSize();
// check for enough space at beginning and end of buffer
if (needed <= _readPos + freeEndCnt) {
// move unread data to front of buffer
memmove(_data, _data + _readPos, avail);
_writePos = avail;
_readPos = 0;
} else { // needs a grow
byte *old_data = _data;
uint32 new_len = avail + needed;
_capacity = new_len + 2048;
_data = (byte *)malloc(_capacity);
if (old_data) {
// copy old unread data to front of new buffer
memcpy(_data, old_data + _readPos, avail);
free(old_data);
_writePos = avail;
_readPos = 0;
}
}
}
uint32 availableSize() const {
if (_readPos >= _writePos)
return 0;
return _writePos - _readPos;
}
uint32 write(const void *dataPtr, uint32 dataSize) {
ensureFree(dataSize);
memcpy(_data + _writePos, dataPtr, dataSize);
_writePos += dataSize;
return dataSize;
}
uint32 read(byte *dataPtr, uint32 dataSize) {
uint32 avail = availableSize();
if (avail == 0)
return 0;
if (dataSize > avail)
dataSize = avail;
memcpy(dataPtr, _data + _readPos, dataSize);
_readPos += dataSize;
return dataSize;
}
private:
uint32 _writePos;
uint32 _readPos;
uint32 _capacity;
byte *_data;
};
// CPU_CLOCK according to AppleWin
static const double APPLEII_CPU_CLOCK = 1020484.5; // ~ 1.02 MHz
/*
* Converts the 1-bit speaker state values into audio samples.
* This is done by aggregation of the speaker states at each
* CPU cycle in a sampling period into an audio sample.
*/
class SampleConverter {
private:
void addSampleToBuffer(int sample) {
int16 value = sample * _volume / _maxVolume;
_buffer.write(&value, sizeof(value));
}
public:
SampleConverter() :
_cyclesPerSampleFP(0),
_missingCyclesFP(0),
_sampleCyclesSumFP(0),
_volume(_maxVolume)
{}
~SampleConverter() {}
void reset() {
_missingCyclesFP = 0;
_sampleCyclesSumFP = 0;
_buffer.clear();
}
uint32 availableSize() const {
return _buffer.availableSize();
}
void setMusicVolume(int vol) {
assert(vol >= 0 && vol <= _maxVolume);
_volume = vol;
}
void setSampleRate(int rate) {
/* ~46 CPU cycles per sample @ 22.05kHz */
_cyclesPerSampleFP = int(APPLEII_CPU_CLOCK * (1 << PREC_SHIFT) / rate);
reset();
}
void addCycles(byte level, const int cycles) {
/* convert to fixed precision floats */
int cyclesFP = cycles << PREC_SHIFT;
// step 1: if cycles are left from the last call, process them first
if (_missingCyclesFP > 0) {
int n = (_missingCyclesFP < cyclesFP) ? _missingCyclesFP : cyclesFP;
if (level)
_sampleCyclesSumFP += n;
cyclesFP -= n;
_missingCyclesFP -= n;
if (_missingCyclesFP == 0) {
addSampleToBuffer(2*32767 * _sampleCyclesSumFP / _cyclesPerSampleFP - 32767);
} else {
return;
}
}
_sampleCyclesSumFP = 0;
// step 2: process blocks of cycles fitting into a whole sample
while (cyclesFP >= _cyclesPerSampleFP) {
addSampleToBuffer(level ? 32767 : -32767);
cyclesFP -= _cyclesPerSampleFP;
}
// step 3: remember cycles left for next call
if (cyclesFP > 0) {
_missingCyclesFP = _cyclesPerSampleFP - cyclesFP;
if (level)
_sampleCyclesSumFP = cyclesFP;
}
}
uint32 readSamples(void *buffer, int numSamples) {
return _buffer.read((byte *)buffer, numSamples * 2) / 2;
}
private:
static const int PREC_SHIFT = 7;
private:
int _cyclesPerSampleFP; /* (fixed precision) */
int _missingCyclesFP; /* (fixed precision) */
int _sampleCyclesSumFP; /* (fixed precision) */
int _volume; /* 0 - 256 */
static const int _maxVolume = 256;
SampleBuffer _buffer;
};
class Player_AppleII;
class AppleII_SoundFunction {
public:
AppleII_SoundFunction() {}
virtual ~AppleII_SoundFunction() {}
virtual void init(Player_AppleII *player, const byte *params) = 0;
/* returns true if finished */
virtual bool update() = 0;
protected:
Player_AppleII *_player = nullptr;
};
class Player_AppleII : public Audio::AudioStream, public MusicEngine {
public:
Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_AppleII() override;
void setMusicVolume(int vol) override { _sampleConverter.setMusicVolume(vol); }
void setSampleRate(int rate) {
_sampleRate = rate;
_sampleConverter.setSampleRate(rate);
}
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getMusicTimer() override;
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
bool endOfData() const override { return false; }
int getRate() const override { return _sampleRate; }
public:
void speakerToggle();
void generateSamples(int cycles);
void wait(int interval, int count);
private:
// sound number
int _soundNr = 0;
// type of sound
int _type = 0;
// number of loops left
int _loop = 0;
// global sound param list
const byte *_params = nullptr;
// speaker toggle state (0 / 1)
byte _speakerState = 0;
// sound function
AppleII_SoundFunction *_soundFunc;
// cycle to sample converter
SampleConverter _sampleConverter;
ScummEngine *_vm;
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
int _sampleRate = 0;
Common::Mutex _mutex;
void resetState();
bool updateSound();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,237 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_he.h"
#include "scumm/scumm.h"
#include "scumm/file.h"
#include "audio/miles.h"
#include "audio/midiparser.h"
#include "audio/mixer.h"
#include "common/memstream.h"
namespace Scumm {
Player_HE::Player_HE(ScummEngine *scumm) :
_vm(scumm),
_currentMusic(-1),
_bank(nullptr),
_parser(nullptr),
_midi(nullptr),
_masterVolume(256),
_bankSize(0) {
for (int chan = 0; chan < 16; chan++)
_channelVolume[chan] = 127;
loadAdLibBank();
Common::MemoryReadStream *bankStream = new Common::MemoryReadStream(_bank, _bankSize);
_midi = Audio::MidiDriver_Miles_AdLib_create("", "", bankStream);
if (!_midi) {
error("Player_HE::Player_HE: could not create midi driver");
}
if (_midi->open() != 0) {
error("Player_HE::Player_HE: could not open midi driver");
}
}
Player_HE::~Player_HE() {
if (_parser) {
_parser->stopPlaying();
delete _parser;
_parser = NULL;
}
if (_midi) {
_midi->setTimerCallback(0, 0);
_midi->close();
delete _midi;
_midi = NULL;
}
if (_bank) {
free(_bank);
}
}
void Player_HE::setMusicVolume(int vol) {
_masterVolume = vol;
for (int chan = 0; chan < 16; chan++) {
byte volume = (_channelVolume[chan] * vol) / 256;
if (_midi)
_midi->send(0x07b0 | chan | (volume << 16));
}
}
void Player_HE::onTimer(void *data) {
Player_HE *player = (Player_HE *)data;
Common::StackLock lock(player->_mutex);
if (player->_parser)
player->_parser->onTimer();
}
void Player_HE::startSoundWithTrackID(int sound, int track) {
Common::StackLock lock(_mutex);
byte *ptr = _vm->getResourceAddress(rtSound, sound);
if (ptr == NULL)
return;
if (_parser) {
_parser->stopPlaying();
delete _parser;
}
_parser = MidiParser::createParser_XMIDI();
_parser->setMidiDriver(this);
_parser->loadMusic(ptr + 40, 0);
_parser->setTrack(track);
_parser->setTimerRate(_midi->getBaseTempo());
_midi->setTimerCallback(this, &Player_HE::onTimer);
_currentMusic = sound;
}
void Player_HE::stopSound(int sound) {
Common::StackLock lock(_mutex);
if (!_parser || _currentMusic != sound)
return;
_parser->stopPlaying();
delete _parser;
_parser = NULL;
}
void Player_HE::stopAllSounds() {
Common::StackLock lock(_mutex);
if (!_parser)
return;
_parser->stopPlaying();
delete _parser;
_parser = NULL;
}
int Player_HE::getSoundStatus(int sound) const {
Common::StackLock lock(_mutex);
return (_parser && _currentMusic == sound) ? _parser->isPlaying() : 0;
}
int Player_HE::getMusicTimer() {
Common::StackLock lock(_mutex);
return _parser ? _parser->getTick() : 0;
}
void Player_HE::loadAdLibBank() {
ScummFile file(_vm);
Common::String drvName;
char entryName[14];
uint32 tag, entrySize, fileSize;
Common::String bankName;
if (_vm->_game.id == GID_PUTTMOON) {
// Use GM bank
bankName = "FAT.AD";
} else {
// Use MT32-like bank
bankName = "MIDPAK.AD";
}
const char *ptr = strchr(_vm->_filenamePattern.pattern, '.');
if (ptr) {
drvName = Common::String(_vm->_filenamePattern.pattern, ptr - _vm->_filenamePattern.pattern + 1);
} else {
drvName = _vm->_filenamePattern.pattern;
drvName += '.';
}
drvName += "drv";
if (!file.open(Common::Path(drvName)))
error("Player_HE::loadAdLibBank(): could not open %s", drvName.c_str());
uint32 size = (uint32)file.size();
for (uint32 offset = 0; offset < size;) {
file.seek(offset, SEEK_SET);
if (size - offset < 31)
error("Player_HE::loadAdLibBank(): unexpected end of file");
tag = file.readUint32BE();
entrySize = file.readUint32BE();
if (size - offset < entrySize)
error("Player_HE::loadAdLibBank(): unexpected end of file");
fileSize = entrySize - 31;
file.read(entryName, 13);
entryName[13] = 0;
if (tag != MKTAG('F', 'I', 'L', 'E'))
error("Player_HE::loadAdLibBank(): unknown entry format");
if (entryName == bankName) {
_bank = (byte*)malloc(fileSize);
file.read(_bank, fileSize);
_bankSize = fileSize;
return;
}
offset += entrySize;
}
error("Player_HE::loadAdLibBank(): could not find %s entry", bankName.c_str());
}
int Player_HE::open() {
if (_midi)
return _midi->open();
return 0;
}
bool Player_HE::isOpen() const {
if (_midi)
return _midi->isOpen();
return false;
}
void Player_HE::close() {
if (_midi)
_midi->close();
}
void Player_HE::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
if (_midi)
_midi->setTimerCallback(timerParam, timerProc);
}
uint32 Player_HE::getBaseTempo() {
if (_midi)
return _midi->getBaseTempo();
return 0;
}
void Player_HE::send(uint32 b) {
byte chan = b & 0x0f;
byte cmd = b & 0xf0;
byte op1 = (b >> 8) & 0x7f;
byte op2 = (b >> 16) & 0x7f;
if (cmd == 0xb0 && op1 == 0x07) {
_channelVolume[chan] = op2;
op2 = (op2 * _masterVolume) / 256;
b = (b & 0xffff) | (op2 << 16);
}
if (_midi)
_midi->send(b);
}
}

View File

@@ -0,0 +1,71 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_HE_H
#define SCUMM_PLAYERS_PLAYER_HE_H
#include "scumm/music.h"
#include "audio/mixer.h"
#include "audio/mididrv.h"
#include "common/mutex.h"
class MidiParser;
namespace Scumm {
class ScummEngine;
class Player_HE : public MusicEngine, public MidiDriver {
public:
Player_HE(ScummEngine *scumm);
~Player_HE() override;
void setMusicVolume(int vol) override;
void startSound(int sound) override { startSoundWithTrackID(sound, 0); }
void startSoundWithTrackID(int sound, int track) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getMusicTimer() override;
int open() override;
bool isOpen() const override;
void close() override;
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
uint32 getBaseTempo() override;
MidiChannel *allocateChannel() override { return NULL; };
MidiChannel *getPercussionChannel() override { return NULL; };
void send(uint32 b) override;
private:
ScummEngine *_vm;
MidiParser *_parser;
MidiDriver *_midi;
Common::Mutex _mutex;
byte *_bank;
int _bankSize;
int _currentMusic;
int _masterVolume;
byte _channelVolume[16];
static void onTimer(void *data);
void loadAdLibBank();
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_MAC_INTERN_H
#define SCUMM_PLAYERS_PLAYER_MAC_INTERN_H
#include "engines/scumm/players/mac_sound_lowlevel.h"
#include "common/serializer.h"
namespace Scumm {
class LegacyMusicDriver;
class ScummEngine;
class Indy3MacSnd final : public VblTaskClientDriver {
private:
Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer);
public:
~Indy3MacSnd();
static Common::SharedPtr<Indy3MacSnd> open(ScummEngine *scumm, Audio::Mixer *mixer);
bool startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit);
void setMusicVolume(int vol);
void setSfxVolume(int vol);
void startSound(int id);
void stopSound(int id);
void stopAllSounds();
int getMusicTimer();
int getSoundStatus(int id) const;
void setQuality(int qual);
void saveLoadWithSerializer(Common::Serializer &ser);
void restoreAfterLoad();
void toggleMusic(bool enable);
void toggleSoundEffects(bool enable);
void vblCallback() override;
void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
const MacSoundDriver::Status &getDriverStatus(Audio::Mixer::SoundType sndType) const override;
private:
void startSong(int id);
void startSoundEffect(int id);
void stopSong();
void stopSoundEffect();
void stopActiveSound();
void finishSong();
void updateSong();
void updateSoundEffect();
void checkRestartSoundEffects();
void endOfTrack();
bool isSong(int id) const;
bool isHiQuality() const;
int _curSound;
int _curSong;
int _lastSoundEffectPrio;
int _soundEffectNumLoops;
int _songTimer;
bool _songUnfinished;
uint _activeChanCount;
byte _songTimerInternal;
byte *_soundUsage;
byte _disableFlags;
bool _soundEffectPlaying;
bool _soundEffectReschedule;
int _qmode;
bool _16bit;
bool _qualHi;
bool _mixerThread;
MacLowLevelPCMDriver::PCMSound _pcmSnd;
MacLowLevelPCMDriver::ChanHandle _sfxChan;
MacPlayerAudioStream *_macstr;
Audio::SoundHandle _soundHandle;
MacPlayerAudioStream::CallbackProc _nextTickProc;
ScummEngine *_vm;
Audio::Mixer *_mixer;
static Common::WeakPtr<Indy3MacSnd> *_inst;
const byte *_musicIDTable;
int _musicIDTableLen;
const int _idRangeMax;
LegacyMusicDriver *_mdrv;
MacLowLevelPCMDriver *_sdrv;
Common::Array<MacSoundDriver*> _drivers;
private:
class MusicChannel {
public:
MusicChannel(Indy3MacSnd *pl);
~MusicChannel();
void clear();
void start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq);
void nextTick();
void parseNextEvents();
void noteOn(uint16 duration, uint8 note);
uint16 checkPeriod() const;
uint16 _frameLen;
uint16 _curPos;
uint16 _freqCur;
uint16 _freqIncr;
uint16 _freqEff;
uint16 _envPhase;
uint16 _envRate;
uint16 _tempo;
uint16 _envSust;
int16 _transpose;
uint16 _envAtt;
uint16 _envShape;
uint16 _envStep;
uint16 _envStepLen;
uint16 _modType;
uint16 _modState;
uint16 _modStep;
uint16 _modSensitivity;
uint16 _modRange;
uint16 _localVars[5];
Common::SharedPtr<const byte> _resource;
bool _hq;
private:
typedef bool (Indy3MacSnd::MusicChannel::*CtrlProc)(const byte *&);
bool ctrl_setShape(const byte *&pos);
bool ctrl_modPara(const byte *&pos);
bool ctrl_init(const byte *&pos);
bool ctrl_returnFromSubroutine(const byte *&pos);
bool ctrl_jumpToSubroutine(const byte *&pos);
bool ctrl_initOther(const byte *&pos);
bool ctrl_decrJumpIf(const byte *&pos);
bool ctrl_writeVar(const byte *&pos);
const CtrlProc *_ctrlProc;
void limitedClear();
uint16 &getMemberRef(int pos);
uint16 **_vars;
int _numVars;
uint16 &_savedOffset;
uint16 _resSize;
Indy3MacSnd *_player;
static MusicChannel *_ctrlChan;
static const uint32 _envShapes[98];
const uint8 *const &_modShapes;
const uint32 &_modShapesTableSize;
bool ctrlProc(int procId, const byte *&arg);
void setFrameLen(uint8 len);
};
MusicChannel **_musicChannels;
const int _numMusicChannels;
const int _numMusicTracks;
public:
MusicChannel *getMusicChannel(uint8 id) const;
};
class MacSndLoader;
class LoomMonkeyMacSnd final : public VblTaskClientDriver, public MacLowLevelPCMDriver::CallbackClient {
private:
LoomMonkeyMacSnd(ScummEngine *vm, Audio::Mixer *mixer);
public:
~LoomMonkeyMacSnd();
static Common::SharedPtr<LoomMonkeyMacSnd> open(ScummEngine *scumm, Audio::Mixer *mixer);
bool startDevice(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit);
void setMusicVolume(int vol);
void setSfxVolume(int vol);
void startSound(int id, int jumpToTick = 0);
void stopSound(int id);
void stopAllSounds();
int getMusicTimer();
int getSoundStatus(int id) const;
void setQuality(int qual);
void saveLoadWithSerializer(Common::Serializer &ser);
void restoreAfterLoad();
void toggleMusic(bool enable);
void toggleSoundEffects(bool enable);
void vblCallback() override;
void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
const MacSoundDriver::Status &getDriverStatus(Audio::Mixer::SoundType sndType) const override;
void sndChannelCallback(uint16 arg1, const void *arg2) override;
private:
void sendSoundCommands(int timeStamp);
void stopActiveSound();
void setupChannels();
void disposeAllChannels();
void updateDisabledState();
void detectQuality();
bool isSoundCardType10() const;
int _curSound;
int _restartSound;
int _curSoundSaveVar;
int &_checkSound;
int _songTimer;
byte _songTimerInternal;
byte *_chanConfigTable;
const int _idRangeMax;
const byte _saveVersionChange;
const byte _legacySaveUnits;
bool _mixerThread;
int _machineRating;
int _selectedQuality;
int _effectiveChanConfig;
int _defaultChanConfig;
bool _16bit;
byte _disableFlags;
MacLowLevelPCMDriver::ChanHandle _sndChannel;
MacLowLevelPCMDriver::ChanHandle _musChannels[4];
MacPlayerAudioStream *_macstr;
MacSndLoader *_loader;
MacLowLevelPCMDriver *_sdrv;
Audio::SoundHandle _soundHandle;
MacPlayerAudioStream::CallbackProc _vblTskProc;
MacLowLevelPCMDriver::ChanCallback _chanCbProc;
ScummEngine *_vm;
Audio::Mixer *_mixer;
static Common::WeakPtr<LoomMonkeyMacSnd> *_inst;
byte _curChanConfig;
byte _chanUse;
byte _curSynthType;
Audio::Mixer::SoundType _curSndType;
Audio::Mixer::SoundType _lastSndType;
byte _chanPlaying;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,903 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_mac_new.h"
#include "scumm/players/player_mac_intern.h"
#include "scumm/resource.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/endian.h"
#include "common/punycode.h"
#include "common/macresman.h"
#include "common/memstream.h"
namespace Scumm {
#define ASC_DEVICE_RATE 0x56EE8BA3
#define PCM_BUFFER_SIZE 1024
class MacSndLoader {
protected:
MacSndLoader(bool useInstrTag);
public:
virtual ~MacSndLoader() {}
virtual bool init() = 0;
virtual bool checkResource(const byte *data, uint32 dataSize) const = 0;
virtual bool blocked(const byte *data, uint32 dataSize) const = 0;
virtual bool loadSound(const byte *data, uint32 dataSize) = 0;
virtual void unblock() = 0;
void setSynthType(byte type) { _synth = type; }
const MacLowLevelPCMDriver::PCMSound *getInstrData(uint16 chan);
virtual bool isInstrUsable(uint16 chan) const = 0;
virtual bool parseNextEvent(uint16 chan, uint16 &duration, uint8 &note, bool &skip, bool &updateInstr) = 0;
virtual uint16 getChanSetup() const = 0;
uint16 getTimbre() const { return _timbre; }
virtual bool isMusic() const = 0;
bool isLooping() const { return _loop; }
virtual bool restartSoundAfterLoad() const = 0;
virtual bool ignoreMachineRating() const = 0;
protected:
bool loadInstruments(const char *const *tryFileNames, uint16 numTryFileNames, uint16 numInstruments);
Common::Array<Common::SharedPtr<MacSndResource> > _instruments;
byte _sndRes6;
byte _isMusic;
byte _sndRes9;
byte _sndRes10;
uint16 _chanSetup;
uint16 _timbre;
bool _loop;
byte _synth;
const byte *_chanSndData[5];
uint32 _chanNumEvents[5];
uint32 _chanCurEvent[5];
const Common::SharedPtr<MacSndResource> *_chanInstr[5];
const Common::SharedPtr<MacSndResource> *_chanInstr2[5];
const bool _useInstrTag;
};
class LoomMacSndLoader final : public MacSndLoader {
public:
LoomMacSndLoader() : MacSndLoader(false) {}
~LoomMacSndLoader() override {}
bool init() override;
bool checkResource(const byte *data, uint32 dataSize) const override;
bool blocked(const byte *data, uint32 dataSize) const override { return false; }
bool loadSound(const byte *data, uint32 dataSize) override;
void unblock() override {}
bool isInstrUsable(uint16 chan) const override;
bool parseNextEvent(uint16 chan, uint16 &duration, uint8 &note, bool &skip, bool &updateInstr) override;
uint16 getChanSetup() const override { return _chanSetup; }
bool isMusic() const override { return (_chanSetup == 0); }
bool restartSoundAfterLoad() const override { return isMusic(); }
bool ignoreMachineRating() const override { return false; }
private:
const Common::SharedPtr<MacSndResource> *fetchInstrument(uint16 id) const;
};
class MonkeyMacSndLoader final : public MacSndLoader {
public:
MonkeyMacSndLoader() : MacSndLoader(true), _numInstrumentsMax(17), _blockSfx(false), _transpose(0) {}
~MonkeyMacSndLoader() override {}
bool init() override;
bool checkResource(const byte *data, uint32 dataSize) const override;
bool blocked(const byte *data, uint32 dataSize) const override;
bool loadSound(const byte *data, uint32 dataSize) override;
void unblock() override { _blockSfx = false; }
bool isInstrUsable(uint16 chan) const override;
bool parseNextEvent(uint16 chan, uint16 &duration, uint8 &note, bool &skip, bool &updateInstr) override;
uint16 getChanSetup() const override { return _isMusic ? _chanSetup : 7; }
bool isMusic() const override { return _isMusic; }
bool restartSoundAfterLoad() const override { return _isMusic && _loop; }
bool ignoreMachineRating() const override { return true; }
private:
const Common::SharedPtr<MacSndResource> *fetchInstrument(const byte *data, uint32 dataSize, uint32 tagOrOffset);
bool _blockSfx;
byte _transpose;
const byte _numInstrumentsMax;
};
MacSndLoader::MacSndLoader(bool useInstrTag) : _sndRes6(0), _isMusic(0), _sndRes9(0), _sndRes10(0), _chanSetup(0),
_timbre(0), _useInstrTag(useInstrTag), _synth(0), _loop(false) {
memset(_chanInstr, 0, sizeof(_chanInstr));
memset(_chanInstr2, 0, sizeof(_chanInstr2));
memset(_chanSndData, 0, sizeof(_chanSndData));
memset(_chanNumEvents, 0, sizeof(_chanNumEvents));
memset(_chanCurEvent, 0, sizeof(_chanCurEvent));
}
const MacLowLevelPCMDriver::PCMSound *MacSndLoader::getInstrData(uint16 chan) {
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan]) ? _chanInstr[chan]->get()->data() : nullptr;
}
bool MacSndLoader::loadInstruments(const char *const *tryFileNames, uint16 numTryFileNames, uint16 numInstruments) {
assert(tryFileNames && numTryFileNames && numInstruments);
uint32 tag = 0;
const Common::CodePage tryCodePages[] = {
Common::kMacRoman,
Common::kISO8859_1
};
Common::MacResManager resMan;
Common::Path resFile;
for (int i = 0; resFile.empty() && i < numTryFileNames; ++i) {
for (int ii = 0; resFile.empty() && ii < ARRAYSIZE(tryCodePages); ++ii) {
Common::U32String fn(tryFileNames[i], tryCodePages[ii]);
resFile = Common::Path(fn.encode(Common::kUtf8));
if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
resMan.close();
resFile = Common::Path(Common::punycode_encodefilename(fn));
if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
resMan.close();
resFile.clear();
}
}
}
}
if (resFile.empty()) {
warning("MacSndLoader::loadInstruments(): Resource fork not found");
return false;
}
Common::MacResIDArray ids = resMan.getResIDArray(MKTAG('s', 'n', 'd', ' '));
for (uint i = 0; i < ids.size(); ++i) {
Common::SeekableReadStream *str = resMan.getResource(MKTAG('s', 'n', 'd', ' '), ids[i]);
if (!str || str->readUint16BE() != 1) {
static const char *const errStr[2] = {"Failed to load", "Invalid sound resource format for"};
warning("MacSndLoader::loadInstruments(): %s instrument with id 0x%04x", errStr[str ? 1 : 0], ids[i]);
delete str;
return false;
}
Common::String nm(resMan.getResName(MKTAG('s', 'n', 'd', ' '), ids[i]));
memcpy(&tag, nm.c_str(), MIN<uint>(nm.size(), sizeof(tag)));
uint32 id = _useInstrTag ? FROM_BE_32(tag) : ids[i];
_instruments.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(id, str, Common::move(nm))));
delete str;
}
if (_instruments.size() != numInstruments)
warning("MacSndLoader::loadInstruments(): Unexpected number of instruments found (expected: %d, found: %d)", numInstruments, _instruments.size());
return true;
}
bool LoomMacSndLoader::init() {
static const char *const execNames[] = {
"Loom",
"Loom\xaa",
"Loom PPC",
"Loom\xaa PPC"
};
return loadInstruments(execNames, ARRAYSIZE(execNames), 10);
}
bool LoomMacSndLoader::checkResource(const byte *data, uint32 dataSize) const {
return (dataSize >= 14 && READ_BE_UINT16(data + 4) == MKTAG16('s','o') && !READ_BE_UINT32(data + 10));
}
bool LoomMacSndLoader::loadSound(const byte *data, uint32 dataSize) {
if (dataSize < 40)
return false;
_sndRes6 = READ_BE_UINT16(data + 6) & 0xff;
_isMusic = READ_BE_UINT16(data + 8) >> 8;
_sndRes10 = READ_BE_UINT16(data + 10) >> 8;
_chanSetup = READ_BE_UINT16(data + 16);
_timbre = READ_BE_UINT16(data + 18);
for (int i = 0; i < 5; ++i) {
_chanInstr[i] = fetchInstrument(READ_BE_UINT16(data + 20 + 2 * i));
_chanSndData[i] = data + READ_BE_UINT16(data + 30 + 2 * i) + 6;
_chanNumEvents[i] = READ_BE_UINT16(_chanSndData[i] - 2);
_chanCurEvent[i] = 0;
}
return true;
}
bool LoomMacSndLoader::isInstrUsable(uint16 chan) const {
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan] && _chanInstr[chan]->get()->id() != 0x2D1C);
}
bool LoomMacSndLoader::parseNextEvent(uint16 chan, uint16 &duration, uint8 &note, bool &skip, bool &updateInstr) {
if (chan >= ARRAYSIZE(_chanSndData) || !_chanSndData[chan] || _chanCurEvent[chan] >= _chanNumEvents[chan])
return false;
const byte *s = _chanSndData[chan] + (_chanCurEvent[chan]++) * 3;
duration = READ_BE_UINT16(s);
note = s[2] & 0x7f;
skip = false;
updateInstr = false;
if (_synth == 4 && chan != 0 && note == 0)
note = 1;
return true;
}
const Common::SharedPtr<MacSndResource> *LoomMacSndLoader::fetchInstrument(uint16 id) const {
Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator instr = _instruments.end();
for (Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
if ((*i)->id() == id)
return i;
else if ((*i)->id() == 0x2D1C)
instr = i;
}
return (instr != _instruments.end()) ? instr : nullptr;
}
bool MonkeyMacSndLoader::init() {
static const char *const execNames[] = {
"Monkey Island"
};
return loadInstruments(execNames, ARRAYSIZE(execNames), _numInstrumentsMax - 1);
}
bool MonkeyMacSndLoader::checkResource(const byte *data, uint32 dataSize) const {
return (dataSize >= 14 && (READ_BE_UINT32(data) == MKTAG('M', 'a', 'c', '0') || READ_BE_UINT32(data) == MKTAG('M', 'a', 'c', '1')));
}
bool MonkeyMacSndLoader::blocked(const byte *data, uint32 dataSize) const {
return (dataSize < 14 || (_blockSfx && !data[13]));
}
bool MonkeyMacSndLoader::loadSound(const byte *data, uint32 dataSize) {
if (dataSize < 32)
return false;
_sndRes9 = data[9];
_isMusic = data[13];
_sndRes10 = data[10];
_chanSetup = data[11];
_timbre = data[12];
for (int i = 0; i < 4; ++i) {
uint32 offs = READ_BE_UINT32(data + 16 + 4 * i);
_chanCurEvent[i] = 0;
if (offs) {
if (dataSize < offs + 12)
return false;
_chanInstr[i] = fetchInstrument(data, dataSize, READ_BE_UINT32(data + offs + 8));
_chanInstr2[i] = nullptr;
_chanSndData[i] = &data[offs + 12];
_chanNumEvents[i] = 0;
for (const byte *s = _chanSndData[i]; s < &data[dataSize - 4]; s += 4) {
uint32 in = READ_BE_UINT32(s);
if (in == MKTAG('L', 'o', 'o', 'p') || in == MKTAG('D', 'o', 'n', 'e')) {
if (i == 1)
_loop = (in == MKTAG('L', 'o', 'o', 'p'));
break;
}
_chanNumEvents[i]++;
}
} else {
_chanInstr[i] = nullptr;
_chanSndData[i] = nullptr;
}
}
_blockSfx = _isMusic;
return true;
}
bool MonkeyMacSndLoader::isInstrUsable(uint16 chan) const {
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan] && _chanInstr[chan]->get()->id() != MKTAG('s', 'i', 'l', 'e'));
}
bool MonkeyMacSndLoader::parseNextEvent(uint16 chan, uint16 &duration, uint8 &note, bool &skip, bool &updateInstr) {
if (chan >= ARRAYSIZE(_chanSndData) || !_chanSndData[chan] || _chanCurEvent[chan] >= _chanNumEvents[chan])
return false;
const byte *s = _chanSndData[chan] + (_chanCurEvent[chan]++) * 4;
duration = READ_BE_UINT16(s);
note = s[2];
skip = false;
updateInstr = true;
if (duration == 0 && _chanCurEvent[chan] == _chanNumEvents[chan])
skip = true;
if (_synth == 4) {
if (!skip && note == 0) {
note = 60;
_chanInstr2[chan] = _chanInstr[chan];
_chanInstr[chan] = fetchInstrument(nullptr, 0, MKTAG('s', 'i', 'l', 'e'));
} else if (_chanInstr2[chan]) {
_chanInstr[chan] = _chanInstr2[chan];
_chanInstr2[chan] = nullptr;
} else {
updateInstr = false;
}
if (note == 1)
skip = true;
else if (s[6] == 1)
duration += READ_BE_UINT16(s + 4);
} else {
updateInstr = false;
if (note == 1)
note = 0;
else if (note != 0)
note += _transpose;
}
return true;
}
const Common::SharedPtr<MacSndResource> *MonkeyMacSndLoader::fetchInstrument(const byte *data, uint32 dataSize, uint32 tagOrOffset) {
Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator instr = _instruments.end();
if (tagOrOffset & ~0x7fffff) {
for (Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
if ((*i)->id() == tagOrOffset)
return i;
else if ((*i)->id() == MKTAG('s', 'i', 'l', 'e'))
instr = i;
}
} else if (dataSize >= tagOrOffset + 8) {
Common::SeekableReadStream *str = new Common::MemoryReadStream(&data[tagOrOffset + 8], READ_BE_UINT32(data + tagOrOffset + 4), DisposeAfterUse::NO);
if (_instruments.size() == _numInstrumentsMax)
_instruments.pop_back();
_instruments.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(READ_BE_UINT32(&data[tagOrOffset]), str, Common::String())));
delete str;
instr = _instruments.end() - 1;
}
return (instr != _instruments.end()) ? instr : nullptr;
}
Common::WeakPtr<LoomMonkeyMacSnd> *LoomMonkeyMacSnd::_inst = nullptr;
LoomMonkeyMacSnd::LoomMonkeyMacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(), _vm(vm), _mixer(mixer), _curSound(0), _loader(nullptr),
_macstr(nullptr), _sdrv(nullptr), _vblTskProc(this, &VblTaskClientDriver::vblCallback), _songTimer(0), _songTimerInternal(0), _disableFlags(0),
_machineRating(0), _selectedQuality(2), _effectiveChanConfig(0), _16bit(false), _idRangeMax(200), _sndChannel(0), _chanUse(0), _defaultChanConfig(0),
_chanConfigTable(nullptr), _chanPlaying(0), _curChanConfig(0), _curSynthType(0), _curSndType(Audio::Mixer::kPlainSoundType), _mixerThread(false),
_restartSound(0), _lastSndType(Audio::Mixer::kPlainSoundType), _chanCbProc(this, &MacLowLevelPCMDriver::CallbackClient::sndChannelCallback),
_curSoundSaveVar(0), _saveVersionChange(vm->_game.id == GID_MONKEY ? 115 : 114), _legacySaveUnits(vm->_game.id == GID_MONKEY ? 3 : 5),
_checkSound(vm->_game.id == GID_MONKEY ? _curSound : _curSoundSaveVar) {
assert(_vm);
assert(_mixer);
static const byte cfgtable[] = { 0, 0, 0, 1, 4, 5, 1, 5, 6, 6, 8, 9 };
_chanConfigTable = new byte[sizeof(cfgtable)]();
memcpy(_chanConfigTable, cfgtable, sizeof(cfgtable));
if (vm->_game.id == GID_MONKEY)
_chanConfigTable[10] = 7;
memset(_musChannels, 0, sizeof(_musChannels));
}
LoomMonkeyMacSnd::~LoomMonkeyMacSnd() {
_mixer->stopHandle(_soundHandle);
delete _macstr;
delete[] _chanConfigTable;
disposeAllChannels();
delete _sdrv;
delete _loader;
delete _inst;
_inst = nullptr;
}
Common::SharedPtr<LoomMonkeyMacSnd> LoomMonkeyMacSnd::open(ScummEngine *vm, Audio::Mixer *mixer) {
Common::SharedPtr<LoomMonkeyMacSnd> scp = nullptr;
if (_inst == nullptr) {
scp = Common::SharedPtr<LoomMonkeyMacSnd>(new LoomMonkeyMacSnd(vm, mixer));
_inst = new Common::WeakPtr<LoomMonkeyMacSnd>(scp);
// We can start this with the ScummVM mixer output rate instead of the ASC rate. The Mac sample rate converter can handle it (at
// least for up to 48 KHz, I haven't tried higher settings) and we don't have to do another rate conversion in the ScummVM mixer.
if ((_inst == nullptr) || (mixer == nullptr) || !(scp->startDevice(mixer->getOutputRate(), mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, PCM_BUFFER_SIZE, true, false, true)))
error("LoomMonkeyMacSnd::open(): Failed to start player");
}
return _inst->lock();
}
bool LoomMonkeyMacSnd::startDevice(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
_macstr = new MacPlayerAudioStream(this, outputRate, stereo, enableInterpolation, internal16Bit);
if (!_macstr || !_mixer)
return false;
if (_vm->_game.id == GID_LOOM)
_loader = new LoomMacSndLoader();
else if(_vm->_game.id == GID_MONKEY)
_loader = new MonkeyMacSndLoader();
if (!_loader || !_loader->init())
return false;
_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, internal16Bit);
if (!_sdrv)
return false;
_effectiveChanConfig = 9;
_16bit = internal16Bit;
_macstr->initBuffers(feedBufferSize);
_macstr->addVolumeGroup(Audio::Mixer::kMusicSoundType);
_macstr->addVolumeGroup(Audio::Mixer::kSFXSoundType);
_macstr->setVblCallback(&_vblTskProc);
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
return true;
}
void LoomMonkeyMacSnd::setMusicVolume(int vol) {
Common::StackLock lock(_mixer->mutex());
if (_macstr)
_macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
}
void LoomMonkeyMacSnd::setSfxVolume(int vol) {
Common::StackLock lock(_mixer->mutex());
if (_macstr)
_macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
}
void LoomMonkeyMacSnd::startSound(int id, int jumpToTick) {
if (_sdrv == nullptr || id < 1 || id >= _idRangeMax) {
warning("LoomMonkeyMacSnd::startSound(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
return;
}
Common::StackLock lock(_mixer->mutex());
uint32 size = _vm->getResourceSize(rtSound, id);
const byte *ptr = _vm->getResourceAddress(rtSound, id);
assert(ptr);
if (!_loader->checkResource(ptr, size)) {
warning("LoomMonkeyMacSnd::startSound(): Sound resource '%d' cannot be played", id);
return;
}
if (_loader->blocked(ptr, size))
return;
if (_curSound)
stopActiveSound();
if (_chanUse <= 1)
disposeAllChannels();
if (!_defaultChanConfig)
detectQuality();
if (!_loader->loadSound(ptr, size)) {
warning("LoomMonkeyMacSnd::startSound(): Sound resource '%d' cannot be played", id);
return;
}
if (_disableFlags) {
if (_loader->restartSoundAfterLoad()) {
_curSoundSaveVar = id;
_loader->unblock();
}
if (_loader->isMusic() || (_disableFlags & 2))
return;
}
_effectiveChanConfig = _loader->getChanSetup() ? _loader->getChanSetup() : _defaultChanConfig;
_curSndType = _loader->isMusic() ? Audio::Mixer::kMusicSoundType : Audio::Mixer::kSFXSoundType;
if (_lastSndType != _curSndType)
_curChanConfig = 0;
_curSound = id;
_curSoundSaveVar = _loader->restartSoundAfterLoad() ? _curSound : 0;
setupChannels();
sendSoundCommands(jumpToTick);
if (!jumpToTick) {
_songTimer = 0;
_songTimerInternal = 0;
}
}
void LoomMonkeyMacSnd::stopSound(int id) {
if (id < 1 || id >= _idRangeMax) {
warning("LoomMonkeyMacSnd::stopSound(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
return;
}
Common::StackLock lock(_mixer->mutex());
if (id == _curSoundSaveVar)
_curSoundSaveVar = 0;
if (id == _curSound)
stopActiveSound();
}
void LoomMonkeyMacSnd::stopAllSounds() {
Common::StackLock lock(_mixer->mutex());
stopActiveSound();
}
int LoomMonkeyMacSnd::getMusicTimer() {
Common::StackLock lock(_mixer->mutex());
return _songTimer;
}
int LoomMonkeyMacSnd::getSoundStatus(int id) const {
if (id < 1 || id >= _idRangeMax) {
warning("LoomMonkeyMacSnd::getSoundStatus(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
return 0;
}
Common::StackLock lock(_mixer->mutex());
return (_checkSound == id) ? 1 : 0;
}
void LoomMonkeyMacSnd::setQuality(int qual) {
assert(qual >= MacSound::kQualityAuto && qual <= MacSound::kQualityHighest);
Common::StackLock lock(_mixer->mutex());
if (qual > MacSound::kQualityAuto) {
qual--;
if (!_loader->ignoreMachineRating())
_machineRating = (qual / 3) + 1;
_selectedQuality = qual % 3;
qual = _chanConfigTable[_machineRating * 3 + _selectedQuality];
if (qual && qual == _defaultChanConfig)
return;
}
int csnd = _curSound;
int32 timeStamp = csnd ? _songTimer * 1000 + ((_songTimerInternal * 1000) / 30) : 0;
stopActiveSound();
detectQuality();
if (csnd)
startSound(csnd, timeStamp);
}
void LoomMonkeyMacSnd::saveLoadWithSerializer(Common::Serializer &ser) {
if (ser.isLoading() && ser.getVersion() < VER(94)) {
_curSound = _curSoundSaveVar = 0;
return;
}
if (ser.isLoading()) {
if (ser.getVersion() < VER(_saveVersionChange)) {
// Skip over old driver savedata, since it is not needed here.
ser.skip(4);
// We need only this
ser.syncAsSint16LE(_curSound);
_curSoundSaveVar = CLIP<int>(_curSound, 0, _idRangeMax - 1);
// Skip the rest
ser.skip(_legacySaveUnits * (ser.getVersion() >= VER(94) && ser.getVersion() <= VER(103) ? 24 : 20));
} else if (ser.getVersion() <= VER(114)) {
_curSoundSaveVar = 0;
byte tmp = 0;
for (int i = 0; !_curSoundSaveVar && i < 200; ++i) {
ser.syncAsByte(tmp);
if (tmp)
_curSoundSaveVar = i;
}
}
}
ser.syncAsSint16LE(_curSoundSaveVar, VER(115));
}
void LoomMonkeyMacSnd::restoreAfterLoad() {
int sound = _curSoundSaveVar;
stopActiveSound();
if (sound)
startSound(sound);
}
void LoomMonkeyMacSnd::toggleMusic(bool enable) {
if ((_disableFlags & 1) == (enable ? 0 : 1))
return;
_disableFlags ^= 1;
updateDisabledState();
}
void LoomMonkeyMacSnd::toggleSoundEffects(bool enable) {
if ((_disableFlags & 2) == (enable ? 0 : 2))
return;
_disableFlags ^= 2;
updateDisabledState();
}
void LoomMonkeyMacSnd::vblCallback() {
if (_songTimerInternal++ == 29) {
_songTimerInternal = 0;
++_songTimer;
}
if (_restartSound) {
startSound(_restartSound);
_restartSound = 0;
}
}
void LoomMonkeyMacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
assert(dst);
memset(dst, 0, len);
_sdrv->feed(dst, len, type, expectStereo);
}
const MacSoundDriver::Status &LoomMonkeyMacSnd::getDriverStatus(Audio::Mixer::SoundType sndType) const {
return _sdrv->getStatus(sndType);
}
void LoomMonkeyMacSnd::sndChannelCallback(uint16 arg1, const void*) {
// The original Loom player stops the track immediately when the first channel invoked its end-of-track
// callback. This cuts of the playback early, in an unpleasant way. The Monkey Island player stops the
// playback not before all channels have finished. We do the same here for both games.
_chanPlaying &= ~arg1;
if (_chanPlaying)
return;
if (_loader->isLooping())
_restartSound = _curSound;
stopActiveSound();
}
void LoomMonkeyMacSnd::sendSoundCommands(int timeStamp) {
if (!_defaultChanConfig || !_curSound)
return;
uint16 duration = 0;
byte note = 0;
bool skip = false;
bool updateInstr = false;
_chanPlaying = 0;
if (_chanUse == 1 && _sndChannel) {
while (_loader->parseNextEvent(0, duration, note, skip, updateInstr)) {
if (timeStamp > 0 && !skip) {
timeStamp -= duration;
if (timeStamp >= 0)
skip = true;
else if (timeStamp < 0)
duration = -timeStamp;
}
if (updateInstr)
_sdrv->loadInstrument(_sndChannel, MacLowLevelPCMDriver::kEnqueue, _loader->getInstrData(0));
if (!skip) {
_sdrv->playNote(_sndChannel, MacLowLevelPCMDriver::kEnqueue, note, duration);
if (note == 0) // Workaround for tempo glitch in original driver
_sdrv->wait(_sndChannel, MacLowLevelPCMDriver::kEnqueue, duration);
}
}
_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kEnqueue);
_sdrv->callback(_sndChannel, MacLowLevelPCMDriver::kEnqueue, 1, nullptr);
_chanPlaying |= 1;
} else if (_chanUse == 4) {
int tmstmp[4];
uint8 busy = 0;
for (int i = 0; i < 4; ++i) {
tmstmp[i] = timeStamp;
if (_musChannels[i])
busy |= (1 << i);
}
while (busy) {
for (int i = 0; i < 4; ++i) {
if (!(busy & (1 << i)) || !_loader->parseNextEvent(i + 1, duration, note, skip, updateInstr)) {
busy &= ~(1 << i);
continue;
}
if (tmstmp[i] > 0 && !skip) {
tmstmp[i] -= duration;
if (tmstmp[i] >= 0)
skip = true;
else if (tmstmp[i] < 0)
duration = -tmstmp[i];
}
if (updateInstr)
_sdrv->loadInstrument(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, _loader->getInstrData(i + 1));
if (!skip) {
_sdrv->playNote(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, note, duration);
// Workaround for tempo glitch in original driver. For the sampled synth in 4 channel mode, there is
// some sort of fix in the original (see parseNextEvent()), but that really does not work well for the other cases.
if (note == 0 && _curSynthType != 4)
_sdrv->wait(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, duration);
}
_chanPlaying |= (1 << i);
}
}
for (int i = 0; i < 4; ++i) {
if (_chanPlaying & (1 << i)) {
_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kEnqueue);
_sdrv->callback(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, 1 << i, nullptr);
}
}
}
}
void LoomMonkeyMacSnd::stopActiveSound() {
if (_sndChannel) {
_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kImmediate);
_sdrv->flush(_sndChannel, MacLowLevelPCMDriver::kImmediate);
}
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (_musChannels[i]) {
_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
_sdrv->flush(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
}
}
_chanPlaying = 0;
_curSound = 0;
_curSoundSaveVar = 0;
_loader->unblock();
}
void LoomMonkeyMacSnd::setupChannels() {
static const byte synthType[] = { 0x00, 0x01, 0x02, 0x04, 0x04, 0x04, 0x02, 0x04, 0x04, 0x04 };
static const byte numChan[] = { 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x04, 0x04 };
static const byte attrib[] = { 0x00, 0x00, 0x04, 0xAC, 0xA4, 0xA0, 0x04, 0xAC, 0xA4, 0xA0 };
if (!_defaultChanConfig)
return;
if (_curChanConfig != _effectiveChanConfig) {
disposeAllChannels();
_curChanConfig = _effectiveChanConfig;
_curSynthType = synthType[_curChanConfig];
_chanUse = numChan[_curChanConfig];
_lastSndType = _curSndType;
_loader->setSynthType(_curSynthType);
switch (_curSynthType) {
case 1:
if (_chanUse == 1 && !_sndChannel) {
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSquareWaveSynth, attrib[_curChanConfig], &_chanCbProc);
}
break;
case 2:
if (_chanUse == 1 && !_sndChannel) {
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig], &_chanCbProc);
} else if (_chanUse == 4) {
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (!_musChannels[i] && _loader->isInstrUsable(i + 1))
_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig] + i, &_chanCbProc);
}
}
break;
case 4:
if (_chanUse == 1 && !_sndChannel) {
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
} else if (_chanUse == 4) {
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (!_musChannels[i] && _loader->isInstrUsable(i + 1))
_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
}
}
break;
default:
break;
}
}
switch (_curSynthType) {
case 1:
if (_sndChannel)
_sdrv->setTimbre(_sndChannel, MacLowLevelPCMDriver::kImmediate, _loader->getTimbre());
break;
case 2:
if (_chanUse == 1) {
if (_sndChannel)
_sdrv->loadWaveTable(_sndChannel, MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
} else {
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (_musChannels[i])
_sdrv->loadWaveTable(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
}
}
break;
case 4:
if (_chanUse == 1) {
if (_sndChannel && _loader->getInstrData(0))
_sdrv->loadInstrument(_sndChannel, MacLowLevelPCMDriver::kImmediate, _loader->getInstrData(0));
} else {
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (_musChannels[i] && _loader->getInstrData(i + 1))
_sdrv->loadInstrument(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _loader->getInstrData(i + 1));
}
}
break;
default:
break;
}
}
void LoomMonkeyMacSnd::disposeAllChannels() {
if (_sndChannel)
_sdrv->disposeChannel(_sndChannel);
_sndChannel = 0;
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
if (_musChannels[i])
_sdrv->disposeChannel(_musChannels[i]);
_musChannels[i] = 0;
}
_curChanConfig = 0;
}
void LoomMonkeyMacSnd::updateDisabledState() {
if (_disableFlags == 0) {
if (_curSoundSaveVar)
startSound(_curSoundSaveVar);
} else {
int sound = _curSoundSaveVar;
stopActiveSound();
_curSoundSaveVar = sound;
}
}
void LoomMonkeyMacSnd::detectQuality() {
if (_machineRating == 0) {
if (!_loader->ignoreMachineRating()) {
if (isSoundCardType10())
_machineRating = 2;
/*else if (0)
_machineRating = 1;*/
}
_machineRating ^= 3;
}
_defaultChanConfig = _effectiveChanConfig = _chanConfigTable[_machineRating * 3 + _selectedQuality];
_curChanConfig = 0;
disposeAllChannels();
setupChannels();
_chanConfigTable[_machineRating * 3 + _selectedQuality] = _defaultChanConfig;
}
bool LoomMonkeyMacSnd::isSoundCardType10() const {
return _mixerThread ? (_machineRating == 1) : (_vm->VAR_SOUNDCARD != 0xff && _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
}
#undef ASC_DEVICE_RATE
#undef PCM_BUFFER_SIZE
} // End of namespace Scumm

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_MAC_INDY3_H
#define SCUMM_PLAYERS_PLAYER_MAC_INDY3_H
#include "scumm/music.h"
namespace Scumm {
class ScummEngine;
namespace MacSound {
enum {
kQualityAuto = 0,
kQualityLowest = 1,
kHardwareRatingLow = 0,
kHardwareRatingMedium = 3,
kHardwareRatingHigh = 6,
kQualitySelectionGood = 1,
kQualitySelectionBetter = 2,
kQualitySelectionBest = 3,
kQualityHighest = 9
};
MusicEngine *createPlayer(ScummEngine *vm);
} // end of namespace MacSound
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,233 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_mod.h"
#include "audio/mixer.h"
#include "audio/rate.h"
#include "audio/decoders/raw.h"
namespace Scumm {
Player_MOD::Player_MOD(Audio::Mixer *mixer)
: _mixer(mixer), _sampleRate(mixer->getOutputRate()) {
int i;
_mixamt = 0;
_mixpos = 0;
for (i = 0; i < MOD_MAXCHANS; i++) {
_channels[i].id = 0;
_channels[i].vol = 0;
_channels[i].freq = 0;
_channels[i].input = nullptr;
_channels[i].ctr = 0;
_channels[i].pos = 0;
}
_playproc = nullptr;
_playparam = nullptr;
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
Player_MOD::~Player_MOD() {
_mixer->stopHandle(_soundHandle);
for (int i = 0; i < MOD_MAXCHANS; i++) {
if (!_channels[i].id)
continue;
delete _channels[i].input;
}
}
void Player_MOD::setMusicVolume(int vol) {
_maxvol = vol;
}
void Player_MOD::setUpdateProc(ModUpdateProc *proc, void *param, int freq) {
Common::StackLock lock(_mutex);
_playproc = proc;
_playparam = param;
_mixamt = _sampleRate / freq;
}
void Player_MOD::clearUpdateProc() {
Common::StackLock lock(_mutex);
_playproc = nullptr;
_playparam = nullptr;
_mixamt = 0;
}
void Player_MOD::startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart, int loopEnd, int8 pan) {
int i;
if (id == 0)
error("player_mod - attempted to start channel id 0");
Common::StackLock lock(_mutex);
for (i = 0; i < MOD_MAXCHANS; i++) {
if (!_channels[i].id)
break;
}
if (i == MOD_MAXCHANS) {
warning("player_mod - too many music channels playing (%i max)",MOD_MAXCHANS);
return;
}
_channels[i].id = id;
_channels[i].vol = vol;
_channels[i].pan = pan;
_channels[i].freq = rate;
_channels[i].ctr = 0;
Audio::SeekableAudioStream *stream = Audio::makeRawStream((const byte *)data, size, rate, 0);
if (loopStart != loopEnd) {
_channels[i].input = new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate));
} else {
_channels[i].input = stream;
}
// read the first sample
_channels[i].input->readBuffer(&_channels[i].pos, 1);
}
void Player_MOD::stopChannel(int id) {
if (id == 0)
error("player_mod - attempted to stop channel id 0");
Common::StackLock lock(_mutex);
for (int i = 0; i < MOD_MAXCHANS; i++) {
if (_channels[i].id == id) {
delete _channels[i].input;
_channels[i].input = nullptr;
_channels[i].id = 0;
_channels[i].vol = 0;
_channels[i].freq = 0;
_channels[i].ctr = 0;
_channels[i].pos = 0;
}
}
}
void Player_MOD::setChannelVol(int id, uint8 vol) {
if (id == 0)
error("player_mod - attempted to set volume for channel id 0");
Common::StackLock lock(_mutex);
for (int i = 0; i < MOD_MAXCHANS; i++) {
if (_channels[i].id == id) {
_channels[i].vol = vol;
break;
}
}
}
void Player_MOD::setChannelPan(int id, int8 pan) {
if (id == 0)
error("player_mod - attempted to set pan for channel id 0");
Common::StackLock lock(_mutex);
for (int i = 0; i < MOD_MAXCHANS; i++) {
if (_channels[i].id == id) {
_channels[i].pan = pan;
break;
}
}
}
void Player_MOD::setChannelFreq(int id, int freq) {
if (id == 0)
error("player_mod - attempted to set frequency for channel id 0");
Common::StackLock lock(_mutex);
for (int i = 0; i < MOD_MAXCHANS; i++) {
if (_channels[i].id == id) {
if (freq > 31400) // this is about as high as WinUAE goes
freq = 31400; // can't easily verify on my own Amiga
_channels[i].freq = freq;
break;
}
}
}
void Player_MOD::do_mix(int16 *data, uint len) {
int i;
int dpos = 0;
uint dlen = 0;
memset(data, 0, 2 * len * sizeof(int16));
while (len) {
if (_playproc) {
dlen = _mixamt - _mixpos;
if (!_mixpos)
_playproc(_playparam);
if (dlen <= len) {
_mixpos = 0;
len -= dlen;
} else {
_mixpos += len;
dlen = len;
len = 0;
}
} else {
dlen = len;
len = 0;
}
for (i = 0; i < MOD_MAXCHANS; i++) {
if (_channels[i].id) {
Audio::st_volume_t vol_l = (127 - _channels[i].pan) * _channels[i].vol / 127;
Audio::st_volume_t vol_r = (127 + _channels[i].pan) * _channels[i].vol / 127;
for (uint j = 0; j < dlen; j++) {
// simple linear resample, unbuffered
int delta = (uint32)(_channels[i].freq * 0x10000) / _sampleRate;
uint16 cfrac = ~_channels[i].ctr & 0xFFFF;
if (_channels[i].ctr + delta < 0x10000)
cfrac = delta;
_channels[i].ctr += delta;
int32 cpos = _channels[i].pos * cfrac / 0x10000;
while (_channels[i].ctr >= 0x10000) {
if (_channels[i].input->readBuffer(&_channels[i].pos, 1) != 1) { // out of data
stopChannel(_channels[i].id);
goto skipchan; // exit 2 loops at once
}
_channels[i].ctr -= 0x10000;
if (_channels[i].ctr > 0x10000)
cpos += _channels[i].pos;
else
cpos += (int32)(_channels[i].pos * (_channels[i].ctr & 0xFFFF)) / 0x10000;
}
int16 pos = 0;
// if too many samples play in a row, the calculation below will overflow and clip
// so try and split it up into pieces it can manage comfortably
while (cpos < -0x8000) {
pos -= 0x80000000 / delta;
cpos += 0x8000;
}
while (cpos > 0x7FFF) {
pos += 0x7FFF0000 / delta;
cpos -= 0x7FFF;
}
pos += cpos * 0x10000 / delta;
Audio::clampedAdd(data[(dpos + j) * 2 + 0], pos * vol_l / Audio::Mixer::kMaxMixerVolume);
Audio::clampedAdd(data[(dpos + j) * 2 + 1], pos * vol_r / Audio::Mixer::kMaxMixerVolume);
}
}
skipchan: ; // channel ran out of data
}
dpos += dlen;
}
}
} // End of namespace Scumm

View File

@@ -0,0 +1,102 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_MOD_H
#define SCUMM_PLAYERS_PLAYER_MOD_H
#include "scumm/scumm.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/mutex.h"
namespace Audio {
class RateConverter;
}
namespace Scumm {
/**
* Generic Amiga MOD mixer - provides a 60Hz 'update' routine.
*/
class Player_MOD : public Audio::AudioStream {
public:
Player_MOD(Audio::Mixer *mixer);
~Player_MOD() override;
virtual void setMusicVolume(int vol);
virtual void startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart = 0, int loopEnd = 0, int8 pan = 0);
virtual void stopChannel(int id);
virtual void setChannelVol(int id, uint8 vol);
virtual void setChannelPan(int id, int8 pan);
virtual void setChannelFreq(int id, int freq);
typedef void ModUpdateProc(void *param);
virtual void setUpdateProc(ModUpdateProc *proc, void *param, int freq);
virtual void clearUpdateProc();
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override {
Common::StackLock lock(_mutex);
do_mix(buffer, numSamples / 2);
return numSamples;
}
bool isStereo() const override { return true; }
bool endOfData() const override { return false; }
int getRate() const override { return _sampleRate; }
private:
enum {
MOD_MAXCHANS = 24
};
struct soundChan {
int id;
uint8 vol;
int8 pan;
uint16 freq;
uint32 ctr;
int16 pos;
Audio::AudioStream *input;
};
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
Common::Mutex _mutex;
uint32 _mixamt;
uint32 _mixpos;
const int _sampleRate;
soundChan _channels[MOD_MAXCHANS];
uint8 _maxvol;
virtual void do_mix(int16 *buf, uint len);
ModUpdateProc *_playproc;
void *_playparam;
};
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_NES_H
#define SCUMM_PLAYERS_PLAYER_NES_H
#include "common/scummsys.h"
#include "scumm/music.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Scumm {
class ScummEngine;
namespace APUe {
class APU;
}
static const int MAXVOLUME = 0x7F;
static const int NUMSLOTS = 3;
static const int NUMCHANS = 4;
/**
* Scumm NES sound/music driver.
*/
class Player_NES : public Audio::AudioStream, public MusicEngine {
public:
Player_NES(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_NES() override;
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
bool endOfData() const override { return false; }
int getRate() const override { return _sampleRate; }
private:
void sound_play();
void playSFX(int nr);
void playMusic();
void chainCommand(int chan);
void checkSilenceChannels(int chan);
void APU_writeChannel(int chan, int offset, byte value);
void APU_writeControl(byte value);
byte APU_readStatus();
ScummEngine *_vm;
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
APUe::APU *_apu;
int _sampleRate;
int _samples_per_frame;
int _current_sample;
int _maxvol;
struct slot {
int framesleft;
int id;
int type;
byte *data;
int offset;
} _slot[NUMSLOTS];
struct mchan {
int command;
int framedelay;
int pitch;
int volume;
int voldelta;
int envflags;
int cmdlock;
} _mchan[NUMCHANS];
bool isSFXplaying, wasSFXplaying;
int numNotes;
byte *auxData1;
byte *auxData2;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,758 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* The PSG_HuC6280 class is based on the HuC6280 sound chip emulator
* by Charles MacDonald (E-mail: cgfm2@hotmail.com, WWW: http://www.techno-junk.org)
* The implementation used here was taken from MESS (http://mess.redump.net)
* the Multiple Emulator Super System (sound/c6280.c).
* LFO and noise channel support have been removed (not used by Loom PCE).
*/
#include "scumm/players/player_pce.h"
#include "common/endian.h"
// PCE sound engine is only used by Loom, which requires 16bit color support
#ifdef USE_RGB_COLOR
namespace Scumm {
// CPU and PSG use the same base clock but with a different divider
const double MASTER_CLOCK = 21477270.0; // ~21.48 MHz
const double CPU_CLOCK = MASTER_CLOCK / 3; // ~7.16 MHz
const double PSG_CLOCK = MASTER_CLOCK / 6; // ~3.58 MHz
const double TIMER_CLOCK = CPU_CLOCK / 1024; // ~6.9 kHz
// The PSG update routine is originally triggered by the timer IRQ (not by VSYNC)
// approx. 120 times per second (TIML=0x39). But as just every second call is used
// to update the PSG we will call the update routine approx. 60 times per second.
const double UPDATE_FREQ = TIMER_CLOCK / (57 + 1) / 2; // ~60 Hz
// $AFA5
static const byte wave_table[7][32] = {
{ // sine
0x10, 0x19, 0x1C, 0x1D, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x19,
0x10, 0x05, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x05,
}, { // mw-shaped
0x10, 0x1C, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x1E, 0x1C, 0x1E, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1C,
0x10, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03,
}, { // square
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
}, { // triangle
0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14,
0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14,
}, { // saw-tooth
0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
}, { // sigmoid
0x07, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19,
0x08, 0x06, 0x05, 0x03, 0x02, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16,
}, { // MW-shaped
0x1F, 0x1E, 0x1D, 0x1D, 0x1C, 0x1A, 0x17, 0x0F, 0x0F, 0x17, 0x1A, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F,
0x00, 0x01, 0x02, 0x02, 0x03, 0x05, 0x08, 0x0F, 0x0F, 0x08, 0x05, 0x03, 0x02, 0x02, 0x01, 0x00
}
};
// AEBC
static const int control_offsets[14] = {
0, 7, 20, 33, 46, 56, 75, 88, 116, 126, 136, 152, 165, 181
};
// AED8
static const byte control_data[205] = {
/* 0*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0xFF,
/* 7*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x3A, 0x00, 0xFD, 0xFF,
/* 20*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x1D, 0x00, 0xF8, 0xFF,
/* 33*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x1E, 0x00, 0xFC, 0xFF,
/* 46*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x07, 0x00, 0xE0, 0xFF,
/* 56*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xD8, 0xF0, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFF,
/* 75*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF8, 0x6E, 0x00, 0xFF, 0xFF,
/* 88*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x08, 0x00, 0xF0, 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x05, 0x00, 0xF0, 0xF0, 0x00, 0xB8, 0xE6, 0x80, 0xFF, 0xE6, 0x80, 0xFF, 0xFF,
/*116*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x05, 0x00, 0xD0, 0xFF,
/*126*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF8, 0xFF,
/*136*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF4, 0xE6, 0xC0, 0xFF, 0xE6, 0xC0, 0xFF, 0xFF,
/*152*/ 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x10, 0x0E, 0x00, 0xFE, 0xFF,
/*165*/ 0xF0, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x18, 0x00, 0x02, 0xE6, 0x80, 0xFE, 0xE6, 0xC0, 0xFF, 0xFF,
/*181*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF8, 0x02, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x02, 0x00, 0x08, 0xF1, 0x99, 0xAF
};
static const uint16 lookup_table[87] = {
0x0D40, 0x0C80, 0x0BC0, 0x0B20, 0x0A80, 0x09E0, 0x0940, 0x08C0,
0x0840, 0x07E0, 0x0760, 0x0700, 0x06A0, 0x0640, 0x05E0, 0x0590,
0x0540, 0x04F0, 0x04A0, 0x0460, 0x0420, 0x03F0, 0x03B0, 0x0380,
0x0350, 0x0320, 0x02F0, 0x02C8, 0x02A0, 0x0278, 0x0250, 0x0230,
0x0210, 0x01F8, 0x01D8, 0x01C0, 0x01A8, 0x0190, 0x0178, 0x0164,
0x0150, 0x013C, 0x0128, 0x0118, 0x0108, 0x00FC, 0x00EC, 0x00E0,
0x00D4, 0x00C8, 0x00BC, 0x00B2, 0x00A8, 0x009E, 0x0094, 0x008C,
0x0084, 0x007E, 0x0076, 0x0070, 0x006A, 0x0064, 0x005E, 0x0059,
0x0054, 0x004F, 0x004A, 0x0046, 0x0042, 0x003F, 0x003B, 0x0038,
0x0035, 0x0032, 0x0030, 0x002D, 0x002A, 0x0028, 0x0026, 0x0024,
0x0022, 0x0020, 0x001E, 0x001C, 0x001B, 0x8E82, 0xB500
};
// B27B
static const uint16 freq_offset[3] = {
0, 2, 9
};
static const uint16 freq_table[] = {
0x0000, 0x0800,
0xFFB0, 0xFFD1, 0xFFE8, 0xFFF1, 0x0005, 0x0000, 0x0800,
0xFF9C, 0xFFD8, 0x0000, 0x000F, 0x0005, 0x0000, 0x0800
};
static const int sound_table[13] = {
0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 1, 10, 11
};
// 0xAE12
// Note:
// - offsets relative to data_table
// - byte one of each sound was always 0x3F (= use all channels) -> removed from table
static const uint16 sounds[13][6] = {
{ 481, 481, 481, 481, 481, 481 },
{ 395, 408, 467, 480, 480, 480 },
{ 85, 96, 109, 109, 109, 109 },
{ 110, 121, 134, 134, 134, 134 },
{ 135, 146, 159, 159, 159, 159 },
{ 160, 171, 184, 184, 184, 184 },
{ 185, 196, 209, 209, 209, 209 },
{ 210, 221, 234, 234, 234, 234 },
{ 235, 246, 259, 259, 259, 259 },
{ 260, 271, 284, 284, 284, 284 },
{ 285, 298, 311, 324, 335, 348 },
{ 349, 360, 361, 362, 373, 384 },
{ 0, 84, 84, 84, 84, 84 } // unused
};
// 0xB2A1
static const byte data_table[482] = {
/* 0*/ 0xE2, 0x0A, 0xE1, 0x0D, 0xE6, 0xED, 0xE0, 0x0F, 0xE2, 0x00, 0xE1, 0x00,
0xF2, 0xF2, 0xB2, 0xE1, 0x01, 0xF2, 0xF2, 0xB2, 0xE1, 0x02, 0xF2, 0xF2,
0xB2, 0xE1, 0x03, 0xF2, 0xF2, 0xB2, 0xE1, 0x04, 0xF2, 0xF2, 0xB2, 0xE1,
0x05, 0xF2, 0xF2, 0xB2, 0xE1, 0x06, 0xF2, 0xF2, 0xB2, 0xE1, 0x07, 0xF2,
0xF2, 0xB2, 0xE1, 0x08, 0xF2, 0xF2, 0xB2, 0xE1, 0x09, 0xF2, 0xF2, 0xB2,
0xE1, 0x0A, 0xF2, 0xF2, 0xB2, 0xE1, 0x0B, 0xF2, 0xF2, 0xB2, 0xE1, 0x0C,
0xF2, 0xF2, 0xB2, 0xE1, 0x0D, 0xF2, 0xF2, 0xB2, 0xFF, 0xD1, 0x03, 0xF3,
/* 84*/ 0xFF,
/* 85*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x07, 0xFF,
/* 96*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x07, 0xFF,
/*109*/ 0xFF,
/*110*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x27, 0xFF,
/*121*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x27, 0xFF,
/*134*/ 0xFF,
/*135*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x47, 0xFF,
/*146*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x47, 0xFF,
/*159*/ 0xFF,
/*160*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x57, 0xFF,
/*171*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x57, 0xFF,
/*184*/ 0xFF,
/*185*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x77, 0xFF,
/*196*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x77, 0xFF,
/*209*/ 0xFF,
/*210*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x97, 0xFF,
/*221*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x97, 0xFF,
/*234*/ 0xFF,
/*235*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0xB7, 0xFF,
/*246*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0xB7, 0xFF,
/*259*/ 0xFF,
/*260*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0x07, 0xFF,
/*271*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD5, 0xF0, 0x0C, 0x07, 0xFF,
/*284*/ 0xFF,
/*285*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x14, 0x0E, 0xFF,
/*298*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x32, 0x1E, 0xFF,
/*311*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x50, 0x1E, 0xFF,
/*324*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0x0E, 0xFF,
/*335*/ 0xE2, 0x0B, 0xE1, 0x02, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x0A, 0x0E, 0xFF,
/*348*/ 0xFF,
/*349*/ 0xE2, 0x03, 0xE1, 0x01, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x17, 0xFF,
/*360*/ 0xFF,
/*361*/ 0xFF,
/*362*/ 0xE2, 0x04, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x06, 0xD5, 0xA7, 0xFF,
/*373*/ 0xE2, 0x03, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x37, 0xFF,
/*384*/ 0xE2, 0x04, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD3, 0x87, 0xFF,
/*395*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x0B, 0xE8, 0x0B, 0xFF,
/*408*/ 0xE2, 0x0C, 0xE1, 0x03, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x00,
0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00,
0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xFF,
/*467*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x1B, 0xE8, 0x1B, 0xFF,
/*480*/ 0xFF,
/*481*/ 0xFF
};
/*
* PSG_HuC6280
*/
class PSG_HuC6280 {
private:
typedef struct {
uint16 frequency;
uint8 control;
uint8 balance;
uint8 waveform[32];
uint8 index;
int16 dda;
uint32 counter;
} channel_t;
double _clock;
double _rate;
uint8 _select;
uint8 _balance;
channel_t _channel[8];
int16 _volumeTable[32];
uint32 _noiseFreqTable[32];
uint32 _waveFreqTable[4096];
public:
void init();
void reset();
void write(int offset, byte data);
void update(int16* samples, int sampleCnt);
PSG_HuC6280(double clock, double samplerate);
};
PSG_HuC6280::PSG_HuC6280(double clock, double samplerate) {
_clock = clock;
_rate = samplerate;
// Initialize PSG_HuC6280 emulator
init();
}
void PSG_HuC6280::init() {
int i;
double step;
// Loudest volume level for table
double level = 65535.0 / 6.0 / 32.0;
// Clear context
reset();
// Make waveform frequency table
for (i = 0; i < 4096; i++) {
step = ((_clock / _rate) * 4096) / (i+1);
_waveFreqTable[(1 + i) & 0xFFF] = (uint32)step;
}
// Make noise frequency table
for (i = 0; i < 32; i++) {
step = ((_clock / _rate) * 32) / (i+1);
_noiseFreqTable[i] = (uint32)step;
}
// Make volume table
// PSG_HuC6280 has 48dB volume range spread over 32 steps
step = 48.0 / 32.0;
for (i = 0; i < 31; i++) {
_volumeTable[i] = (uint16)level;
level /= pow(10.0, step / 20.0);
}
_volumeTable[31] = 0;
}
void PSG_HuC6280::reset() {
_select = 0;
_balance = 0xFF;
memset(_channel, 0, sizeof(_channel));
memset(_volumeTable, 0, sizeof(_volumeTable));
memset(_noiseFreqTable, 0, sizeof(_noiseFreqTable));
memset(_waveFreqTable, 0, sizeof(_waveFreqTable));
}
void PSG_HuC6280::write(int offset, byte data) {
channel_t *chan = &_channel[_select];
switch(offset & 0x0F) {
case 0x00: // Channel select
_select = data & 0x07;
break;
case 0x01: // Global balance
_balance = data;
break;
case 0x02: // Channel frequency (LSB)
chan->frequency = (chan->frequency & 0x0F00) | data;
chan->frequency &= 0x0FFF;
break;
case 0x03: // Channel frequency (MSB)
chan->frequency = (chan->frequency & 0x00FF) | (data << 8);
chan->frequency &= 0x0FFF;
break;
case 0x04: // Channel control (key-on, DDA mode, volume)
// 1-to-0 transition of DDA bit resets waveform index
if ((chan->control & 0x40) && ((data & 0x40) == 0)) {
chan->index = 0;
}
chan->control = data;
break;
case 0x05: // Channel balance
chan->balance = data;
break;
case 0x06: // Channel waveform data
switch(chan->control & 0xC0) {
case 0x00:
chan->waveform[chan->index & 0x1F] = data & 0x1F;
chan->index = (chan->index + 1) & 0x1F;
break;
case 0x40:
default:
break;
case 0x80:
chan->waveform[chan->index & 0x1F] = data & 0x1F;
chan->index = (chan->index + 1) & 0x1F;
break;
case 0xC0:
chan->dda = data & 0x1F;
break;
}
break;
case 0x07: // Noise control (enable, frequency)
case 0x08: // LFO frequency
case 0x09: // LFO control (enable, mode)
break;
default:
break;
}
}
void PSG_HuC6280::update(int16* samples, int sampleCnt) {
static const int scale_tab[] = {
0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F
};
int ch;
int i;
int lmal = (_balance >> 4) & 0x0F;
int rmal = (_balance >> 0) & 0x0F;
int vll, vlr;
lmal = scale_tab[lmal];
rmal = scale_tab[rmal];
// Clear buffer
memset(samples, 0, 2 * sampleCnt * sizeof(int16));
for (ch = 0; ch < 6; ch++) {
// Only look at enabled channels
if (_channel[ch].control & 0x80) {
int lal = (_channel[ch].balance >> 4) & 0x0F;
int ral = (_channel[ch].balance >> 0) & 0x0F;
int al = _channel[ch].control & 0x1F;
lal = scale_tab[lal];
ral = scale_tab[ral];
// Calculate volume just as the patent says
vll = (0x1F - lal) + (0x1F - al) + (0x1F - lmal);
if (vll > 0x1F) vll = 0x1F;
vlr = (0x1F - ral) + (0x1F - al) + (0x1F - rmal);
if (vlr > 0x1F) vlr = 0x1F;
vll = _volumeTable[vll];
vlr = _volumeTable[vlr];
// Check channel mode
if (_channel[ch].control & 0x40) {
/* DDA mode */
for (i = 0; i < sampleCnt; i++) {
samples[2*i] += (int16)(vll * (_channel[ch].dda - 16));
samples[2*i + 1] += (int16)(vlr * (_channel[ch].dda - 16));
}
} else {
/* Waveform mode */
uint32 step = _waveFreqTable[_channel[ch].frequency];
for (i = 0; i < sampleCnt; i += 1) {
int offset;
int16 data;
offset = (_channel[ch].counter >> 12) & 0x1F;
_channel[ch].counter += step;
_channel[ch].counter &= 0x1FFFF;
data = _channel[ch].waveform[offset];
samples[2*i] += (int16)(vll * (data - 16));
samples[2*i + 1] += (int16)(vlr * (data - 16));
}
}
}
}
}
/*
* Player_PCE
*/
void Player_PCE::PSG_Write(int reg, byte data) {
_psg->write(reg, data);
}
void Player_PCE::setupWaveform(byte bank) {
const byte *ptr = wave_table[bank];
PSG_Write(4, 0x40);
PSG_Write(4, 0x00);
for (int i = 0; i < 32; ++i) {
PSG_Write(6, ptr[i]);
}
}
// A541
void Player_PCE::procA541(channel_t *channel) {
channel->soundDataPtr = nullptr;
channel->controlVecShort10 = 0;
channel->controlVecShort03 = 0;
channel->controlVecShort06 = 0;
channel->controlVec8 = 0;
channel->controlVec9 = 0;
channel->controlVec10 = 0;
channel->soundUpdateCounter = 0;
channel->controlVec18 = 0;
channel->controlVec19 = 0;
channel->controlVec23 = false;
channel->controlVec24 = false;
channel->controlVec21 = 0;
channel->waveformCtrl = 0x80;
}
// A592
void Player_PCE::startSound(int sound) {
channel_t *channel;
const uint16 *ptr = sounds[sound_table[sound]];
for (int i = 0; i < 6; ++i) {
channel = &channels[i];
procA541(channel);
channel->controlVec24 = true;
channel->waveformCtrl = 0;
channel->controlVec0 = 0;
channel->controlVec19 = 0;
channel->controlVec18 = 0;
channel->soundDataPtr = &data_table[*ptr++];
}
}
// A64B
void Player_PCE::updateSound() {
for (int i = 0; i < 12; i++) {
channel_t *channel = &channels[i];
bool cond = true;
if (i < 6) {
channel->controlVec21 ^= 0xFF;
if (!channel->controlVec21)
cond = false;
}
if (cond) {
processSoundData(channel);
procAB7F(channel);
procAC24(channel);
channel->controlVec11 = (channel->controlVecShort10 >> 11) | 0x80;
channel->balance = channel->balance2;
}
}
for (int i = 0; i < 6; ++i) {
procA731(&channels[i]);
}
}
int Player_PCE::readBuffer(int16 *buffer, const int numSamples) {
int sampleCopyCnt;
int samplesLeft = numSamples;
int16 *sampleBufferPtr = _sampleBuffer;
Common::StackLock lock(_mutex);
while (true) {
// copy samples to output buffer
sampleCopyCnt = (samplesLeft < _sampleBufferCnt) ? samplesLeft : _sampleBufferCnt;
if (sampleCopyCnt > 0) {
memcpy(buffer, sampleBufferPtr, sampleCopyCnt * sizeof(int16));
buffer += sampleCopyCnt;
samplesLeft -= sampleCopyCnt;
_sampleBufferCnt -= sampleCopyCnt;
sampleBufferPtr += sampleCopyCnt;
}
if (samplesLeft == 0)
break;
// retrieve samples for one timer period
updateSound();
_psg->update(_sampleBuffer, _samplesPerPeriod / 2);
_sampleBufferCnt = _samplesPerPeriod;
sampleBufferPtr = _sampleBuffer;
}
// copy remaining samples to the front of the buffer
if (_sampleBufferCnt > 0) {
memmove(_sampleBuffer,
sampleBufferPtr,
_sampleBufferCnt * sizeof(int16));
}
return numSamples;
}
void Player_PCE::procA731(channel_t *channel) {
PSG_Write(0, channel->id);
PSG_Write(2, channel->freq & 0xFF);
PSG_Write(3, (channel->freq >> 8) & 0xFF);
int tmp = channel->controlVec11;
if ((channel->controlVec11 & 0xC0) == 0x80) {
tmp = channel->controlVec11 & 0x1F;
if (tmp != 0) {
tmp -= channel->controlVec0;
if (tmp >= 0) {
tmp |= 0x80;
} else {
tmp = 0;
}
}
}
PSG_Write(5, channel->balance);
if ((channel->waveformCtrl & 0x80) == 0) {
channel->waveformCtrl |= 0x80;
PSG_Write(0, channel->id);
setupWaveform(channel->waveformCtrl & 0x7F);
}
PSG_Write(4, tmp);
}
// A793
void Player_PCE::processSoundData(channel_t *channel) {
channel->soundUpdateCounter--;
if (channel->soundUpdateCounter > 0) {
return;
}
while (true) {
const byte *ptr = channel->soundDataPtr;
byte value = (ptr ? *ptr++ : 0xFF);
if (value < 0xD0) {
int mult = (value & 0x0F) + 1;
channel->soundUpdateCounter = mult * channel->controlVec1;
value >>= 4;
procAA62(channel, value);
channel->soundDataPtr = ptr;
return;
}
// jump_table (A7F7)
switch (value - 0xD0) {
case 0: /*A85A*/
case 1: /*A85D*/
case 2: /*A861*/
case 3: /*A865*/
case 4: /*A869*/
case 5: /*A86D*/
case 6: /*A871*/
channel->controlVec2 = (value - 0xD0) * 12;
break;
case 11: /*A8A8*/
channel->controlVecShort06 = (int8)*ptr++;
break;
case 16: /*A8C2*/
channel->controlVec1 = *ptr++;
break;
case 17: /*A8CA*/
channel->waveformCtrl = *ptr++;
break;
case 18: /*A8D2*/
channel->controlVec10 = *ptr++;
break;
case 22: /*A8F2*/
value = *ptr;
channel->balance = value;
channel->balance2 = value;
ptr++;
break;
case 24: /*A905*/
channel->controlVec23 = true;
break;
case 32: /*A921*/
ptr++;
break;
case 47:
channel->controlVec24 = false;
channel->controlVec10 &= 0x7F;
channel->controlVecShort10 &= 0x00FF;
return;
default:
// unused -> ignore
break;
}
channel->soundDataPtr = ptr;
}
}
void Player_PCE::procAA62(channel_t *channel, int a) {
procACEA(channel, a);
if (channel->controlVec23) {
channel->controlVec23 = false;
return;
}
channel->controlVec18 = 0;
channel->controlVec10 |= 0x80;
int y = channel->controlVec10 & 0x7F;
channel->controlBufferPos = &control_data[control_offsets[y]];
channel->controlVec5 = 0;
}
void Player_PCE::procAB7F(channel_t *channel) {
uint16 freqValue = channel->controlVecShort02;
channel->controlVecShort02 += channel->controlVecShort03;
int pos = freq_offset[channel->controlVec19] + channel->controlVec18;
freqValue += freq_table[pos];
if (freq_table[pos + 1] != 0x0800) {
channel->controlVec18++;
}
freqValue += channel->controlVecShort06;
channel->freq = freqValue;
}
void Player_PCE::procAC24(channel_t *channel) {
if ((channel->controlVec10 & 0x80) == 0)
return;
if (channel->controlVec5 == 0) {
const byte *ctrlPtr = channel->controlBufferPos;
byte value = *ctrlPtr++;
while (value >= 0xF0) {
if (value == 0xF0) {
channel->controlVecShort10 = READ_LE_UINT16(ctrlPtr);
ctrlPtr += 2;
} else if (value == 0xFF) {
channel->controlVec10 &= 0x7F;
return;
} else {
// unused
}
value = *ctrlPtr++;
}
channel->controlVec5 = value;
channel->controlVecShort09 = READ_LE_UINT16(ctrlPtr);
ctrlPtr += 2;
channel->controlBufferPos = ctrlPtr;
}
channel->controlVecShort10 += channel->controlVecShort09;
channel->controlVec5--;
}
void Player_PCE::procACEA(channel_t *channel, int a) {
int x = a +
channel->controlVec2 +
channel->controlVec8 +
channel->controlVec9;
channel->controlVecShort02 = lookup_table[x];
}
Player_PCE::Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer) {
for (int i = 0; i < 12; ++i) {
memset(&channels[i], 0, sizeof(channel_t));
channels[i].id = i;
}
_mixer = mixer;
_sampleRate = _mixer->getOutputRate();
_vm = scumm;
_samplesPerPeriod = 2 * (int)(_sampleRate / UPDATE_FREQ);
_sampleBuffer = new int16[_samplesPerPeriod];
_sampleBufferCnt = 0;
_psg = new PSG_HuC6280(PSG_CLOCK, _sampleRate);
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
Player_PCE::~Player_PCE() {
_mixer->stopHandle(_soundHandle);
delete[] _sampleBuffer;
delete _psg;
}
void Player_PCE::stopSound(int nr) {
// TODO: implement
}
void Player_PCE::stopAllSounds() {
// TODO: implement
}
int Player_PCE::getSoundStatus(int nr) const {
// TODO: status for each sound
for (int i = 0; i < 6; ++i) {
if (channels[i].controlVec24)
return 1;
}
return 0;
}
int Player_PCE::getMusicTimer() {
return 0;
}
} // End of namespace Scumm
#endif // USE_RGB_COLOR

View 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_PCE_H
#define SCUMM_PLAYERS_PLAYER_PCE_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "scumm/music.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
// PCE sound engine is only used by Loom, which requires 16bit color support
#ifdef USE_RGB_COLOR
namespace Scumm {
class ScummEngine;
class PSG_HuC6280;
class Player_PCE : public Audio::AudioStream, public MusicEngine {
private:
struct channel_t {
int id;
byte controlVec0;
byte controlVec1;
byte controlVec2;
byte controlVec5;
byte balance;
byte balance2;
byte controlVec8;
byte controlVec9;
byte controlVec10;
byte controlVec11;
int16 soundUpdateCounter;
byte controlVec18;
byte controlVec19;
byte waveformCtrl;
byte controlVec21;
bool controlVec23;
bool controlVec24;
uint16 controlVecShort02;
uint16 controlVecShort03;
int16 controlVecShort06;
uint16 freq;
uint16 controlVecShort09;
uint16 controlVecShort10;
const byte* soundDataPtr;
const byte* controlBufferPos;
};
public:
Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_PCE() override;
void setMusicVolume(int vol) override { _maxvol = vol; }
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getMusicTimer() override;
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return true; }
bool endOfData() const override { return false; }
int getRate() const override { return _sampleRate; }
private:
ScummEngine *_vm;
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
int _sampleRate;
int _maxvol;
private:
PSG_HuC6280 *_psg;
channel_t channels[12];
Common::Mutex _mutex;
// number of samples per timer period
int _samplesPerPeriod;
int16* _sampleBuffer;
int _sampleBufferCnt;
void PSG_Write(int reg, byte data);
void setupWaveform(byte bank);
void procA541(channel_t *channel);
void updateSound();
void procA731(channel_t *channel);
void processSoundData(channel_t *channel);
void procAA62(channel_t *channel, int a);
void procAB7F(channel_t *channel);
void procAC24(channel_t *channel);
void procACEA(channel_t *channel, int a);
};
} // End of namespace Scumm
#endif // USE_RGB_COLOR
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_SID_H
#define SCUMM_PLAYERS_PLAYER_SID_H
#include "common/mutex.h"
#include "common/scummsys.h"
#include "scumm/music.h"
#include "audio/sid.h"
namespace Scumm {
// the "channel" parameters seem to be in fact SID register
// offsets. Should be replaced.
enum sid_reg_t {
FREQ_VOICE1,
FREQ_VOICE2,
FREQ_VOICE3,
FREQ_FILTER,
PULSE_VOICE1,
PULSE_VOICE2,
PULSE_VOICE3
};
class ScummEngine;
class Player_SID : public MusicEngine {
public:
Player_SID(ScummEngine *scumm);
~Player_SID() override;
void setMusicVolume(int vol) override { _maxvol = vol; }
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getMusicTimer() override;
private:
SID::SID *_sid;
void SID_Write(int reg, uint8 data);
void initSID();
uint8 *getResource(int resID);
ScummEngine *_vm;
int _maxvol;
Common::Mutex _mutex;
int _music_timer;
uint8* _music;
private:
void initMusic(int songResIndex); // $7de6
int initSound(int soundResID); // $4D0A
void stopSound_intern(int soundResID); // $5093
void stopMusic_intern(); // $4CAA
void resetSID(); // $48D8
void onTimer(); // $481B
void handleMusicBuffer();
int setupSongFileData(); // $36cb
void func_3674(int channel); // $3674
void resetPlayerState(); // $48f7
void processSongData(int channel); // $4939
void readSetSIDFilterAndProps(int *offset, uint8* dataPtr); // $49e7
void saveSongPos(int y, int channel);
void updateFreq(int channel);
void resetFreqDelta(int channel);
void readSongChunk(int channel); // $4a6b
void setSIDFreqAS(int channel); // $4be6
void setSIDWaveCtrlReg(int channel); // $4C0D
int setupSongPtr(int channel); // $4C1C
void unlockResource(int chanResIndex); // $4CDA
void countFreeChannels(); // $4f26
void func_4F45(int channel); // $4F45
void safeUnlockResource(int resIndex); // $4FEA
void releaseResource(int resIndex); // $5031
void releaseResChannels(int resIndex); // $5070
void releaseResourceUnk(int resIndex); // $50A4
void releaseChannel(int channel);
void clearSIDWaveform(int channel);
void stopChannel(int channel);
void swapVars(int channel, int swapIndex); // $51a5
void resetSwapVars(); // $52d0
void prepareSwapVars(int channel); // $52E5
void useSwapVars(int channel); // $5342
void lockResource(int resIndex); // $4ff4
void reserveChannel(int channel, uint8 prioValue, int chanResIndex); // $4ffe
void unlockCodeLocation(); // $513e
void lockCodeLocation(); // $514f
void func_7eae(int channel, uint8* songFileDataPtr); // $7eae
void func_819b(int channel); // $819b
void buildStepTbl(int step); // $82B4
int reserveSoundFilter(uint8 value, uint8 chanResIndex); // $4ED0
int reserveSoundVoice(uint8 value, uint8 chanResIndex); // $4EB8
void findLessPrioChannels(uint8 soundPrio); // $4ED8
void releaseResourceBySound(int resID); // $5088
void readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID); // $4E99
void unused1(); // $50AF
uint8 chanBuffer[3][45];
int resID_song;
// statusBits1A/1B are always equal
uint8 statusBits1A;
uint8 statusBits1B;
uint8 busyChannelBits;
uint8 SIDReg23;
uint8 SIDReg23Stuff;
uint8 SIDReg24;
uint8* chanFileData[3];
uint16 chanDataOffset[3];
uint8* songPosPtr[7];
// 0..2: freq value voice1/2/3
// 3: filter freq
// 4..6: pulse width
uint16 freqReg[7];
// start offset[i] for songFileOrChanBufData to obtain songPosPtr[i]
// vec6[0..2] = 0x0008;
// vec6[4..6] = 0x0019;
uint16 vec6[7];
// current offset[i] for songFileOrChanBufData to obtain songPosPtr[i] (starts with vec6[i], increased later)
uint16 songFileOrChanBufOffset[7];
uint16 freqDelta[7];
int freqDeltaCounter[7];
uint8* swapSongPosPtr[3];
uint8* swapVec5[3];
uint16 swapVec8[3];
uint16 swapVec10[3];
uint16 swapFreqReg[3];
int swapVec11[3];
// never read
//uint8* vec5[7];
// never read
//uint8 vec19[7];
// never read (needed by scumm engine?)
//bool curChannelActive;
uint8* vec20[7];
uint8* swapVec20[3];
// resource status (never read)
// bit7: some flag
// bit6..0: counter (use-count?), maybe just bit0 as flag (used/unused?)
uint8 resStatus[70];
uint8* songFileOrChanBufData;
uint8* actSongFileData;
uint16 stepTbl[33];
bool initializing;
bool _soundInQueue;
bool isVoiceChannel;
bool isMusicPlaying;
bool swapVarLoaded;
bool bgSoundActive;
bool filterUsed;
uint8 bgSoundResID;
uint8 freeChannelCount;
// seems to be used for managing the three voices
// bit[0..2]: 0 -> unused, 1 -> already in use
uint8 usedChannelBits;
uint8 attackReg[3];
uint8 sustainReg[3];
// -1/0/1
int var481A;
// bit-array: 00000cba
// a/b/c: channel1/2/3
uint8 songChannelBits;
bool pulseWidthSwapped;
bool swapPrepared;
// never read
//uint8 var5163;
bool filterSwapped;
uint8 SIDReg24_HiNibble;
bool keepSwapVars;
uint8 phaseBit[3];
bool releasePhase[3];
// values: a resID or -1
// resIDs: 3, 4, 5 or song-number
int _soundQueue[7];
// values: a resID or 0
// resIDs: 3, 4, 5 or song-number
int channelMap[7];
uint8 songPosUpdateCounter[7];
// priortity of channel contents
// MM: 1: lowest .. 120: highest (1,2,A,64,6E,73,78)
// Zak: -???: lowest .. 120: highest (5,32,64,65,66,6E,78, A5,A6,AF,D7)
uint8 chanPrio[7];
// only [0..2] used?
uint8 waveCtrlReg[7];
uint8 swapAttack[2];
uint8 swapSustain[2];
uint8 swapSongPrio[3];
int swapVec479C[3];
uint8 swapVec19[3];
uint8 swapSongPosUpdateCounter[3];
uint8 swapWaveCtrlReg[3];
bool actFilterHasLowerPrio;
uint8 chansWithLowerPrioCount;
uint8 minChanPrio;
uint8 minChanPrioIndex;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,741 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/sound.h"
#include "scumm/players/player_towns.h"
namespace Scumm {
Player_Towns::Player_Towns(ScummEngine *vm, bool isVersion2) : _vm(vm), _v2(isVersion2), _numSoundMax(isVersion2 ? 256 : 200) {
memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound));
}
void Player_Towns::setSfxVolume(int vol) {
if (!_intf)
return;
_intf->setSoundEffectVolume(vol);
}
int Player_Towns::getSoundStatus(int sound) const {
if (!_intf)
return 0;
for (int i = 1; i < 9; i++) {
if (_pcmCurrentSound[i].index == sound)
return _intf->callback(40, 0x3f + i) ? 1 : 0;
}
return 0;
}
void syncWithSerializer(Common::Serializer &s, Player_Towns::PcmCurrentSound &pcs) {
s.syncAsSint16LE(pcs.index, VER(81));
s.syncAsSint16LE(pcs.chan, VER(81));
s.syncAsByte(pcs.note, VER(81));
s.syncAsByte(pcs.velo, VER(81));
s.syncAsByte(pcs.pan, VER(81));
s.syncAsByte(pcs.paused, VER(81));
s.syncAsByte(pcs.looping, VER(81));
s.syncAsUint32LE(pcs.priority, VER(81));
}
void Player_Towns::saveLoadWithSerializer(Common::Serializer &s) {
for (int i = 1; i < 9; i++) {
if (!_pcmCurrentSound[i].index)
continue;
if (_intf->callback(40, i + 0x3f))
continue;
_intf->callback(39, i + 0x3f);
_pcmCurrentSound[i].index = 0;
}
s.syncArray(_pcmCurrentSound, 9, syncWithSerializer);
}
void Player_Towns::restoreAfterLoad() {
Common::Array<uint16> restoredSounds;
for (int i = 1; i < 9; i++) {
if (!_pcmCurrentSound[i].index || _pcmCurrentSound[i].index == 0xffff)
continue;
// Don't restart multichannel sounds more than once
if (Common::find(restoredSounds.begin(), restoredSounds.end(), _pcmCurrentSound[i].index) != restoredSounds.end())
continue;
if (!_v2)
restoredSounds.push_back(_pcmCurrentSound[i].index);
uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
if (!ptr)
continue;
if (_vm->_game.version != 3)
ptr += 2;
if (ptr[13])
continue;
playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note, _pcmCurrentSound[i].priority);
}
}
void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note, int priority) {
if (!_intf)
return;
const uint8 *sfxData = data + 16;
int numChan = _v2 ? 1 : data[14];
for (int i = 0; i < numChan; i++) {
int chan = allocatePcmChannel(sound, i, priority);
if (!chan)
return;
_intf->callback(70, _unkFlags);
_intf->callback(3, chan + 0x3f, pan);
_intf->callback(37, chan + 0x3f, note, velo, sfxData);
_pcmCurrentSound[chan].note = note;
_pcmCurrentSound[chan].velo = velo;
_pcmCurrentSound[chan].pan = pan;
_pcmCurrentSound[chan].paused = 0;
_pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0;
sfxData += (READ_LE_UINT32(&sfxData[12]) + 32);
}
}
void Player_Towns::stopPcmTrack(int sound) {
if (!_intf)
return;
for (int i = 1; i < 9; i++) {
if (sound == _pcmCurrentSound[i].index || !sound) {
_intf->callback(39, i + 0x3f);
_pcmCurrentSound[i].index = 0;
}
}
}
int Player_Towns::allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority) {
if (!_intf)
return 0;
int chan = 0;
if (_v2 && priority > 255) {
chan = 8;
if (_intf->callback(40, 0x47))
_intf->callback(39, 0x47);
} else {
for (int i = 8; i; i--) {
if (!_pcmCurrentSound[i].index) {
chan = i;
continue;
}
if (_intf->callback(40, i + 0x3f))
continue;
chan = i;
if (_pcmCurrentSound[chan].index == 0xffff)
_intf->callback(39, chan + 0x3f);
else
_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
}
if (!chan) {
for (int i = 1; i < 9; i++) {
if (priority >= _pcmCurrentSound[i].priority)
chan = i;
}
if (_pcmCurrentSound[chan].index == 0xffff)
_intf->callback(39, chan + 0x3f);
else
_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
}
}
if (chan) {
_pcmCurrentSound[chan].index = sound;
_pcmCurrentSound[chan].chan = sfxChanRelIndex;
_pcmCurrentSound[chan].priority = priority;
}
return chan;
}
Player_Towns_v1::Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer) : Player_Towns(vm, false) {
if (_vm->_game.version == 3) {
_soundOverride = new SoundOvrParameters[_numSoundMax]();
}
_player = new EuphonyPlayer(mixer);
_intf = new TownsAudioInterface(mixer, nullptr);
}
Player_Towns_v1::~Player_Towns_v1() {
delete _intf;
delete _player;
delete[] _soundOverride;
}
bool Player_Towns_v1::init() {
if (!_player)
return false;
if (!_player->init())
return false;
_player->driver()->reserveSoundEffectChannels(8);
// Treat all 6 fm channels and all 8 pcm channels as sound effect channels
// since music seems to exist as CD audio only in the games which use this
// MusicEngine implementation.
_intf->setSoundEffectChanMask(-1);
setVolumeCD(255, 255);
return true;
}
void Player_Towns_v1::setMusicVolume(int vol) {
_player->driver()->setMusicVolume(vol);
}
void Player_Towns_v1::startSound(int sound) {
uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
assert(ptr);
if (_vm->_game.version != 3)
ptr += 2;
int type = ptr[13];
if (type == 0) {
uint8 velocity = 0;
uint8 note = 0;
if (_vm->_game.version == 3) {
velocity = (_soundOverride[sound].vLeft + _soundOverride[sound].vRight);
note = _soundOverride[sound].note;
}
velocity = velocity ? velocity >> 2 : ptr[14] >> 1;
uint16 len = READ_LE_UINT16(ptr) + 2;
playPcmTrack(sound, ptr + 6, velocity, 64, note ? note : (len > 50 ? ptr[50] : 60), READ_LE_UINT16(ptr + 10));
} else if (type == 1 || (_vm->_game.id == GID_INDY3 && sound == 40 && _vm->enhancementEnabled(kEnhAudioChanges))) {
// WORKAROUND: Indy 3 FMTOWNS: No/distorted music in Venice
// The Venice music does not exist as CD audio and the original doesn't feature music
// in this scene. It does, however, exist as Euphony track albeit with an odd sound
// type (255 instead of 1).
// It doesn't sound great but we'll enable it to have music at all in this scene.
// See Trac#1873 and Trac#10561.
playEuphonyTrack(sound, ptr + 6);
} else if (type == 2) {
playCdaTrack(sound, ptr + 6);
}
if (_vm->_game.version == 3)
_soundOverride[sound].vLeft = _soundOverride[sound].vRight = _soundOverride[sound].note = 0;
}
void Player_Towns_v1::stopSound(int sound) {
if (sound == 0 || sound == _cdaCurrentSound) {
_cdaCurrentSound = 0;
_vm->_sound->stopCD();
}
if (sound != 0 && sound == _eupCurrentSound) {
_eupCurrentSound = 0;
_eupLooping = false;
_player->stop();
}
stopPcmTrack(sound);
}
void Player_Towns_v1::stopAllSounds() {
_cdaCurrentSound = 0;
_vm->_sound->stopCD();
_eupCurrentSound = 0;
_eupLooping = false;
_player->stop();
stopPcmTrack(0);
}
int Player_Towns_v1::getSoundStatus(int sound) const {
if (sound == _cdaCurrentSound)
return _vm->_sound->pollCD();
if (sound == _eupCurrentSound)
return _player->isPlaying() ? 1 : 0;
return Player_Towns::getSoundStatus(sound);
}
int32 Player_Towns_v1::doCommand(int numargs, int args[]) {
int32 res = 0;
switch (args[0]) {
case 2:
_player->driver()->cdaToggle(0);
break;
case 3:
restartLoopingSounds();
break;
case 8:
startSound(args[1]);
break;
case 9:
_vm->_sound->stopSound(args[1]);
break;
case 11:
stopPcmTrack(0);
break;
case 14:
startSoundEx(args[1], args[2], args[3], args[4]);
break;
case 15:
stopSoundSuspendLooping(args[1]);
break;
default:
warning("Player_Towns_v1::doCommand: Unknown command %d", args[0]);
break;
}
return res;
}
void Player_Towns_v1::setVolumeCD(int left, int right) {
_cdaVolLeft = left & 0xff;
_cdaVolRight = right & 0xff;
_player->driver()->setOutputVolume(1, left >> 1, right >> 1);
}
void Player_Towns_v1::setSoundVolume(int sound, int left, int right) {
if (_soundOverride && sound > 0 && sound < _numSoundMax) {
_soundOverride[sound].vLeft = left;
_soundOverride[sound].vRight = right;
}
}
void Player_Towns_v1::setSoundNote(int sound, int note) {
if (_soundOverride && sound > 0 && sound < _numSoundMax)
_soundOverride[sound].note = note;
}
void Player_Towns_v1::saveLoadWithSerializer(Common::Serializer &s) {
_cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0;
_cdaNumLoopsTemp = _cdaNumLoops & 0xff;
s.syncAsByte(_cdaCurrentSoundTemp, VER(81));
s.syncAsByte(_cdaNumLoopsTemp, VER(81));
s.syncAsByte(_cdaVolLeft, VER(81));
s.syncAsByte(_cdaVolRight, VER(81));
if (!_eupLooping && !_player->isPlaying())
_eupCurrentSound = 0;
s.syncAsByte(_eupCurrentSound, VER(81));
s.syncAsByte(_eupLooping, VER(81));
s.syncAsByte(_eupVolLeft, VER(81));
s.syncAsByte(_eupVolRight, VER(81));
Player_Towns::saveLoadWithSerializer(s);
}
void Player_Towns_v1::restoreAfterLoad() {
setVolumeCD(_cdaVolLeft, _cdaVolRight);
if (_cdaCurrentSoundTemp) {
uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6;
if (_vm->_game.version != 3)
ptr += 2;
if (ptr[7] == 2) {
playCdaTrack(_cdaCurrentSoundTemp, ptr, true);
_cdaCurrentSound = _cdaCurrentSoundTemp;
_cdaNumLoops = _cdaNumLoopsTemp;
}
}
if (_eupCurrentSound) {
uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6;
if (_vm->_game.version != 3)
ptr += 2;
// WORKAROUND for bug #1873 INDY3 FMTOWNS: Music in Venice is distorted
// The resource for sound 40 accidentally sets the sound type to 255 instead of 1.
if (ptr[7] == 1 || (_vm->_game.id == GID_INDY3 && _eupCurrentSound == 40)) {
setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight);
playEuphonyTrack(_eupCurrentSound, ptr);
}
}
Player_Towns::restoreAfterLoad();
}
void Player_Towns_v1::restartLoopingSounds() {
if (_cdaNumLoops && !_cdaForceRestart)
_cdaForceRestart = 1;
for (int i = 1; i < 9; i++) {
if (!_pcmCurrentSound[i].paused)
continue;
_pcmCurrentSound[i].paused = 0;
uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
if (!ptr)
continue;
ptr += 24;
int c = 1;
while (_pcmCurrentSound[i].chan != c) {
ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32;
c++;
}
_player->driver()->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr);
}
_player->driver()->cdaToggle(1);
}
void Player_Towns_v1::startSoundEx(int sound, int velo, int pan, int note) {
uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2;
if (pan > 99)
pan = 99;
velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14];
velo = CLIP(velo, 1, 255);
uint16 pri = READ_LE_UINT16(ptr + 10);
if (ptr[13] == 0) {
velo >>= 1;
if (!velo)
velo = 1;
pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64;
playPcmTrack(sound, ptr + 6, velo, pan, note ? note : ptr[50], pri);
} else if (ptr[13] == 2) {
int volLeft = velo;
int volRight = velo;
if (pan < 50)
volRight = ((pan * 2 + 1) * velo + 50) / 100;
else if (pan > 50)
volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100;
setVolumeCD(volLeft, volRight);
if (!_cdaForceRestart && sound == _cdaCurrentSound)
return;
playCdaTrack(sound, ptr + 6, true);
}
}
void Player_Towns_v1::stopSoundSuspendLooping(int sound) {
if (!sound) {
return;
} else if (sound == _cdaCurrentSound) {
if (_cdaNumLoops && _cdaForceRestart)
_cdaForceRestart = 1;
} else {
for (int i = 1; i < 9; i++) {
if (sound == _pcmCurrentSound[i].index) {
if (!_player->driver()->soundEffectIsPlaying(i + 0x3f))
continue;
_player->driver()->stopSoundEffect(i + 0x3f);
if (_pcmCurrentSound[i].looping)
_pcmCurrentSound[i].paused = 1;
else
_pcmCurrentSound[i].index = 0;
}
}
}
}
void Player_Towns_v1::playEuphonyTrack(int sound, const uint8 *data) {
const uint8 *pos = data + 16;
const uint8 *src = pos + data[14] * 48;
const uint8 *trackData = src + 150;
for (int i = 0; i < 32; i++)
_player->configPart_enable(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_setType(i, 0xff);
for (int i = 0; i < 32; i++)
_player->configPart_remap(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_adjustVolume(i, *src++);
for (int i = 0; i < 32; i++)
_player->configPart_setTranspose(i, *src++);
src += 8;
for (int i = 0; i < 6; i++)
_player->driver()->assignPartToChannel(i, *src++);
for (int i = 0; i < data[14]; i++) {
_player->driver()->loadInstrument(i, i, pos + i * 48);
_player->driver()->setInstrument(i, i);
}
_eupVolLeft = _soundOverride[sound].vLeft;
_eupVolRight = _soundOverride[sound].vRight;
int lvl = _soundOverride[sound].vLeft + _soundOverride[sound].vRight;
if (!lvl)
lvl = data[8] + data[9];
lvl >>= 2;
for (int i = 0; i < 6; i++)
_player->driver()->channelVolume(i, lvl);
uint32 trackSize = READ_LE_UINT32(src);
src += 4;
uint8 startTick = *src++;
_player->setTempo(*src++);
_player->startTrack(trackData, trackSize, startTick);
_eupLooping = (*src != 1) ? 1 : 0;
_player->setLoopStatus(_eupLooping != 0);
_player->resume();
_eupCurrentSound = sound;
}
void Player_Towns_v1::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) {
const uint8 *ptr = data;
if (!sound)
return;
if (!skipTrackVelo) {
if (_vm->_game.version == 3) {
if (_soundOverride[sound].vLeft + _soundOverride[sound].vRight)
setVolumeCD(_soundOverride[sound].vLeft, _soundOverride[sound].vRight);
else
setVolumeCD(ptr[8], ptr[9]);
} else {
setVolumeCD(ptr[8], ptr[9]);
}
}
if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1)
return;
ptr += 16;
int track = ptr[0];
_cdaNumLoops = ptr[1];
int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
_vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start);
_cdaForceRestart = 0;
_cdaCurrentSound = sound;
}
Player_Towns_v2::Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse)
: Player_Towns(vm, true), _imuse(imuse), _imuseDispose(disposeIMuse) {
_soundOverride = new SoundOvrParameters[_numSoundMax]();
_intf = new TownsAudioInterface(mixer, nullptr, true);
}
Player_Towns_v2::~Player_Towns_v2() {
delete _intf;
_intf = nullptr;
if (_imuseDispose)
delete _imuse;
delete[] _sblData;
delete[] _soundOverride;
}
bool Player_Towns_v2::init() {
if (!_intf)
return false;
if (!_intf->init())
return false;
_intf->callback(33, 8);
_intf->setSoundEffectChanMask(~0x3f);
return true;
}
void Player_Towns_v2::setMusicVolume(int vol) {
_imuse->setMusicVolume(vol);
}
int Player_Towns_v2::getSoundStatus(int sound) const {
if (_soundOverride[sound].type == 7)
return Player_Towns::getSoundStatus(sound);
return _imuse->getSoundStatus(sound);
}
void Player_Towns_v2::startSound(int sound) {
uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
assert(ptr);
if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) {
_soundOverride[sound].type = 7;
uint8 velo = _soundOverride[sound].velo ? _soundOverride[sound].velo - 1: (ptr[10] + ptr[11] + 1) >> 1;
uint8 pan = _soundOverride[sound].pan ? _soundOverride[sound].pan - 1 : 64;
uint8 pri = ptr[9];
_soundOverride[sound].velo = _soundOverride[sound].pan = 0;
playPcmTrack(sound, ptr + 8, velo, pan, ptr[52], pri);
} else if (READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) {
_soundOverride[sound].type = 5;
playVocTrack(ptr + 27);
} else {
_soundOverride[sound].type = 3;
_imuse->startSound(sound);
}
}
void Player_Towns_v2::stopSound(int sound) {
if (_soundOverride[sound].type == 7) {
stopPcmTrack(sound);
} else {
_imuse->stopSound(sound);
}
}
void Player_Towns_v2::stopAllSounds() {
stopPcmTrack(0);
_imuse->stopAllSounds();
}
int32 Player_Towns_v2::doCommand(int numargs, int args[]) {
int32 res = -1;
uint8 *ptr = nullptr;
switch (args[0]) {
case 8:
startSound(args[1]);
res = 0;
break;
case 9:
case 15:
stopSound(args[1]);
res = 0;
break;
case 11:
stopPcmTrack(0);
break;
case 13:
res = getSoundStatus(args[1]);
break;
case 258:
if (_soundOverride[args[1]].type == 0) {
ptr = _vm->getResourceAddress(rtSound, args[1]);
if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
_soundOverride[args[1]].type = 7;
}
if (_soundOverride[args[1]].type == 7) {
_soundOverride[args[1]].velo = args[2] + 1;
res = 0;
}
break;
case 259:
if (_soundOverride[args[1]].type == 0) {
ptr = _vm->getResourceAddress(rtSound, args[1]);
if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S'))
_soundOverride[args[1]].type = 7;
}
if (_soundOverride[args[1]].type == 7) {
_soundOverride[args[1]].pan = 64 - CLIP<int>(args[2], -63, 63);
res = 0;
}
break;
default:
break;
}
if (res == -1)
return _imuse->doCommand(numargs, args);
return res;
}
void Player_Towns_v2::saveLoadWithSerializer(Common::Serializer &s) {
if (s.getVersion() >= VER(83))
Player_Towns::saveLoadWithSerializer(s);
}
void Player_Towns_v2::playVocTrack(const uint8 *data) {
static const uint8 header[] = {
0x54, 0x61, 0x6C, 0x6B, 0x69, 0x65, 0x20, 0x20,
0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x36, 0x04, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00
};
uint32 len = (READ_LE_UINT32(data) >> 8) - 2;
int chan = allocatePcmChannel(0xffff, 0, 0x1000);
if (!chan)
return;
delete[] _sblData;
_sblData = new uint8[len + 32];
memcpy(_sblData, header, 32);
WRITE_LE_UINT32(_sblData + 12, len);
const uint8 *src = data + 6;
uint8 *dst = _sblData + 32;
for (uint32 i = 0; i < len; i++)
*dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
_intf->callback(37, 0x3f + chan, 60, 127, _sblData);
_pcmCurrentSound[chan].paused = 0;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,178 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_TOWNS_H
#define SCUMM_PLAYERS_PLAYER_TOWNS_H
#include "scumm/scumm.h"
#include "scumm/imuse/imuse.h"
#include "scumm/imuse/drivers/fmtowns.h"
#include "audio/softsynth/fmtowns_pc98/towns_euphony.h"
namespace Scumm {
class Player_Towns : public MusicEngine {
public:
Player_Towns(ScummEngine *vm, bool isVersion2);
~Player_Towns() override {}
virtual bool init() = 0;
void setSfxVolume(int vol) override;
int getSoundStatus(int sound) const override;
virtual int32 doCommand(int numargs, int args[]) = 0;
void saveLoadWithSerializer(Common::Serializer &ser) override;
void restoreAfterLoad() override;
// version 1 specific
virtual int getCurrentCdaSound() { return 0; }
virtual int getCurrentCdaVolume() { return 0; }
virtual void setVolumeCD(int left, int right) {}
virtual void setSoundVolume(int sound, int left, int right) {}
virtual void setSoundNote(int sound, int note) {}
protected:
void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0, int priority = 0);
void stopPcmTrack(int sound);
int allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority);
struct PcmCurrentSound {
uint16 index;
uint16 chan;
uint8 note;
uint8 velo;
uint8 pan;
uint8 paused;
uint8 looping;
uint32 priority;
} _pcmCurrentSound[9];
friend void syncWithSerializer(Common::Serializer &, PcmCurrentSound &);
uint8 _unkFlags = 0x33;
TownsAudioInterface *_intf = nullptr;
ScummEngine *_vm;
const int _numSoundMax;
const bool _v2;
};
class Player_Towns_v1 : public Player_Towns {
public:
Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer);
~Player_Towns_v1() override;
bool init() override;
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getCurrentCdaSound() override { return _cdaCurrentSound; }
int getCurrentCdaVolume() override { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; }
int32 doCommand(int numargs, int args[]) override;
void setVolumeCD(int left, int right) override;
void setSoundVolume(int sound, int left, int right) override;
void setSoundNote(int sound, int note) override;
void saveLoadWithSerializer(Common::Serializer &ser) override;
void restoreAfterLoad() override;
private:
void restartLoopingSounds();
void startSoundEx(int sound, int velo, int pan, int note);
void stopSoundSuspendLooping(int sound);
void playEuphonyTrack(int sound, const uint8 *data);
void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false);
struct SoundOvrParameters {
uint8 vLeft;
uint8 vRight;
uint8 note;
};
SoundOvrParameters *_soundOverride = nullptr;
uint8 _cdaVolLeft = 0;
uint8 _cdaVolRight = 0;
uint8 _eupCurrentSound = 0;
uint8 _eupLooping = 0;
uint8 _eupVolLeft = 0;
uint8 _eupVolRight = 0;
uint8 _cdaCurrentSound = 0;
uint8 _cdaNumLoops = 0;
uint8 _cdaForceRestart = 0;
uint8 _cdaCurrentSoundTemp = 0;
uint8 _cdaNumLoopsTemp = 0;
EuphonyPlayer *_player = nullptr;
};
class Player_Towns_v2 : public Player_Towns {
public:
Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse);
~Player_Towns_v2() override;
bool init() override;
void setMusicVolume(int vol) override;
int getSoundStatus(int sound) const override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int32 doCommand(int numargs, int args[]) override;
void saveLoadWithSerializer(Common::Serializer &ser) override;
private:
void playVocTrack(const uint8 *data);
struct SoundOvrParameters {
uint8 velo;
uint8 pan;
uint8 type;
};
SoundOvrParameters *_soundOverride = nullptr;
uint8 *_sblData = nullptr;
IMuse *_imuse = nullptr;
const bool _imuseDispose;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,622 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/engine.h"
#include "scumm/players/player_v1.h"
#include "scumm/scumm.h"
namespace Scumm {
#define FB_WNOISE 0x12000 /* feedback for white noise */
#define FB_PNOISE 0x08000 /* feedback for periodic noise */
#define TIMER_BASE_FREQ 1193000
#define FIXP_SHIFT 16
Player_V1::Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
: Player_V2(scumm, mixer, pcjr) {
// Initialize channel code
for (int i = 0; i < 4; ++i)
clear_channel(i);
_mplex_step = (_sampleRate << FIXP_SHIFT) / 1193000;
_next_chunk = _repeat_chunk = nullptr;
_forced_level = 0;
_random_lsr = 0;
}
Player_V1::~Player_V1() {
}
void Player_V1::chainSound(int nr, byte *data) {
uint i;
for (i = 0; i < 4; ++i)
clear_channel(i);
_current_nr = nr;
_current_data = data;
_repeat_chunk = _next_chunk = data + (_pcjr ? 2 : 4);
debug(4, "Chaining new sound %d", nr);
if (_pcjr)
parsePCjrChunk();
else
parseSpeakerChunk();
}
void Player_V1::startSound(int nr) {
Common::StackLock lock(_mutex);
byte *data = _vm->getResourceAddress(rtSound, nr);
assert(data);
int offset = _pcjr ? READ_LE_UINT16(data+4) : 6;
int cprio = _current_data ? *(_current_data) & 0x7f : 0;
int prio = *(data + offset) & 0x7f;
int restartable = *(data + offset) & 0x80;
debug(4, "startSound %d: prio %d%s, cprio %d",
nr, prio, restartable ? " restartable" : "", cprio);
if (!_current_nr || cprio <= prio) {
if (_current_data && (*(_current_data) & 0x80)) {
_next_nr = _current_nr;
_next_data = _current_data;
}
chainSound(nr, data + offset);
}
}
void Player_V1::stopAllSounds() {
Common::StackLock lock(_mutex);
for (int i = 0; i < 4; i++)
clear_channel(i);
_repeat_chunk = _next_chunk = nullptr;
_next_nr = _current_nr = 0;
_next_data = _current_data = nullptr;
}
void Player_V1::stopSound(int nr) {
Common::StackLock lock(_mutex);
if (_next_nr == nr) {
_next_nr = 0;
_next_data = nullptr;
}
if (_current_nr == nr) {
for (int i = 0; i < 4; i++) {
clear_channel(i);
}
_repeat_chunk = _next_chunk = nullptr;
_current_nr = 0;
_current_data = nullptr;
chainNextSound();
}
}
void Player_V1::clear_channel(int i) {
_channels[i].freq = 0;
_channels[i].volume = 15;
}
int Player_V1::getMusicTimer() {
/* Do V1 games have a music timer? */
return 0;
}
void Player_V1::parseSpeakerChunk() {
set_mplex(3000);
_forced_level = 0;
parse_again:
_chunk_type = READ_LE_UINT16(_next_chunk);
debug(6, "parseSpeakerChunk: sound %d, offset %lx, chunk %x",
_current_nr, (long)(_next_chunk - _current_data), _chunk_type);
_next_chunk += 2;
switch (_chunk_type) {
case 0xffff:
_current_nr = 0;
_current_data = nullptr;
_channels[0].freq = 0;
_next_chunk = nullptr;
chainNextSound();
break;
case 0xfffe:
_repeat_chunk = _next_chunk;
goto parse_again;
case 0xfffd:
_next_chunk = _repeat_chunk;
goto parse_again;
case 0xfffc:
/* handle reset. We don't need this do we? */
goto parse_again;
case 0:
_time_left = 1;
set_mplex(READ_LE_UINT16(_next_chunk));
_next_chunk += 2;
break;
case 1:
set_mplex(READ_LE_UINT16(_next_chunk));
_start = READ_LE_UINT16(_next_chunk + 2);
_end = READ_LE_UINT16(_next_chunk + 4);
_delta = (int16) READ_LE_UINT16(_next_chunk + 6);
_repeat_ctr = READ_LE_UINT16(_next_chunk + 8);
_channels[0].freq = _start;
_next_chunk += 10;
debug(6, "chunk 1: mplex %d, freq %d -> %d step %d x %d",
_mplex, _start, _end, _delta, _repeat_ctr);
break;
case 2:
_start = READ_LE_UINT16(_next_chunk);
_end = READ_LE_UINT16(_next_chunk + 2);
_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
_channels[0].freq = 0;
_next_chunk += 6;
_forced_level = -1;
debug(6, "chunk 2: %d -> %d step %d",
_start, _end, _delta);
break;
case 3:
_start = READ_LE_UINT16(_next_chunk);
_end = READ_LE_UINT16(_next_chunk + 2);
_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
_channels[0].freq = 0;
_next_chunk += 6;
_forced_level = -1;
debug(6, "chunk 3: %d -> %d step %d",
_start, _end, _delta);
break;
default:
break;
}
}
void Player_V1::nextSpeakerCmd() {
uint16 lsr;
switch (_chunk_type) {
case 0:
if (--_time_left)
return;
_time_left = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
if (_time_left == 0xfffb) {
/* handle 0xfffb?? */
_time_left = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
}
debug(7, "nextSpeakerCmd: chunk %d, offset %4lx: notelen %d",
_chunk_type, (long)(_next_chunk - 2 - _current_data), _time_left);
if (_time_left == 0) {
parseSpeakerChunk();
} else {
_channels[0].freq = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
debug(7, "freq_current: %d", _channels[0].freq);
}
break;
case 1:
_channels[0].freq = (_channels[0].freq + _delta) & 0xffff;
if (_channels[0].freq == _end) {
if (!--_repeat_ctr) {
parseSpeakerChunk();
return;
}
_channels[0].freq = _start;
}
break;
case 2:
_start = (_start + _delta) & 0xffff;
if (_start == _end) {
parseSpeakerChunk();
return;
}
set_mplex(_start);
_forced_level = -_forced_level;
break;
case 3:
_start = (_start + _delta) & 0xffff;
if (_start == _end) {
parseSpeakerChunk();
return;
}
lsr = _random_lsr + 0x9248;
lsr = (lsr >> 3) | (lsr << 13);
_random_lsr = lsr;
set_mplex((_start & lsr) | 0x180);
_forced_level = -_forced_level;
break;
default:
break;
}
}
void Player_V1::parsePCjrChunk() {
uint tmp;
uint i;
set_mplex(3000);
_forced_level = 0;
parse_again:
_chunk_type = READ_LE_UINT16(_next_chunk);
debug(6, "parsePCjrChunk: sound %d, offset %4lx, chunk %x",
_current_nr, (long)(_next_chunk - _current_data), _chunk_type);
_next_chunk += 2;
switch (_chunk_type) {
case 0xffff:
for (i = 0; i < 4; ++i)
clear_channel(i);
_current_nr = 0;
_current_data = nullptr;
_repeat_chunk = _next_chunk = nullptr;
chainNextSound();
break;
case 0xfffe:
_repeat_chunk = _next_chunk;
goto parse_again;
case 0xfffd:
_next_chunk = _repeat_chunk;
goto parse_again;
case 0xfffc:
/* handle reset. We don't need this do we? */
goto parse_again;
case 0:
set_mplex(READ_LE_UINT16(_next_chunk));
_next_chunk += 2;
for (i = 0; i < 4; i++) {
tmp = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
if (tmp == 0xffff) {
_channels[i].cmd_ptr = nullptr;
continue;
}
_channels[i].attack = READ_LE_UINT16(_current_data + tmp);
_channels[i].decay = READ_LE_UINT16(_current_data + tmp + 2);
_channels[i].level = READ_LE_UINT16(_current_data + tmp + 4);
_channels[i].sustain_1 = READ_LE_UINT16(_current_data + tmp + 6);
_channels[i].sustain_2 = READ_LE_UINT16(_current_data + tmp + 8);
_channels[i].notelen = 1;
_channels[i].volume = 15;
_channels[i].cmd_ptr = _current_data + tmp + 10;
}
break;
case 1:
set_mplex(READ_LE_UINT16(_next_chunk));
tmp = READ_LE_UINT16(_next_chunk + 2);
_channels[0].cmd_ptr = tmp != 0xffff ? _current_data + tmp : nullptr;
tmp = READ_LE_UINT16(_next_chunk + 4);
_start = READ_LE_UINT16(_next_chunk + 6);
_delta = (int16) READ_LE_UINT16(_next_chunk + 8);
_time_left = READ_LE_UINT16(_next_chunk + 10);
_next_chunk += 12;
if (tmp >= 0xe0) {
_channels[3].freq = tmp & 0xf;
_value_ptr = &_channels[3].volume;
} else {
assert(!(tmp & 0x10));
tmp = (tmp & 0x60) >> 5;
_value_ptr = &_channels[tmp].freq;
_channels[tmp].volume = 0;
}
*_value_ptr = _start;
if (_channels[0].cmd_ptr) {
tmp = READ_LE_UINT16(_channels[0].cmd_ptr);
_start_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 2);
_delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 4);
_time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 6);
_channels[0].cmd_ptr += 8;
if (_value_ptr == &_channels[3].volume) {
tmp = (tmp & 0x70) >> 4;
if (tmp & 1)
_value_ptr_2 = &_channels[tmp >> 1].volume;
else
_value_ptr_2 = &_channels[tmp >> 1].freq;
} else {
assert(!(tmp & 0x10));
tmp = (tmp & 0x60) >> 5;
_value_ptr_2 = &_channels[tmp].freq;
_channels[tmp].volume = 0;
}
*_value_ptr_2 = _start_2;
}
debug(6, "chunk 1: %lu: %d step %d for %d, %lu: %d step %d for %d",
(long)(_value_ptr - (uint *)_channels), _start, _delta, _time_left,
(long)(_value_ptr_2 - (uint *)_channels), _start_2, _delta_2, _time_left_2);
break;
case 2:
_start = READ_LE_UINT16(_next_chunk);
_end = READ_LE_UINT16(_next_chunk + 2);
_delta = (int16) READ_LE_UINT16(_next_chunk + 4);
_channels[0].freq = 0;
_next_chunk += 6;
_forced_level = -1;
debug(6, "chunk 2: %d -> %d step %d",
_start, _end, _delta);
break;
case 3:
set_mplex(READ_LE_UINT16(_next_chunk));
tmp = READ_LE_UINT16(_next_chunk + 2);
assert((tmp & 0xf0) == 0xe0);
_channels[3].freq = tmp & 0xf;
if ((tmp & 3) == 3) {
_next_chunk += 2;
_channels[2].freq = READ_LE_UINT16(_next_chunk + 2);
}
_channels[3].volume = READ_LE_UINT16(_next_chunk + 4);
_repeat_ctr = READ_LE_UINT16(_next_chunk + 6);
_delta = (int16) READ_LE_UINT16(_next_chunk + 8);
_next_chunk += 10;
break;
default:
break;
}
}
void Player_V1::nextPCjrCmd() {
uint i;
int dummy;
switch (_chunk_type) {
case 0:
for (i = 0; i < 4; i++) {
if (!_channels[i].cmd_ptr)
continue;
if (!--_channels[i].notelen) {
dummy = READ_LE_UINT16(_channels[i].cmd_ptr);
if (dummy >= 0xfffe) {
if (dummy == 0xfffe)
_next_chunk = _current_data + 2;
parsePCjrChunk();
return;
}
_channels[i].notelen = 4 * dummy;
dummy = READ_LE_UINT16(_channels[i].cmd_ptr + 2);
if (dummy == 0) {
_channels[i].hull_counter = 4;
_channels[i].sustctr = _channels[i].sustain_2;
} else {
_channels[i].hull_counter = 1;
_channels[i].freq = dummy;
}
debug(7, "chunk 0: channel %d play %d for %d",
i, dummy, _channels[i].notelen);
_channels[i].cmd_ptr += 4;
}
switch (_channels[i].hull_counter) {
case 1:
_channels[i].volume -= _channels[i].attack;
if ((int)_channels[i].volume <= 0) {
_channels[i].volume = 0;
_channels[i].hull_counter++;
}
break;
case 2:
_channels[i].volume += _channels[i].decay;
if (_channels[i].volume >= _channels[i].level) {
_channels[i].volume = _channels[i].level;
_channels[i].hull_counter++;
}
break;
case 4:
if (--_channels[i].sustctr < 0) {
_channels[i].sustctr = _channels[i].sustain_2;
_channels[i].volume += _channels[i].sustain_1;
if ((int)_channels[i].volume >= 15) {
_channels[i].volume = 15;
_channels[i].hull_counter++;
}
}
break;
default:
break;
}
}
break;
case 1:
_start += _delta;
*_value_ptr = _start;
if (!--_time_left) {
_start = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
if (_start == 0xffff) {
parsePCjrChunk();
return;
}
_delta = (int16) READ_LE_UINT16(_next_chunk);
_time_left = READ_LE_UINT16(_next_chunk + 2);
_next_chunk += 4;
*_value_ptr = _start;
}
if (_channels[0].cmd_ptr) {
_start_2 += _delta_2;
*_value_ptr_2 = _start_2;
if (!--_time_left_2) {
_start_2 = READ_LE_UINT16(_channels[0].cmd_ptr);
if (_start_2 == 0xffff) {
_next_chunk = _channels[0].cmd_ptr + 2;
parsePCjrChunk();
return;
}
_delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 2);
_time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 4);
_channels[0].cmd_ptr += 6;
}
}
break;
case 2:
_start += _delta;
if (_start == _end) {
parsePCjrChunk();
return;
}
set_mplex(_start);
debug(7, "chunk 2: mplex %d curve %d", _start, _forced_level);
_forced_level = -_forced_level;
break;
case 3:
dummy = _channels[3].volume + _delta;
if (dummy >= 15) {
_channels[3].volume = 15;
} else if (dummy <= 0) {
_channels[3].volume = 0;
} else {
_channels[3].volume = dummy;
break;
}
if (!--_repeat_ctr) {
parsePCjrChunk();
return;
}
_delta = READ_LE_UINT16(_next_chunk);
_next_chunk += 2;
break;
default:
break;
}
}
void Player_V1::set_mplex(uint mplex) {
if (mplex == 0)
mplex = 65536;
_mplex = mplex;
_tick_len = _mplex_step * mplex;
}
void Player_V1::nextTick() {
if (_next_chunk) {
if (_pcjr)
nextPCjrCmd();
else
nextSpeakerCmd();
}
}
void Player_V1::generateSpkSamples(int16 *data, uint len) {
uint i;
memset(data, 0, 2 * sizeof(int16) * len);
if (_channels[0].freq == 0) {
if (_forced_level) {
int sample = _forced_level * _volumetable[0];
for (i = 0; i < len; i++)
data[2*i] = data[2*i+1] = sample;
debug(9, "speaker: %8x: forced one", _tick_len);
} else if (!_level) {
return;
}
} else {
squareGenerator(0, _channels[0].freq, 0, 0, data, len);
debug(9, "speaker: %8x: freq %d %.1f", _tick_len,
_channels[0].freq, 1193000.0 / _channels[0].freq);
}
lowPassFilter(data, len);
}
void Player_V1::generatePCjrSamples(int16 *data, uint len) {
uint i, j;
uint freq, vol;
bool hasdata = false;
memset(data, 0, 2 * sizeof(int16) * len);
if (_forced_level) {
int sample = _forced_level * _volumetable[0];
for (i = 0; i < len; i++)
data[2*i] = data[2*i+1] = sample;
hasdata = true;
debug(9, "channel[4]: %8x: forced one", _tick_len);
}
for (i = 1; i < 3; i++) {
freq = _channels[i].freq;
if (freq) {
for (j = 0; j < i; j++) {
if (freq == _channels[j].freq) {
/* HACK: this channel is playing at
* the same frequency as another.
* Synchronize it to the same phase to
* prevent interference.
*/
_timer_count[i] = _timer_count[j];
_timer_output ^= (1 << i) &
(_timer_output ^ _timer_output << (i - j));
}
}
}
}
for (i = 0; i < 4; i++) {
freq = _channels[i].freq;
vol = _channels[i].volume;
if (!_volumetable[_channels[i].volume]) {
_timer_count[i] -= len << FIXP_SHIFT;
if (_timer_count[i] < 0)
_timer_count[i] = 0;
} else if (i < 3) {
hasdata = true;
squareGenerator(i, freq, vol, 0, data, len);
debug(9, "channel[%d]: %8x: freq %d %.1f ; volume %d",
i, _tick_len, freq, 111860.0 / freq, vol);
} else {
int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
int n = (freq & 3);
freq = (n == 3) ? 2 * (_channels[2].freq) : 1 << (5 + n);
hasdata = true;
squareGenerator(i, freq, vol, noiseFB, data, len);
debug(9, "channel[%d]: %x: noise freq %d %.1f ; volume %d",
i, _tick_len, freq, 111860.0 / freq, vol);
}
}
if (_level || hasdata)
lowPassFilter(data, len);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,94 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V1_H
#define SCUMM_PLAYERS_PLAYER_V1_H
#include "scumm/players/player_v2.h"
namespace Scumm {
/**
* Scumm V1 PC-Speaker player.
*/
class Player_V1 : public Player_V2 {
public:
Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
~Player_V1() override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getMusicTimer() override;
protected:
void nextTick() override;
void clear_channel(int i) override;
void chainSound(int nr, byte *data) override;
void generateSpkSamples(int16 *data, uint len) override;
void generatePCjrSamples(int16 *data, uint len) override;
void set_mplex(uint mplex);
void parseSpeakerChunk();
void nextSpeakerCmd();
void parsePCjrChunk();
void nextPCjrCmd();
private:
struct channel_data_v1 {
uint freq;
uint volume;
byte *cmd_ptr;
uint notelen;
uint hull_counter;
uint attack;
uint decay;
uint level;
uint sustain_1;
uint sustain_2;
int sustctr;
};
channel_data_v1 _channels[4];
byte *_next_chunk;
byte *_repeat_chunk;
uint _chunk_type;
uint _mplex_step;
uint _mplex;
uint _repeat_ctr;
int _forced_level;
uint16 _random_lsr;
uint *_value_ptr;
uint _time_left;
uint _start;
uint _end;
int _delta;
uint *_value_ptr_2;
uint _time_left_2;
uint _start_2;
int _delta_2;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,333 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_v2.h"
#include "scumm/scumm.h"
#define FREQ_HZ 236 // Don't change!
namespace Scumm {
#define SPK_DECAY 0xa000 /* Depends on sample rate */
#define PCJR_DECAY 0xa000 /* Depends on sample rate */
#define NG_PRESET 0x0f35 /* noise generator preset */
#define FB_WNOISE 0x12000 /* feedback for white noise */
#define FB_PNOISE 0x08000 /* feedback for periodic noise */
Player_V2::Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr)
: Player_V2Base(scumm, pcjr),
_mixer(mixer),
_sampleRate(_mixer->getOutputRate()) {
int i;
_next_tick = 0;
_tick_len = (_sampleRate << FIXP_SHIFT) / FREQ_HZ;
// Initialize square generator
_level = 0;
_RNG = NG_PRESET;
_pcjr = pcjr;
if (_pcjr) {
_decay = PCJR_DECAY;
_update_step = (_sampleRate << FIXP_SHIFT) / (111860 * 2);
} else {
_decay = SPK_DECAY;
_update_step = (_sampleRate << FIXP_SHIFT) / (1193000 * 2);
}
// Adapt _decay to sample rate. It must be squared when
// sample rate doubles.
for (i = 0; (_sampleRate << i) < 30000; i++)
_decay = _decay * _decay / 65536;
_timer_output = 0;
for (i = 0; i < 4; i++)
_timer_count[i] = 0;
setMusicVolume(255);
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
Player_V2::~Player_V2() {
Common::StackLock lock(_mutex);
_mixer->stopHandle(_soundHandle);
}
void Player_V2::setMusicVolume (int vol) {
if (vol > 255)
vol = 255;
/* scale to int16, FIXME: find best value */
double out = vol * 128.0 / 3.0;
/* build volume table (2dB per step) */
for (int i = 0; i < 15; i++) {
/* limit volume to avoid clipping */
if (out > 0xffff)
_volumetable[i] = 0xffff;
else
_volumetable[i] = (int)out;
out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */
}
_volumetable[15] = 0;
}
void Player_V2::stopAllSounds() {
Common::StackLock lock(_mutex);
for (int i = 0; i < 4; i++) {
clear_channel(i);
}
_next_nr = _current_nr = 0;
_next_data = _current_data = nullptr;
}
void Player_V2::stopSound(int nr) {
Common::StackLock lock(_mutex);
if (_next_nr == nr) {
_next_nr = 0;
_next_data = nullptr;
}
if (_current_nr == nr) {
for (int i = 0; i < 4; i++) {
clear_channel(i);
}
_current_nr = 0;
_current_data = nullptr;
chainNextSound();
}
}
void Player_V2::startSound(int nr) {
Common::StackLock lock(_mutex);
byte *data = _vm->getResourceAddress(rtSound, nr);
assert(data);
int cprio = _current_data ? *(_current_data + _header_len) : 0;
int prio = *(data + _header_len);
int nprio = _next_data ? *(_next_data + _header_len) : 0;
int restartable = *(data + _header_len + 1);
if (!_current_nr || cprio <= prio) {
int tnr = _current_nr;
int tprio = cprio;
byte *tdata = _current_data;
chainSound(nr, data);
nr = tnr;
prio = tprio;
data = tdata;
restartable = data ? *(data + _header_len + 1) : 0;
}
if (!_current_nr) {
nr = 0;
_next_nr = 0;
_next_data = nullptr;
}
if (nr != _current_nr
&& restartable
&& (!_next_nr
|| nprio <= prio)) {
_next_nr = nr;
_next_data = data;
}
}
int Player_V2::getSoundStatus(int nr) const {
return _current_nr == nr || _next_nr == nr;
}
int Player_V2::readBuffer(int16 *data, const int numSamples) {
Common::StackLock lock(_mutex);
uint step;
uint len = numSamples / 2;
do {
if (!(_next_tick >> FIXP_SHIFT)) {
_next_tick += _tick_len;
nextTick();
}
step = len;
if (step > (_next_tick >> FIXP_SHIFT))
step = (_next_tick >> FIXP_SHIFT);
if (_pcjr)
generatePCjrSamples(data, step);
else
generateSpkSamples(data, step);
data += 2 * step;
_next_tick -= step << FIXP_SHIFT;
} while (len -= step);
return numSamples;
}
void Player_V2::lowPassFilter(int16 *sample, uint len) {
for (uint i = 0; i < len; i++) {
_level = (int)(_level * _decay
+ sample[0] * (0x10000 - _decay)) >> 16;
sample[0] = sample[1] = _level;
sample += 2;
}
}
void Player_V2::squareGenerator(int channel, int freq, int vol,
int noiseFeedback, int16 *sample, uint len) {
int32 period = _update_step * freq;
int32 nsample;
if (period == 0)
period = _update_step;
for (uint i = 0; i < len; i++) {
uint32 duration = 0;
if (_timer_output & (1 << channel))
duration += _timer_count[channel];
_timer_count[channel] -= (1 << FIXP_SHIFT);
while (_timer_count[channel] <= 0) {
if (noiseFeedback) {
if (_RNG & 1) {
_RNG ^= noiseFeedback;
_timer_output ^= (1 << channel);
}
_RNG >>= 1;
} else {
_timer_output ^= (1 << channel);
}
if (_timer_output & (1 << channel))
duration += period;
_timer_count[channel] += period;
}
if (_timer_output & (1 << channel))
duration -= _timer_count[channel];
nsample = *sample +
(((int32) (duration - (1 << (FIXP_SHIFT - 1)))
* (int32) _volumetable[vol]) >> FIXP_SHIFT);
/* overflow: clip value */
if (nsample > 0x7fff)
nsample = 0x7fff;
if (nsample < -0x8000)
nsample = -0x8000;
*sample = nsample;
// The following write isn't necessary, because the lowPassFilter does it for us
//sample[1] = sample[0];
sample += 2;
}
}
void Player_V2::generateSpkSamples(int16 *data, uint len) {
int winning_channel = -1;
for (int i = 0; i < 4; i++) {
if (winning_channel == -1
&& _channels[i].d.volume
&& _channels[i].d.time_left) {
winning_channel = i;
}
}
memset(data, 0, 2 * sizeof(int16) * len);
if (winning_channel != -1) {
squareGenerator(0, _channels[winning_channel].d.freq, 0,
0, data, len);
} else if (_level == 0)
/* shortcut: no sound is being played. */
return;
lowPassFilter(data, len);
}
void Player_V2::generatePCjrSamples(int16 *data, uint len) {
int i, j;
int freq, vol;
memset(data, 0, 2 * sizeof(int16) * len);
bool hasdata = false;
for (i = 1; i < 3; i++) {
freq = _channels[i].d.freq >> 6;
if (_channels[i].d.volume && _channels[i].d.time_left) {
for (j = 0; j < i; j++) {
if (_channels[j].d.volume
&& _channels[j].d.time_left
&& freq == (_channels[j].d.freq >> 6)) {
/* HACK: this channel is playing at
* the same frequency as another.
* Synchronize it to the same phase to
* prevent interference.
*/
_timer_count[i] = _timer_count[j];
_timer_output ^= (1 << i) &
(_timer_output ^ _timer_output << (i - j));
}
}
}
}
for (i = 0; i < 4; i++) {
freq = _channels[i].d.freq >> 6;
vol = (65535 - _channels[i].d.volume) >> 12;
if (!_channels[i].d.volume || !_channels[i].d.time_left) {
_timer_count[i] -= len << FIXP_SHIFT;
if (_timer_count[i] < 0)
_timer_count[i] = 0;
} else if (i < 3) {
hasdata = true;
squareGenerator(i, freq, vol, 0, data, len);
} else {
int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE;
int n = (freq & 3);
freq = (n == 3) ? 2 * (_channels[2].d.freq>>6) : 1 << (5 + n);
hasdata = true;
squareGenerator(i, freq, vol, noiseFB, data, len);
}
#if 0
debug(9, "channel[%d]: freq %d %.1f ; volume %d",
i, freq, 111860.0 / freq, vol);
#endif
}
if (_level || hasdata)
lowPassFilter(data, len);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,89 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V2_H
#define SCUMM_PLAYERS_PLAYER_V2_H
#include "scumm/players/player_v2base.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Scumm {
/**
* Scumm V2 PC-Speaker MIDI driver.
* This simulates the pc speaker sound, which is driven by the 8253 (square
* wave generator) and a low-band filter.
*/
class Player_V2 : public Audio::AudioStream, public Player_V2Base {
public:
Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr);
~Player_V2() override;
// MusicEngine API
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
// virtual int getMusicTimer();
int getSoundStatus(int sound) const override;
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return true; }
bool endOfData() const override { return false; }
int getRate() const override { return _sampleRate; }
protected:
enum {
FIXP_SHIFT = 16
};
unsigned int _update_step;
unsigned int _decay;
int _level;
unsigned int _RNG;
unsigned int _volumetable[16];
int _timer_count[4];
int _timer_output;
Audio::Mixer *_mixer;
Audio::SoundHandle _soundHandle;
const uint32 _sampleRate;
Common::Mutex _mutex;
uint32 _next_tick;
uint32 _tick_len;
protected:
virtual void generateSpkSamples(int16 *data, uint len);
virtual void generatePCjrSamples(int16 *data, uint len);
void lowPassFilter(int16 *data, uint len);
void squareGenerator(int channel, int freq, int vol,
int noiseFeedback, int16 *sample, uint len);
};
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V2A_H
#define SCUMM_PLAYERS_PLAYER_V2A_H
#include "common/scummsys.h"
#include "scumm/music.h"
#include "scumm/players/player_mod.h"
class Mixer;
namespace Scumm {
class ScummEngine;
class V2A_Sound;
/**
* Scumm V2 Amiga sound/music driver.
*/
class Player_V2A : public MusicEngine {
public:
Player_V2A(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_V2A() override;
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getMusicTimer() override;
int getSoundStatus(int sound) const override;
private:
enum {
V2A_MAXSLOTS = 8
};
struct soundSlot {
int id;
V2A_Sound *sound;
};
ScummEngine *_vm;
Player_MOD *_mod;
soundSlot _slot[V2A_MAXSLOTS];
int getSoundSlot(int id = 0) const;
static void update_proc(void *param);
void updateSound();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,654 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_v2base.h"
#include "scumm/scumm.h"
#define MAX_OUTPUT 0x7fff
namespace Scumm {
const uint8 note_lengths[] = {
0,
0, 0, 2,
0, 3, 4,
5, 6, 8,
9, 12, 16,
18, 24, 32,
36, 48, 64,
72, 96
};
static const uint16 hull_offsets[] = {
0, 12, 24, 36, 48, 60,
72, 88, 104, 120, 136, 256,
152, 164, 180
};
static const int16 hulls[] = {
// hull 0
3, -1, 0, 0, 0, 0, 0, 0,
0, -1, 0, 0,
// hull 1 (staccato)
3, -1, 0, 32, 0, -1, 0, 0,
0, -1, 0, 0,
// hull 2 (legato)
3, -1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
// hull 3 (staccatissimo)
3, -1, 0, 2, 0, -1, 0, 0,
0, -1, 0, 0,
// hull 4
3, -1, 0, 6, 0, -1, 0, 0,
0, -1, 0, 0,
// hull 5
3, -1, 0, 16, 0, -1, 0, 0,
0, -1, 0, 0,
// hull 6
(int16) 60000, -1, -1000, 20, 0, 0, 0, 0,
(int16) 40000, -1, -5000, 5, 0, -1, 0, 0,
// hull 7
(int16) 50000, -1, 0, 8, 30000, -1, 0, 0,
28000, -1, -5000, 5, 0, -1, 0, 0,
// hull 8
(int16) 60000, -1, -2000, 16, 0, 0, 0, 0,
28000, -1, -6000, 5, 0, -1, 0, 0,
// hull 9
(int16) 55000, -1, 0, 8, (int16) 35000, -1, 0, 0,
(int16) 40000, -1, -2000, 10, 0, -1, 0, 0,
// hull 10
(int16) 60000, -1, 0, 4, -2000, 8, 0, 0,
(int16) 40000, -1, -6000, 5, 0, -1, 0, 0,
// hull 12
0, -1, 150, 340, -150, 340, 0, -1,
0, -1, 0, 0,
// hull 13 == 164
20000, -1, 4000, 7, 1000, 15, 0, 0,
(int16) 35000, -1, -2000, 15, 0, -1, 0, 0,
// hull 14 == 180
(int16) 35000, -1, 500, 20, 0, 0, 0, 0,
(int16) 45000, -1, -500, 60, 0, -1, 0, 0,
// hull misc = 196
(int16) 44000, -1, -4400, 10, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 53000, -1, -5300, 10, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 63000, -1, -6300, 10, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 44000, -1, -1375, 32, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 53000, -1, -1656, 32, 0, -1, 0, 0,
0, -1, 0, 0,
// hull 11 == 256
(int16) 63000, -1, -1968, 32, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 44000, -1, - 733, 60, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 53000, -1, - 883, 60, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 63000, -1, -1050, 60, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 44000, -1, - 488, 90, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 53000, -1, - 588, 90, 0, -1, 0, 0,
0, -1, 0, 0,
(int16) 63000, -1, - 700, 90, 0, -1, 0, 0,
0, -1, 0, 0
};
static const uint16 freqmod_lengths[] = {
0x1000, 0x1000, 0x20, 0x2000, 0x1000
};
static const uint16 freqmod_offsets[] = {
0, 0x100, 0x200, 0x302, 0x202
};
static const int8 freqmod_table[0x502] = {
0, 3, 6, 9, 12, 15, 18, 21,
24, 27, 30, 33, 36, 39, 42, 45,
48, 51, 54, 57, 59, 62, 65, 67,
70, 73, 75, 78, 80, 82, 85, 87,
89, 91, 94, 96, 98, 100, 102, 103,
105, 107, 108, 110, 112, 113, 114, 116,
117, 118, 119, 120, 121, 122, 123, 123,
124, 125, 125, 126, 126, 126, 126, 126,
126, 126, 126, 126, 126, 126, 125, 125,
124, 123, 123, 122, 121, 120, 119, 118,
117, 116, 114, 113, 112, 110, 108, 107,
105, 103, 102, 100, 98, 96, 94, 91,
89, 87, 85, 82, 80, 78, 75, 73,
70, 67, 65, 62, 59, 57, 54, 51,
48, 45, 42, 39, 36, 33, 30, 27,
24, 21, 18, 15, 12, 9, 6, 3,
0, -3, -6, -9, -12, -15, -18, -21,
-24, -27, -30, -33, -36, -39, -42, -45,
-48, -51, -54, -57, -59, -62, -65, -67,
-70, -73, -75, -78, -80, -82, -85, -87,
-89, -91, -94, -96, -98,-100,-102,-103,
-105,-107,-108,-110,-112,-113,-114,-116,
-117,-118,-119,-120,-121,-122,-123,-123,
-124,-125,-125,-126,-126,-126,-126,-126,
-126,-126,-126,-126,-126,-126,-125,-125,
-124,-123,-123,-122,-121,-120,-119,-118,
-117,-116,-114,-113,-112,-110,-108,-107,
-105,-103,-102,-100, -98, -96, -94, -91,
-89, -87, -85, -82, -80, -78, -75, -73,
-70, -67, -65, -62, -59, -57, -54, -51,
-48, -45, -42, -39, -36, -33, -30, -27,
-24, -21, -18, -15, -12, -9, -6, -3,
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127,
-128,-127,-126,-125,-124,-123,-122,-121,
-120,-119,-118,-117,-116,-115,-114,-113,
-112,-111,-110,-109,-108,-107,-106,-105,
-104,-103,-102,-101,-100, -99, -98, -97,
-96, -95, -94, -93, -92, -91, -90, -89,
-88, -87, -86, -85, -84, -83, -82, -81,
-80, -79, -78, -77, -76, -75, -74, -73,
-72, -71, -70, -69, -68, -67, -66, -65,
-64, -63, -62, -61, -60, -59, -58, -57,
-56, -55, -54, -53, -52, -51, -50, -49,
-48, -47, -46, -45, -44, -43, -42, -41,
-40, -39, -38, -37, -36, -35, -34, -33,
-32, -31, -30, -29, -28, -27, -26, -25,
-24, -23, -22, -21, -20, -19, -18, -17,
-16, -15, -14, -13, -12, -11, -10, -9,
-8, -7, -6, -5, -4, -3, -2, -1,
-120, 120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
-120,-120,-120,-120,-120,-120,-120,-120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
120, 120, 120, 120, 120, 120, 120, 120,
41, 35, -66,-124, -31, 108, -42, -82,
82,-112, 73, -15, -15, -69, -23, -21,
-77, -90, -37, 60,-121, 12, 62,-103,
36, 94, 13, 28, 6, -73, 71, -34,
-77, 18, 77, -56, 67, -69,-117, -90,
31, 3, 90, 125, 9, 56, 37, 31,
93, -44, -53, -4,-106, -11, 69, 59,
19, 13,-119, 10, 28, -37, -82, 50,
32,-102, 80, -18, 64, 120, 54, -3,
18, 73, 50, -10, -98, 125, 73, -36,
-83, 79, 20, -14, 68, 64, 102, -48,
107, -60, 48, -73, 50, 59, -95, 34,
-10, 34,-111, -99, -31,-117, 31, -38,
-80, -54,-103, 2, -71, 114, -99, 73,
44,-128, 126, -59,-103, -43, -23,-128,
-78, -22, -55, -52, 83, -65, 103, -42,
-65, 20, -42, 126, 45, -36,-114, 102,
-125, -17, 87, 73, 97, -1, 105,-113,
97, -51, -47, 30, -99,-100, 22, 114,
114, -26, 29, -16,-124, 79, 74, 119,
2, -41, -24, 57, 44, 83, -53, -55,
18, 30, 51, 116, -98, 12, -12, -43,
-44, -97, -44, -92, 89, 126, 53, -49,
50, 34, -12, -52, -49, -45,-112, 45,
72, -45,-113, 117, -26, -39, 29, 42,
-27, -64, -9, 43, 120,-127,-121, 68,
14, 95, 80, 0, -44, 97,-115, -66,
123, 5, 21, 7, 59, 51,-126, 31,
24, 112,-110, -38, 100, 84, -50, -79,
-123, 62, 105, 21, -8, 70, 106, 4,
-106, 115, 14, -39, 22, 47, 103, 104,
-44, -9, 74, 74, -48, 87, 104, 118,
-6, 22, -69, 17, -83, -82, 36,-120,
121, -2, 82, -37, 37, 67, -27, 60,
-12, 69, -45, -40, 40, -50, 11, -11,
-59, 96, 89, 61,-105, 39,-118, 89,
118, 45, -48, -62, -55, -51, 104, -44,
73, 106, 121, 37, 8, 97, 64, 20,
-79, 59, 106, -91, 17, 40, -63,-116,
-42, -87, 11,-121,-105,-116, 47, -15,
21, 29,-102,-107, -63,-101, -31, -64,
126, -23, -88,-102, -89,-122, -62, -75,
84, -65,-102, -25, -39, 35, -47, 85,
-112, 56, 40, -47, -39, 108, -95, 102,
94, 78, -31, 48,-100, -2, -39, 113,
-97, -30, -91, -30, 12,-101, -76, 71,
101, 56, 42, 70,-119, -87,-126, 121,
122, 118, 120, -62, 99, -79, 38, -33,
-38, 41, 109, 62, 98, -32,-106, 18,
52, -65, 57, -90, 63,-119, 94, -15,
109, 14, -29, 108, 40, -95, 30, 32,
29, -53, -62, 3, 63, 65, 7,-124,
15, 20, 5, 101, 27, 40, 97, -55,
-59, -25, 44,-114, 70, 54, 8, -36,
-13, -88,-115, -2, -66, -14, -21, 113,
-1, -96, -48, 59, 117, 6,-116, 126,
-121, 120, 115, 77, -48, -66,-126, -66,
-37, -62, 70, 65, 43,-116, -6, 48,
127, 112, -16, -89, 84,-122, 50,-107,
-86, 91, 104, 19, 11, -26, -4, -11,
-54, -66, 125, -97,-119,-118, 65, 27,
-3, -72, 79, 104, -10, 114, 123, 20,
-103, -51, -45, 13, -16, 68, 58, -76,
-90, 102, 83, 51, 11, -53, -95, 16
};
static const uint16 spk_freq_table[12] = {
36484, 34436, 32503, 30679, 28957, 27332,
25798, 24350, 22983, 21693, 20476, 19326
};
static const uint16 pcjr_freq_table[12] = {
65472, 61760, 58304, 55040, 52032, 49024,
46272, 43648, 41216, 38912, 36736, 34624
};
Player_V2Base::Player_V2Base(ScummEngine *scumm, bool pcjr)
: _vm(scumm),
_pcjr(pcjr) {
_isV3Game = (scumm->_game.version >= 3);
_header_len = (scumm->_game.features & GF_OLD_BUNDLE) ? 4 : 6;
// Initialize sound queue
_current_nr = _next_nr = 0;
_current_data = _next_data = nullptr;
_retaddr = nullptr;
// Initialize channel code
for (int i = 0; i < 4; ++i)
clear_channel(i);
// Initialize V3 music timer
_music_timer_ctr = _music_timer = 0;
_ticks_per_music_timer = 65535;
if (_pcjr) {
_freqs_table = pcjr_freq_table;
} else {
_freqs_table = spk_freq_table;
}
}
Player_V2Base::~Player_V2Base() {
}
void Player_V2Base::chainSound(int nr, byte *data) {
int offset = _header_len + (_pcjr ? 10 : 2);
_current_nr = nr;
_current_data = data;
for (int i = 0; i < 4; i++) {
clear_channel(i);
_channels[i].d.music_script_nr = nr;
if (data) {
_channels[i].d.next_cmd = READ_LE_UINT16(data + offset + 2 * i);
if (_channels[i].d.next_cmd) {
_channels[i].d.time_left = 1;
}
}
}
_music_timer = 0;
}
void Player_V2Base::chainNextSound() {
if (_next_nr) {
chainSound(_next_nr, _next_data);
_next_nr = 0;
_next_data = nullptr;
}
}
// TODO: Merge stopAllSounds, stopSound() and startSound(), using some overriding
// perhaps? Player_V2CMS's implementations start like that of Player_V2
// but then add some MIDI related stuff.
void Player_V2Base::clear_channel(int i) {
ChannelInfo *channel = &_channels[i];
memset(channel, 0, sizeof(ChannelInfo));
}
int Player_V2Base::getMusicTimer() {
if (_isV3Game)
return _music_timer;
else
return _channels[0].d.music_timer;
}
void Player_V2Base::execute_cmd(ChannelInfo *channel) {
uint16 value;
int16 offset;
uint8 *script_ptr;
ChannelInfo * current_channel;
ChannelInfo * dest_channel;
current_channel = channel;
if (channel->d.next_cmd == 0)
goto check_stopped;
script_ptr = &_current_data[channel->d.next_cmd];
for (;;) {
uint8 opcode = *script_ptr++;
if (opcode >= 0xf8) {
switch (opcode) {
case 0xf8: // set hull curve
debug(7, "channels[%d]: hull curve %2d",
(uint)(channel - _channels), *script_ptr);
channel->d.hull_curve = hull_offsets[*script_ptr / 2];
script_ptr++;
break;
case 0xf9: // set freqmod curve
debug(7, "channels[%d]: freqmod curve %2d",
(uint)(channel - _channels), *script_ptr);
channel->d.freqmod_table = freqmod_offsets[*script_ptr / 4];
channel->d.freqmod_modulo = freqmod_lengths[*script_ptr / 4];
script_ptr++;
break;
case 0xfd: // clear other channel
value = READ_LE_UINT16 (script_ptr) / sizeof (ChannelInfo);
debug(7, "clear channel %d", value);
script_ptr += 2;
// In Indy3, when traveling to Venice a command is
// issued to clear channel 4. So we introduce a 4th
// channel, which is never used. All OOB accesses are
// mapped to this channel.
//
// The original game had room for 8 channels, but only
// channels 0-3 are read, changes to other channels
// had no effect.
if (value >= ARRAYSIZE (_channels))
value = 4;
channel = &_channels[value];
// fall through
case 0xfa: // clear current channel
if (opcode == 0xfa)
debug(7, "clear channel");
channel->d.next_cmd = 0;
channel->d.base_freq = 0;
channel->d.freq_delta = 0;
channel->d.freq = 0;
channel->d.volume = 0;
channel->d.volume_delta = 0;
channel->d.inter_note_pause = 0;
channel->d.transpose = 0;
channel->d.hull_curve = 0;
channel->d.hull_offset = 0;
channel->d.hull_counter = 0;
channel->d.freqmod_table = 0;
channel->d.freqmod_offset = 0;
channel->d.freqmod_incr = 0;
channel->d.freqmod_multiplier = 0;
channel->d.freqmod_modulo = 0;
break;
case 0xfb: // ret from subroutine
debug(7, "ret from sub");
script_ptr = _retaddr;
break;
case 0xfc: // call subroutine
offset = READ_LE_UINT16 (script_ptr);
debug(7, "subroutine %d", offset);
script_ptr += 2;
_retaddr = script_ptr;
script_ptr = _current_data + offset;
break;
case 0xfe: // loop music
opcode = *script_ptr++;
offset = READ_LE_UINT16 (script_ptr);
script_ptr += 2;
debug(7, "loop if %d to %d", opcode, offset);
if (!channel->array[opcode / 2] || --channel->array[opcode/2])
script_ptr += offset;
break;
case 0xff: // set parameter
opcode = *script_ptr++;
value = READ_LE_UINT16 (script_ptr);
channel->array[opcode / 2] = value;
debug(7, "channels[%d]: set param %2d = %5d",
(uint)(channel - _channels), opcode, value);
script_ptr += 2;
if (opcode == 14) {
/* tempo var */
_ticks_per_music_timer = 125;
}
if (opcode == 0)
goto end;
break;
default:
break;
}
} else { // opcode < 0xf8
for (;;) {
int16 note, octave;
int is_last_note;
dest_channel = &_channels[(opcode >> 5) & 3];
if (!(opcode & 0x80)) {
int tempo = channel->d.tempo;
if (!tempo)
tempo = 1;
channel->d.time_left = tempo * note_lengths[opcode & 0x1f];
note = *script_ptr++;
is_last_note = note & 0x80;
note &= 0x7f;
if (note == 0x7f) {
debug(8, "channels[%d]: pause %d",
(uint)(channel - _channels), channel->d.time_left);
goto end;
}
} else {
channel->d.time_left = ((opcode & 7) << 8) | *script_ptr++;
if ((opcode & 0x10)) {
debug(8, "channels[%d]: pause %d",
(uint)(channel - _channels), channel->d.time_left);
goto end;
}
is_last_note = 0;
note = (*script_ptr++) & 0x7f;
}
debug(8, "channels[%d]: @%04x note: %3d+%d len: %2d hull: %d mod: %d/%d/%d %s",
(uint)(dest_channel - channel), (uint)(script_ptr - _current_data - 2),
note, (signed short) dest_channel->d.transpose, channel->d.time_left,
dest_channel->d.hull_curve, dest_channel->d.freqmod_table,
dest_channel->d.freqmod_incr,dest_channel->d.freqmod_multiplier,
is_last_note ? "last":"");
uint16 myfreq;
dest_channel->d.time_left = channel->d.time_left;
dest_channel->d.note_length =
channel->d.time_left - dest_channel->d.inter_note_pause;
note += dest_channel->d.transpose;
while (note < 0)
note += 12;
octave = note / 12;
note = note % 12;
dest_channel->d.hull_offset = 0;
dest_channel->d.hull_counter = 1;
if (_pcjr && dest_channel == &_channels[3]) {
dest_channel->d.hull_curve = 196 + note * 12;
myfreq = 384 - 64 * octave;
} else {
myfreq = _freqs_table[note] >> octave;
}
dest_channel->d.freq = dest_channel->d.base_freq = myfreq;
if (is_last_note)
goto end;
opcode = *script_ptr++;
}
}
}
end:
channel = current_channel;
if (channel->d.time_left) {
channel->d.next_cmd = script_ptr - _current_data;
return;
}
channel->d.next_cmd = 0;
check_stopped:
int i;
for (i = 0; i < 4; i++) {
if (_channels[i].d.time_left)
return;
}
_current_nr = 0;
_current_data = nullptr;
chainNextSound();
}
void Player_V2Base::next_freqs(ChannelInfo *channel) {
channel->d.volume += channel->d.volume_delta;
channel->d.base_freq += channel->d.freq_delta;
if (channel->d.freqmod_modulo > 0)
channel->d.freqmod_offset = (channel->d.freqmod_offset + channel->d.freqmod_incr) % channel->d.freqmod_modulo;
else
channel->d.freqmod_offset = 0;
channel->d.freq =
(int)(freqmod_table[channel->d.freqmod_table + (channel->d.freqmod_offset >> 4)])
* (int)channel->d.freqmod_multiplier / 256
+ channel->d.base_freq;
debug(9, "Freq: %d/%d, %d/%d/%d*%d %d",
channel->d.base_freq, (int16)channel->d.freq_delta,
channel->d.freqmod_table, channel->d.freqmod_offset,
channel->d.freqmod_incr, channel->d.freqmod_multiplier,
channel->d.freq);
if (channel->d.note_length && !--channel->d.note_length) {
channel->d.hull_offset = 16;
channel->d.hull_counter = 1;
}
if (!--channel->d.time_left) {
execute_cmd(channel);
}
if (channel->d.hull_counter && !--channel->d.hull_counter) {
for (;;) {
const int16 *hull_ptr = hulls
+ channel->d.hull_curve + channel->d.hull_offset / 2;
if (hull_ptr[1] == -1) {
channel->d.volume = hull_ptr[0];
if (hull_ptr[0] == 0)
channel->d.volume_delta = 0;
channel->d.hull_offset += 4;
} else {
channel->d.volume_delta = hull_ptr[0];
channel->d.hull_counter = hull_ptr[1];
channel->d.hull_offset += 4;
break;
}
}
}
}
void Player_V2Base::nextTick() {
for (int i = 0; i < 4; i++) {
if (!_channels[i].d.time_left)
continue;
next_freqs(&_channels[i]);
}
if (_music_timer_ctr++ >= _ticks_per_music_timer) {
_music_timer_ctr = 0;
_music_timer++;
}
}
extern const uint8 *const g_pv2ModTbl = reinterpret_cast<const uint8*>(freqmod_table);
extern const uint32 g_pv2ModTblSize = ARRAYSIZE(freqmod_table);
} // End of namespace Scumm

View File

@@ -0,0 +1,119 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V2BASE_H
#define SCUMM_PLAYERS_PLAYER_V2BASE_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "scumm/music.h"
namespace Scumm {
class ScummEngine;
#include "common/pack-start.h" // START STRUCT PACKING
struct channel_data {
uint16 time_left; // 00
uint16 next_cmd; // 02
uint16 base_freq; // 04
uint16 freq_delta; // 06
uint16 freq; // 08
uint16 volume; // 10
uint16 volume_delta; // 12
uint16 tempo; // 14
uint16 inter_note_pause; // 16
uint16 transpose; // 18
uint16 note_length; // 20
uint16 hull_curve; // 22
uint16 hull_offset; // 24
uint16 hull_counter; // 26
uint16 freqmod_table; // 28
uint16 freqmod_offset; // 30
uint16 freqmod_incr; // 32
uint16 freqmod_multiplier; // 34
uint16 freqmod_modulo; // 36
uint16 unknown[4]; // 38 - 44
uint16 music_timer; // 46
uint16 music_script_nr; // 48
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
/**
* Common base class for Player_V2 and Player_V2CMS.
*/
class Player_V2Base : public MusicEngine {
public:
Player_V2Base(ScummEngine *scumm, bool pcjr);
~Player_V2Base() override;
// MusicEngine API
// virtual void setMusicVolume(int vol);
// virtual void startSound(int sound);
// virtual void stopSound(int sound);
// virtual void stopAllSounds();
int getMusicTimer() override;
// virtual int getSoundStatus(int sound) const;
protected:
bool _isV3Game;
ScummEngine *_vm;
bool _pcjr;
int _header_len;
int _current_nr;
byte *_current_data;
int _next_nr;
byte *_next_data;
byte *_retaddr;
union ChannelInfo {
channel_data d;
uint16 array[sizeof(channel_data)/2];
};
ChannelInfo _channels[5];
private:
int _music_timer;
int _music_timer_ctr;
int _ticks_per_music_timer;
const uint16 *_freqs_table;
protected:
virtual void nextTick();
virtual void clear_channel(int i);
virtual void chainSound(int nr, byte *data);
virtual void chainNextSound();
void execute_cmd(ChannelInfo *channel);
void next_freqs(ChannelInfo *channel);
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,781 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "scumm/players/player_v2cms.h"
#include "scumm/scumm.h"
#include "audio/cms.h"
#define FREQ_HZ 236 // Don't change!
namespace Scumm {
Player_V2CMS::Player_V2CMS(ScummEngine *scumm)
: Player_V2Base(scumm, true), _cmsVoicesBase(), _cmsVoices(),
_cmsChips(), _midiDelay(0), _octaveMask(0), _looping(0), _tempo(0),
_tempoSum(0), _midiData(nullptr), _midiSongBegin(nullptr), _musicTimer(0),
_musicTimerTicks(0), _voiceTimer(0), _loadedMidiSong(0),
_outputTableReady(0), _midiChannel(), _midiChannelUse(),
_lastMidiCommand(0) {
setMusicVolume(255);
memset(_sfxFreq, 0xFF, sizeof(_sfxFreq));
memset(_sfxAmpl, 0x00, sizeof(_sfxAmpl));
memset(_sfxOctave, 0x66, sizeof(_sfxOctave));
_cmsVoices[0].amplitudeOutput = &_cmsChips[0].ampl[0];
_cmsVoices[0].freqOutput = &_cmsChips[0].freq[0];
_cmsVoices[0].octaveOutput = &_cmsChips[0].octave[0];
_cmsVoices[1].amplitudeOutput = &_cmsChips[0].ampl[1];
_cmsVoices[1].freqOutput = &_cmsChips[0].freq[1];
_cmsVoices[1].octaveOutput = &_cmsChips[0].octave[0];
_cmsVoices[2].amplitudeOutput = &_cmsChips[0].ampl[2];
_cmsVoices[2].freqOutput = &_cmsChips[0].freq[2];
_cmsVoices[2].octaveOutput = &_cmsChips[0].octave[1];
_cmsVoices[3].amplitudeOutput = &_cmsChips[0].ampl[3];
_cmsVoices[3].freqOutput = &_cmsChips[0].freq[3];
_cmsVoices[3].octaveOutput = &_cmsChips[0].octave[1];
_cmsVoices[4].amplitudeOutput = &_cmsChips[1].ampl[0];
_cmsVoices[4].freqOutput = &_cmsChips[1].freq[0];
_cmsVoices[4].octaveOutput = &_cmsChips[1].octave[0];
_cmsVoices[5].amplitudeOutput = &_cmsChips[1].ampl[1];
_cmsVoices[5].freqOutput = &_cmsChips[1].freq[1];
_cmsVoices[5].octaveOutput = &_cmsChips[1].octave[0];
_cmsVoices[6].amplitudeOutput = &_cmsChips[1].ampl[2];
_cmsVoices[6].freqOutput = &_cmsChips[1].freq[2];
_cmsVoices[6].octaveOutput = &_cmsChips[1].octave[1];
_cmsVoices[7].amplitudeOutput = &_cmsChips[1].ampl[3];
_cmsVoices[7].freqOutput = &_cmsChips[1].freq[3];
_cmsVoices[7].octaveOutput = &_cmsChips[1].octave[1];
// inits the CMS Emulator like in the original
_cmsEmu = CMS::Config::create();
if (!_cmsEmu || !_cmsEmu->init())
error("Failed to initialise CMS emulator");
for (int i = 0, cmsPort = 0x220; i < 2; cmsPort += 2, ++i) {
for (int off = 0; off < 13; ++off) {
// TODO: Use _cmsEmu->writeReg instead?
_cmsEmu->write(cmsPort+1, _cmsInitData[off*2]);
_cmsEmu->write(cmsPort, _cmsInitData[off*2+1]);
}
}
_cmsEmu->start(new Common::Functor0Mem<void, Player_V2CMS>(this, &Player_V2CMS::onTimer), FREQ_HZ);
}
Player_V2CMS::~Player_V2CMS() {
Common::StackLock lock(_mutex);
_cmsEmu->stop();
delete _cmsEmu;
}
void Player_V2CMS::setMusicVolume(int vol) {
}
int Player_V2CMS::getMusicTimer() {
return _midiData ? _musicTimer : Player_V2Base::getMusicTimer();
}
void Player_V2CMS::stopAllSounds() {
Common::StackLock lock(_mutex);
for (int i = 0; i < 4; i++) {
clear_channel(i);
}
_next_nr = _current_nr = 0;
_next_data = _current_data = nullptr;
_loadedMidiSong = 0;
_midiData = nullptr;
_midiSongBegin = nullptr;
_midiDelay = 0;
_musicTimer = _musicTimerTicks = 0;
offAllChannels();
}
void Player_V2CMS::stopSound(int nr) {
Common::StackLock lock(_mutex);
if (_next_nr == nr) {
_next_nr = 0;
_next_data = nullptr;
}
if (_current_nr == nr) {
for (int i = 0; i < 4; i++) {
clear_channel(i);
}
_current_nr = 0;
_current_data = nullptr;
chainNextSound();
}
if (_loadedMidiSong == nr) {
_loadedMidiSong = 0;
_midiData = nullptr;
_midiSongBegin = nullptr;
_midiDelay = 0;
offAllChannels();
}
}
void Player_V2CMS::startSound(int nr) {
Common::StackLock lock(_mutex);
byte *data = _vm->getResourceAddress(rtSound, nr);
assert(data);
if (data[6] == 0x80) {
_musicTimer = _musicTimerTicks = 0;
loadMidiData(data, nr);
} else {
int cprio = _current_data ? *(_current_data + _header_len) : 0;
int prio = *(data + _header_len);
int nprio = _next_data ? *(_next_data + _header_len) : 0;
int restartable = *(data + _header_len + 1);
if (!_current_nr || cprio <= prio) {
int tnr = _current_nr;
int tprio = cprio;
byte *tdata = _current_data;
chainSound(nr, data);
nr = tnr;
prio = tprio;
data = tdata;
restartable = data ? *(data + _header_len + 1) : 0;
}
if (!_current_nr) {
nr = 0;
_next_nr = 0;
_next_data = nullptr;
}
if (nr != _current_nr
&& restartable
&& (!_next_nr
|| nprio <= prio)) {
_next_nr = nr;
_next_data = data;
}
}
}
void Player_V2CMS::loadMidiData(byte *data, int sound) {
memset(_midiChannelUse, 0, sizeof(_midiChannelUse));
memset(_midiChannel, 0, sizeof(_midiChannel));
_tempo = data[7];
_looping = data[8];
byte channels = data[14];
byte curChannel = 0;
byte *voice2 = data + 23;
for (; channels != 0; ++curChannel, --channels, voice2 += 16) {
if (*(data + 15 + curChannel)) {
byte channel = *(data + 15 + curChannel) - 1;
_midiChannelUse[channel] = 1;
Voice *voiceDef = &_cmsVoicesBase[channel];
byte attackDecay = voice2[10];
voiceDef->attack = _attackRate[attackDecay >> 4];
voiceDef->decay = _decayRate[attackDecay & 0x0F];
byte sustainRelease = voice2[11];
voiceDef->sustain = _sustainRate[sustainRelease >> 4];
voiceDef->release = _releaseRate[sustainRelease & 0x0F];
if (voice2[3] & 0x40) {
voiceDef->vibrato = 0x0301;
if (voice2[13] & 0x40) {
voiceDef->vibrato = 0x0601;
}
} else {
voiceDef->vibrato = 0;
}
if (voice2[8] & 0x80) {
voiceDef->vibrato2 = 0x0506;
if (voice2[13] & 0x80) {
voiceDef->vibrato2 = 0x050C;
}
} else {
voiceDef->vibrato2 = 0;
}
if ((voice2[8] & 0x0F) > 1) {
voiceDef->octadd = 0x01;
} else {
voiceDef->octadd = 0x00;
}
}
}
for (int i = 0; i < 8; ++i) {
_cmsVoices[i].chanNumber = 0xFF;
_cmsVoices[i].curVolume = 0;
_cmsVoices[i].nextVoice = nullptr;
}
_midiDelay = 0;
memset(_cmsChips, 0, sizeof(MusicChip)*2);
_midiData = data + 151;
_midiSongBegin = _midiData + data[9];
_loadedMidiSong = sound;
}
int Player_V2CMS::getSoundStatus(int nr) const {
return _current_nr == nr || _next_nr == nr || _loadedMidiSong == nr;
}
void Player_V2CMS::processMidiData() {
byte *currentData = _midiData;
byte command = 0x00;
int16 temp = 0;
++_musicTimerTicks;
if (_musicTimerTicks > 60) {
_musicTimerTicks = 0;
++_musicTimer;
}
if (!_midiDelay) {
while (true) {
if ((command = *currentData++) == 0xFF) {
if ((command = *currentData++) == 0x2F) {
if (_looping == 0) {
currentData = _midiData = _midiSongBegin;
continue;
}
_midiData = _midiSongBegin = nullptr;
_midiDelay = 0;
_loadedMidiSong = 0;
offAllChannels();
return;
} else {
if (command == 0x58) {
currentData += 6;
}
}
} else {
_lastMidiCommand = command;
if (command < 0x90) {
clearNote(currentData);
} else {
playNote(currentData);
}
}
temp = command = *currentData++;
if (command & 0x80) {
temp = (command & 0x7F) << 8;
command = *currentData++;
temp |= (command << 1);
temp >>= 1;
}
temp >>= 1;
int lastBit = temp & 1;
temp >>= 1;
temp += lastBit;
if (temp)
break;
}
_midiData = currentData;
_midiDelay = temp;
}
--_midiDelay;
if (_midiDelay < 0)
_midiDelay = 0;
return;
}
void Player_V2CMS::onTimer() {
Common::StackLock lock(_mutex);
if (_midiData) {
--_voiceTimer;
if (!(_voiceTimer & 0x01))
playVoice();
int newTempoSum = _tempo + _tempoSum;
_tempoSum = newTempoSum & 0xFF;
if (newTempoSum > 0xFF)
processMidiData();
} else {
nextTick();
play();
}
}
void Player_V2CMS::playVoice() {
if (_outputTableReady) {
playMusicChips(_cmsChips);
_outputTableReady = 0;
}
_octaveMask = 0xF0;
Voice2 *voice = nullptr;
for (int i = 0; i < 8; ++i) {
voice = &_cmsVoices[i];
_octaveMask = ~_octaveMask;
if (voice->chanNumber != 0xFF) {
processChannel(voice);
} else {
if (!voice->curVolume) {
*(voice->amplitudeOutput) = 0;
}
int volume = voice->curVolume - voice->releaseRate;
if (volume < 0)
volume = 0;
voice->curVolume = volume;
*(voice->amplitudeOutput) = ((volume >> 4) | (volume & 0xF0)) & voice->channel;
++_outputTableReady;
}
}
}
void Player_V2CMS::processChannel(Voice2 *channel) {
++_outputTableReady;
switch (channel->nextProcessState) {
case Voice2::kEnvelopeAttack:
processAttack(channel);
break;
case Voice2::kEnvelopeDecay:
processDecay(channel);
break;
case Voice2::kEnvelopeSustain:
processSustain(channel);
break;
case Voice2::kEnvelopeRelease:
processRelease(channel);
break;
default:
break;
}
}
void Player_V2CMS::processRelease(Voice2 *channel) {
int newVolume = channel->curVolume - channel->releaseRate;
if (newVolume < 0)
newVolume = 0;
channel->curVolume = newVolume;
processVibrato(channel);
}
void Player_V2CMS::processAttack(Voice2 *channel) {
int newVolume = channel->curVolume + channel->attackRate;
if (newVolume > channel->maxAmpl) {
channel->curVolume = channel->maxAmpl;
channel->nextProcessState = Voice2::kEnvelopeDecay;
} else {
channel->curVolume = newVolume;
}
processVibrato(channel);
}
void Player_V2CMS::processDecay(Voice2 *channel) {
int newVolume = channel->curVolume - channel->decayRate;
if (newVolume <= channel->sustainRate) {
channel->curVolume = channel->sustainRate;
channel->nextProcessState = Voice2::kEnvelopeSustain;
} else {
channel->curVolume = newVolume;
}
processVibrato(channel);
}
void Player_V2CMS::processSustain(Voice2 *channel) {
if (channel->unkVibratoRate) {
int16 volume = channel->curVolume + channel->unkRate;
if (volume & 0xFF00) {
volume = int8(volume >> 8);
volume = -volume;
}
channel->curVolume = volume;
--channel->unkCount;
if (!channel->unkCount) {
channel->unkRate = -channel->unkRate;
channel->unkCount = (channel->unkVibratoDepth & 0x0F) << 1;
}
}
processVibrato(channel);
}
void Player_V2CMS::processVibrato(Voice2 *channel) {
if (channel->vibratoRate) {
int16 temp = channel->curFreq + channel->curVibratoRate;
channel->curOctave += (temp & 0xFF00) >> 8;
channel->curFreq = temp & 0xFF;
--channel->curVibratoUnk;
if (!channel->curVibratoUnk) {
channel->curVibratoRate = -channel->curVibratoRate;
channel->curVibratoUnk = (channel->vibratoDepth & 0x0F) << 1;
}
}
byte *output = channel->amplitudeOutput;
*output = ((channel->curVolume >> 4) | (channel->curVolume & 0xF0)) & channel->channel;
output = channel->freqOutput;
*output = channel->curFreq;
output = channel->octaveOutput;
*output = (((channel->curOctave << 4) | (channel->curOctave & 0x0F)) & _octaveMask) | ((~_octaveMask) & *output);
}
void Player_V2CMS::offAllChannels() {
for (int cmsPort = 0x220, i = 0; i < 2; cmsPort += 2, ++i) {
for (int off = 1; off <= 10; ++off) {
// TODO: Use _cmsEmu->writeReg instead?
_cmsEmu->write(cmsPort+1, _cmsInitData[off*2]);
_cmsEmu->write(cmsPort, _cmsInitData[off*2+1]);
}
}
}
Player_V2CMS::Voice2 *Player_V2CMS::getFreeVoice() {
Voice2 *curVoice = nullptr;
Voice2 *selected = nullptr;
uint8 volume = 0xFF;
for (int i = 0; i < 8; ++i) {
curVoice = &_cmsVoices[i];
if (curVoice->chanNumber == 0xFF) {
if (!curVoice->curVolume) {
selected = curVoice;
break;
}
if (curVoice->curVolume < volume) {
selected = curVoice;
volume = selected->curVolume;
}
}
}
if (selected) {
selected->chanNumber = _lastMidiCommand & 0x0F;
uint8 channel = selected->chanNumber;
Voice2 *oldChannel = _midiChannel[channel];
_midiChannel[channel] = selected;
selected->nextVoice = oldChannel;
}
return selected;
}
void Player_V2CMS::playNote(byte *&data) {
byte channel = _lastMidiCommand & 0x0F;
if (_midiChannelUse[channel]) {
Voice2 *freeVoice = getFreeVoice();
if (freeVoice) {
Voice *voice = &_cmsVoicesBase[freeVoice->chanNumber];
freeVoice->attackRate = voice->attack;
freeVoice->decayRate = voice->decay;
freeVoice->sustainRate = voice->sustain;
freeVoice->releaseRate = voice->release;
freeVoice->octaveAdd = voice->octadd;
freeVoice->vibratoRate = freeVoice->curVibratoRate = voice->vibrato & 0xFF;
freeVoice->vibratoDepth = freeVoice->curVibratoUnk = voice->vibrato >> 8;
freeVoice->unkVibratoRate = freeVoice->unkRate = voice->vibrato2 & 0xFF;
freeVoice->unkVibratoDepth = freeVoice->unkCount = voice->vibrato2 >> 8;
freeVoice->maxAmpl = 0xFF;
uint8 rate = freeVoice->attackRate;
uint8 volume = freeVoice->curVolume >> 1;
if (rate < volume)
rate = volume;
rate -= freeVoice->attackRate;
freeVoice->curVolume = rate;
freeVoice->playingNote = *data;
int effectiveNote = freeVoice->playingNote + 3;
if (effectiveNote < 0 || effectiveNote >= ARRAYSIZE(_midiNotes)) {
warning("Player_V2CMS::playNote: Note %d out of bounds", effectiveNote);
effectiveNote = CLIP<int>(effectiveNote, 0, ARRAYSIZE(_midiNotes) - 1);
}
int octave = _midiNotes[effectiveNote].baseOctave + freeVoice->octaveAdd - 3;
if (octave < 0)
octave = 0;
if (octave > 7)
octave = 7;
if (!octave)
++octave;
freeVoice->curOctave = octave;
freeVoice->curFreq = _midiNotes[effectiveNote].frequency;
freeVoice->curVolume = 0;
freeVoice->nextProcessState = Voice2::kEnvelopeAttack;
if (!(_lastMidiCommand & 1))
freeVoice->channel = 0xF0;
else
freeVoice->channel = 0x0F;
}
}
data += 2;
}
Player_V2CMS::Voice2 *Player_V2CMS::getPlayVoice(byte param) {
byte channelNum = _lastMidiCommand & 0x0F;
Voice2 *curVoice = _midiChannel[channelNum];
if (curVoice) {
Voice2 *prevVoice = nullptr;
while (true) {
if (curVoice->playingNote == param)
break;
prevVoice = curVoice;
curVoice = curVoice->nextVoice;
if (!curVoice)
return nullptr;
}
if (prevVoice)
prevVoice->nextVoice = curVoice->nextVoice;
else
_midiChannel[channelNum] = curVoice->nextVoice;
}
return curVoice;
}
void Player_V2CMS::clearNote(byte *&data) {
Voice2 *voice = getPlayVoice(*data);
if (voice) {
voice->chanNumber = 0xFF;
voice->nextVoice = nullptr;
voice->nextProcessState = Voice2::kEnvelopeRelease;
}
data += 2;
}
void Player_V2CMS::play() {
_octaveMask = 0xF0;
channel_data *chan = &_channels[0].d;
byte noiseGen = 3;
for (int i = 1; i <= 4; ++i) {
if (chan->time_left) {
uint16 freq = chan->freq;
if (i == 4) {
if ((freq >> 8) & 0x40) {
noiseGen = freq & 0xFF;
} else {
noiseGen = 3;
_sfxFreq[0] = _sfxFreq[3];
_sfxOctave[0] = (_sfxOctave[0] & 0xF0) | ((_sfxOctave[1] & 0xF0) >> 4);
}
} else {
if (freq == 0) {
freq = 0xFFC0;
}
int cmsOct = 2;
int freqOct = 0x8000;
while (true) {
if (freq >= freqOct) {
break;
}
freqOct >>= 1;
++cmsOct;
if (cmsOct == 8) {
--cmsOct;
freq = 1024;
break;
}
}
byte oct = cmsOct << 4;
oct |= cmsOct;
oct &= _octaveMask;
oct |= (~_octaveMask) & _sfxOctave[(i & 3) >> 1];
_sfxOctave[(i & 3) >> 1] = oct;
freq >>= -(cmsOct - 9);
_sfxFreq[i & 3] = (-(freq - 511)) & 0xFF;
}
_sfxAmpl[i & 3] = _volumeTable[chan->volume >> 12];
} else {
_sfxAmpl[i & 3] = 0;
}
chan = &_channels[i].d;
_octaveMask ^= 0xFF;
}
// with the high nibble of the volumeReg value
// the right channels amplitude is set
// with the low value the left channels amplitude
// TODO: Use _cmsEmu->writeReg instead?
_cmsEmu->write(0x221, 0);
_cmsEmu->write(0x220, _sfxAmpl[0]);
_cmsEmu->write(0x221, 1);
_cmsEmu->write(0x220, _sfxAmpl[1]);
_cmsEmu->write(0x221, 2);
_cmsEmu->write(0x220, _sfxAmpl[2]);
_cmsEmu->write(0x221, 3);
_cmsEmu->write(0x220, _sfxAmpl[3]);
_cmsEmu->write(0x221, 8);
_cmsEmu->write(0x220, _sfxFreq[0]);
_cmsEmu->write(0x221, 9);
_cmsEmu->write(0x220, _sfxFreq[1]);
_cmsEmu->write(0x221, 10);
_cmsEmu->write(0x220, _sfxFreq[2]);
_cmsEmu->write(0x221, 11);
_cmsEmu->write(0x220, _sfxFreq[3]);
_cmsEmu->write(0x221, 0x10);
_cmsEmu->write(0x220, _sfxOctave[0]);
_cmsEmu->write(0x221, 0x11);
_cmsEmu->write(0x220, _sfxOctave[1]);
_cmsEmu->write(0x221, 0x14);
_cmsEmu->write(0x220, 0x3E);
_cmsEmu->write(0x221, 0x15);
_cmsEmu->write(0x220, 0x01);
_cmsEmu->write(0x221, 0x16);
_cmsEmu->write(0x220, noiseGen);
}
void Player_V2CMS::playMusicChips(const MusicChip *table) {
int cmsPort = 0x21E;
do {
cmsPort += 2;
// TODO: Use _cmsEmu->writeReg instead?
_cmsEmu->write(cmsPort+1, 0);
_cmsEmu->write(cmsPort, table->ampl[0]);
_cmsEmu->write(cmsPort+1, 1);
_cmsEmu->write(cmsPort, table->ampl[1]);
_cmsEmu->write(cmsPort+1, 2);
_cmsEmu->write(cmsPort, table->ampl[2]);
_cmsEmu->write(cmsPort+1, 3);
_cmsEmu->write(cmsPort, table->ampl[3]);
_cmsEmu->write(cmsPort+1, 8);
_cmsEmu->write(cmsPort, table->freq[0]);
_cmsEmu->write(cmsPort+1, 9);
_cmsEmu->write(cmsPort, table->freq[1]);
_cmsEmu->write(cmsPort+1, 10);
_cmsEmu->write(cmsPort, table->freq[2]);
_cmsEmu->write(cmsPort+1, 11);
_cmsEmu->write(cmsPort, table->freq[3]);
_cmsEmu->write(cmsPort+1, 0x10);
_cmsEmu->write(cmsPort, table->octave[0]);
_cmsEmu->write(cmsPort+1, 0x11);
_cmsEmu->write(cmsPort, table->octave[1]);
_cmsEmu->write(cmsPort+1, 0x14);
_cmsEmu->write(cmsPort, 0x3F);
_cmsEmu->write(cmsPort+1, 0x15);
_cmsEmu->write(cmsPort, 0x00);
++table;
} while ((cmsPort & 2) == 0);
}
const Player_V2CMS::MidiNote Player_V2CMS::_midiNotes[132] = {
{ 3, 0 }, { 31, 0 }, { 58, 0 }, { 83, 0 },
{ 107, 0 }, { 130, 0 }, { 151, 0 }, { 172, 0 },
{ 191, 0 }, { 209, 0 }, { 226, 0 }, { 242, 0 },
{ 3, 1 }, { 31, 1 }, { 58, 1 }, { 83, 1 },
{ 107, 1 }, { 130, 1 }, { 151, 1 }, { 172, 1 },
{ 191, 1 }, { 209, 1 }, { 226, 1 }, { 242, 1 },
{ 3, 2 }, { 31, 2 }, { 58, 2 }, { 83, 2 },
{ 107, 2 }, { 130, 2 }, { 151, 2 }, { 172, 2 },
{ 191, 2 }, { 209, 2 }, { 226, 2 }, { 242, 2 },
{ 3, 3 }, { 31, 3 }, { 58, 3 }, { 83, 3 },
{ 107, 3 }, { 130, 3 }, { 151, 3 }, { 172, 3 },
{ 191, 3 }, { 209, 3 }, { 226, 3 }, { 242, 3 },
{ 3, 4 }, { 31, 4 }, { 58, 4 }, { 83, 4 },
{ 107, 4 }, { 130, 4 }, { 151, 4 }, { 172, 4 },
{ 191, 4 }, { 209, 4 }, { 226, 4 }, { 242, 4 },
{ 3, 5 }, { 31, 5 }, { 58, 5 }, { 83, 5 },
{ 107, 5 }, { 130, 5 }, { 151, 5 }, { 172, 5 },
{ 191, 5 }, { 209, 5 }, { 226, 5 }, { 242, 5 },
{ 3, 6 }, { 31, 6 }, { 58, 6 }, { 83, 6 },
{ 107, 6 }, { 130, 6 }, { 151, 6 }, { 172, 6 },
{ 191, 6 }, { 209, 6 }, { 226, 6 }, { 242, 6 },
{ 3, 7 }, { 31, 7 }, { 58, 7 }, { 83, 7 },
{ 107, 7 }, { 130, 7 }, { 151, 7 }, { 172, 7 },
{ 191, 7 }, { 209, 7 }, { 226, 7 }, { 242, 7 },
{ 3, 8 }, { 31, 8 }, { 58, 8 }, { 83, 8 },
{ 107, 8 }, { 130, 8 }, { 151, 8 }, { 172, 8 },
{ 191, 8 }, { 209, 8 }, { 226, 8 }, { 242, 8 },
{ 3, 9 }, { 31, 9 }, { 58, 9 }, { 83, 9 },
{ 107, 9 }, { 130, 9 }, { 151, 9 }, { 172, 9 },
{ 191, 9 }, { 209, 9 }, { 226, 9 }, { 242, 9 },
{ 3, 10 }, { 31, 10 }, { 58, 10 }, { 83, 10 },
{ 107, 10 }, { 130, 10 }, { 151, 10 }, { 172, 10 },
{ 191, 10 }, { 209, 10 }, { 226, 10 }, { 242, 10 }
};
const byte Player_V2CMS::_attackRate[16] = {
0, 2, 4, 7, 14, 26, 48, 82,
128, 144, 160, 176, 192, 208, 224, 255
};
const byte Player_V2CMS::_decayRate[16] = {
0, 1, 2, 3, 4, 6, 12, 24,
48, 96, 192, 215, 255, 255, 255, 255
};
const byte Player_V2CMS::_sustainRate[16] = {
255, 180, 128, 96, 80, 64, 56, 48,
42, 36, 32, 28, 24, 20, 16, 0
};
const byte Player_V2CMS::_releaseRate[16] = {
0, 1, 2, 4, 6, 9, 14, 22,
36, 56, 80, 100, 120, 140, 160, 255
};
const byte Player_V2CMS::_volumeTable[16] = {
0x00, 0x10, 0x10, 0x11, 0x11, 0x21, 0x22, 0x22,
0x33, 0x44, 0x55, 0x66, 0x88, 0xAA, 0xCC, 0xFF
};
const byte Player_V2CMS::_cmsInitData[26] = {
0x1C, 0x02,
0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
0x14, 0x3F, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1C, 0x01
};
} // End of namespace Scumm

View File

@@ -0,0 +1,178 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V2CMS_H
#define SCUMM_PLAYERS_PLAYER_V2CMS_H
#include "scumm/players/player_v2base.h" // for channel_data
namespace CMS {
class CMS;
}
namespace Scumm {
/**
* Scumm V2 CMS/Gameblaster MIDI driver.
*/
class Player_V2CMS : public Player_V2Base {
public:
Player_V2CMS(ScummEngine *scumm);
~Player_V2CMS() override;
// MusicEngine API
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getMusicTimer() override;
int getSoundStatus(int sound) const override;
void onTimer();
private:
struct Voice {
byte attack;
byte decay;
byte sustain;
byte release;
byte octadd;
int16 vibrato;
int16 vibrato2;
int16 noise;
};
struct Voice2 {
byte *amplitudeOutput;
byte *freqOutput;
byte *octaveOutput;
uint8 channel;
int8 sustainLevel;
uint8 attackRate;
uint8 maxAmpl;
uint8 decayRate;
uint8 sustainRate;
uint8 releaseRate;
uint8 releaseTime;
int8 vibratoRate;
int8 vibratoDepth;
int8 curVibratoRate;
int8 curVibratoUnk;
int8 unkVibratoRate;
int8 unkVibratoDepth;
int8 unkRate;
int8 unkCount;
enum EnvelopeState {
kEnvelopeAttack,
kEnvelopeDecay,
kEnvelopeSustain,
kEnvelopeRelease
};
EnvelopeState nextProcessState;
uint8 curVolume;
uint8 curOctave;
uint8 curFreq;
int8 octaveAdd;
int8 playingNote;
Voice2 *nextVoice;
byte chanNumber;
};
struct MusicChip {
byte ampl[4];
byte freq[4];
byte octave[2];
};
Voice _cmsVoicesBase[16];
Voice2 _cmsVoices[8];
MusicChip _cmsChips[2];
uint8 _tempo;
uint8 _tempoSum;
byte _looping;
byte _octaveMask;
int16 _midiDelay;
Voice2 *_midiChannel[16];
byte _midiChannelUse[16];
byte *_midiData;
byte *_midiSongBegin;
int _loadedMidiSong;
byte _sfxFreq[4], _sfxAmpl[4], _sfxOctave[2];
byte _lastMidiCommand;
uint _outputTableReady;
byte _voiceTimer;
int _musicTimer, _musicTimerTicks;
void loadMidiData(byte *data, int sound);
void play();
void processChannel(Voice2 *channel);
void processRelease(Voice2 *channel);
void processAttack(Voice2 *channel);
void processDecay(Voice2 *channel);
void processSustain(Voice2 *channel);
void processVibrato(Voice2 *channel);
void playMusicChips(const MusicChip *table);
void playNote(byte *&data);
void clearNote(byte *&data);
void offAllChannels();
void playVoice();
void processMidiData();
Voice2 *getFreeVoice();
Voice2 *getPlayVoice(byte param);
struct MidiNote {
byte frequency;
byte baseOctave;
};
static const MidiNote _midiNotes[132];
static const byte _attackRate[16];
static const byte _decayRate[16];
static const byte _sustainRate[16];
static const byte _releaseRate[16];
static const byte _volumeTable[16];
static const byte _cmsInitData[26];
CMS::CMS *_cmsEmu;
Common::Mutex _mutex;
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,601 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/engine.h"
#include "scumm/players/player_v3a.h"
#include "scumm/scumm.h"
namespace Scumm {
Player_V3A::Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer)
: Paula(true, mixer->getOutputRate(), mixer->getOutputRate() / (scumm->getAmigaMusicTimerFrequency() / 4)),
_vm(scumm),
_mixer(mixer),
_soundHandle(),
_songData(nullptr),
_wavetableData(nullptr),
_wavetablePtrs(nullptr),
_musicTimer(0),
_initState(kInitStateNotReady) {
assert(scumm);
assert(mixer); // this one's a bit pointless, since we had to dereference it to initialize Paula
assert((_vm->_game.id == GID_INDY3) || (_vm->_game.id == GID_LOOM));
stopAllSounds();
// As in the original game, the same Paula is shared between both SFX and music and plays continuously.
// Doing them separately would require subclassing Paula and creating two instances
// (since all of the important methods are protected)
startPaula();
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
bool Player_V3A::init() {
byte *ptr;
int numInstruments;
// Determine which sound resource contains the wavetable data and how large it is
// This is hardcoded into each game's executable
if (_vm->_game.id == GID_INDY3) {
ptr = _vm->getResourceAddress(rtSound, 83);
numInstruments = 12;
} else if (_vm->_game.id == GID_LOOM) {
ptr = _vm->getResourceAddress(rtSound, 79);
numInstruments = 9;
} else {
error("player_v3a - unknown game");
return false;
}
if (!ptr) {
error("player_v3a - unable to load music samples resource");
return false;
}
// Keep a copy of the resource data, since the original pointer may eventually go bad
int length = READ_LE_UINT16(ptr);
_wavetableData = new int8[length];
if (!_wavetableData) {
error("player_v3a - failed to allocate copy of wavetable data");
return false;
}
memcpy(_wavetableData, ptr, length);
int offset = 4;
// Parse the header tables into a more convenient structure
_wavetablePtrs = new InstData[numInstruments];
for (int i = 0; i < numInstruments; i++) {
// Each instrument defines 6 octaves
for (int j = 0; j < 6; j++) {
// Offset/length for intro/main component
int dataOff = READ_BE_UINT16(_wavetableData + offset + 0);
int dataLen = READ_BE_UINT16(_wavetableData + offset + 2);
if (dataLen) {
_wavetablePtrs[i].mainLen[j] = dataLen;
_wavetablePtrs[i].mainData[j] = &_wavetableData[dataOff];
} else {
_wavetablePtrs[i].mainLen[j] = 0;
_wavetablePtrs[i].mainData[j] = nullptr;
}
// Offset/length for looped component, if any
dataOff = READ_BE_UINT16(ptr + offset + 4);
dataLen = READ_BE_UINT16(ptr + offset + 6);
if (dataLen) {
_wavetablePtrs[i].loopLen[j] = dataLen;
_wavetablePtrs[i].loopData[j] = &_wavetableData[dataOff];
} else {
_wavetablePtrs[i].loopLen[j] = 0;
_wavetablePtrs[i].loopData[j] = nullptr;
}
// Octave shift for this octave
_wavetablePtrs[i].octave[j] = READ_BE_INT16(ptr + offset + 8);
offset += 10;
}
// Fadeout rate, in 1/256ths of a volume level
_wavetablePtrs[i].volumeFade = READ_BE_INT16(ptr + offset);
offset += 2;
if (_vm->_game.id == GID_LOOM) {
// Loom's sound samples aren't all in tune with each other,
// so it stores an extra adjustment here
_wavetablePtrs[i].pitchAdjust = READ_BE_INT16(ptr + offset);
offset += 2;
} else {
_wavetablePtrs[i].pitchAdjust = 0;
}
}
return true;
}
Player_V3A::~Player_V3A() {
_mixer->stopHandle(_soundHandle);
if (_initState == kInitStateReady) {
delete[] _wavetableData;
delete[] _wavetablePtrs;
}
}
void Player_V3A::setMusicVolume (int vol) {
_mixer->setChannelVolume(_soundHandle, CLIP<int>(vol, 0, 255));
}
void Player_V3A::stopAllSounds() {
for (int i = 0; i < 4; i++) {
clearVoice(i);
_channels[i].resourceId = -1;
}
_curSong = -1;
_songPtr = 0;
_songDelay = 0;
_songData = nullptr;
}
void Player_V3A::stopSound(int nr) {
if (nr <= 0)
return;
for (int i = 0; i < 4; i++) {
if (_channels[i].resourceId == nr) {
clearVoice(i);
_channels[i].resourceId = -1;
}
}
if (nr == _curSong) {
_curSong = -1;
_songDelay = 0;
_songPtr = 0;
_songData = nullptr;
}
}
void Player_V3A::startSound(int nr) {
assert(_vm);
int8 *data = (int8 *)_vm->getResourceAddress(rtSound, nr);
if (!data)
return;
if ((_vm->_game.id != GID_INDY3) && (_vm->_game.id != GID_LOOM))
error("player_v3a - unknown game");
if (_initState == kInitStateNotReady)
_initState = init() ? kInitStateReady : kInitStateFailed;
// is this a Music resource?
if (data[26]) {
if (_initState == kInitStateReady) {
stopAllSounds();
for (int i = 0; i < 4; i++) {
_channels[i].haltTimer = 0;
_channels[i].resourceId = nr;
_channels[i].priority = READ_BE_UINT16(data + 4);
}
// Keep a local copy of the song data
_songData = data;
_curSong = nr;
_songPtr = 0;
_songDelay = 1;
// Start timer at 0 and increment every 30 frames (see below)
_musicTimer = 0;
} else {
// debug("player_v3a - wavetable unavailable, cannot play music");
}
} else {
int priority = READ_BE_UINT16(data + 4);
int channel = READ_BE_UINT16(data + 6);
if (_channels[channel].resourceId != -1 && _channels[channel].priority > priority)
return;
int chan1 = SFX_CHANNEL_MAP[channel][0];
int chan2 = SFX_CHANNEL_MAP[channel][1];
int offsetL = READ_BE_UINT16(data + 8);
int offsetR = READ_BE_UINT16(data + 10);
int lengthL = READ_BE_UINT16(data + 12);
int lengthR = READ_BE_UINT16(data + 14);
// Period and Volume are both stored in fixed-point
_channels[chan1].period = READ_BE_UINT16(data + 20) << 16;
_channels[chan2].period = READ_BE_UINT16(data + 22) << 16;
_channels[chan1].volume = data[24] << 8;
_channels[chan2].volume = data[25] << 8;
_channels[chan1].loopCount = data[27];
_channels[chan2].loopCount = data[27];
int sweepOffset = READ_BE_UINT16(data + 16);
if (sweepOffset) {
// This data contains a list of offset/value pairs, processed in sequence
// The offset points into a data structure in the original sound engine
// Offset 0x18 sets the channel's Sweep Rate (fractional)
// Offset 0x2C with nonzero value delays until reading the next packet
// Offset 0x2C with zero value stops playback immediately
// The other offsets are unknown, but they are never used
// Indy3 always uses 0x18, 0x2C-nonzero, then 0x2C-zero
// Loom doesn't use these at all
for (int i = 0; i < 3; i++)
{
int offset = READ_BE_UINT32(data + sweepOffset + i*8 + 0);
int value = READ_BE_INT32(data + sweepOffset + i*8 + 4);
if (offset == 0x18)
{
_channels[chan1].sweepRate = value;
_channels[chan2].sweepRate = value;
}
if (offset == 0x2c && value != 0)
{
_channels[chan1].haltTimer = value;
_channels[chan2].haltTimer = value;
}
}
} else {
_channels[chan1].sweepRate = 0;
_channels[chan1].haltTimer = 0;
}
_channels[chan1].priority = priority;
_channels[chan2].priority = priority;
_channels[chan1].resourceId = nr;
_channels[chan2].resourceId = nr;
// Start the Paula playing it
setChannelInterrupt(chan1, true);
setChannelInterrupt(chan2, true);
setChannelPeriod(chan1, MAX((_channels[chan1].period >> 16) & 0xFFFF, 124));
setChannelPeriod(chan2, MAX((_channels[chan2].period >> 16) & 0xFFFF, 124));
setChannelVolume(chan1, MIN((_channels[chan1].volume >> 8) & 0x3F, 0x3F));
setChannelVolume(chan2, MIN((_channels[chan2].volume >> 8) & 0x3F, 0x3F));
// Start as looped, then generate interrupts to handle looping properly
setChannelData(chan1, (int8 *)data + offsetL, (int8 *)data + offsetL, lengthL, lengthL);
setChannelData(chan2, (int8 *)data + offsetR, (int8 *)data + offsetR, lengthR, lengthR);
interruptChannel(chan1);
interruptChannel(chan2);
}
}
void Player_V3A::interrupt() {
if (_vm->_game.id == GID_INDY3) {
updateMusicIndy();
} else if (_vm->_game.id == GID_LOOM) {
updateMusicLoom();
}
updateSounds();
}
void Player_V3A::interruptChannel(byte channel) {
// check looping
if (_channels[channel].loopCount == -1)
return;
if (_channels[channel].loopCount) {
_channels[channel].loopCount--;
if (_channels[channel].loopCount <= 0) {
// On the last loop, set it to no longer repeat
setChannelInterrupt(channel, false);
setChannelSampleStart(channel, nullptr);
setChannelSampleLen(channel, 0);
// If there was no music playing, mark the channel as Unused
if (_curSong == -1)
_channels[channel].resourceId = -1;
}
}
}
void Player_V3A::updateSounds() {
for (int i = 0; i < 4; i++) {
if (!_channels[i].loopCount)
continue;
setChannelVolume(i, MIN((_channels[i].volume >> 8) & 0x3F, 0x3F));
setChannelPeriod(i, MAX((_channels[i].period >> 16) & 0xFFFF, 124));
// Only process ones that are sweeping, since others are handled by interruptChannel above
if (!_channels[i].sweepRate)
continue;
if (_channels[i].haltTimer) {
_channels[i].haltTimer--;
if (!_channels[i].haltTimer) {
// Once the timer reaches zero, immediately it stop looping
_channels[i].loopCount = 1;
interruptChannel(i);
}
}
_channels[i].period += _channels[i].sweepRate;
}
}
void Player_V3A::updateMusicIndy() {
// technically, musicTimer should only be incremented during playback, but that seems to cause problems
_musicTimer++;
if (!_songDelay || !_songData)
return;
for (int i = 0; i < 4; i++) {
if (_channels[i].haltTimer)
_channels[i].haltTimer--;
// When a looped sample runs out, fade the volume to zero
// Non-looped samples will be allowed to continue playing
if (!_channels[i].haltTimer && _channels[i].loopCount) {
_channels[i].volume -= _channels[i].fadeRate;
// Once the volume hits zero, immediately silence it
if (_channels[i].volume < 1) {
_channels[i].volume = 0;
_channels[i].loopCount = 0;
clearVoice(i);
setChannelInterrupt(i, false);
} else
setChannelVolume(i, MIN((_channels[i].volume >> 8) & 0x3F, 0x3F));
}
}
if (--_songDelay)
return;
int8 *songData = &_songData[0x1C + _songPtr];
while (1) {
int code = songData[0];
if ((code & 0xF0) == 0x80) {
// play a note
int instrument = songData[0] & 0xF;
int pitch = songData[1] & 0xFF;
int volume = (songData[2] / 2) & 0xFF;
int duration = songData[3] & 0xFF;
_songPtr += 4;
songData += 4;
// pitch 0 == global rest
if (pitch == 0) {
_songDelay = duration;
return;
}
// Find an available sound channel
// Indy3 starts at channel (inst & 3) and tries them in sequence
int channel = instrument & 0x3;
for (int i = 0; i < 4; i++) {
if (!_channels[channel].haltTimer)
break;
channel = (channel + 1) & 3;
}
startNote(channel, instrument, pitch, volume, duration);
} else {
// Reached the end
for (int i = 0; i < 4; i++) {
// Subtle bug in the original engine - it only checks the LAST playing channel
// (rather than checking all of them)
if (_channels[i].loopCount)
_songDelay = _channels[i].haltTimer;
}
if (_songDelay == 0) {
if ((code & 0xFF) == 0xFB) {
// repeat
_songPtr = 0;
_songDelay = 1;
} else {
// stop
stopSound(_curSong);
}
}
}
if ((_songDelay) || (_curSong == -1))
break;
}
}
void Player_V3A::updateMusicLoom() {
// technically, musicTimer should only be incremented during playback, but that seems to cause problems
_musicTimer++;
if (!_songDelay || !_songData)
return;
// Update all playing notes
for (int i = 0; i < 4; i++) {
// Mark all notes that were started during a previous update
_channels[i].canOverride = 1;
if (_channels[i].haltTimer)
_channels[i].haltTimer--;
// When a looped sample runs out, fade the volume to zero
// Non-looped samples will be allowed to continue playing
if (!_channels[i].haltTimer && _channels[i].loopCount) {
_channels[i].volume -= _channels[i].fadeRate;
// Once the volume hits zero, immediately silence it
if (_channels[i].volume < 1) {
_channels[i].volume = 0;
_channels[i].loopCount = 0;
clearVoice(i);
setChannelInterrupt(i, false);
} else
setChannelVolume(i, MIN((_channels[i].volume >> 8) & 0x3F, 0x3F));
}
}
if (--_songDelay)
return;
int8 *songData = &_songData[0x1C + _songPtr];
// Loom uses an elaborate queue to deal with overlapping notes and limited sound channels
int queuePos = 0;
int queueInstrument[4];
int queuePitch[4];
int queueVolume[4];
int queueDuration[4];
while (1) {
int code = songData[0];
if ((code & 0xF0) == 0x80) {
// play a note
int instrument = songData[0] & 0xF;
int pitch = songData[1] & 0xFF;
int volume = (((songData[2] < 0) ? (songData[2] + 1) : songData[2]) / 2) & 0xFF;
int duration = songData[3] & 0xFF;
_songPtr += 4;
songData += 4;
// pitch 0 == global rest
if (pitch == 0) {
_songDelay = duration;
break;
}
// Try to find an appropriate channel to use
// Channel must be playing the same instrument, started during a previous loop, and within 6 frames of ending
int channel;
for (channel = 0; channel < 4; channel++)
if ((_channels[channel].instrument == instrument) && (_channels[channel].canOverride) && (_channels[channel].haltTimer < 6))
break;
if (channel != 4) {
// Channel was found, so start playing the note
startNote(channel, instrument, pitch, volume, duration);
} else if (queuePos < 4) {
// No channel found - put it in a queue to process at the end
queueInstrument[queuePos] = instrument;
queuePitch[queuePos] = pitch;
queueVolume[queuePos] = volume;
queueDuration[queuePos] = duration;
++queuePos;
}
} else {
// Reached end of song
for (int i = 0; i < 4; i++) {
// Subtle bug in the original engine - it only checks the LAST playing channel
// rather than checking ALL of them
if (_channels[i].loopCount)
_songDelay = _channels[i].haltTimer;
}
if (_songDelay == 0) {
if ((code & 0xFF) == 0xFB) {
// repeat
_songPtr = 0;
_songDelay = 1;
} else {
// stop
stopSound(_curSong);
}
}
}
if ((_songDelay) || (_curSong == -1))
break;
}
while (queuePos--) {
// Take all of the enqueued note requests and try to fit them somewhere
int channel;
for (channel = 0; channel < 4; channel++) {
// First, find a soon-to-expire channel that wasn't explicitly assigned this loop
if ((_channels[channel].canOverride) && (_channels[channel].haltTimer < 6))
break;
}
if (channel == 4) {
// If no channel found, pick the first channel playing this instrument
for (channel = 0; channel < 4; channel++) {
if (_channels[channel].instrument == queueInstrument[queuePos])
break;
}
}
if (channel != 4) {
// If we found a channel, play the note there - otherwise, it gets lost
startNote(channel, queueInstrument[queuePos], queuePitch[queuePos], queueVolume[queuePos], queueDuration[queuePos]);
}
}
}
void Player_V3A::startNote(int channel, int instrument, int pitch, int volume, int duration) {
const InstData &instData = _wavetablePtrs[instrument];
SndChan &curChan = _channels[channel];
// for Loom, adjust pitch
pitch += instData.pitchAdjust;
// and set channel precedence parameters
curChan.instrument = instrument;
curChan.canOverride = 0;
// Split pitch into octave+offset, truncating as needed
int octave = (pitch / 12) - 2;
pitch = pitch % 12;
if (octave < 0)
octave = 0;
if (octave > 5)
octave = 5;
int actualOctave = instData.octave[octave];
curChan.period = NOTE_FREQS[actualOctave][pitch] << 16;
curChan.volume = (volume & 0xFF) << 8;
curChan.sweepRate = 0;
curChan.fadeRate = instData.volumeFade;
curChan.haltTimer = duration;
// For music, pre-decrement the loop counter and skip the initial interrupt
if (instData.loopLen[octave]) {
curChan.loopCount = -1;
setChannelInterrupt(channel, true);
} else {
curChan.loopCount = 0;
setChannelInterrupt(channel, false);
}
setChannelPeriod(channel, MAX((curChan.period >> 16) & 0xFFFF, 124));
setChannelVolume(channel, MIN((curChan.volume >> 8) & 0x3F, 0x3F));
setChannelData(channel, instData.mainData[octave], instData.loopData[octave], instData.mainLen[octave], instData.loopLen[octave]);
}
int Player_V3A::getMusicTimer() {
// Actual code in Amiga version returns 5+timer/28, which syncs poorly in ScummVM
// Presumably, this was meant to help slower machines sync better
return _musicTimer / 30;
}
int Player_V3A::getSoundStatus(int nr) const {
if (nr == -1)
return 0;
if (nr == _curSong)
return 1;
for (int i = 0; i < 4; i++)
if (_channels[i].resourceId == nr)
return 1;
return 0;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,127 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V3A_H
#define SCUMM_PLAYERS_PLAYER_V3A_H
#include "common/scummsys.h"
#include "common/util.h"
#include "scumm/music.h"
#include "audio/mixer.h"
#include "audio/mods/paula.h"
class Mixer;
namespace Scumm {
class ScummEngine;
/**
* Scumm V3 Amiga sound/music driver.
*/
class Player_V3A : public MusicEngine, Audio::Paula {
public:
Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_V3A() override;
// MusicEngine API
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override;
int getMusicTimer() override;
protected:
// Paula API
void interrupt() override;
void interruptChannel(byte channel) override;
private:
struct SndChan {
int period; /* 16.16 fixed point */
int volume; /* 8.8 fixed point */
int loopCount; /* decrement once per loop, halt playback upon reaching zero */
int sweepRate; /* add to period once per frame */
int haltTimer; /* decrement once per frame, halt playback upon reaching zero */
int fadeRate; /* if haltTimer is zero, subtract 0x100*this from volume once per frame */
int resourceId;
int priority;
// Both of these are used exclusively by the Loom music engine
int instrument;
int canOverride;
};
struct InstData {
int8 *mainData[6];
uint16 mainLen[6];
int8 *loopData[6];
uint16 loopLen[6];
int16 octave[6];
int16 pitchAdjust;
int16 volumeFade;
};
ScummEngine *const _vm;
Audio::Mixer *const _mixer;
Audio::SoundHandle _soundHandle;
SndChan _channels[4];
const int SFX_CHANNEL_MAP[2][2] = {
{ 0, 1 },
{ 3, 2 }
};
const uint16 NOTE_FREQS[4][12] = {
{0x06B0, 0x0650, 0x05F4, 0x05A0, 0x054C, 0x0500, 0x04B8, 0x0474, 0x0434, 0x03F8, 0x03C0, 0x0388},
{0x0358, 0x0328, 0x02FA, 0x02D0, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C4},
{0x01AC, 0x0194, 0x017D, 0x0168, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, 0x00FE, 0x00F0, 0x00E2},
{0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00A9, 0x00A0, 0x0097, 0x008E, 0x0086, 0x007F, 0x00F0, 0x00E2}
};
int _curSong;
int8 *_songData;
uint16 _songPtr;
uint16 _songDelay;
int _musicTimer;
enum {
kInitStateFailed = -1,
kInitStateNotReady = 0,
kInitStateReady = 1
} _initState;
int8 *_wavetableData;
InstData *_wavetablePtrs;
void updateMusicIndy();
void updateMusicLoom();
void updateSounds();
void startNote(int channel, int instrument, int pitch, int volume, int duration);
bool init();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,208 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/engine.h"
#include "scumm/players/player_v4a.h"
#include "scumm/scumm.h"
#include "common/file.h"
namespace Scumm {
Player_V4A::Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer)
: _vm(scumm),
_mixer(mixer),
_tfmxMusic(_mixer->getOutputRate(), true),
_tfmxSfx(_mixer->getOutputRate(), true),
_musicHandle(),
_sfxHandle(),
_musicId(),
_sfxSlots(),
_initState(0),
_signal(0),
_lastSong(-1) {
assert(scumm);
assert(mixer);
assert(_vm->_game.id == GID_MONKEY_VGA);
_tfmxMusic.setSignalPtr(&_signal, 1);
}
bool Player_V4A::init() {
if (_vm->_game.id != GID_MONKEY_VGA)
error("player_v4a - unknown game");
Common::File fileMdat, fileSample;
if (fileMdat.open("music.dat") && fileSample.open("sample.dat")) {
// explicitly request that no instance delets the resources automatically
if (_tfmxMusic.load(fileMdat, fileSample, false)) {
_tfmxSfx.setModuleData(_tfmxMusic);
return true;
}
} else
warning("player_v4a: couldnt load one of the music resources: music.dat, sample.dat");
return false;
}
Player_V4A::~Player_V4A() {
_mixer->stopHandle(_musicHandle);
_mixer->stopHandle(_sfxHandle);
_tfmxMusic.freeResources();
}
void Player_V4A::setMusicVolume(int vol) {
debug(5, "player_v4a: setMusicVolume %i", vol);
}
void Player_V4A::stopAllSounds() {
debug(5, "player_v4a: stopAllSounds");
if (_initState > 0) {
_tfmxMusic.stopSong();
_signal = 0;
_musicId = 0;
_tfmxSfx.stopSong();
clearSfxSlots();
} else
_mixer->stopHandle(_musicHandle);
_lastSong = -1;
}
void Player_V4A::stopSound(int nr) {
debug(5, "player_v4a: stopSound %d", nr);
if (nr == 0)
return;
if (nr == _musicId) {
_musicId = 0;
if (_initState > 0)
_tfmxMusic.stopSong();
else
_mixer->stopHandle(_musicHandle);
_signal = 0;
_lastSong = -1;
} else {
const int chan = getSfxChan(nr);
if (chan != -1) {
setSfxSlot(chan, 0);
_tfmxSfx.stopMacroEffect(chan);
_lastSong = -1;
}
}
}
void Player_V4A::startSound(int nr) {
static const int8 monkeyCommands[52] = {
-1, -2, -3, -4, -5, -6, -7, -8,
-9, -10, -11, -12, -13, -14, 18, 17,
-17, -18, -19, -20, -21, -22, -23, -24,
-25, -26, -27, -28, -29, -30, -31, -32,
-33, 16, -35, 0, 1, 2, 3, 7,
8, 10, 11, 4, 5, 14, 15, 12,
6, 13, 9, 19
};
const byte *ptr = _vm->getResourceAddress(rtSound, nr);
assert(ptr);
const int val = ptr[9];
if (val < 0 || val >= ARRAYSIZE(monkeyCommands)) {
warning("player_v4a: illegal Songnumber %i", val);
return;
}
if (!_initState)
_initState = init() ? 1 : -1;
if (_initState < 0)
return;
int index = monkeyCommands[val];
const byte type = ptr[6];
if (index < 0) { // SoundFX
index = -index - 1;
debug(3, "player_v4a: play %d: custom %i - %02X", nr, index, type);
// start an empty Song so timing is setup
if (_tfmxSfx.getSongIndex() < 0)
_tfmxSfx.doSong(0x18);
const int chan = _tfmxSfx.doSfx((uint16)index);
if (chan >= 0 && chan < ARRAYSIZE(_sfxSlots))
setSfxSlot(chan, nr, type);
else
warning("player_v4a: custom %i is not of required type", index);
// the Tfmx-player never "ends" the output by itself, so this should be threadsafe
if (!_mixer->isSoundHandleActive(_sfxHandle))
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, &_tfmxSfx, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
_lastSong = -1;
} else { // Song
debug(3, "player_v4a: play %d: song %i - %02X", nr, index, type);
if (ptr[6] != 0x7F)
warning("player_v4a: Song has wrong type");
_tfmxMusic.doSong(index);
_signal = 2;
// the Tfmx-player never "ends" the output by itself, so this should be threadsafe
if (!_mixer->isSoundHandleActive(_musicHandle))
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, &_tfmxMusic, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
_musicId = _lastSong = nr;
}
}
int Player_V4A::getMusicTimer() {
// A workaround if the modplayer couldnt load the datafiles - just return a number big enough to pass all tests
if (_initState < 0)
return 2000;
if (_musicId) {
// The titlesong (and a few others) is running with ~70 ticks per second and the scale seems to be based on that.
// The Game itself doesn't get the timing from the Tfmx Player however, so we just use the elapsed time
// 357 ~ 1000 * 25 * (1 / 70)
return _mixer->getSoundElapsedTime(_musicHandle) / 357;
}
return 0;
}
int Player_V4A::getSoundStatus(int nr) const {
// For music the game queues a variable the Tfmx Player sets through a special command.
// For sfx there seems to be no way to queue them, and the game doesn't try to.
return (nr == _musicId) ? _signal : 0;
}
void Player_V4A::saveLoadWithSerializer(Common::Serializer &ser) {
if (ser.isLoading() && ser.getVersion() < VER(118))
_lastSong = -1;
else
ser.syncAsSint16BE(_lastSong, VER(118));
}
void Player_V4A::restoreAfterLoad() {
int snd = _lastSong;
stopAllSounds();
if (snd != -1)
startSound(snd);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,98 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_PLAYERS_PLAYER_V4A_H
#define SCUMM_PLAYERS_PLAYER_V4A_H
#include "common/scummsys.h"
#include "common/util.h"
#include "scumm/music.h"
#include "audio/mixer.h"
#include "audio/mods/tfmx.h"
class Mixer;
namespace Scumm {
class ScummEngine;
/**
* Scumm V4 Amiga sound/music driver.
*/
class Player_V4A : public MusicEngine {
public:
Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer);
~Player_V4A() override;
void setMusicVolume(int vol) override;
void startSound(int sound) override;
void stopSound(int sound) override;
void stopAllSounds() override;
int getMusicTimer() override;
int getSoundStatus(int sound) const override;
void saveLoadWithSerializer(Common::Serializer &ser) override;
void restoreAfterLoad() override;
private:
ScummEngine *const _vm;
Audio::Mixer *const _mixer;
Audio::Tfmx _tfmxMusic;
Audio::Tfmx _tfmxSfx;
Audio::SoundHandle _musicHandle;
Audio::SoundHandle _sfxHandle;
int _musicId;
int16 _lastSong;
uint16 _signal;
struct SfxChan {
int id;
// byte type;
} _sfxSlots[4];
int8 _initState; // < 0: failed, 0: uninitialized, > 0: initialized
int getSfxChan(int id) const {
for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i)
if (_sfxSlots[i].id == id)
return i;
return -1;
}
void setSfxSlot(int channel, int id, byte type = 0) {
_sfxSlots[channel].id = id;
// _sfxSlots[channel].type = type;
}
void clearSfxSlots() {
for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i){
_sfxSlots[i].id = 0;
// _sfxSlots[i].type = 0;
}
}
bool init();
};
} // End of namespace Scumm
#endif