Initial commit
This commit is contained in:
264
engines/scumm/players/mac_sound_lowlevel.h
Normal file
264
engines/scumm/players/mac_sound_lowlevel.h
Normal 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
|
||||
1195
engines/scumm/players/player_ad.cpp
Normal file
1195
engines/scumm/players/player_ad.cpp
Normal file
File diff suppressed because it is too large
Load Diff
222
engines/scumm/players/player_ad.h
Normal file
222
engines/scumm/players/player_ad.h
Normal 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
|
||||
503
engines/scumm/players/player_apple2.cpp
Normal file
503
engines/scumm/players/player_apple2.cpp
Normal 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
|
||||
292
engines/scumm/players/player_apple2.h
Normal file
292
engines/scumm/players/player_apple2.h
Normal 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
|
||||
237
engines/scumm/players/player_he.cpp
Normal file
237
engines/scumm/players/player_he.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
71
engines/scumm/players/player_he.h
Normal file
71
engines/scumm/players/player_he.h
Normal 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
|
||||
1144
engines/scumm/players/player_mac_indy3.cpp
Normal file
1144
engines/scumm/players/player_mac_indy3.cpp
Normal file
File diff suppressed because it is too large
Load Diff
269
engines/scumm/players/player_mac_intern.h
Normal file
269
engines/scumm/players/player_mac_intern.h
Normal 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
|
||||
903
engines/scumm/players/player_mac_loom_monkey.cpp
Normal file
903
engines/scumm/players/player_mac_loom_monkey.cpp
Normal 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 ¬e, 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 ¬e, 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 ¬e, 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 ¬e, 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 ¬e, 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
|
||||
1452
engines/scumm/players/player_mac_new.cpp
Normal file
1452
engines/scumm/players/player_mac_new.cpp
Normal file
File diff suppressed because it is too large
Load Diff
49
engines/scumm/players/player_mac_new.h
Normal file
49
engines/scumm/players/player_mac_new.h
Normal 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
|
||||
233
engines/scumm/players/player_mod.cpp
Normal file
233
engines/scumm/players/player_mod.cpp
Normal 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
|
||||
102
engines/scumm/players/player_mod.h
Normal file
102
engines/scumm/players/player_mod.h
Normal 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
|
||||
1066
engines/scumm/players/player_nes.cpp
Normal file
1066
engines/scumm/players/player_nes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
109
engines/scumm/players/player_nes.h
Normal file
109
engines/scumm/players/player_nes.h
Normal 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
|
||||
758
engines/scumm/players/player_pce.cpp
Normal file
758
engines/scumm/players/player_pce.cpp
Normal 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
|
||||
124
engines/scumm/players/player_pce.h
Normal file
124
engines/scumm/players/player_pce.h
Normal file
@@ -0,0 +1,124 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 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
|
||||
1349
engines/scumm/players/player_sid.cpp
Normal file
1349
engines/scumm/players/player_sid.cpp
Normal file
File diff suppressed because it is too large
Load Diff
254
engines/scumm/players/player_sid.h
Normal file
254
engines/scumm/players/player_sid.h
Normal 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
|
||||
741
engines/scumm/players/player_towns.cpp
Normal file
741
engines/scumm/players/player_towns.cpp
Normal 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
|
||||
178
engines/scumm/players/player_towns.h
Normal file
178
engines/scumm/players/player_towns.h
Normal 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
|
||||
622
engines/scumm/players/player_v1.cpp
Normal file
622
engines/scumm/players/player_v1.cpp
Normal 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
|
||||
94
engines/scumm/players/player_v1.h
Normal file
94
engines/scumm/players/player_v1.h
Normal 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
|
||||
333
engines/scumm/players/player_v2.cpp
Normal file
333
engines/scumm/players/player_v2.cpp
Normal 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
|
||||
89
engines/scumm/players/player_v2.h
Normal file
89
engines/scumm/players/player_v2.h
Normal 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
|
||||
1960
engines/scumm/players/player_v2a.cpp
Normal file
1960
engines/scumm/players/player_v2a.cpp
Normal file
File diff suppressed because it is too large
Load Diff
72
engines/scumm/players/player_v2a.h
Normal file
72
engines/scumm/players/player_v2a.h
Normal 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
|
||||
654
engines/scumm/players/player_v2base.cpp
Normal file
654
engines/scumm/players/player_v2base.cpp
Normal 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
|
||||
119
engines/scumm/players/player_v2base.h
Normal file
119
engines/scumm/players/player_v2base.h
Normal 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
|
||||
781
engines/scumm/players/player_v2cms.cpp
Normal file
781
engines/scumm/players/player_v2cms.cpp
Normal 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
|
||||
178
engines/scumm/players/player_v2cms.h
Normal file
178
engines/scumm/players/player_v2cms.h
Normal 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
|
||||
601
engines/scumm/players/player_v3a.cpp
Normal file
601
engines/scumm/players/player_v3a.cpp
Normal 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
|
||||
127
engines/scumm/players/player_v3a.h
Normal file
127
engines/scumm/players/player_v3a.h
Normal 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
|
||||
208
engines/scumm/players/player_v4a.cpp
Normal file
208
engines/scumm/players/player_v4a.cpp
Normal 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
|
||||
98
engines/scumm/players/player_v4a.h
Normal file
98
engines/scumm/players/player_v4a.h
Normal 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
|
||||
Reference in New Issue
Block a user