ALL: synced with ScummVM
This commit is contained in:
parent
fc33643a38
commit
11b457122b
34 changed files with 7815 additions and 863 deletions
|
@ -927,18 +927,20 @@ static void createLookupTable() {
|
|||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
class MidiDriver_ADLIB : public MidiDriver_Emulated {
|
||||
class MidiDriver_ADLIB : public MidiDriver {
|
||||
friend class AdLibPart;
|
||||
friend class AdLibPercussionChannel;
|
||||
|
||||
public:
|
||||
MidiDriver_ADLIB(Audio::Mixer *mixer);
|
||||
MidiDriver_ADLIB();
|
||||
|
||||
int open();
|
||||
void close();
|
||||
void send(uint32 b);
|
||||
void send(byte channel, uint32 b); // Supports higher than channel 15
|
||||
uint32 property(int prop, uint32 param);
|
||||
bool isOpen() const { return _isOpen; }
|
||||
uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
|
||||
|
||||
void setPitchBendRange(byte channel, uint range);
|
||||
void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
|
||||
|
@ -946,10 +948,7 @@ public:
|
|||
MidiChannel *allocateChannel();
|
||||
MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported
|
||||
|
||||
|
||||
// AudioStream API
|
||||
bool isStereo() const { return _opl->isStereo(); }
|
||||
int getRate() const { return _mixer->getOutputRate(); }
|
||||
virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
|
||||
|
||||
private:
|
||||
bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games
|
||||
|
@ -963,6 +962,9 @@ private:
|
|||
byte *_regCacheSecondary;
|
||||
#endif
|
||||
|
||||
Common::TimerManager::TimerProc _adlibTimerProc;
|
||||
void *_adlibTimerParam;
|
||||
|
||||
int _timerCounter;
|
||||
|
||||
uint16 _channelTable2[9];
|
||||
|
@ -974,7 +976,8 @@ private:
|
|||
AdLibPart _parts[32];
|
||||
AdLibPercussionChannel _percussion;
|
||||
|
||||
void generateSamples(int16 *buf, int len);
|
||||
bool _isOpen;
|
||||
|
||||
void onTimer();
|
||||
void partKeyOn(AdLibPart *part, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan);
|
||||
void partKeyOff(AdLibPart *part, byte note);
|
||||
|
@ -1376,8 +1379,7 @@ void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *ins
|
|||
|
||||
// MidiDriver method implementations
|
||||
|
||||
MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer)
|
||||
: MidiDriver_Emulated(mixer) {
|
||||
MidiDriver_ADLIB::MidiDriver_ADLIB() {
|
||||
uint i;
|
||||
|
||||
_scummSmallHeader = false;
|
||||
|
@ -1403,13 +1405,16 @@ MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer)
|
|||
_timerIncrease = 0xD69;
|
||||
_timerThreshold = 0x411B;
|
||||
_opl = 0;
|
||||
_adlibTimerProc = 0;
|
||||
_adlibTimerParam = 0;
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
int MidiDriver_ADLIB::open() {
|
||||
if (_isOpen)
|
||||
return MERR_ALREADY_OPEN;
|
||||
|
||||
MidiDriver_Emulated::open();
|
||||
_isOpen = true;
|
||||
|
||||
int i;
|
||||
AdLibVoice *voice;
|
||||
|
@ -1434,7 +1439,7 @@ int MidiDriver_ADLIB::open() {
|
|||
_opl3Mode = false;
|
||||
}
|
||||
#endif
|
||||
_opl->init(getRate());
|
||||
_opl->init();
|
||||
|
||||
_regCache = (byte *)calloc(256, 1);
|
||||
|
||||
|
@ -1452,8 +1457,7 @@ int MidiDriver_ADLIB::open() {
|
|||
}
|
||||
#endif
|
||||
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
|
||||
_opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB>(this, &MidiDriver_ADLIB::onTimer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1462,7 +1466,8 @@ void MidiDriver_ADLIB::close() {
|
|||
return;
|
||||
_isOpen = false;
|
||||
|
||||
_mixer->stopHandle(_mixerSoundHandle);
|
||||
// Stop the OPL timer
|
||||
_opl->stop();
|
||||
|
||||
uint i;
|
||||
for (i = 0; i < ARRAYSIZE(_voices); ++i) {
|
||||
|
@ -1616,14 +1621,10 @@ void MidiDriver_ADLIB::adlibWriteSecondary(byte reg, byte value) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void MidiDriver_ADLIB::generateSamples(int16 *data, int len) {
|
||||
if (_opl->isStereo()) {
|
||||
len *= 2;
|
||||
}
|
||||
_opl->readBuffer(data, len);
|
||||
}
|
||||
|
||||
void MidiDriver_ADLIB::onTimer() {
|
||||
if (_adlibTimerProc)
|
||||
(*_adlibTimerProc)(_adlibTimerParam);
|
||||
|
||||
_timerCounter += _timerIncrease;
|
||||
while (_timerCounter >= _timerThreshold) {
|
||||
_timerCounter -= _timerThreshold;
|
||||
|
@ -1655,6 +1656,11 @@ void MidiDriver_ADLIB::onTimer() {
|
|||
}
|
||||
}
|
||||
|
||||
void MidiDriver_ADLIB::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
|
||||
_adlibTimerProc = timerProc;
|
||||
_adlibTimerParam = timerParam;
|
||||
}
|
||||
|
||||
void MidiDriver_ADLIB::mcOff(AdLibVoice *voice) {
|
||||
AdLibVoice *tmp;
|
||||
|
||||
|
@ -2300,7 +2306,7 @@ MusicDevices AdLibEmuMusicPlugin::getDevices() const {
|
|||
}
|
||||
|
||||
Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
|
||||
*mididriver = new MidiDriver_ADLIB(g_system->getMixer());
|
||||
*mididriver = new MidiDriver_ADLIB();
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
349
audio/alsa_opl.cpp
Normal file
349
audio/alsa_opl.cpp
Normal file
|
@ -0,0 +1,349 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/* OPL implementation for hardware OPL using ALSA Direct FM API.
|
||||
*
|
||||
* Caveats and limitations:
|
||||
* - Pretends to be a softsynth (emitting silence).
|
||||
* - Dual OPL2 mode requires OPL3 hardware.
|
||||
* - Every register write leads to a series of register writes on the hardware,
|
||||
* due to the lack of direct register access in the ALSA Direct FM API.
|
||||
* - No timers
|
||||
*/
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/str.h"
|
||||
#include "audio/fmopl.h"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <sound/asound_fm.h>
|
||||
|
||||
namespace OPL {
|
||||
namespace ALSA {
|
||||
|
||||
class OPL : public ::OPL::RealOPL {
|
||||
private:
|
||||
enum {
|
||||
kOpl2Voices = 9,
|
||||
kVoices = 18,
|
||||
kOpl2Operators = 18,
|
||||
kOperators = 36
|
||||
};
|
||||
|
||||
Config::OplType _type;
|
||||
int _iface;
|
||||
snd_hwdep_t *_opl;
|
||||
snd_dm_fm_voice _oper[kOperators];
|
||||
snd_dm_fm_note _voice[kVoices];
|
||||
snd_dm_fm_params _params;
|
||||
int index[2];
|
||||
static const int voiceToOper0[kVoices];
|
||||
static const int regOffsetToOper[0x20];
|
||||
|
||||
void writeOplReg(int c, int r, int v);
|
||||
void clear();
|
||||
|
||||
public:
|
||||
OPL(Config::OplType type);
|
||||
~OPL();
|
||||
|
||||
bool init();
|
||||
void reset();
|
||||
|
||||
void write(int a, int v);
|
||||
byte read(int a);
|
||||
|
||||
void writeReg(int r, int v);
|
||||
};
|
||||
|
||||
const int OPL::voiceToOper0[OPL::kVoices] =
|
||||
{ 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 };
|
||||
|
||||
const int OPL::regOffsetToOper[0x20] =
|
||||
{ 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1,
|
||||
12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||
|
||||
OPL::OPL(Config::OplType type) : _type(type), _opl(nullptr), _iface(0) {
|
||||
}
|
||||
|
||||
OPL::~OPL() {
|
||||
stop();
|
||||
|
||||
if (_opl) {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
|
||||
snd_hwdep_close(_opl);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL::clear() {
|
||||
index[0] = index[1] = 0;
|
||||
|
||||
memset(_oper, 0, sizeof(_oper));
|
||||
memset(_voice, 0, sizeof(_voice));
|
||||
memset(&_params, 0, sizeof(_params));
|
||||
|
||||
for (int i = 0; i < kOperators; ++i) {
|
||||
_oper[i].op = (i / 3) % 2;
|
||||
_oper[i].voice = (i / 6) * 3 + (i % 3);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVoices; ++i)
|
||||
_voice[i].voice = i;
|
||||
|
||||
// For OPL3 hardware we need to set up the panning in OPL2 modes
|
||||
if (_iface == SND_HWDEP_IFACE_OPL3) {
|
||||
if (_type == Config::kDualOpl2) {
|
||||
for (int i = 0; i < kOpl2Operators; ++i)
|
||||
_oper[i].left = 1; // FIXME below
|
||||
for (int i = kOpl2Operators; i < kOperators; ++i)
|
||||
_oper[i].right = 1;
|
||||
} else if (_type == Config::kOpl2) {
|
||||
for (int i = 0; i < kOpl2Operators; ++i) {
|
||||
_oper[i].left = 1;
|
||||
_oper[i].right = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OPL::init() {
|
||||
clear();
|
||||
|
||||
int card = -1;
|
||||
snd_ctl_t *ctl;
|
||||
snd_hwdep_info_t *info;
|
||||
snd_hwdep_info_alloca(&info);
|
||||
|
||||
int iface = SND_HWDEP_IFACE_OPL3;
|
||||
if (_type == Config::kOpl2)
|
||||
iface = SND_HWDEP_IFACE_OPL2;
|
||||
|
||||
// Look for OPL hwdep interface
|
||||
while (!snd_card_next(&card) && card >= 0) {
|
||||
int dev = -1;
|
||||
Common::String name = Common::String::format("hw:%d", card);
|
||||
|
||||
if (snd_ctl_open(&ctl, name.c_str(), 0) < 0)
|
||||
continue;
|
||||
|
||||
while (!snd_ctl_hwdep_next_device(ctl, &dev) && dev >= 0) {
|
||||
name = Common::String::format("hw:%d,%d", card, dev);
|
||||
|
||||
if (snd_hwdep_open(&_opl, name.c_str(), SND_HWDEP_OPEN_WRITE) < 0)
|
||||
continue;
|
||||
|
||||
if (!snd_hwdep_info(_opl, info)) {
|
||||
int found = snd_hwdep_info_get_iface(info);
|
||||
// OPL3 can be used for (Dual) OPL2 mode
|
||||
if (found == iface || found == SND_HWDEP_IFACE_OPL3) {
|
||||
snd_ctl_close(ctl);
|
||||
_iface = found;
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrong interface, try next device
|
||||
snd_hwdep_close(_opl);
|
||||
_opl = nullptr;
|
||||
}
|
||||
|
||||
snd_ctl_close(ctl);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OPL::reset() {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
|
||||
if (_iface == SND_HWDEP_IFACE_OPL3)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_MODE, (void *)SNDRV_DM_FM_MODE_OPL3);
|
||||
|
||||
clear();
|
||||
|
||||
// Sync up with the hardware
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kVoices : kOpl2Voices); ++i)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[i]);
|
||||
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kOperators : kOpl2Operators); ++i)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[i]);
|
||||
}
|
||||
|
||||
void OPL::write(int port, int val) {
|
||||
val &= 0xff;
|
||||
int chip = (port & 2) >> 1;
|
||||
|
||||
if (port & 1) {
|
||||
switch(_type) {
|
||||
case Config::kOpl2:
|
||||
writeOplReg(0, index[0], val);
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
if (port & 8) {
|
||||
writeOplReg(0, index[0], val);
|
||||
writeOplReg(1, index[1], val);
|
||||
} else
|
||||
writeOplReg(chip, index[chip], val);
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
writeOplReg(chip, index[chip], val);
|
||||
}
|
||||
} else {
|
||||
switch(_type) {
|
||||
case Config::kOpl2:
|
||||
index[0] = val;
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
if (port & 8) {
|
||||
index[0] = val;
|
||||
index[1] = val;
|
||||
} else
|
||||
index[chip] = val;
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
index[chip] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte OPL::read(int port) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OPL::writeReg(int r, int v) {
|
||||
switch (_type) {
|
||||
case Config::kOpl2:
|
||||
writeOplReg(0, r, v);
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
writeOplReg(0, r, v);
|
||||
writeOplReg(1, r, v);
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
writeOplReg(r >= 0x100, r & 0xff, v);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL::writeOplReg(int c, int r, int v) {
|
||||
if (r == 0x04 && c == 1 && _type == Config::kOpl3) {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_CONNECTION, reinterpret_cast<void *>(v & 0x3f));
|
||||
} else if (r == 0x08 && c == 0) {
|
||||
_params.kbd_split = (v >> 6) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
} else if (r == 0xbd && c == 0) {
|
||||
_params.hihat = v & 0x1;
|
||||
_params.cymbal = (v >> 1) & 0x1;
|
||||
_params.tomtom = (v >> 2) & 0x1;
|
||||
_params.snare = (v >> 3) & 0x1;
|
||||
_params.bass = (v >> 4) & 0x1;
|
||||
_params.rhythm = (v >> 5) & 0x1;
|
||||
_params.vib_depth = (v >> 6) & 0x1;
|
||||
_params.am_depth = (v >> 7) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
} else if (r < 0xa0 || r >= 0xe0) {
|
||||
// Operator
|
||||
int idx = regOffsetToOper[r & 0x1f];
|
||||
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (c == 1)
|
||||
idx += kOpl2Operators;
|
||||
|
||||
switch (r & 0xf0) {
|
||||
case 0x20:
|
||||
case 0x30:
|
||||
_oper[idx].harmonic = v & 0xf;
|
||||
_oper[idx].kbd_scale = (v >> 4) & 0x1;
|
||||
_oper[idx].do_sustain = (v >> 5) & 0x1;
|
||||
_oper[idx].vibrato = (v >> 6) & 0x1;
|
||||
_oper[idx].am = (v >> 7) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x40:
|
||||
case 0x50:
|
||||
_oper[idx].volume = ~v & 0x3f;
|
||||
_oper[idx].scale_level = (v >> 6) & 0x3;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x60:
|
||||
case 0x70:
|
||||
_oper[idx].decay = v & 0xf;
|
||||
_oper[idx].attack = (v >> 4) & 0xf;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
_oper[idx].release = v & 0xf;
|
||||
_oper[idx].sustain = (v >> 4) & 0xf;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0xe0:
|
||||
case 0xf0:
|
||||
_oper[idx].waveform = v & (_type == Config::kOpl3 ? 0x7 : 0x3);
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
}
|
||||
} else {
|
||||
// Voice
|
||||
int idx = r & 0xf;
|
||||
|
||||
if (idx >= kOpl2Voices)
|
||||
return;
|
||||
|
||||
if (c == 1)
|
||||
idx += kOpl2Voices;
|
||||
|
||||
int opIdx = voiceToOper0[idx];
|
||||
|
||||
switch (r & 0xf0) {
|
||||
case 0xa0:
|
||||
_voice[idx].fnum = (_voice[idx].fnum & 0x300) | (v & 0xff);
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
|
||||
break;
|
||||
case 0xb0:
|
||||
_voice[idx].fnum = ((v << 8) & 0x300) | (_voice[idx].fnum & 0xff);
|
||||
_voice[idx].octave = (v >> 2) & 0x7;
|
||||
_voice[idx].key_on = (v >> 5) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
|
||||
break;
|
||||
case 0xc0:
|
||||
_oper[opIdx].connection = _oper[opIdx + 3].connection = v & 0x1;
|
||||
_oper[opIdx].feedback = _oper[opIdx + 3].feedback = (v >> 1) & 0x7;
|
||||
if (_type == Config::kOpl3) {
|
||||
_oper[opIdx].left = _oper[opIdx + 3].left = (v >> 4) & 0x1;
|
||||
_oper[opIdx].right = _oper[opIdx + 3].right = (v >> 5) & 0x1;
|
||||
}
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[opIdx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OPL *create(Config::OplType type) {
|
||||
return new OPL(type);
|
||||
}
|
||||
|
||||
} // End of namespace ALSA
|
||||
} // End of namespace OPL
|
343
audio/decoders/3do.cpp
Normal file
343
audio/decoders/3do.cpp
Normal file
|
@ -0,0 +1,343 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/3do.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Reuses ADPCM table
|
||||
#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable
|
||||
#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable
|
||||
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
warning("make3DO_ADP4Stream(): stereo currently not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_ADP4_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_ADP4_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) {
|
||||
int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex];
|
||||
int32 decodedSample = _curDecoderData.lastSample;
|
||||
int16 delta = currentStep >> 3;
|
||||
|
||||
if (compressedNibble & 1)
|
||||
delta += currentStep >> 2;
|
||||
|
||||
if (compressedNibble & 2)
|
||||
delta += currentStep >> 1;
|
||||
|
||||
if (compressedNibble & 4)
|
||||
delta += currentStep;
|
||||
|
||||
if (compressedNibble & 8) {
|
||||
decodedSample -= delta;
|
||||
} else {
|
||||
decodedSample += delta;
|
||||
}
|
||||
|
||||
_curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767);
|
||||
|
||||
_curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07];
|
||||
_curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1);
|
||||
|
||||
return _curDecoderData.lastSample;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = NULL;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = 0;
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// in case caller requests an uneven amount of samples, we will return an even amount
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4);
|
||||
decodedSamplesCount++;
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f);
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
static int16 audio_3DO_SDX2_SquareTable[256] = {
|
||||
-32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322,
|
||||
-27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762,
|
||||
-23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602,
|
||||
-19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842,
|
||||
-15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482,
|
||||
-12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522,
|
||||
-9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962,
|
||||
-6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802,
|
||||
-4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042,
|
||||
-2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682,
|
||||
-1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722,
|
||||
-648, -578, -512, -450, -392, -338, -288, -242, -200, -162,
|
||||
-128, -98, -72, -50, -32, -18, -8, -2, 0, 2,
|
||||
8, 18, 32, 50, 72, 98, 128, 162, 200, 242,
|
||||
288, 338, 392, 450, 512, 578, 648, 722, 800, 882,
|
||||
968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922,
|
||||
2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362,
|
||||
3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202,
|
||||
5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442,
|
||||
7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082,
|
||||
10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122,
|
||||
13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562,
|
||||
16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402,
|
||||
20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642,
|
||||
25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282,
|
||||
29768, 30258, 30752, 31250, 31752, 32258
|
||||
};
|
||||
|
||||
Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_SDX2_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_SDX2_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = NULL;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
uint8 squareTableOffset = 0;
|
||||
int16 decodedSample = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_stereo) {
|
||||
// We expect numSamples to be even in case of Stereo audio
|
||||
assert((numSamples & 1) == 0);
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples;
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
if (!_stereo) {
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
} else {
|
||||
// Stereo
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(decodedSamplesCount & 1)) {
|
||||
// First channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
} else {
|
||||
// Second channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample2 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample2 = decodedSample;
|
||||
}
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
if (stream->size() & 1) {
|
||||
warning("make3DO_SDX2Stream(): stereo data is uneven size");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
158
audio/decoders/3do.h
Normal file
158
audio/decoders/3do.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - sherlock (3DO version of Serrated Scalpel)
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_3DO_SDX2_H
|
||||
#define AUDIO_3DO_SDX2_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
#include "common/substream.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
// amount of bytes to be used within the decoder classes as buffers
|
||||
#define AUDIO_3DO_CACHE_SIZE 1024
|
||||
|
||||
// persistent spaces
|
||||
struct audio_3DO_ADP4_PersistentSpace {
|
||||
int16 lastSample;
|
||||
int16 stepIndex;
|
||||
};
|
||||
|
||||
struct audio_3DO_SDX2_PersistentSpace {
|
||||
int16 lastSample1;
|
||||
int16 lastSample2;
|
||||
};
|
||||
|
||||
class Audio3DO_ADP4_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_ADP4_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _curDecoderData;
|
||||
|
||||
private:
|
||||
int16 decodeSample(byte compressedNibble);
|
||||
};
|
||||
|
||||
class Audio3DO_SDX2_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_SDX2_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _curDecoderData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
/**
|
||||
* Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
|
@ -24,16 +24,19 @@
|
|||
* The code in this file is based on information found at
|
||||
* http://www.borg.com/~jglatt/tech/aiff.htm
|
||||
*
|
||||
* We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX
|
||||
* (http://sox.sourceforge.net) may be a good place to start from.
|
||||
* Also partially based on libav's aiffdec.c
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/aiff.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/3do.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
|
@ -62,23 +65,34 @@ uint32 readExtended(Common::SeekableReadStream &stream) {
|
|||
return mantissa;
|
||||
}
|
||||
|
||||
bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) {
|
||||
byte buf[4];
|
||||
// AIFF versions
|
||||
static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F');
|
||||
static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C');
|
||||
|
||||
stream.read(buf, 4);
|
||||
if (memcmp(buf, "FORM", 4) != 0) {
|
||||
warning("loadAIFFFromStream: No 'FORM' header");
|
||||
return false;
|
||||
// Codecs
|
||||
static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original
|
||||
|
||||
RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) {
|
||||
warning("makeAIFFStream: No 'FORM' header");
|
||||
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream.readUint32BE();
|
||||
stream->readUint32BE(); // file size
|
||||
|
||||
// This could be AIFC, but we don't handle that case.
|
||||
uint32 version = stream->readUint32BE();
|
||||
|
||||
stream.read(buf, 4);
|
||||
if (memcmp(buf, "AIFF", 4) != 0) {
|
||||
warning("loadAIFFFromStream: No 'AIFF' header");
|
||||
return false;
|
||||
if (version != kVersionAIFF && version != kVersionAIFC) {
|
||||
warning("makeAIFFStream: No 'AIFF' or 'AIFC' header");
|
||||
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// From here on, we only care about the COMM and SSND chunks, which are
|
||||
|
@ -87,95 +101,131 @@ bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate
|
|||
bool foundCOMM = false;
|
||||
bool foundSSND = false;
|
||||
|
||||
uint16 numChannels = 0, bitsPerSample = 0;
|
||||
uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0;
|
||||
uint16 channels = 0, bitsPerSample = 0;
|
||||
uint32 blockAlign = 0, rate = 0;
|
||||
uint32 codec = kCodecPCM; // AIFF default
|
||||
Common::SeekableReadStream *dataStream = 0;
|
||||
|
||||
while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) {
|
||||
uint32 length, pos;
|
||||
while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) {
|
||||
uint32 tag = stream->readUint32BE();
|
||||
uint32 length = stream->readUint32BE();
|
||||
uint32 pos = stream->pos();
|
||||
|
||||
stream.read(buf, 4);
|
||||
length = stream.readUint32BE();
|
||||
pos = stream.pos();
|
||||
if (stream->eos() || stream->err())
|
||||
break;
|
||||
|
||||
if (memcmp(buf, "COMM", 4) == 0) {
|
||||
switch (tag) {
|
||||
case MKTAG('C', 'O', 'M', 'M'):
|
||||
foundCOMM = true;
|
||||
numChannels = stream.readUint16BE();
|
||||
numSampleFrames = stream.readUint32BE();
|
||||
bitsPerSample = stream.readUint16BE();
|
||||
rate = readExtended(stream);
|
||||
size = numSampleFrames * numChannels * (bitsPerSample / 8);
|
||||
} else if (memcmp(buf, "SSND", 4) == 0) {
|
||||
channels = stream->readUint16BE();
|
||||
/* frameCount = */ stream->readUint32BE();
|
||||
bitsPerSample = stream->readUint16BE();
|
||||
rate = readExtended(*stream);
|
||||
|
||||
if (version == kVersionAIFC)
|
||||
codec = stream->readUint32BE();
|
||||
break;
|
||||
case MKTAG('S', 'S', 'N', 'D'):
|
||||
foundSSND = true;
|
||||
offset = stream.readUint32BE();
|
||||
blockSize = stream.readUint32BE();
|
||||
soundOffset = stream.pos();
|
||||
/* uint32 offset = */ stream->readUint32BE();
|
||||
blockAlign = stream->readUint32BE();
|
||||
dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + length - 8, disposeAfterUse);
|
||||
break;
|
||||
case MKTAG('F', 'V', 'E', 'R'):
|
||||
switch (stream->readUint32BE()) {
|
||||
case 0:
|
||||
version = kVersionAIFF;
|
||||
break;
|
||||
case 0xA2805140:
|
||||
version = kVersionAIFC;
|
||||
break;
|
||||
default:
|
||||
warning("Unknown AIFF version chunk version");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MKTAG('w', 'a', 'v', 'e'):
|
||||
warning("Found unhandled AIFF-C extra data chunk");
|
||||
|
||||
if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
delete dataStream;
|
||||
return 0;
|
||||
default:
|
||||
debug(1, "Skipping AIFF '%s' chunk", tag2str(tag));
|
||||
break;
|
||||
}
|
||||
|
||||
stream.seek(pos + length);
|
||||
stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned
|
||||
}
|
||||
|
||||
if (!foundCOMM) {
|
||||
warning("loadAIFFFromStream: Cound not find 'COMM' chunk");
|
||||
return false;
|
||||
warning("makeAIFFStream: Cound not find 'COMM' chunk");
|
||||
|
||||
if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
delete dataStream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!foundSSND) {
|
||||
warning("loadAIFFFromStream: Cound not find 'SSND' chunk");
|
||||
return false;
|
||||
warning("makeAIFFStream: Cound not find 'SSND' chunk");
|
||||
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We only implement a subset of the AIFF standard.
|
||||
|
||||
if (numChannels < 1 || numChannels > 2) {
|
||||
warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bitsPerSample != 8 && bitsPerSample != 16) {
|
||||
warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (offset != 0 || blockSize != 0) {
|
||||
warning("loadAIFFFromStream: Block-aligned data is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Samples are always signed, and big endian.
|
||||
|
||||
flags = 0;
|
||||
if (bitsPerSample == 16)
|
||||
flags |= Audio::FLAG_16BITS;
|
||||
if (numChannels == 2)
|
||||
flags |= Audio::FLAG_STEREO;
|
||||
|
||||
stream.seek(soundOffset);
|
||||
|
||||
// Stream now points at the sample data
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse) {
|
||||
int size, rate;
|
||||
byte *data, flags;
|
||||
|
||||
if (!loadAIFFFromStream(*stream, size, rate, flags)) {
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
if (channels < 1 || channels > 2) {
|
||||
warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels);
|
||||
delete dataStream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
data = (byte *)malloc(size);
|
||||
assert(data);
|
||||
stream->read(data, size);
|
||||
// Seek to the start of dataStream, required for at least FileStream
|
||||
dataStream->seek(0);
|
||||
|
||||
if (disposeAfterUse == DisposeAfterUse::YES)
|
||||
delete stream;
|
||||
switch (codec) {
|
||||
case kCodecPCM:
|
||||
case MKTAG('t', 'w', 'o', 's'):
|
||||
case MKTAG('s', 'o', 'w', 't'): {
|
||||
// PCM samples are always signed.
|
||||
byte rawFlags = 0;
|
||||
if (bitsPerSample == 16)
|
||||
rawFlags |= Audio::FLAG_16BITS;
|
||||
if (channels == 2)
|
||||
rawFlags |= Audio::FLAG_STEREO;
|
||||
if (codec == MKTAG('s', 'o', 'w', 't'))
|
||||
rawFlags |= Audio::FLAG_LITTLE_ENDIAN;
|
||||
|
||||
// Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
|
||||
return makeRawStream(data, size, rate, flags);
|
||||
return makeRawStream(dataStream, rate, rawFlags);
|
||||
}
|
||||
case MKTAG('i', 'm', 'a', '4'):
|
||||
// TODO: Use QT IMA ADPCM
|
||||
warning("Unhandled AIFF-C QT IMA ADPCM compression");
|
||||
break;
|
||||
case MKTAG('Q', 'D', 'M', '2'):
|
||||
// TODO: Need to figure out how to integrate this
|
||||
// (But hopefully never needed)
|
||||
warning("Unhandled AIFF-C QDM2 compression");
|
||||
break;
|
||||
case MKTAG('A', 'D', 'P', '4'):
|
||||
// ADP4 on 3DO
|
||||
return make3DO_ADP4AudioStream(dataStream, rate, channels == 2);
|
||||
case MKTAG('S', 'D', 'X', '2'):
|
||||
// SDX2 on 3DO
|
||||
return make3DO_SDX2AudioStream(dataStream, rate, channels == 2);
|
||||
default:
|
||||
warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec));
|
||||
}
|
||||
|
||||
delete dataStream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - bbvs
|
||||
* - pegasus
|
||||
* - saga
|
||||
* - sci
|
||||
|
@ -41,28 +42,17 @@ class SeekableReadStream;
|
|||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
/**
|
||||
* Try to load an AIFF from the given seekable stream. Returns true if
|
||||
* successful. In that case, the stream's seek position will be set to the
|
||||
* start of the audio data, and size, rate and flags contain information
|
||||
* necessary for playback. Currently this function only supports uncompressed
|
||||
* raw PCM.
|
||||
*/
|
||||
extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
|
||||
class RewindableAudioStream;
|
||||
|
||||
/**
|
||||
* Try to load an AIFF from the given seekable stream and create an AudioStream
|
||||
* from that data.
|
||||
*
|
||||
* This function uses loadAIFFFromStream() internally.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the AIFF data
|
||||
* @param disposeAfterUse whether to delete the stream after use
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
SeekableAudioStream *makeAIFFStream(
|
||||
RewindableAudioStream *makeAIFFStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
|
|
|
@ -23,15 +23,25 @@
|
|||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - access
|
||||
* - agos
|
||||
* - cge
|
||||
* - cge2
|
||||
* - fullpipe
|
||||
* - gob
|
||||
* - hopkins
|
||||
* - mohawk
|
||||
* - prince
|
||||
* - saga
|
||||
* - sci
|
||||
* - scumm
|
||||
* - sherlock
|
||||
* - sword1
|
||||
* - sword2
|
||||
* - tony
|
||||
* - tucker
|
||||
* - wintermute
|
||||
* - zvision
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_WAVE_H
|
||||
|
|
216
audio/fmopl.cpp
216
audio/fmopl.cpp
|
@ -22,21 +22,33 @@
|
|||
|
||||
#include "audio/fmopl.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/softsynth/opl/dosbox.h"
|
||||
#include "audio/softsynth/opl/mame.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace OPL {
|
||||
|
||||
// Factory functions
|
||||
|
||||
#ifdef USE_ALSA
|
||||
namespace ALSA {
|
||||
OPL *create(Config::OplType type);
|
||||
} // End of namespace ALSA
|
||||
#endif // USE_ALSA
|
||||
|
||||
// Config implementation
|
||||
|
||||
enum OplEmulator {
|
||||
kAuto = 0,
|
||||
kMame = 1,
|
||||
kDOSBox = 2
|
||||
kDOSBox = 2,
|
||||
kALSA = 3
|
||||
};
|
||||
|
||||
OPL::OPL() {
|
||||
|
@ -50,6 +62,9 @@ const Config::EmulatorDescription Config::_drivers[] = {
|
|||
{ "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 },
|
||||
#ifndef DISABLE_DOSBOX_OPL
|
||||
{ "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
|
||||
#endif
|
||||
#ifdef USE_ALSA
|
||||
{ "alsa", _s("ALSA Direct FM"), kALSA, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
|
||||
#endif
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
@ -63,6 +78,15 @@ Config::DriverId Config::parse(const Common::String &name) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
const Config::EmulatorDescription *Config::findDriver(DriverId id) {
|
||||
for (int i = 0; _drivers[i].name; ++i) {
|
||||
if (_drivers[i].id == id)
|
||||
return &_drivers[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Config::DriverId Config::detect(OplType type) {
|
||||
uint32 flags = 0;
|
||||
switch (type) {
|
||||
|
@ -80,12 +104,21 @@ Config::DriverId Config::detect(OplType type) {
|
|||
}
|
||||
|
||||
DriverId drv = parse(ConfMan.get("opl_driver"));
|
||||
if (drv == kAuto) {
|
||||
// Since the "auto" can be explicitly set for a game, and this
|
||||
// driver shows up in the GUI as "<default>", check if there is
|
||||
// a global setting for it before resorting to auto-detection.
|
||||
drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain));
|
||||
}
|
||||
|
||||
// When a valid driver is selected, check whether it supports
|
||||
// the requested OPL chip.
|
||||
if (drv != -1 && drv != kAuto) {
|
||||
const EmulatorDescription *driverDesc = findDriver(drv);
|
||||
// If the chip is supported, just use the driver.
|
||||
if ((flags & _drivers[drv].flags)) {
|
||||
if (!driverDesc) {
|
||||
warning("The selected OPL driver %d could not be found", drv);
|
||||
} else if ((flags & driverDesc->flags)) {
|
||||
return drv;
|
||||
} else {
|
||||
// Else we will output a warning and just
|
||||
|
@ -145,6 +178,11 @@ OPL *Config::create(DriverId driver, OplType type) {
|
|||
return new DOSBox::OPL(type);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALSA
|
||||
case kALSA:
|
||||
return ALSA::create(type);
|
||||
#endif
|
||||
|
||||
default:
|
||||
warning("Unsupported OPL emulator %d", driver);
|
||||
// TODO: Maybe we should add some dummy emulator too, which just outputs
|
||||
|
@ -153,43 +191,143 @@ OPL *Config::create(DriverId driver, OplType type) {
|
|||
}
|
||||
}
|
||||
|
||||
void OPL::start(TimerCallback *callback, int timerFrequency) {
|
||||
_callback.reset(callback);
|
||||
startCallbacks(timerFrequency);
|
||||
}
|
||||
|
||||
void OPL::stop() {
|
||||
stopCallbacks();
|
||||
_callback.reset();
|
||||
}
|
||||
|
||||
bool OPL::_hasInstance = false;
|
||||
|
||||
RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) {
|
||||
}
|
||||
|
||||
RealOPL::~RealOPL() {
|
||||
// Stop callbacks, just in case. If it's still playing at this
|
||||
// point, there's probably a bigger issue, though. The subclass
|
||||
// needs to call stop() or the pointer can still use be used in
|
||||
// the mixer thread at the same time.
|
||||
stop();
|
||||
}
|
||||
|
||||
void RealOPL::setCallbackFrequency(int timerFrequency) {
|
||||
stopCallbacks();
|
||||
startCallbacks(timerFrequency);
|
||||
}
|
||||
|
||||
void RealOPL::startCallbacks(int timerFrequency) {
|
||||
_baseFreq = timerFrequency;
|
||||
assert(_baseFreq > 0);
|
||||
|
||||
// We can't request more a timer faster than 100Hz. We'll handle this by calling
|
||||
// the proc multiple times in onTimer() later on.
|
||||
if (timerFrequency > kMaxFreq)
|
||||
timerFrequency = kMaxFreq;
|
||||
|
||||
_remainingTicks = 0;
|
||||
g_system->getTimerManager()->installTimerProc(timerProc, 1000000 / timerFrequency, this, "RealOPL");
|
||||
}
|
||||
|
||||
void RealOPL::stopCallbacks() {
|
||||
g_system->getTimerManager()->removeTimerProc(timerProc);
|
||||
_baseFreq = 0;
|
||||
_remainingTicks = 0;
|
||||
}
|
||||
|
||||
void RealOPL::timerProc(void *refCon) {
|
||||
static_cast<RealOPL *>(refCon)->onTimer();
|
||||
}
|
||||
|
||||
void RealOPL::onTimer() {
|
||||
uint callbacks = 1;
|
||||
|
||||
if (_baseFreq > kMaxFreq) {
|
||||
// We run faster than our max, so run the callback multiple
|
||||
// times to approximate the actual timer callback frequency.
|
||||
uint totalTicks = _baseFreq + _remainingTicks;
|
||||
callbacks = totalTicks / kMaxFreq;
|
||||
_remainingTicks = totalTicks % kMaxFreq;
|
||||
}
|
||||
|
||||
// Call the callback multiple times. The if is on the inside of the
|
||||
// loop in case the callback removes itself.
|
||||
for (uint i = 0; i < callbacks; i++)
|
||||
if (_callback && _callback->isValid())
|
||||
(*_callback)();
|
||||
}
|
||||
|
||||
EmulatedOPL::EmulatedOPL() :
|
||||
_nextTick(0),
|
||||
_samplesPerTick(0),
|
||||
_baseFreq(0),
|
||||
_handle(new Audio::SoundHandle()) {
|
||||
}
|
||||
|
||||
EmulatedOPL::~EmulatedOPL() {
|
||||
// Stop callbacks, just in case. If it's still playing at this
|
||||
// point, there's probably a bigger issue, though. The subclass
|
||||
// needs to call stop() or the pointer can still use be used in
|
||||
// the mixer thread at the same time.
|
||||
stop();
|
||||
|
||||
delete _handle;
|
||||
}
|
||||
|
||||
int EmulatedOPL::readBuffer(int16 *buffer, const int numSamples) {
|
||||
const int stereoFactor = isStereo() ? 2 : 1;
|
||||
int len = numSamples / stereoFactor;
|
||||
int step;
|
||||
|
||||
do {
|
||||
step = len;
|
||||
if (step > (_nextTick >> FIXP_SHIFT))
|
||||
step = (_nextTick >> FIXP_SHIFT);
|
||||
|
||||
generateSamples(buffer, step * stereoFactor);
|
||||
|
||||
_nextTick -= step << FIXP_SHIFT;
|
||||
if (!(_nextTick >> FIXP_SHIFT)) {
|
||||
if (_callback && _callback->isValid())
|
||||
(*_callback)();
|
||||
|
||||
_nextTick += _samplesPerTick;
|
||||
}
|
||||
|
||||
buffer += step * stereoFactor;
|
||||
len -= step;
|
||||
} while (len);
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
int EmulatedOPL::getRate() const {
|
||||
return g_system->getMixer()->getOutputRate();
|
||||
}
|
||||
|
||||
void EmulatedOPL::startCallbacks(int timerFrequency) {
|
||||
setCallbackFrequency(timerFrequency);
|
||||
g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
}
|
||||
|
||||
void EmulatedOPL::stopCallbacks() {
|
||||
g_system->getMixer()->stopHandle(*_handle);
|
||||
}
|
||||
|
||||
void EmulatedOPL::setCallbackFrequency(int timerFrequency) {
|
||||
_baseFreq = timerFrequency;
|
||||
assert(_baseFreq != 0);
|
||||
|
||||
int d = getRate() / _baseFreq;
|
||||
int r = getRate() % _baseFreq;
|
||||
|
||||
// This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ
|
||||
// but less prone to arithmetic overflow.
|
||||
|
||||
_samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq;
|
||||
}
|
||||
|
||||
} // End of namespace OPL
|
||||
|
||||
void OPLDestroy(FM_OPL *OPL) {
|
||||
delete OPL;
|
||||
}
|
||||
|
||||
void OPLResetChip(FM_OPL *OPL) {
|
||||
OPL->reset();
|
||||
}
|
||||
|
||||
void OPLWrite(FM_OPL *OPL, int a, int v) {
|
||||
OPL->write(a, v);
|
||||
}
|
||||
|
||||
unsigned char OPLRead(FM_OPL *OPL, int a) {
|
||||
return OPL->read(a);
|
||||
}
|
||||
|
||||
void OPLWriteReg(FM_OPL *OPL, int r, int v) {
|
||||
OPL->writeReg(r, v);
|
||||
}
|
||||
|
||||
void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) {
|
||||
OPL->readBuffer(buffer, length);
|
||||
}
|
||||
|
||||
FM_OPL *makeAdLibOPL(int rate) {
|
||||
FM_OPL *opl = OPL::Config::create();
|
||||
|
||||
if (opl) {
|
||||
if (!opl->init(rate)) {
|
||||
delete opl;
|
||||
opl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return opl;
|
||||
}
|
||||
|
|
156
audio/fmopl.h
156
audio/fmopl.h
|
@ -23,8 +23,16 @@
|
|||
#ifndef AUDIO_FMOPL_H
|
||||
#define AUDIO_FMOPL_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
|
||||
#include "common/func.h"
|
||||
#include "common/ptr.h"
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace Audio {
|
||||
class SoundHandle;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
class String;
|
||||
}
|
||||
|
@ -70,6 +78,12 @@ public:
|
|||
*/
|
||||
static DriverId parse(const Common::String &name);
|
||||
|
||||
/**
|
||||
* @return The driver description for the given id or 0 in case it is not
|
||||
* available.
|
||||
*/
|
||||
static const EmulatorDescription *findDriver(DriverId id);
|
||||
|
||||
/**
|
||||
* Detects a driver for the specific type.
|
||||
*
|
||||
|
@ -92,6 +106,14 @@ private:
|
|||
static const EmulatorDescription _drivers[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the OPL timer callback functor.
|
||||
*/
|
||||
typedef Common::Functor0<void> TimerCallback;
|
||||
|
||||
/**
|
||||
* A representation of a Yamaha OPL chip.
|
||||
*/
|
||||
class OPL {
|
||||
private:
|
||||
static bool _hasInstance;
|
||||
|
@ -102,10 +124,9 @@ public:
|
|||
/**
|
||||
* Initializes the OPL emulator.
|
||||
*
|
||||
* @param rate output sample rate
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
virtual bool init(int rate) = 0;
|
||||
virtual bool init() = 0;
|
||||
|
||||
/**
|
||||
* Reinitializes the OPL emulator
|
||||
|
@ -139,6 +160,101 @@ public:
|
|||
*/
|
||||
virtual void writeReg(int r, int v) = 0;
|
||||
|
||||
/**
|
||||
* Start the OPL with callbacks.
|
||||
*/
|
||||
void start(TimerCallback *callback, int timerFrequency = kDefaultCallbackFrequency);
|
||||
|
||||
/**
|
||||
* Stop the OPL
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Change the callback frequency. This must only be called from a
|
||||
* timer proc.
|
||||
*/
|
||||
virtual void setCallbackFrequency(int timerFrequency) = 0;
|
||||
|
||||
enum {
|
||||
/**
|
||||
* The default callback frequency that start() uses
|
||||
*/
|
||||
kDefaultCallbackFrequency = 250
|
||||
};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Start the callbacks.
|
||||
*/
|
||||
virtual void startCallbacks(int timerFrequency) = 0;
|
||||
|
||||
/**
|
||||
* Stop the callbacks.
|
||||
*/
|
||||
virtual void stopCallbacks() = 0;
|
||||
|
||||
/**
|
||||
* The functor for callbacks.
|
||||
*/
|
||||
Common::ScopedPtr<TimerCallback> _callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* An OPL that represents a real OPL, as opposed to an emulated one.
|
||||
*
|
||||
* This will use an actual timer instead of using one calculated from
|
||||
* the number of samples in an AudioStream::readBuffer call.
|
||||
*/
|
||||
class RealOPL : public OPL {
|
||||
public:
|
||||
RealOPL();
|
||||
virtual ~RealOPL();
|
||||
|
||||
// OPL API
|
||||
void setCallbackFrequency(int timerFrequency);
|
||||
|
||||
protected:
|
||||
// OPL API
|
||||
void startCallbacks(int timerFrequency);
|
||||
void stopCallbacks();
|
||||
|
||||
private:
|
||||
static void timerProc(void *refCon);
|
||||
void onTimer();
|
||||
|
||||
uint _baseFreq;
|
||||
uint _remainingTicks;
|
||||
|
||||
enum {
|
||||
kMaxFreq = 100
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An OPL that represents an emulated OPL.
|
||||
*
|
||||
* This will send callbacks based on the number of samples
|
||||
* decoded in readBuffer().
|
||||
*/
|
||||
class EmulatedOPL : public OPL, protected Audio::AudioStream {
|
||||
public:
|
||||
EmulatedOPL();
|
||||
virtual ~EmulatedOPL();
|
||||
|
||||
// OPL API
|
||||
void setCallbackFrequency(int timerFrequency);
|
||||
|
||||
// AudioStream API
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
int getRate() const;
|
||||
bool endOfData() const { return false; }
|
||||
|
||||
protected:
|
||||
// OPL API
|
||||
void startCallbacks(int timerFrequency);
|
||||
void stopCallbacks();
|
||||
|
||||
/**
|
||||
* Read up to 'length' samples.
|
||||
*
|
||||
|
@ -149,33 +265,21 @@ public:
|
|||
* So if you request 4 samples from a stereo OPL, you will get
|
||||
* a total of two left channel and two right channel samples.
|
||||
*/
|
||||
virtual void readBuffer(int16 *buffer, int length) = 0;
|
||||
virtual void generateSamples(int16 *buffer, int numSamples) = 0;
|
||||
|
||||
/**
|
||||
* Returns whether the setup OPL mode is stereo or not
|
||||
*/
|
||||
virtual bool isStereo() const = 0;
|
||||
private:
|
||||
int _baseFreq;
|
||||
|
||||
enum {
|
||||
FIXP_SHIFT = 16
|
||||
};
|
||||
|
||||
int _nextTick;
|
||||
int _samplesPerTick;
|
||||
|
||||
Audio::SoundHandle *_handle;
|
||||
};
|
||||
|
||||
} // End of namespace OPL
|
||||
|
||||
// Legacy API
|
||||
// !You should not write any new code using the legacy API!
|
||||
typedef OPL::OPL FM_OPL;
|
||||
|
||||
void OPLDestroy(FM_OPL *OPL);
|
||||
|
||||
void OPLResetChip(FM_OPL *OPL);
|
||||
void OPLWrite(FM_OPL *OPL, int a, int v);
|
||||
unsigned char OPLRead(FM_OPL *OPL, int a);
|
||||
void OPLWriteReg(FM_OPL *OPL, int r, int v);
|
||||
void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length);
|
||||
|
||||
/**
|
||||
* Legacy factory to create an AdLib (OPL2) chip.
|
||||
*
|
||||
* !You should not write any new code using the legacy API!
|
||||
*/
|
||||
FM_OPL *makeAdLibOPL(int rate);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -370,6 +370,7 @@ public:
|
|||
|
||||
public:
|
||||
typedef void (*XMidiCallbackProc)(byte eventData, void *refCon);
|
||||
typedef void (*XMidiNewTimbreListProc)(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
|
||||
|
||||
MidiParser();
|
||||
virtual ~MidiParser() { allNotesOff(); }
|
||||
|
@ -395,7 +396,7 @@ public:
|
|||
static void defaultXMidiCallback(byte eventData, void *refCon);
|
||||
|
||||
static MidiParser *createParser_SMF();
|
||||
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
|
||||
static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, XMidiNewTimbreListProc newTimbreListProc = NULL, MidiDriver_BASE *newTimbreListDriver = NULL);
|
||||
static MidiParser *createParser_QT();
|
||||
static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
|
||||
};
|
||||
|
|
83
audio/miles.h
Normal file
83
audio/miles.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_MILES_MIDIDRIVER_H
|
||||
#define AUDIO_MILES_MIDIDRIVER_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "common/error.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
#define MILES_MIDI_CHANNEL_COUNT 16
|
||||
|
||||
// Miles Audio supported controllers for control change messages
|
||||
#define MILES_CONTROLLER_SELECT_PATCH_BANK 114
|
||||
#define MILES_CONTROLLER_PROTECT_VOICE 112
|
||||
#define MILES_CONTROLLER_PROTECT_TIMBRE 113
|
||||
#define MILES_CONTROLLER_MODULATION 1
|
||||
#define MILES_CONTROLLER_VOLUME 7
|
||||
#define MILES_CONTROLLER_EXPRESSION 11
|
||||
#define MILES_CONTROLLER_PANNING 10
|
||||
#define MILES_CONTROLLER_SUSTAIN 64
|
||||
#define MILES_CONTROLLER_PITCH_RANGE 6
|
||||
#define MILES_CONTROLLER_RESET_ALL 121
|
||||
#define MILES_CONTROLLER_ALL_NOTES_OFF 123
|
||||
#define MILES_CONTROLLER_PATCH_REVERB 59
|
||||
#define MILES_CONTROLLER_PATCH_BENDER 60
|
||||
#define MILES_CONTROLLER_REVERB_MODE 61
|
||||
#define MILES_CONTROLLER_REVERB_TIME 62
|
||||
#define MILES_CONTROLLER_REVERB_LEVEL 63
|
||||
#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58
|
||||
|
||||
// 3 SysEx controllers, each range 5
|
||||
// 32-36 for 1st queue
|
||||
// 37-41 for 2nd queue
|
||||
// 42-46 for 3rd queue
|
||||
#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32
|
||||
#define MILES_CONTROLLER_SYSEX_RANGE_END 46
|
||||
|
||||
#define MILES_CONTROLLER_SYSEX_QUEUE_COUNT 3
|
||||
#define MILES_CONTROLLER_SYSEX_QUEUE_SIZE 32
|
||||
|
||||
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1 0
|
||||
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2 1
|
||||
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3 2
|
||||
#define MILES_CONTROLLER_SYSEX_COMMAND_DATA 3
|
||||
#define MILES_CONTROLLER_SYSEX_COMMAND_SEND 4
|
||||
|
||||
#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
|
||||
#define MILES_CONTROLLER_XMIDI_RANGE_END 120
|
||||
|
||||
// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
|
||||
#define MILES_PITCHBENDER_DEFAULT 0x2000
|
||||
|
||||
extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
|
||||
|
||||
extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename);
|
||||
|
||||
extern void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif // AUDIO_MILES_MIDIDRIVER_H
|
1274
audio/miles_adlib.cpp
Normal file
1274
audio/miles_adlib.cpp
Normal file
File diff suppressed because it is too large
Load diff
912
audio/miles_mt32.cpp
Normal file
912
audio/miles_mt32.cpp
Normal file
|
@ -0,0 +1,912 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/miles.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Miles Audio MT32 driver
|
||||
//
|
||||
|
||||
#define MILES_MT32_PATCHES_COUNT 128
|
||||
#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
|
||||
|
||||
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
|
||||
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
|
||||
|
||||
#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14
|
||||
#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58
|
||||
#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
|
||||
#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
|
||||
|
||||
#define MILES_MT32_SYSEX_TERMINATOR 0xFF
|
||||
|
||||
struct MilesMT32InstrumentEntry {
|
||||
byte bankId;
|
||||
byte patchId;
|
||||
byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
|
||||
byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
|
||||
};
|
||||
|
||||
const byte milesMT32SysExResetParameters[] = {
|
||||
0x01, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExChansSetup[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExPartialReserveTable[] = {
|
||||
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExInitReverb[] = {
|
||||
0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
|
||||
};
|
||||
|
||||
class MidiDriver_Miles_MT32 : public MidiDriver {
|
||||
public:
|
||||
MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
|
||||
virtual ~MidiDriver_Miles_MT32();
|
||||
|
||||
// MidiDriver
|
||||
int open();
|
||||
void close();
|
||||
bool isOpen() const { return _isOpen; }
|
||||
|
||||
void send(uint32 b);
|
||||
|
||||
MidiChannel *allocateChannel() {
|
||||
if (_driver)
|
||||
return _driver->allocateChannel();
|
||||
return NULL;
|
||||
}
|
||||
MidiChannel *getPercussionChannel() {
|
||||
if (_driver)
|
||||
return _driver->getPercussionChannel();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(timer_param, timer_proc);
|
||||
}
|
||||
|
||||
uint32 getBaseTempo() {
|
||||
if (_driver) {
|
||||
return _driver->getBaseTempo();
|
||||
}
|
||||
return 1000000 / _baseFreq;
|
||||
}
|
||||
|
||||
protected:
|
||||
Common::Mutex _mutex;
|
||||
MidiDriver *_driver;
|
||||
bool _MT32;
|
||||
bool _nativeMT32;
|
||||
|
||||
bool _isOpen;
|
||||
int _baseFreq;
|
||||
|
||||
public:
|
||||
void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
|
||||
|
||||
private:
|
||||
void resetMT32();
|
||||
|
||||
void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
|
||||
|
||||
uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
|
||||
|
||||
void writeRhythmSetup(byte note, byte customTimbreId);
|
||||
void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
|
||||
void writePatchByte(byte patchId, byte index, byte patchValue);
|
||||
void writeToSystemArea(byte index, byte value);
|
||||
|
||||
void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
|
||||
void programChange(byte midiChannel, byte patchId);
|
||||
|
||||
const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId);
|
||||
int16 searchCustomTimbre(byte patchBank, byte patchId);
|
||||
|
||||
void setupPatch(byte patchBank, byte patchId);
|
||||
int16 installCustomTimbre(byte patchBank, byte patchId);
|
||||
|
||||
private:
|
||||
struct MidiChannelEntry {
|
||||
byte currentPatchBank;
|
||||
byte currentPatchId;
|
||||
|
||||
bool usingCustomTimbre;
|
||||
byte currentCustomTimbreId;
|
||||
|
||||
MidiChannelEntry() : currentPatchBank(0),
|
||||
currentPatchId(0),
|
||||
usingCustomTimbre(false),
|
||||
currentCustomTimbreId(0) { }
|
||||
};
|
||||
|
||||
struct MidiCustomTimbreEntry {
|
||||
bool used;
|
||||
bool protectionEnabled;
|
||||
byte currentPatchBank;
|
||||
byte currentPatchId;
|
||||
|
||||
uint32 lastUsedNoteCounter;
|
||||
|
||||
MidiCustomTimbreEntry() : used(false),
|
||||
protectionEnabled(false),
|
||||
currentPatchBank(0),
|
||||
currentPatchId(0),
|
||||
lastUsedNoteCounter(0) {}
|
||||
};
|
||||
|
||||
struct MilesMT32SysExQueueEntry {
|
||||
uint32 targetAddress;
|
||||
byte dataPos;
|
||||
byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
|
||||
|
||||
MilesMT32SysExQueueEntry() : targetAddress(0),
|
||||
dataPos(0) {
|
||||
memset(data, 0, sizeof(data));
|
||||
}
|
||||
};
|
||||
|
||||
// stores information about all MIDI channels
|
||||
MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
|
||||
|
||||
// stores information about all custom timbres
|
||||
MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
|
||||
|
||||
byte _patchesBank[MILES_MT32_PATCHES_COUNT];
|
||||
|
||||
// holds all instruments
|
||||
MilesMT32InstrumentEntry *_instrumentTablePtr;
|
||||
uint16 _instrumentTableCount;
|
||||
|
||||
uint32 _noteCounter; // used to figure out, which timbres are outdated
|
||||
|
||||
// SysEx Queues
|
||||
MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
|
||||
};
|
||||
|
||||
MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) {
|
||||
_instrumentTablePtr = instrumentTablePtr;
|
||||
_instrumentTableCount = instrumentTableCount;
|
||||
|
||||
_driver = NULL;
|
||||
_isOpen = false;
|
||||
_MT32 = false;
|
||||
_nativeMT32 = false;
|
||||
_baseFreq = 250;
|
||||
|
||||
_noteCounter = 0;
|
||||
|
||||
memset(_patchesBank, 0, sizeof(_patchesBank));
|
||||
}
|
||||
|
||||
MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() {
|
||||
Common::StackLock lock(_mutex);
|
||||
if (_driver) {
|
||||
_driver->setTimerCallback(0, 0);
|
||||
_driver->close();
|
||||
delete _driver;
|
||||
}
|
||||
_driver = NULL;
|
||||
}
|
||||
|
||||
int MidiDriver_Miles_MT32::open() {
|
||||
assert(!_driver);
|
||||
|
||||
// Setup midi driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
|
||||
MusicType musicType = MidiDriver::getMusicType(dev);
|
||||
|
||||
switch (musicType) {
|
||||
case MT_MT32:
|
||||
_nativeMT32 = true;
|
||||
break;
|
||||
case MT_GM:
|
||||
if (ConfMan.getBool("native_mt32")) {
|
||||
_nativeMT32 = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_nativeMT32) {
|
||||
error("MILES-MT32: non-mt32 currently not supported!");
|
||||
}
|
||||
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
if (!_driver)
|
||||
return 255;
|
||||
|
||||
if (_nativeMT32)
|
||||
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (_nativeMT32) {
|
||||
_driver->sendMT32Reset();
|
||||
|
||||
resetMT32();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::close() {
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::resetMT32() {
|
||||
// reset all internal parameters / patches
|
||||
MT32SysEx(0x7F0000, milesMT32SysExResetParameters);
|
||||
|
||||
// init part/channel assignments
|
||||
MT32SysEx(0x10000D, milesMT32SysExChansSetup);
|
||||
|
||||
// partial reserve table
|
||||
MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
|
||||
|
||||
// init reverb
|
||||
MT32SysEx(0x100001, milesMT32SysExInitReverb);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
|
||||
byte sysExMessage[270];
|
||||
uint16 sysExPos = 0;
|
||||
byte sysExByte = 0;
|
||||
uint16 sysExChecksum = 0;
|
||||
|
||||
memset(&sysExMessage, 0, sizeof(sysExMessage));
|
||||
|
||||
sysExMessage[0] = 0x41; // Roland
|
||||
sysExMessage[1] = 0x10;
|
||||
sysExMessage[2] = 0x16; // Model MT32
|
||||
sysExMessage[3] = 0x12; // Command DT1
|
||||
|
||||
sysExChecksum = 0;
|
||||
|
||||
sysExMessage[4] = (targetAddress >> 16) & 0xFF;
|
||||
sysExMessage[5] = (targetAddress >> 8) & 0xFF;
|
||||
sysExMessage[6] = targetAddress & 0xFF;
|
||||
|
||||
for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
|
||||
assert(sysExMessage[targetAddressByte] < 0x80); // security check
|
||||
sysExChecksum -= sysExMessage[targetAddressByte];
|
||||
}
|
||||
|
||||
sysExPos = 7;
|
||||
while (1) {
|
||||
sysExByte = *dataPtr++;
|
||||
if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
|
||||
break; // Message done
|
||||
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
assert(sysExByte < 0x80); // security check
|
||||
sysExMessage[sysExPos++] = sysExByte;
|
||||
sysExChecksum -= sysExByte;
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
|
||||
|
||||
// Send SysEx
|
||||
_driver->sysEx(sysExMessage, sysExPos);
|
||||
|
||||
// Wait the time it takes to send the SysEx data
|
||||
uint32 delay = (sysExPos + 2) * 1000 / 3125;
|
||||
|
||||
// Plus an additional delay for the MT-32 rev00
|
||||
if (_nativeMT32)
|
||||
delay += 40;
|
||||
|
||||
g_system->delayMillis(delay);
|
||||
}
|
||||
|
||||
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
|
||||
void MidiDriver_Miles_MT32::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte midiChannel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
switch (command) {
|
||||
case 0x80: // note off
|
||||
case 0x90: // note on
|
||||
case 0xa0: // Polyphonic key pressure (aftertouch)
|
||||
case 0xd0: // Channel pressure (aftertouch)
|
||||
case 0xe0: // pitch bend change
|
||||
_noteCounter++;
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// Remember that this timbre got used now
|
||||
_customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
|
||||
}
|
||||
_driver->send(b);
|
||||
break;
|
||||
case 0xb0: // Control change
|
||||
controlChange(midiChannel, op1, op2);
|
||||
break;
|
||||
case 0xc0: // Program Change
|
||||
programChange(midiChannel, op1);
|
||||
break;
|
||||
case 0xf0: // SysEx
|
||||
warning("MILES-MT32: SysEx: %x", b);
|
||||
break;
|
||||
default:
|
||||
warning("MILES-MT32: Unknown event %02x", command);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
|
||||
byte channelPatchId = 0;
|
||||
byte channelCustomTimbreId = 0;
|
||||
|
||||
switch (controllerNumber) {
|
||||
case MILES_CONTROLLER_SELECT_PATCH_BANK:
|
||||
_midiChannels[midiChannel].currentPatchBank = controllerValue;
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PATCH_REVERB:
|
||||
channelPatchId = _midiChannels[midiChannel].currentPatchId;
|
||||
|
||||
writePatchByte(channelPatchId, 6, controllerValue);
|
||||
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PATCH_BENDER:
|
||||
channelPatchId = _midiChannels[midiChannel].currentPatchId;
|
||||
|
||||
writePatchByte(channelPatchId, 4, controllerValue);
|
||||
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_MODE:
|
||||
writeToSystemArea(1, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_TIME:
|
||||
writeToSystemArea(2, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_LEVEL:
|
||||
writeToSystemArea(3, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// custom timbre is set on current channel
|
||||
writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId);
|
||||
}
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PROTECT_TIMBRE:
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// custom timbre set on current channel
|
||||
channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId;
|
||||
if (controllerValue >= 64) {
|
||||
// enable protection
|
||||
_customTimbres[channelCustomTimbreId].protectionEnabled = true;
|
||||
} else {
|
||||
// disable protection
|
||||
_customTimbres[channelCustomTimbreId].protectionEnabled = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
|
||||
// send SysEx
|
||||
byte sysExQueueNr = 0;
|
||||
|
||||
// figure out which queue is accessed
|
||||
controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
|
||||
while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) {
|
||||
sysExQueueNr++;
|
||||
controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1);
|
||||
}
|
||||
assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
|
||||
|
||||
byte sysExPos = _sysExQueues[sysExQueueNr].dataPos;
|
||||
bool sysExSend = false;
|
||||
|
||||
switch(controllerNumber) {
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= controllerValue;
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
|
||||
if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
|
||||
// Space left? put current byte into queue
|
||||
_sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
|
||||
sysExPos++;
|
||||
_sysExQueues[sysExQueueNr].dataPos = sysExPos;
|
||||
if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
|
||||
// overflow? -> send it now
|
||||
sysExSend = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_SEND:
|
||||
sysExSend = true;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (sysExSend) {
|
||||
if (sysExPos > 0) {
|
||||
// data actually available? -> send it
|
||||
_sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
|
||||
|
||||
// Execute SysEx
|
||||
MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data);
|
||||
|
||||
// adjust target address to point at the end of the current data
|
||||
_sysExQueues[sysExQueueNr].targetAddress += sysExPos;
|
||||
// reset queue data buffer
|
||||
_sysExQueues[sysExQueueNr].dataPos = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
|
||||
// XMIDI controllers? ignore those
|
||||
return;
|
||||
}
|
||||
|
||||
_driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16));
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) {
|
||||
byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank;
|
||||
byte activePatchBank = _patchesBank[patchId];
|
||||
|
||||
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
|
||||
|
||||
// remember patch id for the current MIDI-channel
|
||||
_midiChannels[midiChannel].currentPatchId = patchId;
|
||||
|
||||
if (channelPatchBank != activePatchBank) {
|
||||
// associate patch with timbre
|
||||
setupPatch(channelPatchBank, patchId);
|
||||
}
|
||||
|
||||
// If this is a custom patch, remember customTimbreId
|
||||
int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
|
||||
if (customTimbre >= 0) {
|
||||
_midiChannels[midiChannel].usingCustomTimbre = true;
|
||||
_midiChannels[midiChannel].currentCustomTimbreId = customTimbre;
|
||||
} else {
|
||||
_midiChannels[midiChannel].usingCustomTimbre = false;
|
||||
}
|
||||
|
||||
// Finally send program change to MT32
|
||||
_driver->send(0xC0 | midiChannel | (patchId << 8));
|
||||
}
|
||||
|
||||
int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) {
|
||||
byte customTimbreId = 0;
|
||||
|
||||
for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
|
||||
if (_customTimbres[customTimbreId].used) {
|
||||
if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
|
||||
return customTimbreId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) {
|
||||
const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
|
||||
|
||||
for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
|
||||
if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
|
||||
return instrumentPtr;
|
||||
instrumentPtr++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) {
|
||||
_patchesBank[patchId] = patchBank;
|
||||
|
||||
if (patchBank) {
|
||||
// non-built-in bank
|
||||
int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
|
||||
if (customTimbreId >= 0) {
|
||||
// now available? -> use this timbre
|
||||
writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// for built-in bank (or timbres, that are not available) use default MT32 timbres
|
||||
byte timbreId = patchId & 0x3F;
|
||||
if (!(patchId & 0x40)) {
|
||||
writePatchTimbre(patchId, 0, timbreId); // Group A
|
||||
} else {
|
||||
writePatchTimbre(patchId, 1, timbreId); // Group B
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
|
||||
uint16 timbreCount = 0;
|
||||
uint32 expectedSize = 0;
|
||||
const byte *timbreListSeeker = timbreListPtr;
|
||||
|
||||
if (timbreListSize < 2) {
|
||||
warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk");
|
||||
return;
|
||||
}
|
||||
|
||||
timbreCount = READ_LE_UINT16(timbreListPtr);
|
||||
expectedSize = timbreCount * 2;
|
||||
if (expectedSize > timbreListSize) {
|
||||
warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch");
|
||||
return;
|
||||
}
|
||||
|
||||
timbreListSeeker += 2;
|
||||
|
||||
while (timbreCount) {
|
||||
const byte patchId = *timbreListSeeker++;
|
||||
const byte patchBank = *timbreListSeeker++;
|
||||
int16 customTimbreId = 0;
|
||||
|
||||
switch (patchBank) {
|
||||
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
|
||||
case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
|
||||
// ignore those 2 banks
|
||||
break;
|
||||
|
||||
default:
|
||||
// Check, if this timbre was already loaded
|
||||
customTimbreId = searchCustomTimbre(patchBank, patchId);
|
||||
|
||||
if (customTimbreId < 0) {
|
||||
// currently not loaded, try to install it
|
||||
installCustomTimbre(patchBank, patchId);
|
||||
}
|
||||
}
|
||||
timbreCount--;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) {
|
||||
switch(patchBank) {
|
||||
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
|
||||
case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Original driver did a search for custom timbre here
|
||||
// and in case it was found, it would call setup_patch()
|
||||
// we are called from within setup_patch(), so this isn't needed
|
||||
|
||||
int16 customTimbreId = -1;
|
||||
int16 leastUsedTimbreId = -1;
|
||||
uint32 leastUsedTimbreNoteCounter = _noteCounter;
|
||||
const MilesMT32InstrumentEntry *instrumentPtr = NULL;
|
||||
|
||||
// Check, if requested instrument is actually available
|
||||
instrumentPtr = searchCustomInstrument(patchBank, patchId);
|
||||
if (!instrumentPtr) {
|
||||
warning("MILES-MT32: instrument not found during installCustomTimbre()");
|
||||
return -1; // not found -> bail out
|
||||
}
|
||||
|
||||
// Look for an empty timbre slot
|
||||
// or get the least used non-protected slot
|
||||
for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
|
||||
if (!_customTimbres[customTimbreNr].used) {
|
||||
// found an empty slot -> use this one
|
||||
customTimbreId = customTimbreNr;
|
||||
break;
|
||||
} else {
|
||||
// used slot
|
||||
if (!_customTimbres[customTimbreNr].protectionEnabled) {
|
||||
// not protected
|
||||
uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
|
||||
if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
|
||||
leastUsedTimbreId = customTimbreNr;
|
||||
leastUsedTimbreNoteCounter = customTimbreNoteCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (customTimbreId < 0) {
|
||||
// no empty slot found, check if we got a least used non-protected slot
|
||||
if (leastUsedTimbreId < 0) {
|
||||
// everything is protected, bail out
|
||||
warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()");
|
||||
return -1;
|
||||
}
|
||||
customTimbreId = leastUsedTimbreId;
|
||||
}
|
||||
|
||||
// setup timbre slot
|
||||
_customTimbres[customTimbreId].used = true;
|
||||
_customTimbres[customTimbreId].currentPatchBank = patchBank;
|
||||
_customTimbres[customTimbreId].currentPatchId = patchId;
|
||||
_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
|
||||
_customTimbres[customTimbreId].protectionEnabled = false;
|
||||
|
||||
uint32 targetAddress = 0x080000 | (customTimbreId << 9);
|
||||
uint32 targetAddressCommon = targetAddress + 0x000000;
|
||||
uint32 targetAddressPartial1 = targetAddress + 0x00000E;
|
||||
uint32 targetAddressPartial2 = targetAddress + 0x000048;
|
||||
uint32 targetAddressPartial3 = targetAddress + 0x000102;
|
||||
uint32 targetAddressPartial4 = targetAddress + 0x00013C;
|
||||
|
||||
#if 0
|
||||
byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
|
||||
uint16 parameterDataPos = 0;
|
||||
|
||||
memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
|
||||
|
||||
MT32SysEx(targetAddressCommon, parameterData);
|
||||
#endif
|
||||
|
||||
// upload common parameter data
|
||||
MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter);
|
||||
// upload partial parameter data
|
||||
MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]);
|
||||
MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]);
|
||||
MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]);
|
||||
MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]);
|
||||
|
||||
setupPatch(patchBank, patchId);
|
||||
|
||||
return customTimbreId;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
|
||||
uint16 targetAddressLSB = baseAddress & 0xFF;
|
||||
uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
|
||||
uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
|
||||
|
||||
// add index to it, but use 7-bit of the index for each byte
|
||||
targetAddressLSB += (index & 0x7F);
|
||||
targetAddressKSB += ((index >> 7) & 0x7F);
|
||||
targetAddressMSB += ((index >> 14) & 0x7F);
|
||||
|
||||
// adjust bytes, so that none of them is above or equal 0x80
|
||||
while (targetAddressLSB >= 0x80) {
|
||||
targetAddressLSB -= 0x80;
|
||||
targetAddressKSB++;
|
||||
}
|
||||
while (targetAddressKSB >= 0x80) {
|
||||
targetAddressKSB -= 0x80;
|
||||
targetAddressMSB++;
|
||||
}
|
||||
assert(targetAddressMSB < 0x80);
|
||||
|
||||
// put everything together
|
||||
return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
|
||||
|
||||
sysExData[0] = customTimbreId;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
|
||||
byte sysExData[3];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
// write to patch memory (starts at 0x050000, each entry is 8 bytes)
|
||||
targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
|
||||
|
||||
sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
|
||||
sysExData[1] = timbreId; // timbre number (0-63)
|
||||
sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
|
||||
|
||||
sysExData[0] = patchValue;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x100000, index);
|
||||
|
||||
sysExData[0] = value;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
|
||||
MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
|
||||
uint16 instrumentTableCount = 0;
|
||||
|
||||
if (!instrumentDataFilename.empty()) {
|
||||
// Load MT32 instrument data from file SAMPLE.MT
|
||||
Common::File *fileStream = new Common::File();
|
||||
uint32 fileSize = 0;
|
||||
byte *fileDataPtr = NULL;
|
||||
uint32 fileDataOffset = 0;
|
||||
uint32 fileDataLeft = 0;
|
||||
|
||||
byte curBankId = 0;
|
||||
byte curPatchId = 0;
|
||||
|
||||
MilesMT32InstrumentEntry *instrumentPtr = NULL;
|
||||
uint32 instrumentOffset = 0;
|
||||
uint16 instrumentDataSize = 0;
|
||||
|
||||
if (!fileStream->open(instrumentDataFilename))
|
||||
error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str());
|
||||
|
||||
fileSize = fileStream->size();
|
||||
|
||||
fileDataPtr = new byte[fileSize];
|
||||
|
||||
if (fileStream->read(fileDataPtr, fileSize) != fileSize)
|
||||
error("MILES-MT32: error while reading instrument file");
|
||||
fileStream->close();
|
||||
delete fileStream;
|
||||
|
||||
// File is like this:
|
||||
// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
|
||||
// ...
|
||||
// until patch + bank are both 0xFF, which signals end of header
|
||||
|
||||
// First we check how many entries there are
|
||||
fileDataOffset = 0;
|
||||
fileDataLeft = fileSize;
|
||||
while (1) {
|
||||
if (fileDataLeft < 6)
|
||||
error("MILES-MT32: unexpected EOF in instrument file");
|
||||
|
||||
curPatchId = fileDataPtr[fileDataOffset++];
|
||||
curBankId = fileDataPtr[fileDataOffset++];
|
||||
|
||||
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
|
||||
break;
|
||||
|
||||
fileDataOffset += 4; // skip over offset
|
||||
instrumentTableCount++;
|
||||
}
|
||||
|
||||
if (instrumentTableCount == 0)
|
||||
error("MILES-MT32: no instruments in instrument file");
|
||||
|
||||
// Allocate space for instruments
|
||||
instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
|
||||
|
||||
// Now actually read all entries
|
||||
instrumentPtr = instrumentTablePtr;
|
||||
|
||||
fileDataOffset = 0;
|
||||
fileDataLeft = fileSize;
|
||||
while (1) {
|
||||
curPatchId = fileDataPtr[fileDataOffset++];
|
||||
curBankId = fileDataPtr[fileDataOffset++];
|
||||
|
||||
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
|
||||
break;
|
||||
|
||||
instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
|
||||
fileDataOffset += 4;
|
||||
|
||||
instrumentPtr->bankId = curBankId;
|
||||
instrumentPtr->patchId = curPatchId;
|
||||
|
||||
instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
|
||||
if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
|
||||
error("MILES-MT32: unsupported instrument size");
|
||||
|
||||
instrumentOffset += 2;
|
||||
// Copy common parameter data
|
||||
memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
|
||||
instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
|
||||
instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
|
||||
|
||||
// Copy partial parameter data
|
||||
for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
|
||||
memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
|
||||
instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
}
|
||||
|
||||
// Instrument read, next instrument please
|
||||
instrumentPtr++;
|
||||
}
|
||||
|
||||
// Free instrument file data
|
||||
delete[] fileDataPtr;
|
||||
}
|
||||
|
||||
return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) {
|
||||
MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver);
|
||||
|
||||
if (driverMT32) {
|
||||
driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
|
@ -1,16 +1,20 @@
|
|||
MODULE := audio
|
||||
|
||||
MODULE_OBJS := \
|
||||
adlib.o \
|
||||
audiostream.o \
|
||||
fmopl.o \
|
||||
mididrv.o \
|
||||
midiparser.o \
|
||||
midiparser_qt.o \
|
||||
midiparser.o \
|
||||
miles_adlib.o \
|
||||
miles_mt32.o \
|
||||
mixer.o \
|
||||
mpu401.o \
|
||||
musicplugin.o \
|
||||
null.o \
|
||||
timestamp.o \
|
||||
decoders/3do.o \
|
||||
decoders/aac.o \
|
||||
decoders/adpcm.o \
|
||||
decoders/aiff.o \
|
||||
|
@ -27,7 +31,6 @@ MODULE_OBJS := \
|
|||
decoders/wave.o \
|
||||
decoders/wma.o \
|
||||
decoders/xa.o \
|
||||
softsynth/adlib.o \
|
||||
softsynth/cms.o \
|
||||
softsynth/opl/dbopl.o \
|
||||
softsynth/opl/dosbox.o \
|
||||
|
@ -35,6 +38,11 @@ MODULE_OBJS := \
|
|||
softsynth/fluidsynth.o \
|
||||
softsynth/mt32.o
|
||||
|
||||
ifdef USE_ALSA
|
||||
MODULE_OBJS += \
|
||||
alsa_opl.o
|
||||
endif
|
||||
|
||||
ifndef USE_ARM_SOUND_ASM
|
||||
MODULE_OBJS += \
|
||||
rate.o
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "dosbox.h"
|
||||
#include "dbopl.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "common/system.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/util.h"
|
||||
|
@ -148,6 +149,7 @@ OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) {
|
|||
}
|
||||
|
||||
OPL::~OPL() {
|
||||
stop();
|
||||
free();
|
||||
}
|
||||
|
||||
|
@ -156,7 +158,7 @@ void OPL::free() {
|
|||
_emulator = 0;
|
||||
}
|
||||
|
||||
bool OPL::init(int rate) {
|
||||
bool OPL::init() {
|
||||
free();
|
||||
|
||||
memset(&_reg, 0, sizeof(_reg));
|
||||
|
@ -167,19 +169,19 @@ bool OPL::init(int rate) {
|
|||
return false;
|
||||
|
||||
DBOPL::InitTables();
|
||||
_emulator->Setup(rate);
|
||||
_rate = g_system->getMixer()->getOutputRate();
|
||||
_emulator->Setup(_rate);
|
||||
|
||||
if (_type == Config::kDualOpl2) {
|
||||
// Setup opl3 mode in the hander
|
||||
_emulator->WriteReg(0x105, 1);
|
||||
}
|
||||
|
||||
_rate = rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OPL::reset() {
|
||||
init(_rate);
|
||||
init();
|
||||
}
|
||||
|
||||
void OPL::write(int port, int val) {
|
||||
|
@ -307,7 +309,7 @@ void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) {
|
|||
_emulator->WriteReg(fullReg, val);
|
||||
}
|
||||
|
||||
void OPL::readBuffer(int16 *buffer, int length) {
|
||||
void OPL::generateSamples(int16 *buffer, int length) {
|
||||
// For stereo OPL cards, we divide the sample count by 2,
|
||||
// to match stereo AudioStream behavior.
|
||||
if (_type != Config::kOpl2)
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace DBOPL {
|
|||
struct Chip;
|
||||
} // end of namespace DBOPL
|
||||
|
||||
class OPL : public ::OPL::OPL {
|
||||
class OPL : public ::OPL::EmulatedOPL {
|
||||
private:
|
||||
Config::OplType _type;
|
||||
uint _rate;
|
||||
|
@ -87,7 +87,7 @@ public:
|
|||
OPL(Config::OplType type);
|
||||
~OPL();
|
||||
|
||||
bool init(int rate);
|
||||
bool init();
|
||||
void reset();
|
||||
|
||||
void write(int a, int v);
|
||||
|
@ -95,8 +95,10 @@ public:
|
|||
|
||||
void writeReg(int r, int v);
|
||||
|
||||
void readBuffer(int16 *buffer, int length);
|
||||
bool isStereo() const { return _type != Config::kOpl2; }
|
||||
|
||||
protected:
|
||||
void generateSamples(int16 *buffer, int length);
|
||||
};
|
||||
|
||||
} // End of namespace DOSBox
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
#include "mame.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
|
||||
|
@ -46,15 +48,19 @@ namespace OPL {
|
|||
namespace MAME {
|
||||
|
||||
OPL::~OPL() {
|
||||
stop();
|
||||
MAME::OPLDestroy(_opl);
|
||||
_opl = 0;
|
||||
}
|
||||
|
||||
bool OPL::init(int rate) {
|
||||
if (_opl)
|
||||
bool OPL::init() {
|
||||
if (_opl) {
|
||||
stopCallbacks();
|
||||
MAME::OPLDestroy(_opl);
|
||||
}
|
||||
|
||||
_opl = MAME::makeAdLibOPL(g_system->getMixer()->getOutputRate());
|
||||
|
||||
_opl = MAME::makeAdLibOPL(rate);
|
||||
return (_opl != 0);
|
||||
}
|
||||
|
||||
|
@ -74,7 +80,7 @@ void OPL::writeReg(int r, int v) {
|
|||
MAME::OPLWriteReg(_opl, r, v);
|
||||
}
|
||||
|
||||
void OPL::readBuffer(int16 *buffer, int length) {
|
||||
void OPL::generateSamples(int16 *buffer, int length) {
|
||||
MAME::YM3812UpdateOne(_opl, buffer, length);
|
||||
}
|
||||
|
||||
|
|
|
@ -174,14 +174,14 @@ void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length);
|
|||
FM_OPL *makeAdLibOPL(int rate);
|
||||
|
||||
// OPL API implementation
|
||||
class OPL : public ::OPL::OPL {
|
||||
class OPL : public ::OPL::EmulatedOPL {
|
||||
private:
|
||||
FM_OPL *_opl;
|
||||
public:
|
||||
OPL() : _opl(0) {}
|
||||
~OPL();
|
||||
|
||||
bool init(int rate);
|
||||
bool init();
|
||||
void reset();
|
||||
|
||||
void write(int a, int v);
|
||||
|
@ -189,8 +189,10 @@ public:
|
|||
|
||||
void writeReg(int r, int v);
|
||||
|
||||
void readBuffer(int16 *buffer, int length);
|
||||
bool isStereo() const { return false; }
|
||||
|
||||
protected:
|
||||
void generateSamples(int16 *buffer, int length);
|
||||
};
|
||||
|
||||
} // End of namespace MAME
|
||||
|
|
2318
b/audio/adlib.cpp
Normal file
2318
b/audio/adlib.cpp
Normal file
File diff suppressed because it is too large
Load diff
349
b/audio/alsa_opl.cpp
Normal file
349
b/audio/alsa_opl.cpp
Normal file
|
@ -0,0 +1,349 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/* OPL implementation for hardware OPL using ALSA Direct FM API.
|
||||
*
|
||||
* Caveats and limitations:
|
||||
* - Pretends to be a softsynth (emitting silence).
|
||||
* - Dual OPL2 mode requires OPL3 hardware.
|
||||
* - Every register write leads to a series of register writes on the hardware,
|
||||
* due to the lack of direct register access in the ALSA Direct FM API.
|
||||
* - No timers
|
||||
*/
|
||||
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/str.h"
|
||||
#include "audio/fmopl.h"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <sound/asound_fm.h>
|
||||
|
||||
namespace OPL {
|
||||
namespace ALSA {
|
||||
|
||||
class OPL : public ::OPL::RealOPL {
|
||||
private:
|
||||
enum {
|
||||
kOpl2Voices = 9,
|
||||
kVoices = 18,
|
||||
kOpl2Operators = 18,
|
||||
kOperators = 36
|
||||
};
|
||||
|
||||
Config::OplType _type;
|
||||
int _iface;
|
||||
snd_hwdep_t *_opl;
|
||||
snd_dm_fm_voice _oper[kOperators];
|
||||
snd_dm_fm_note _voice[kVoices];
|
||||
snd_dm_fm_params _params;
|
||||
int index[2];
|
||||
static const int voiceToOper0[kVoices];
|
||||
static const int regOffsetToOper[0x20];
|
||||
|
||||
void writeOplReg(int c, int r, int v);
|
||||
void clear();
|
||||
|
||||
public:
|
||||
OPL(Config::OplType type);
|
||||
~OPL();
|
||||
|
||||
bool init();
|
||||
void reset();
|
||||
|
||||
void write(int a, int v);
|
||||
byte read(int a);
|
||||
|
||||
void writeReg(int r, int v);
|
||||
};
|
||||
|
||||
const int OPL::voiceToOper0[OPL::kVoices] =
|
||||
{ 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 };
|
||||
|
||||
const int OPL::regOffsetToOper[0x20] =
|
||||
{ 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1,
|
||||
12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||
|
||||
OPL::OPL(Config::OplType type) : _type(type), _opl(nullptr), _iface(0) {
|
||||
}
|
||||
|
||||
OPL::~OPL() {
|
||||
stop();
|
||||
|
||||
if (_opl) {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
|
||||
snd_hwdep_close(_opl);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL::clear() {
|
||||
index[0] = index[1] = 0;
|
||||
|
||||
memset(_oper, 0, sizeof(_oper));
|
||||
memset(_voice, 0, sizeof(_voice));
|
||||
memset(&_params, 0, sizeof(_params));
|
||||
|
||||
for (int i = 0; i < kOperators; ++i) {
|
||||
_oper[i].op = (i / 3) % 2;
|
||||
_oper[i].voice = (i / 6) * 3 + (i % 3);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVoices; ++i)
|
||||
_voice[i].voice = i;
|
||||
|
||||
// For OPL3 hardware we need to set up the panning in OPL2 modes
|
||||
if (_iface == SND_HWDEP_IFACE_OPL3) {
|
||||
if (_type == Config::kDualOpl2) {
|
||||
for (int i = 0; i < kOpl2Operators; ++i)
|
||||
_oper[i].left = 1; // FIXME below
|
||||
for (int i = kOpl2Operators; i < kOperators; ++i)
|
||||
_oper[i].right = 1;
|
||||
} else if (_type == Config::kOpl2) {
|
||||
for (int i = 0; i < kOpl2Operators; ++i) {
|
||||
_oper[i].left = 1;
|
||||
_oper[i].right = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OPL::init() {
|
||||
clear();
|
||||
|
||||
int card = -1;
|
||||
snd_ctl_t *ctl;
|
||||
snd_hwdep_info_t *info;
|
||||
snd_hwdep_info_alloca(&info);
|
||||
|
||||
int iface = SND_HWDEP_IFACE_OPL3;
|
||||
if (_type == Config::kOpl2)
|
||||
iface = SND_HWDEP_IFACE_OPL2;
|
||||
|
||||
// Look for OPL hwdep interface
|
||||
while (!snd_card_next(&card) && card >= 0) {
|
||||
int dev = -1;
|
||||
Common::String name = Common::String::format("hw:%d", card);
|
||||
|
||||
if (snd_ctl_open(&ctl, name.c_str(), 0) < 0)
|
||||
continue;
|
||||
|
||||
while (!snd_ctl_hwdep_next_device(ctl, &dev) && dev >= 0) {
|
||||
name = Common::String::format("hw:%d,%d", card, dev);
|
||||
|
||||
if (snd_hwdep_open(&_opl, name.c_str(), SND_HWDEP_OPEN_WRITE) < 0)
|
||||
continue;
|
||||
|
||||
if (!snd_hwdep_info(_opl, info)) {
|
||||
int found = snd_hwdep_info_get_iface(info);
|
||||
// OPL3 can be used for (Dual) OPL2 mode
|
||||
if (found == iface || found == SND_HWDEP_IFACE_OPL3) {
|
||||
snd_ctl_close(ctl);
|
||||
_iface = found;
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrong interface, try next device
|
||||
snd_hwdep_close(_opl);
|
||||
_opl = nullptr;
|
||||
}
|
||||
|
||||
snd_ctl_close(ctl);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OPL::reset() {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr);
|
||||
if (_iface == SND_HWDEP_IFACE_OPL3)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_MODE, (void *)SNDRV_DM_FM_MODE_OPL3);
|
||||
|
||||
clear();
|
||||
|
||||
// Sync up with the hardware
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kVoices : kOpl2Voices); ++i)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[i]);
|
||||
for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kOperators : kOpl2Operators); ++i)
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[i]);
|
||||
}
|
||||
|
||||
void OPL::write(int port, int val) {
|
||||
val &= 0xff;
|
||||
int chip = (port & 2) >> 1;
|
||||
|
||||
if (port & 1) {
|
||||
switch(_type) {
|
||||
case Config::kOpl2:
|
||||
writeOplReg(0, index[0], val);
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
if (port & 8) {
|
||||
writeOplReg(0, index[0], val);
|
||||
writeOplReg(1, index[1], val);
|
||||
} else
|
||||
writeOplReg(chip, index[chip], val);
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
writeOplReg(chip, index[chip], val);
|
||||
}
|
||||
} else {
|
||||
switch(_type) {
|
||||
case Config::kOpl2:
|
||||
index[0] = val;
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
if (port & 8) {
|
||||
index[0] = val;
|
||||
index[1] = val;
|
||||
} else
|
||||
index[chip] = val;
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
index[chip] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte OPL::read(int port) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OPL::writeReg(int r, int v) {
|
||||
switch (_type) {
|
||||
case Config::kOpl2:
|
||||
writeOplReg(0, r, v);
|
||||
break;
|
||||
case Config::kDualOpl2:
|
||||
writeOplReg(0, r, v);
|
||||
writeOplReg(1, r, v);
|
||||
break;
|
||||
case Config::kOpl3:
|
||||
writeOplReg(r >= 0x100, r & 0xff, v);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL::writeOplReg(int c, int r, int v) {
|
||||
if (r == 0x04 && c == 1 && _type == Config::kOpl3) {
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_CONNECTION, reinterpret_cast<void *>(v & 0x3f));
|
||||
} else if (r == 0x08 && c == 0) {
|
||||
_params.kbd_split = (v >> 6) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
} else if (r == 0xbd && c == 0) {
|
||||
_params.hihat = v & 0x1;
|
||||
_params.cymbal = (v >> 1) & 0x1;
|
||||
_params.tomtom = (v >> 2) & 0x1;
|
||||
_params.snare = (v >> 3) & 0x1;
|
||||
_params.bass = (v >> 4) & 0x1;
|
||||
_params.rhythm = (v >> 5) & 0x1;
|
||||
_params.vib_depth = (v >> 6) & 0x1;
|
||||
_params.am_depth = (v >> 7) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params);
|
||||
} else if (r < 0xa0 || r >= 0xe0) {
|
||||
// Operator
|
||||
int idx = regOffsetToOper[r & 0x1f];
|
||||
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (c == 1)
|
||||
idx += kOpl2Operators;
|
||||
|
||||
switch (r & 0xf0) {
|
||||
case 0x20:
|
||||
case 0x30:
|
||||
_oper[idx].harmonic = v & 0xf;
|
||||
_oper[idx].kbd_scale = (v >> 4) & 0x1;
|
||||
_oper[idx].do_sustain = (v >> 5) & 0x1;
|
||||
_oper[idx].vibrato = (v >> 6) & 0x1;
|
||||
_oper[idx].am = (v >> 7) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x40:
|
||||
case 0x50:
|
||||
_oper[idx].volume = ~v & 0x3f;
|
||||
_oper[idx].scale_level = (v >> 6) & 0x3;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x60:
|
||||
case 0x70:
|
||||
_oper[idx].decay = v & 0xf;
|
||||
_oper[idx].attack = (v >> 4) & 0xf;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
_oper[idx].release = v & 0xf;
|
||||
_oper[idx].sustain = (v >> 4) & 0xf;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
break;
|
||||
case 0xe0:
|
||||
case 0xf0:
|
||||
_oper[idx].waveform = v & (_type == Config::kOpl3 ? 0x7 : 0x3);
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]);
|
||||
}
|
||||
} else {
|
||||
// Voice
|
||||
int idx = r & 0xf;
|
||||
|
||||
if (idx >= kOpl2Voices)
|
||||
return;
|
||||
|
||||
if (c == 1)
|
||||
idx += kOpl2Voices;
|
||||
|
||||
int opIdx = voiceToOper0[idx];
|
||||
|
||||
switch (r & 0xf0) {
|
||||
case 0xa0:
|
||||
_voice[idx].fnum = (_voice[idx].fnum & 0x300) | (v & 0xff);
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
|
||||
break;
|
||||
case 0xb0:
|
||||
_voice[idx].fnum = ((v << 8) & 0x300) | (_voice[idx].fnum & 0xff);
|
||||
_voice[idx].octave = (v >> 2) & 0x7;
|
||||
_voice[idx].key_on = (v >> 5) & 0x1;
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]);
|
||||
break;
|
||||
case 0xc0:
|
||||
_oper[opIdx].connection = _oper[opIdx + 3].connection = v & 0x1;
|
||||
_oper[opIdx].feedback = _oper[opIdx + 3].feedback = (v >> 1) & 0x7;
|
||||
if (_type == Config::kOpl3) {
|
||||
_oper[opIdx].left = _oper[opIdx + 3].left = (v >> 4) & 0x1;
|
||||
_oper[opIdx].right = _oper[opIdx + 3].right = (v >> 5) & 0x1;
|
||||
}
|
||||
snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[opIdx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OPL *create(Config::OplType type) {
|
||||
return new OPL(type);
|
||||
}
|
||||
|
||||
} // End of namespace ALSA
|
||||
} // End of namespace OPL
|
343
b/audio/decoders/3do.cpp
Normal file
343
b/audio/decoders/3do.cpp
Normal file
|
@ -0,0 +1,343 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include "audio/decoders/3do.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/adpcm_intern.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Reuses ADPCM table
|
||||
#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable
|
||||
#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable
|
||||
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
warning("make3DO_ADP4Stream(): stereo currently not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_ADP4_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_ADP4_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) {
|
||||
int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex];
|
||||
int32 decodedSample = _curDecoderData.lastSample;
|
||||
int16 delta = currentStep >> 3;
|
||||
|
||||
if (compressedNibble & 1)
|
||||
delta += currentStep >> 2;
|
||||
|
||||
if (compressedNibble & 2)
|
||||
delta += currentStep >> 1;
|
||||
|
||||
if (compressedNibble & 4)
|
||||
delta += currentStep;
|
||||
|
||||
if (compressedNibble & 8) {
|
||||
decodedSample -= delta;
|
||||
} else {
|
||||
decodedSample += delta;
|
||||
}
|
||||
|
||||
_curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767);
|
||||
|
||||
_curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07];
|
||||
_curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1);
|
||||
|
||||
return _curDecoderData.lastSample;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = NULL;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = 0;
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// in case caller requests an uneven amount of samples, we will return an even amount
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4);
|
||||
decodedSamplesCount++;
|
||||
buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f);
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
static int16 audio_3DO_SDX2_SquareTable[256] = {
|
||||
-32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322,
|
||||
-27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762,
|
||||
-23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602,
|
||||
-19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842,
|
||||
-15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482,
|
||||
-12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522,
|
||||
-9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962,
|
||||
-6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802,
|
||||
-4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042,
|
||||
-2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682,
|
||||
-1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722,
|
||||
-648, -578, -512, -450, -392, -338, -288, -242, -200, -162,
|
||||
-128, -98, -72, -50, -32, -18, -8, -2, 0, 2,
|
||||
8, 18, 32, 50, 72, 98, 128, 162, 200, 242,
|
||||
288, 338, 392, 450, 512, 578, 648, 722, 800, 882,
|
||||
968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922,
|
||||
2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362,
|
||||
3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202,
|
||||
5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442,
|
||||
7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082,
|
||||
10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122,
|
||||
13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562,
|
||||
16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402,
|
||||
20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642,
|
||||
25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282,
|
||||
29768, 30258, 30752, 31250, 31752, 32258
|
||||
};
|
||||
|
||||
Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace)
|
||||
: _sampleRate(sampleRate), _stereo(stereo),
|
||||
_stream(stream, disposeAfterUse) {
|
||||
|
||||
_callerDecoderData = persistentSpace;
|
||||
memset(&_initialDecoderData, 0, sizeof(_initialDecoderData));
|
||||
_initialRead = true;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Audio3DO_SDX2_Stream::reset() {
|
||||
memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData));
|
||||
_streamBytesLeft = _stream->size();
|
||||
_stream->seek(0);
|
||||
}
|
||||
|
||||
bool Audio3DO_SDX2_Stream::rewind() {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written
|
||||
int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) {
|
||||
int8 byteCache[AUDIO_3DO_CACHE_SIZE];
|
||||
int8 *byteCachePtr = NULL;
|
||||
int byteCacheSize = 0;
|
||||
int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample
|
||||
int decodedSamplesCount = 0;
|
||||
|
||||
int8 compressedByte = 0;
|
||||
uint8 squareTableOffset = 0;
|
||||
int16 decodedSample = 0;
|
||||
|
||||
if (endOfData())
|
||||
return 0; // no more bytes left
|
||||
|
||||
if (_stereo) {
|
||||
// We expect numSamples to be even in case of Stereo audio
|
||||
assert((numSamples & 1) == 0);
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data over
|
||||
memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData));
|
||||
if (_initialRead) {
|
||||
_initialRead = false;
|
||||
memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData));
|
||||
}
|
||||
}
|
||||
|
||||
requestedBytesLeft = numSamples;
|
||||
if (requestedBytesLeft > _streamBytesLeft)
|
||||
requestedBytesLeft = _streamBytesLeft; // not enough bytes left
|
||||
|
||||
// buffering, so that direct decoding of files and such runs way faster
|
||||
while (requestedBytesLeft) {
|
||||
if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) {
|
||||
byteCacheSize = AUDIO_3DO_CACHE_SIZE;
|
||||
} else {
|
||||
byteCacheSize = requestedBytesLeft;
|
||||
}
|
||||
|
||||
requestedBytesLeft -= byteCacheSize;
|
||||
_streamBytesLeft -= byteCacheSize;
|
||||
|
||||
// Fill our byte cache
|
||||
_stream->read(byteCache, byteCacheSize);
|
||||
|
||||
byteCachePtr = byteCache;
|
||||
|
||||
if (!_stereo) {
|
||||
// Mono
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
} else {
|
||||
// Stereo
|
||||
while (byteCacheSize) {
|
||||
compressedByte = *byteCachePtr++;
|
||||
byteCacheSize--;
|
||||
squareTableOffset = compressedByte + 128;
|
||||
|
||||
if (!(decodedSamplesCount & 1)) {
|
||||
// First channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample1 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample1 = decodedSample;
|
||||
} else {
|
||||
// Second channel
|
||||
if (!(compressedByte & 1))
|
||||
_curDecoderData.lastSample2 = 0;
|
||||
|
||||
decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset];
|
||||
_curDecoderData.lastSample2 = decodedSample;
|
||||
}
|
||||
|
||||
buffer[decodedSamplesCount] = decodedSample;
|
||||
decodedSamplesCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_callerDecoderData) {
|
||||
// copy caller decoder data back
|
||||
memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData));
|
||||
}
|
||||
|
||||
return decodedSamplesCount;
|
||||
}
|
||||
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) {
|
||||
if (stereo) {
|
||||
if (stream->size() & 1) {
|
||||
warning("make3DO_SDX2Stream(): stereo data is uneven size");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioLengthMSecsPtr) {
|
||||
// Caller requires the milliseconds of audio
|
||||
uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample
|
||||
if (stereo) {
|
||||
audioLengthMSecs /= 2;
|
||||
}
|
||||
*audioLengthMSecsPtr = audioLengthMSecs;
|
||||
}
|
||||
|
||||
return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace);
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
158
b/audio/decoders/3do.h
Normal file
158
b/audio/decoders/3do.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Sound decoder used in engines:
|
||||
* - sherlock (3DO version of Serrated Scalpel)
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_3DO_SDX2_H
|
||||
#define AUDIO_3DO_SDX2_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/types.h"
|
||||
#include "common/substream.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Audio {
|
||||
|
||||
class SeekableAudioStream;
|
||||
|
||||
// amount of bytes to be used within the decoder classes as buffers
|
||||
#define AUDIO_3DO_CACHE_SIZE 1024
|
||||
|
||||
// persistent spaces
|
||||
struct audio_3DO_ADP4_PersistentSpace {
|
||||
int16 lastSample;
|
||||
int16 stepIndex;
|
||||
};
|
||||
|
||||
struct audio_3DO_SDX2_PersistentSpace {
|
||||
int16 lastSample1;
|
||||
int16 lastSample2;
|
||||
};
|
||||
|
||||
class Audio3DO_ADP4_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_ADP4_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_ADP4_PersistentSpace _curDecoderData;
|
||||
|
||||
private:
|
||||
int16 decodeSample(byte compressedNibble);
|
||||
};
|
||||
|
||||
class Audio3DO_SDX2_Stream : public RewindableAudioStream {
|
||||
public:
|
||||
Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr);
|
||||
|
||||
protected:
|
||||
const uint16 _sampleRate;
|
||||
const bool _stereo;
|
||||
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
int32 _streamBytesLeft;
|
||||
|
||||
void reset();
|
||||
bool rewind();
|
||||
bool endOfData() const { return (_stream->pos() >= _stream->size()); }
|
||||
bool isStereo() const { return _stereo; }
|
||||
int getRate() const { return _sampleRate; }
|
||||
|
||||
int readBuffer(int16 *buffer, const int numSamples);
|
||||
|
||||
bool _initialRead;
|
||||
audio_3DO_SDX2_PersistentSpace *_callerDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _initialDecoderData;
|
||||
audio_3DO_SDX2_PersistentSpace _curDecoderData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_ADP4AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
/**
|
||||
* Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream
|
||||
* from that data.
|
||||
*
|
||||
* @param stream the SeekableReadStream from which to read the 3DO SDX2 data
|
||||
* @sampleRate sample rate
|
||||
* @stereo if it's stereo or mono
|
||||
* @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds
|
||||
* @disposeAfterUse disposeAfterUse whether to delete the stream after use
|
||||
* @persistentSpacePtr pointer to the persistent space structure
|
||||
* @return a new SeekableAudioStream, or NULL, if an error occurred
|
||||
*/
|
||||
RewindableAudioStream *make3DO_SDX2AudioStream(
|
||||
Common::SeekableReadStream *stream,
|
||||
uint16 sampleRate,
|
||||
bool stereo,
|
||||
uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds
|
||||
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES,
|
||||
audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL
|
||||
);
|
||||
|
||||
} // End of namespace Audio
|
||||
|
||||
#endif
|
215
common/dcl.cpp
215
common/dcl.cpp
|
@ -30,17 +30,15 @@ namespace Common {
|
|||
|
||||
class DecompressorDCL {
|
||||
public:
|
||||
bool unpack(ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
|
||||
bool unpack(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initialize decompressor.
|
||||
* @param src source stream to read from
|
||||
* @param dest destination stream to write to
|
||||
* @param nPacked size of packed data
|
||||
* @param nUnpacked size of unpacked data
|
||||
* @param sourceStream source stream to read from
|
||||
* @param targetStream target memory stream to write to
|
||||
*/
|
||||
void init(ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked);
|
||||
void init(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize);
|
||||
|
||||
/**
|
||||
* Get a number of bits from _src stream, starting with the least
|
||||
|
@ -68,34 +66,36 @@ protected:
|
|||
|
||||
uint32 _dwBits; ///< bits buffer
|
||||
byte _nBits; ///< number of unread bits in _dwBits
|
||||
uint32 _szPacked; ///< size of the compressed data
|
||||
uint32 _szUnpacked; ///< size of the decompressed data
|
||||
uint32 _dwRead; ///< number of bytes read from _src
|
||||
uint32 _dwWrote; ///< number of bytes written to _dest
|
||||
ReadStream *_src;
|
||||
byte *_dest;
|
||||
uint32 _sourceSize; ///< size of the source stream
|
||||
uint32 _targetSize; ///< size of the target stream (if fixed)
|
||||
bool _targetFixedSize; ///< if target stream is fixed size or dynamic size
|
||||
uint32 _bytesRead; ///< number of bytes read from _sourceStream
|
||||
uint32 _bytesWritten; ///< number of bytes written to _targetStream
|
||||
SeekableReadStream *_sourceStream;
|
||||
WriteStream *_targetStream;
|
||||
};
|
||||
|
||||
void DecompressorDCL::init(ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
_src = src;
|
||||
_dest = dest;
|
||||
_szPacked = nPacked;
|
||||
_szUnpacked = nUnpacked;
|
||||
void DecompressorDCL::init(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize) {
|
||||
_sourceStream = sourceStream;
|
||||
_targetStream = targetStream;
|
||||
_sourceSize = sourceStream->size();
|
||||
_targetSize = targetSize;
|
||||
_targetFixedSize = targetFixedSize;
|
||||
_nBits = 0;
|
||||
_dwRead = _dwWrote = 0;
|
||||
_bytesRead = _bytesWritten = 0;
|
||||
_dwBits = 0;
|
||||
}
|
||||
|
||||
void DecompressorDCL::fetchBitsLSB() {
|
||||
while (_nBits <= 24) {
|
||||
_dwBits |= ((uint32)_src->readByte()) << _nBits;
|
||||
_dwBits |= ((uint32)_sourceStream->readByte()) << _nBits;
|
||||
_nBits += 8;
|
||||
_dwRead++;
|
||||
_bytesRead++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 DecompressorDCL::getBitsLSB(int n) {
|
||||
// fetching more data to buffer if needed
|
||||
// Fetching more data to buffer if needed
|
||||
if (_nBits < n)
|
||||
fetchBitsLSB();
|
||||
uint32 ret = (_dwBits & ~((~0) << n));
|
||||
|
@ -109,7 +109,8 @@ byte DecompressorDCL::getByteLSB() {
|
|||
}
|
||||
|
||||
void DecompressorDCL::putByte(byte b) {
|
||||
_dest[_dwWrote++] = b;
|
||||
_targetStream->writeByte(b);
|
||||
_bytesWritten++;
|
||||
}
|
||||
|
||||
#define HUFFMAN_LEAF 0x40000000
|
||||
|
@ -331,97 +332,189 @@ int DecompressorDCL::huffman_lookup(const int *tree) {
|
|||
#define DCL_BINARY_MODE 0
|
||||
#define DCL_ASCII_MODE 1
|
||||
|
||||
bool DecompressorDCL::unpack(ReadStream *src, byte *dest, uint32 nPacked, uint32 nUnpacked) {
|
||||
init(src, dest, nPacked, nUnpacked);
|
||||
#define MIDI_SETUP_BUNDLE_FILE_MAXIMUM_DICTIONARY_SIZE 4096
|
||||
|
||||
bool DecompressorDCL::unpack(SeekableReadStream *sourceStream, WriteStream *targetStream, uint32 targetSize, bool targetFixedSize) {
|
||||
byte dictionary[MIDI_SETUP_BUNDLE_FILE_MAXIMUM_DICTIONARY_SIZE];
|
||||
uint16 dictionaryPos = 0;
|
||||
uint16 dictionarySize = 0;
|
||||
uint16 dictionaryMask = 0;
|
||||
int value;
|
||||
uint32 val_distance, val_length;
|
||||
uint16 tokenOffset = 0;
|
||||
uint16 tokenLength = 0;
|
||||
|
||||
int mode = getByteLSB();
|
||||
int length_param = getByteLSB();
|
||||
init(sourceStream, targetStream, targetSize, targetFixedSize);
|
||||
|
||||
byte mode = getByteLSB();
|
||||
byte dictionaryType = getByteLSB();
|
||||
|
||||
if (mode != DCL_BINARY_MODE && mode != DCL_ASCII_MODE) {
|
||||
warning("DCL-INFLATE: Error: Encountered mode %02x, expected 00 or 01", mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length_param < 3 || length_param > 6)
|
||||
warning("Unexpected length_param value %d (expected in [3,6])", length_param);
|
||||
// TODO: original code supported 3 as well???
|
||||
// Was this an accident or on purpose? And the original code did just give out a warning
|
||||
// and didn't error out at all
|
||||
switch (dictionaryType) {
|
||||
case 4:
|
||||
dictionarySize = 1024;
|
||||
break;
|
||||
case 5:
|
||||
dictionarySize = 2048;
|
||||
break;
|
||||
case 6:
|
||||
dictionarySize = 4096;
|
||||
break;
|
||||
default:
|
||||
warning("DCL-INFLATE: Error: unsupported dictionary type %02x", dictionaryType);
|
||||
return false;
|
||||
}
|
||||
dictionaryMask = dictionarySize - 1;
|
||||
|
||||
while (_dwWrote < _szUnpacked) {
|
||||
while ((!targetFixedSize) || (_bytesWritten < _targetSize)) {
|
||||
if (getBitsLSB(1)) { // (length,distance) pair
|
||||
value = huffman_lookup(length_tree);
|
||||
|
||||
if (value < 8)
|
||||
val_length = value + 2;
|
||||
tokenLength = value + 2;
|
||||
else
|
||||
val_length = 8 + (1 << (value - 7)) + getBitsLSB(value - 7);
|
||||
tokenLength = 8 + (1 << (value - 7)) + getBitsLSB(value - 7);
|
||||
|
||||
if (tokenLength == 519)
|
||||
break; // End of stream signal
|
||||
|
||||
debug(8, " | ");
|
||||
|
||||
value = huffman_lookup(distance_tree);
|
||||
|
||||
if (val_length == 2)
|
||||
val_distance = (value << 2) | getBitsLSB(2);
|
||||
if (tokenLength == 2)
|
||||
tokenOffset = (value << 2) | getBitsLSB(2);
|
||||
else
|
||||
val_distance = (value << length_param) | getBitsLSB(length_param);
|
||||
val_distance ++;
|
||||
tokenOffset = (value << dictionaryType) | getBitsLSB(dictionaryType);
|
||||
tokenOffset++;
|
||||
|
||||
debug(8, "\nCOPY(%d from %d)\n", val_length, val_distance);
|
||||
debug(8, "\nCOPY(%d from %d)\n", tokenLength, tokenOffset);
|
||||
|
||||
if (val_length + _dwWrote > _szUnpacked) {
|
||||
if (_targetFixedSize) {
|
||||
if (tokenLength + _bytesWritten > _targetSize) {
|
||||
warning("DCL-INFLATE Error: Write out of bounds while copying %d bytes (declared unpacked size is %d bytes, current is %d + %d bytes)",
|
||||
val_length, _szUnpacked, _dwWrote, val_length);
|
||||
tokenLength, _targetSize, _bytesWritten, tokenLength);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_dwWrote < val_distance) {
|
||||
if (_bytesWritten < tokenOffset) {
|
||||
warning("DCL-INFLATE Error: Attempt to copy from before beginning of input stream (declared unpacked size is %d bytes, current is %d bytes)",
|
||||
_szUnpacked, _dwWrote);
|
||||
_targetSize, _bytesWritten);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (val_length) {
|
||||
uint32 copy_length = (val_length > val_distance) ? val_distance : val_length;
|
||||
assert(val_distance >= copy_length);
|
||||
uint32 pos = _dwWrote - val_distance;
|
||||
for (uint32 i = 0; i < copy_length; i++)
|
||||
putByte(dest[pos + i]);
|
||||
uint16 dictionaryBaseIndex = (dictionaryPos - tokenOffset) & dictionaryMask;
|
||||
uint16 dictionaryIndex = dictionaryBaseIndex;
|
||||
uint16 dictionaryNextIndex = dictionaryPos;
|
||||
|
||||
for (uint32 i = 0; i < copy_length; i++)
|
||||
debug(9, "\33[32;31m%02x\33[37;37m ", dest[pos + i]);
|
||||
debug(9, "\n");
|
||||
while (tokenLength) {
|
||||
// Write byte from dictionary
|
||||
putByte(dictionary[dictionaryIndex]);
|
||||
debug(9, "\33[32;31m%02x\33[37;37m ", dictionary[dictionaryIndex]);
|
||||
|
||||
val_length -= copy_length;
|
||||
val_distance += copy_length;
|
||||
dictionary[dictionaryNextIndex] = dictionary[dictionaryIndex];
|
||||
|
||||
dictionaryNextIndex = (dictionaryNextIndex + 1) & dictionaryMask;
|
||||
dictionaryIndex = (dictionaryIndex + 1) & dictionaryMask;
|
||||
|
||||
if (dictionaryIndex == dictionaryPos)
|
||||
dictionaryIndex = dictionaryBaseIndex;
|
||||
if (dictionaryNextIndex == dictionarySize)
|
||||
dictionaryNextIndex = 0;
|
||||
|
||||
tokenLength--;
|
||||
}
|
||||
dictionaryPos = dictionaryNextIndex;
|
||||
debug(9, "\n");
|
||||
|
||||
} else { // Copy byte verbatim
|
||||
value = (mode == DCL_ASCII_MODE) ? huffman_lookup(ascii_tree) : getByteLSB();
|
||||
putByte(value);
|
||||
|
||||
// Also remember it inside dictionary
|
||||
dictionary[dictionaryPos] = value;
|
||||
dictionaryPos++;
|
||||
if (dictionaryPos >= dictionarySize)
|
||||
dictionaryPos = 0;
|
||||
|
||||
debug(9, "\33[32;31m%02x \33[37;37m", value);
|
||||
}
|
||||
}
|
||||
|
||||
return _dwWrote == _szUnpacked;
|
||||
if (_targetFixedSize) {
|
||||
return _bytesWritten == _targetSize;
|
||||
}
|
||||
return true; // For targets featuring dynamic size we always succeed
|
||||
}
|
||||
|
||||
bool decompressDCL(ReadStream *src, byte *dest, uint32 packedSize, uint32 unpackedSize) {
|
||||
bool success = false;
|
||||
DecompressorDCL dcl;
|
||||
|
||||
if (!src || !dest)
|
||||
return false;
|
||||
|
||||
DecompressorDCL dcl;
|
||||
return dcl.unpack(src, dest, packedSize, unpackedSize);
|
||||
byte *sourceBufferPtr = (byte *)malloc(packedSize);
|
||||
if (!sourceBufferPtr)
|
||||
return false;
|
||||
|
||||
// Read source into memory
|
||||
src->read(sourceBufferPtr, packedSize);
|
||||
|
||||
Common::MemoryReadStream *sourceStream = new MemoryReadStream(sourceBufferPtr, packedSize, DisposeAfterUse::NO);
|
||||
Common::MemoryWriteStream *targetStream = new MemoryWriteStream(dest, unpackedSize);
|
||||
|
||||
success = dcl.unpack(sourceStream, targetStream, unpackedSize, true);
|
||||
delete sourceStream;
|
||||
delete targetStream;
|
||||
return success;
|
||||
}
|
||||
|
||||
SeekableReadStream *decompressDCL(ReadStream *src, uint32 packedSize, uint32 unpackedSize) {
|
||||
byte *data = (byte *)malloc(unpackedSize);
|
||||
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream, uint32 packedSize, uint32 unpackedSize) {
|
||||
bool success = false;
|
||||
byte *targetPtr = nullptr;
|
||||
Common::MemoryWriteStream *targetStream;
|
||||
DecompressorDCL dcl;
|
||||
|
||||
if (decompressDCL(src, data, packedSize, unpackedSize))
|
||||
return new MemoryReadStream(data, unpackedSize, DisposeAfterUse::YES);
|
||||
targetPtr = (byte *)malloc(unpackedSize);
|
||||
if (!targetPtr)
|
||||
return nullptr;
|
||||
|
||||
free(data);
|
||||
return 0;
|
||||
targetStream = new MemoryWriteStream(targetPtr, unpackedSize);
|
||||
|
||||
success = dcl.unpack(sourceStream, targetStream, unpackedSize, true);
|
||||
delete targetStream;
|
||||
|
||||
if (!success) {
|
||||
free(targetPtr);
|
||||
return nullptr;
|
||||
}
|
||||
return new MemoryReadStream(targetPtr, unpackedSize, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
// This one figures out the unpacked size by itself
|
||||
// Needed for at least Simon 2, because the unpacked size is not stored anywhere
|
||||
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream) {
|
||||
Common::MemoryWriteStreamDynamic *targetStream;
|
||||
DecompressorDCL dcl;
|
||||
|
||||
targetStream = new MemoryWriteStreamDynamic(DisposeAfterUse::NO);
|
||||
|
||||
if (dcl.unpack(sourceStream, targetStream, 0, false)) {
|
||||
byte *targetPtr = targetStream->getData();
|
||||
uint32 unpackedSize = targetStream->size();
|
||||
delete targetStream;
|
||||
return new MemoryReadStream(targetPtr, unpackedSize, DisposeAfterUse::YES);
|
||||
}
|
||||
delete targetStream;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
||||
|
|
17
common/dcl.h
17
common/dcl.h
|
@ -22,7 +22,8 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* PKWARE DCL ("explode") decompressor used in engines:
|
||||
* PKWARE DCL ("explode") ("PKWARE data compression library") decompressor used in engines:
|
||||
* - agos (exclusively for Simon 2 setup.shr file)
|
||||
* - mohawk
|
||||
* - sci
|
||||
*/
|
||||
|
@ -38,16 +39,22 @@ class ReadStream;
|
|||
class SeekableReadStream;
|
||||
|
||||
/**
|
||||
* Try to decompress a PKWARE DCL compressed stream. Returns true if
|
||||
* Try to decompress a PKWARE DCL (PKWARE data compression library) compressed stream. Returns true if
|
||||
* successful.
|
||||
*/
|
||||
bool decompressDCL(ReadStream *src, byte *dest, uint32 packedSize, uint32 unpackedSize);
|
||||
bool decompressDCL(ReadStream *sourceStream, byte *dest, uint32 packedSize, uint32 unpackedSize);
|
||||
|
||||
/**
|
||||
* Try to decompress a PKWARE DCL compressed stream. Returns a valid pointer
|
||||
* Try to decompress a PKWARE DCL (PKWARE data compression library) compressed stream. Returns a valid pointer
|
||||
* if successful and 0 otherwise.
|
||||
*/
|
||||
SeekableReadStream *decompressDCL(ReadStream *src, uint32 packedSize, uint32 unpackedSize);
|
||||
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream, uint32 packedSize, uint32 unpackedSize);
|
||||
|
||||
/**
|
||||
* Try to decompress a PKWARE DCL (PKWARE data compression library) compressed stream. Returns a valid pointer
|
||||
* if successful and 0 otherwise. This method is meant for cases, where the unpacked size is not known.
|
||||
*/
|
||||
SeekableReadStream *decompressDCL(SeekableReadStream *sourceStream);
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
|
|
|
@ -56,6 +56,10 @@ FFT::FFT(int bits, int inverse) : _bits(bits), _inverse(inverse) {
|
|||
}
|
||||
|
||||
FFT::~FFT() {
|
||||
for (int i = 0; i < ARRAYSIZE(_cosTables); i++) {
|
||||
delete _cosTables[i];
|
||||
}
|
||||
|
||||
delete[] _revTab;
|
||||
delete[] _expTab;
|
||||
delete[] _tmpBuf;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
//#define ENABLE_XCODE
|
||||
#define ENABLE_XCODE
|
||||
|
||||
// HACK to allow building with the SDL backend on MinGW
|
||||
// see bug #1800764 "TOOLS: MinGW tools building broken"
|
||||
|
@ -336,7 +336,12 @@ int main(int argc, char *argv[]) {
|
|||
setup.defines.splice(setup.defines.begin(), featureDefines);
|
||||
|
||||
// Windows only has support for the SDL backend, so we hardcode it here (along with winmm)
|
||||
if (projectType != kProjectXcode) {
|
||||
setup.defines.push_back("WIN32");
|
||||
} else {
|
||||
setup.defines.push_back("POSIX");
|
||||
setup.defines.push_back("MACOSX"); // This will break iOS, but allows OS X to catch up on browser_osx.
|
||||
}
|
||||
setup.defines.push_back("SDL_BACKEND");
|
||||
if (!useSDL2) {
|
||||
cout << "\nLinking to SDL 1.2\n\n";
|
||||
|
@ -410,7 +415,6 @@ int main(int argc, char *argv[]) {
|
|||
globalWarnings.push_back("-Wwrite-strings");
|
||||
// The following are not warnings at all... We should consider adding them to
|
||||
// a different list of parameters.
|
||||
globalWarnings.push_back("-fno-rtti");
|
||||
globalWarnings.push_back("-fno-exceptions");
|
||||
globalWarnings.push_back("-fcheck-new");
|
||||
|
||||
|
@ -572,7 +576,7 @@ int main(int argc, char *argv[]) {
|
|||
globalWarnings.push_back("-fno-exceptions");
|
||||
globalWarnings.push_back("-fcheck-new");
|
||||
|
||||
provider = new CreateProjectTool::XCodeProvider(globalWarnings, projectWarnings);
|
||||
provider = new CreateProjectTool::XcodeProvider(globalWarnings, projectWarnings);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1039,7 +1043,7 @@ bool producesObjectFile(const std::string &fileName) {
|
|||
std::string n, ext;
|
||||
splitFilename(fileName, n, ext);
|
||||
|
||||
if (ext == "cpp" || ext == "c" || ext == "asm")
|
||||
if (ext == "cpp" || ext == "c" || ext == "asm" || ext == "m" || ext == "mm")
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
@ -1279,8 +1283,9 @@ void ProjectProvider::createProject(BuildSetup &setup) {
|
|||
for (UUIDMap::const_iterator i = _uuidMap.begin(); i != _uuidMap.end(); ++i) {
|
||||
if (i->first == setup.projectName)
|
||||
continue;
|
||||
|
||||
// Retain the files between engines if we're creating a single project
|
||||
in.clear(); ex.clear();
|
||||
|
||||
const std::string moduleDir = setup.srcDir + targetFolder + i->first;
|
||||
|
||||
createModuleList(moduleDir, setup.defines, setup.testDirs, in, ex);
|
||||
|
@ -1290,7 +1295,6 @@ void ProjectProvider::createProject(BuildSetup &setup) {
|
|||
if (setup.tests) {
|
||||
// Create the main project file.
|
||||
in.clear(); ex.clear();
|
||||
|
||||
createModuleList(setup.srcDir + "/backends", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/backends/platform/sdl", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/base", setup.defines, setup.testDirs, in, ex);
|
||||
|
@ -1305,7 +1309,6 @@ void ProjectProvider::createProject(BuildSetup &setup) {
|
|||
} else if (!setup.devTools) {
|
||||
// Last but not least create the main project file.
|
||||
in.clear(); ex.clear();
|
||||
|
||||
// File list for the Project file
|
||||
createModuleList(setup.srcDir + "/backends", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/backends/platform/sdl", setup.defines, setup.testDirs, in, ex);
|
||||
|
|
|
@ -30,6 +30,14 @@ namespace CreateProjectTool {
|
|||
|
||||
#define DEBUG_XCODE_HASH 0
|
||||
|
||||
#ifdef ENABLE_IOS
|
||||
#define IOS_TARGET 0
|
||||
#define OSX_TARGET 1
|
||||
#define SIM_TARGET 2
|
||||
#else
|
||||
#define OSX_TARGET 0
|
||||
#endif
|
||||
|
||||
#define ADD_DEFINE(defines, name) \
|
||||
defines.push_back(name);
|
||||
|
||||
|
@ -54,39 +62,172 @@ namespace CreateProjectTool {
|
|||
#define REMOVE_SETTING(config, key) \
|
||||
config.settings.erase(key);
|
||||
|
||||
#define ADD_BUILD_FILE(id, name, comment) { \
|
||||
#define ADD_BUILD_FILE(id, name, fileRefId, comment) { \
|
||||
Object *buildFile = new Object(this, id, name, "PBXBuildFile", "PBXBuildFile", comment); \
|
||||
buildFile->addProperty("fileRef", getHash(name), name, SettingsNoValue); \
|
||||
buildFile->addProperty("fileRef", fileRefId, name, SettingsNoValue); \
|
||||
_buildFile.add(buildFile); \
|
||||
_buildFile.flags = SettingsSingleItem; \
|
||||
}
|
||||
|
||||
#define ADD_FILE_REFERENCE(name, properties) { \
|
||||
Object *fileRef = new Object(this, name, name, "PBXFileReference", "PBXFileReference", name); \
|
||||
#define ADD_FILE_REFERENCE(id, name, properties) { \
|
||||
Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name); \
|
||||
if (!properties.fileEncoding.empty()) fileRef->addProperty("fileEncoding", properties.fileEncoding, "", SettingsNoValue); \
|
||||
if (!properties.lastKnownFileType.empty()) fileRef->addProperty("lastKnownFileType", properties.lastKnownFileType, "", SettingsNoValue); \
|
||||
if (!properties.fileName.empty()) fileRef->addProperty("name", properties.fileName, "", SettingsNoValue); \
|
||||
if (!properties.filePath.empty()) fileRef->addProperty("path", properties.filePath, "", SettingsNoValue); \
|
||||
if (!properties.lastKnownFileType.empty()) fileRef->addProperty("lastKnownFileType", properties.lastKnownFileType, "", SettingsNoValue|SettingsQuoteVariable); \
|
||||
if (!properties.fileName.empty()) fileRef->addProperty("name", properties.fileName, "", SettingsNoValue|SettingsQuoteVariable); \
|
||||
if (!properties.filePath.empty()) fileRef->addProperty("path", properties.filePath, "", SettingsNoValue|SettingsQuoteVariable); \
|
||||
if (!properties.sourceTree.empty()) fileRef->addProperty("sourceTree", properties.sourceTree, "", SettingsNoValue); \
|
||||
_fileReference.add(fileRef); \
|
||||
_fileReference.flags = SettingsSingleItem; \
|
||||
}
|
||||
|
||||
XCodeProvider::XCodeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version)
|
||||
: ProjectProvider(global_warnings, project_warnings, version) {
|
||||
bool producesObjectFileOnOSX(const std::string &fileName) {
|
||||
std::string n, ext;
|
||||
splitFilename(fileName, n, ext);
|
||||
|
||||
// Note that the difference between this and the general producesObjectFile is that
|
||||
// this one adds Objective-C(++), and removes asm-support.
|
||||
if (ext == "cpp" || ext == "c" || ext == "m" || ext == "mm")
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void XCodeProvider::createWorkspace(const BuildSetup &setup) {
|
||||
XcodeProvider::Group::Group(XcodeProvider *objectParent, const std::string &groupName, const std::string &uniqueName, const std::string &path) : Object(objectParent, uniqueName, groupName, "PBXGroup", "", groupName) {
|
||||
addProperty("name", name, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
addProperty("sourceTree", "<group>", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
|
||||
if (path != "") {
|
||||
addProperty("path", path, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
}
|
||||
_childOrder = 0;
|
||||
_treeName = uniqueName;
|
||||
}
|
||||
|
||||
void XcodeProvider::Group::ensureChildExists(const std::string &name) {
|
||||
std::map<std::string, Group*>::iterator it = _childGroups.find(name);
|
||||
if (it == _childGroups.end()) {
|
||||
Group *child = new Group(parent, name, this->_treeName + '/' + name, name);
|
||||
_childGroups[name] = child;
|
||||
addChildGroup(child);
|
||||
parent->_groups.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
void XcodeProvider::Group::addChildInternal(const std::string &id, const std::string &comment) {
|
||||
if (properties.find("children") == properties.end()) {
|
||||
Property children;
|
||||
children.hasOrder = true;
|
||||
children.flags = SettingsAsList;
|
||||
properties["children"] = children;
|
||||
}
|
||||
properties["children"].settings[id] = Setting("", comment + " in Sources", SettingsNoValue, 0, _childOrder++);
|
||||
if (_childOrder == 1) {
|
||||
// Force children to use () even when there is only 1 child.
|
||||
// Also this enforces the use of "," after the single item, instead of ; (see writeProperty)
|
||||
properties["children"].flags |= SettingsSingleItem;
|
||||
} else {
|
||||
properties["children"].flags ^= SettingsSingleItem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void XcodeProvider::Group::addChildGroup(const Group* group) {
|
||||
addChildInternal(parent->getHash(group->_treeName), group->_treeName);
|
||||
}
|
||||
|
||||
void XcodeProvider::Group::addChildFile(const std::string &name) {
|
||||
std::string id = "FileReference_" + _treeName + "/" + name;
|
||||
addChildInternal(parent->getHash(id), name);
|
||||
FileProperty property = FileProperty(name, name, name, "\"<group>\"");
|
||||
|
||||
parent->addFileReference(id, name, property);
|
||||
if (producesObjectFileOnOSX(name)) {
|
||||
parent->addBuildFile(_treeName + "/" + name, name, parent->getHash(id), name + " in Sources");
|
||||
}
|
||||
}
|
||||
|
||||
void XcodeProvider::Group::addChildByHash(const std::string &hash, const std::string &name) {
|
||||
addChildInternal(hash, name);
|
||||
}
|
||||
|
||||
XcodeProvider::Group *XcodeProvider::Group::getChildGroup(const std::string &name) {
|
||||
std::map<std::string, Group*>::iterator it = _childGroups.find(name);
|
||||
assert(it != _childGroups.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
XcodeProvider::Group *XcodeProvider::touchGroupsForPath(const std::string &path) {
|
||||
if (_rootSourceGroup == NULL) {
|
||||
assert (path == _projectRoot);
|
||||
_rootSourceGroup = new Group(this, "Sources", path, path);
|
||||
_groups.add(_rootSourceGroup);
|
||||
return _rootSourceGroup;
|
||||
} else {
|
||||
assert(path.find(_projectRoot) == 0);
|
||||
std::string subPath = path.substr(_projectRoot.size() + 1);
|
||||
Group *currentGroup = _rootSourceGroup;
|
||||
size_t firstPathComponent = subPath.find_first_of('/');
|
||||
// We assume here that all paths have trailing '/', otherwise this breaks.
|
||||
while (firstPathComponent != std::string::npos) {
|
||||
currentGroup->ensureChildExists(subPath.substr(0, firstPathComponent));
|
||||
currentGroup = currentGroup->getChildGroup(subPath.substr(0, firstPathComponent));
|
||||
subPath = subPath.substr(firstPathComponent + 1);
|
||||
firstPathComponent = subPath.find_first_of('/');
|
||||
}
|
||||
return currentGroup;
|
||||
}
|
||||
}
|
||||
|
||||
void XcodeProvider::addFileReference(const std::string &id, const std::string &name, FileProperty properties) {
|
||||
Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name);
|
||||
if (!properties.fileEncoding.empty()) fileRef->addProperty("fileEncoding", properties.fileEncoding, "", SettingsNoValue);
|
||||
if (!properties.lastKnownFileType.empty()) fileRef->addProperty("lastKnownFileType", properties.lastKnownFileType, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
if (!properties.fileName.empty()) fileRef->addProperty("name", properties.fileName, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
if (!properties.filePath.empty()) fileRef->addProperty("path", properties.filePath, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
if (!properties.sourceTree.empty()) fileRef->addProperty("sourceTree", properties.sourceTree, "", SettingsNoValue);
|
||||
_fileReference.add(fileRef);
|
||||
_fileReference.flags = SettingsSingleItem;
|
||||
}
|
||||
|
||||
void XcodeProvider::addProductFileReference(const std::string &id, const std::string &name) {
|
||||
Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name);
|
||||
fileRef->addProperty("explicitFileType", "compiled.mach-o.executable", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
fileRef->addProperty("includeInIndex", "0", "", SettingsNoValue);
|
||||
fileRef->addProperty("path", name, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
fileRef->addProperty("sourceTree", "BUILT_PRODUCTS_DIR", "", SettingsNoValue);
|
||||
_fileReference.add(fileRef);
|
||||
_fileReference.flags = SettingsSingleItem;
|
||||
}
|
||||
|
||||
void XcodeProvider::addBuildFile(const std::string &id, const std::string &name, const std::string &fileRefId, const std::string &comment) {
|
||||
|
||||
Object *buildFile = new Object(this, id, name, "PBXBuildFile", "PBXBuildFile", comment);
|
||||
buildFile->addProperty("fileRef", fileRefId, name, SettingsNoValue);
|
||||
_buildFile.add(buildFile);
|
||||
_buildFile.flags = SettingsSingleItem;
|
||||
}
|
||||
|
||||
XcodeProvider::XcodeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version)
|
||||
: ProjectProvider(global_warnings, project_warnings, version) {
|
||||
_rootSourceGroup = NULL;
|
||||
}
|
||||
|
||||
void XcodeProvider::createWorkspace(const BuildSetup &setup) {
|
||||
// Create project folder
|
||||
std::string workspace = setup.outputDir + '/' + PROJECT_NAME ".xcodeproj";
|
||||
createDirectory(workspace);
|
||||
_projectRoot = setup.srcDir;
|
||||
touchGroupsForPath(_projectRoot);
|
||||
|
||||
// Setup global objects
|
||||
setupDefines(setup);
|
||||
#ifdef ENABLE_IOS
|
||||
_targets.push_back(PROJECT_DESCRIPTION "-iPhone");
|
||||
#endif
|
||||
_targets.push_back(PROJECT_DESCRIPTION "-OS X");
|
||||
#ifdef ENABLE_IOS
|
||||
_targets.push_back(PROJECT_DESCRIPTION "-Simulator");
|
||||
|
||||
#endif
|
||||
setupCopyFilesBuildPhase();
|
||||
setupFrameworksBuildPhase();
|
||||
setupNativeTarget();
|
||||
|
@ -97,7 +238,7 @@ void XCodeProvider::createWorkspace(const BuildSetup &setup) {
|
|||
|
||||
// We are done with constructing all the object graph and we got through every project, output the main project file
|
||||
// (this is kind of a hack since other providers use separate project files)
|
||||
void XCodeProvider::createOtherBuildFiles(const BuildSetup &setup) {
|
||||
void XcodeProvider::createOtherBuildFiles(const BuildSetup &setup) {
|
||||
// This needs to be done at the end when all build files have been accounted for
|
||||
setupSourcesBuildPhase();
|
||||
|
||||
|
@ -105,7 +246,7 @@ void XCodeProvider::createOtherBuildFiles(const BuildSetup &setup) {
|
|||
}
|
||||
|
||||
// Store information about a project here, for use at the end
|
||||
void XCodeProvider::createProjectFile(const std::string &, const std::string &, const BuildSetup &setup, const std::string &moduleDir,
|
||||
void XcodeProvider::createProjectFile(const std::string &, const std::string &, const BuildSetup &setup, const std::string &moduleDir,
|
||||
const StringList &includeList, const StringList &excludeList) {
|
||||
std::string modulePath;
|
||||
if (!moduleDir.compare(0, setup.srcDir.size(), setup.srcDir)) {
|
||||
|
@ -124,7 +265,7 @@ void XCodeProvider::createProjectFile(const std::string &, const std::string &,
|
|||
//////////////////////////////////////////////////////////////////////////
|
||||
// Main Project file
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void XCodeProvider::ouputMainProjectFile(const BuildSetup &setup) {
|
||||
void XcodeProvider::ouputMainProjectFile(const BuildSetup &setup) {
|
||||
std::ofstream project((setup.outputDir + '/' + PROJECT_NAME ".xcodeproj" + '/' + "project.pbxproj").c_str());
|
||||
if (!project)
|
||||
error("Could not open \"" + setup.outputDir + '/' + PROJECT_NAME ".xcodeproj" + '/' + "project.pbxproj\" for writing");
|
||||
|
@ -164,92 +305,93 @@ void XCodeProvider::ouputMainProjectFile(const BuildSetup &setup) {
|
|||
//////////////////////////////////////////////////////////////////////////
|
||||
// Files
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void XCodeProvider::writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation,
|
||||
void XcodeProvider::writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation,
|
||||
const StringList &duplicate, const std::string &objPrefix, const std::string &filePrefix) {
|
||||
|
||||
// Add comments for shared lists
|
||||
_buildFile.comment = "PBXBuildFile";
|
||||
_fileReference.comment = "PBXFileReference";
|
||||
|
||||
// Init root group
|
||||
_groups.comment = "PBXGroup";
|
||||
|
||||
// Create group
|
||||
std::string name = getLastPathComponent(dir.name);
|
||||
Object *group = new Object(this, "PBXGroup_" + name , "PBXGroup", "PBXGroup", "", name);
|
||||
|
||||
// List of children
|
||||
Property children;
|
||||
children.hasOrder = true;
|
||||
children.flags = SettingsAsList;
|
||||
|
||||
group->addProperty("name", name, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
group->addProperty("sourceTree", "<group>", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
|
||||
int order = 0;
|
||||
// Ensure that top-level groups are generated for i.e. engines/
|
||||
Group *group = touchGroupsForPath(filePrefix);
|
||||
for (FileNode::NodeList::const_iterator i = dir.children.begin(); i != dir.children.end(); ++i) {
|
||||
const FileNode *node = *i;
|
||||
|
||||
std::string id = "FileReference_" + node->name;
|
||||
FileProperty property = FileProperty(node->name, node->name, node->name, "<group>");
|
||||
|
||||
ADD_SETTING_ORDER_NOVALUE(children, getHash(id), node->name, order++);
|
||||
ADD_BUILD_FILE(id, node->name, node->name + " in Sources");
|
||||
ADD_FILE_REFERENCE(node->name, property);
|
||||
|
||||
// Iff it is a file, then add (build) file references. Since we're using Groups and not File References
|
||||
// for folders, we shouldn't add folders as file references, obviously.
|
||||
if (node->children.empty()) {
|
||||
group->addChildFile(node->name);
|
||||
}
|
||||
// Process child nodes
|
||||
if (!node->children.empty())
|
||||
writeFileListToProject(*node, projectFile, indentation + 1, duplicate, objPrefix + node->name + '_', filePrefix + node->name + '/');
|
||||
}
|
||||
|
||||
group->properties["children"] = children;
|
||||
|
||||
_groups.add(group);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Setup functions
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void XCodeProvider::setupCopyFilesBuildPhase() {
|
||||
void XcodeProvider::setupCopyFilesBuildPhase() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
#define DEF_SYSFRAMEWORK(framework) properties[framework".framework"] = FileProperty("wrapper.framework", framework".framework", "System/Library/Frameworks/" framework ".framework", "SDKROOT"); \
|
||||
ADD_SETTING_ORDER_NOVALUE(children, getHash(framework".framework"), framework".framework", fwOrder++);
|
||||
|
||||
#define DEF_LOCALLIB_STATIC(lib) properties[lib".a"] = FileProperty("archive.ar", lib".a", "/opt/local/lib/" lib ".a", "\"<group>\""); \
|
||||
ADD_SETTING_ORDER_NOVALUE(children, getHash(lib".a"), lib".a", fwOrder++);
|
||||
|
||||
/**
|
||||
* Sets up the frameworks build phase.
|
||||
*
|
||||
* (each native target has different build rules)
|
||||
*/
|
||||
void XCodeProvider::setupFrameworksBuildPhase() {
|
||||
void XcodeProvider::setupFrameworksBuildPhase() {
|
||||
_frameworksBuildPhase.comment = "PBXFrameworksBuildPhase";
|
||||
|
||||
// Just use a hardcoded id for the Frameworks-group
|
||||
Group *frameworksGroup = new Group(this, "Frameworks", "PBXGroup_CustomTemplate_Frameworks_", "");
|
||||
|
||||
Property children;
|
||||
children.hasOrder = true;
|
||||
children.flags = SettingsAsList;
|
||||
|
||||
// Setup framework file properties
|
||||
std::map<std::string, FileProperty> properties;
|
||||
|
||||
int fwOrder = 0;
|
||||
// Frameworks
|
||||
properties["ApplicationServices.framework"] = FileProperty("wrapper.framework", "ApplicationServices.framework", "System/Library/Frameworks/ApplicationServices.framework", "SDKROOT");
|
||||
properties["AudioToolbox.framework"] = FileProperty("wrapper.framework", "AudioToolbox.framework", "System/Library/Frameworks/AudioToolbox.framework", "SDKROOT");
|
||||
properties["AudioUnit.framework"] = FileProperty("wrapper.framework", "AudioUnit.framework", "System/Library/Frameworks/AudioUnit.framework", "SDKROOT");
|
||||
properties["Carbon.framework"] = FileProperty("wrapper.framework", "Carbon.framework", "System/Library/Frameworks/Carbon.framework", "SDKROOT");
|
||||
properties["Cocoa.framework"] = FileProperty("wrapper.framework", "Cocoa.framework", "System/Library/Frameworks/Cocoa.framework", "SDKROOT");
|
||||
properties["CoreAudio.framework"] = FileProperty("wrapper.framework", "CoreAudio.framework", "System/Library/Frameworks/CoreAudio.framework", "SDKROOT");
|
||||
properties["CoreFoundation.framework"] = FileProperty("wrapper.framework", "CoreFoundation.framework", "System/Library/Frameworks/CoreFoundation.framework", "SDKROOT");
|
||||
properties["CoreMIDI.framework"] = FileProperty("wrapper.framework", "CoreMIDI.framework", "System/Library/Frameworks/CoreMIDI.framework", "SDKROOT");
|
||||
properties["Foundation.framework"] = FileProperty("wrapper.framework", "Foundation.framework", "System/Library/Frameworks/Foundation.framework", "SDKROOT");
|
||||
properties["IOKit.framework"] = FileProperty("wrapper.framework", "IOKit.framework", "System/Library/Frameworks/IOKit.framework", "SDKROOT");
|
||||
properties["OpenGLES.framework"] = FileProperty("wrapper.framework", "OpenGLES.framework", "System/Library/Frameworks/OpenGLES.framework", "SDKROOT");
|
||||
properties["QuartzCore.framework"] = FileProperty("wrapper.framework", "QuartzCore.framework", "System/Library/Frameworks/QuartzCore.framework", "SDKROOT");
|
||||
properties["QuickTime.framework"] = FileProperty("wrapper.framework", "QuickTime.framework", "System/Library/Frameworks/QuickTime.framework", "SDKROOT");
|
||||
properties["UIKit.framework"] = FileProperty("wrapper.framework", "UIKit.framework", "System/Library/Frameworks/UIKit.framework", "SDKROOT");
|
||||
DEF_SYSFRAMEWORK("ApplicationServices");
|
||||
DEF_SYSFRAMEWORK("AudioToolbox");
|
||||
DEF_SYSFRAMEWORK("AudioUnit");
|
||||
DEF_SYSFRAMEWORK("Carbon");
|
||||
DEF_SYSFRAMEWORK("Cocoa");
|
||||
DEF_SYSFRAMEWORK("CoreAudio");
|
||||
DEF_SYSFRAMEWORK("CoreFoundation");
|
||||
DEF_SYSFRAMEWORK("CoreMIDI");
|
||||
DEF_SYSFRAMEWORK("Foundation");
|
||||
DEF_SYSFRAMEWORK("IOKit");
|
||||
DEF_SYSFRAMEWORK("OpenGLES");
|
||||
DEF_SYSFRAMEWORK("QuartzCore");
|
||||
DEF_SYSFRAMEWORK("QuickTime");
|
||||
DEF_SYSFRAMEWORK("UIKit");
|
||||
// Optionals:
|
||||
DEF_SYSFRAMEWORK("OpenGL");
|
||||
|
||||
// Local libraries
|
||||
properties["libFLAC.a"] = FileProperty("archive.ar", "libFLAC.a", "lib/libFLAC.a", "\"<group>\"");
|
||||
properties["libmad.a"] = FileProperty("archive.ar", "libmad.a", "lib/libmad.a", "\"<group>\"");
|
||||
//properties["libmpeg2.a"] = FileProperty("archive.ar", "libmpeg2.a", "lib/libmpeg2.a", "\"<group>\"");
|
||||
properties["libvorbisidec.a"] = FileProperty("archive.ar", "libvorbisidec.a", "lib/libvorbisidec.a", "\"<group>\"");
|
||||
DEF_LOCALLIB_STATIC("libFLAC");
|
||||
DEF_LOCALLIB_STATIC("libmad");
|
||||
DEF_LOCALLIB_STATIC("libvorbisidec");
|
||||
DEF_LOCALLIB_STATIC("libfreetype");
|
||||
// DEF_LOCALLIB_STATIC("libmpeg2");
|
||||
|
||||
frameworksGroup->properties["children"] = children;
|
||||
_groups.add(frameworksGroup);
|
||||
// Force this to be added as a sub-group in the root.
|
||||
_rootSourceGroup->addChildGroup(frameworksGroup);
|
||||
|
||||
|
||||
// Declare this here, as it's used across the three targets
|
||||
int order = 0;
|
||||
#ifdef ENABLE_IOS
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// iPhone
|
||||
Object *framework_iPhone = new Object(this, "PBXFrameworksBuildPhase_" + _targets[0], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
Object *framework_iPhone = new Object(this, "PBXFrameworksBuildPhase_" + _targets[IOS_TARGET], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
|
||||
framework_iPhone->addProperty("buildActionMask", "2147483647", "", SettingsNoValue);
|
||||
framework_iPhone->addProperty("runOnlyForDeploymentPostprocessing", "0", "", SettingsNoValue);
|
||||
|
@ -272,23 +414,22 @@ void XCodeProvider::setupFrameworksBuildPhase() {
|
|||
frameworks_iPhone.push_back("libvorbisidec.a");
|
||||
frameworks_iPhone.push_back("OpenGLES.framework");
|
||||
|
||||
int order = 0;
|
||||
for (ValueList::iterator framework = frameworks_iPhone.begin(); framework != frameworks_iPhone.end(); framework++) {
|
||||
std::string id = "Frameworks_" + *framework + "_iphone";
|
||||
std::string comment = *framework + " in Frameworks";
|
||||
|
||||
ADD_SETTING_ORDER_NOVALUE(iPhone_files, getHash(id), comment, order++);
|
||||
ADD_BUILD_FILE(id, *framework, comment);
|
||||
ADD_FILE_REFERENCE(*framework, properties[*framework]);
|
||||
ADD_BUILD_FILE(id, *framework, getHash(*framework), comment);
|
||||
ADD_FILE_REFERENCE(*framework, *framework, properties[*framework]);
|
||||
}
|
||||
|
||||
framework_iPhone->properties["files"] = iPhone_files;
|
||||
|
||||
_frameworksBuildPhase.add(framework_iPhone);
|
||||
|
||||
#endif
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ScummVM-OS X
|
||||
Object *framework_OSX = new Object(this, "PBXFrameworksBuildPhase_" + _targets[1], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
Object *framework_OSX = new Object(this, "PBXFrameworksBuildPhase_" + _targets[OSX_TARGET], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
|
||||
framework_OSX->addProperty("buildActionMask", "2147483647", "", SettingsNoValue);
|
||||
framework_OSX->addProperty("runOnlyForDeploymentPostprocessing", "0", "", SettingsNoValue);
|
||||
|
@ -311,6 +452,8 @@ void XCodeProvider::setupFrameworksBuildPhase() {
|
|||
frameworks_osx.push_back("IOKit.framework");
|
||||
frameworks_osx.push_back("Cocoa.framework");
|
||||
frameworks_osx.push_back("AudioUnit.framework");
|
||||
// Optionals:
|
||||
frameworks_osx.push_back("OpenGL.framework");
|
||||
|
||||
order = 0;
|
||||
for (ValueList::iterator framework = frameworks_osx.begin(); framework != frameworks_osx.end(); framework++) {
|
||||
|
@ -318,17 +461,17 @@ void XCodeProvider::setupFrameworksBuildPhase() {
|
|||
std::string comment = *framework + " in Frameworks";
|
||||
|
||||
ADD_SETTING_ORDER_NOVALUE(osx_files, getHash(id), comment, order++);
|
||||
ADD_BUILD_FILE(id, *framework, comment);
|
||||
ADD_FILE_REFERENCE(*framework, properties[*framework]);
|
||||
ADD_BUILD_FILE(id, *framework, getHash(*framework), comment);
|
||||
ADD_FILE_REFERENCE(*framework, *framework, properties[*framework]);
|
||||
}
|
||||
|
||||
framework_OSX->properties["files"] = osx_files;
|
||||
|
||||
_frameworksBuildPhase.add(framework_OSX);
|
||||
|
||||
#ifdef ENABLE_IOS
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Simulator
|
||||
Object *framework_simulator = new Object(this, "PBXFrameworksBuildPhase_" + _targets[2], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
Object *framework_simulator = new Object(this, "PBXFrameworksBuildPhase_" + _targets[SIM_TARGET], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
|
||||
|
||||
framework_simulator->addProperty("buildActionMask", "2147483647", "", SettingsNoValue);
|
||||
framework_simulator->addProperty("runOnlyForDeploymentPostprocessing", "0", "", SettingsNoValue);
|
||||
|
@ -353,20 +496,28 @@ void XCodeProvider::setupFrameworksBuildPhase() {
|
|||
std::string comment = *framework + " in Frameworks";
|
||||
|
||||
ADD_SETTING_ORDER_NOVALUE(simulator_files, getHash(id), comment, order++);
|
||||
ADD_BUILD_FILE(id, *framework, comment);
|
||||
ADD_FILE_REFERENCE(*framework, properties[*framework]);
|
||||
ADD_BUILD_FILE(id, *framework, getHash(*framework), comment);
|
||||
ADD_FILE_REFERENCE(*framework, *framework, properties[*framework]);
|
||||
}
|
||||
|
||||
framework_simulator->properties["files"] = simulator_files;
|
||||
|
||||
_frameworksBuildPhase.add(framework_simulator);
|
||||
#endif
|
||||
}
|
||||
|
||||
void XCodeProvider::setupNativeTarget() {
|
||||
void XcodeProvider::setupNativeTarget() {
|
||||
_nativeTarget.comment = "PBXNativeTarget";
|
||||
|
||||
// Just use a hardcoded id for the Products-group
|
||||
Group *productsGroup = new Group(this, "Products", "PBXGroup_CustomTemplate_Products_" , "");
|
||||
// Output native target section
|
||||
for (unsigned int i = 0; i < _targets.size(); i++) {
|
||||
#ifndef ENABLE_IOS
|
||||
if (i != OSX_TARGET) { // TODO: Fix iOS-targets, for now just disable them.
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
Object *target = new Object(this, "PBXNativeTarget_" + _targets[i], "PBXNativeTarget", "PBXNativeTarget", "", _targets[i]);
|
||||
|
||||
target->addProperty("buildConfigurationList", getHash("XCConfigurationList_" + _targets[i]), "Build configuration list for PBXNativeTarget \"" + _targets[i] + "\"", SettingsNoValue);
|
||||
|
@ -385,14 +536,18 @@ void XCodeProvider::setupNativeTarget() {
|
|||
|
||||
target->addProperty("name", _targets[i], "", SettingsNoValue|SettingsQuoteVariable);
|
||||
target->addProperty("productName", PROJECT_NAME, "", SettingsNoValue);
|
||||
addProductFileReference("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i], PROJECT_DESCRIPTION ".app");
|
||||
productsGroup->addChildByHash(getHash("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i]), PROJECT_DESCRIPTION ".app");
|
||||
target->addProperty("productReference", getHash("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i]), PROJECT_DESCRIPTION ".app", SettingsNoValue);
|
||||
target->addProperty("productType", "com.apple.product-type.application", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
|
||||
_nativeTarget.add(target);
|
||||
}
|
||||
_rootSourceGroup->addChildGroup(productsGroup);
|
||||
_groups.add(productsGroup);
|
||||
}
|
||||
|
||||
void XCodeProvider::setupProject() {
|
||||
void XcodeProvider::setupProject() {
|
||||
_project.comment = "PBXProject";
|
||||
|
||||
Object *project = new Object(this, "PBXProject", "PBXProject", "PBXProject", "", "Project object");
|
||||
|
@ -411,22 +566,30 @@ void XCodeProvider::setupProject() {
|
|||
ADD_SETTING_ORDER_NOVALUE(regions, "German", "", 3);
|
||||
project->properties["knownRegions"] = regions;
|
||||
|
||||
project->addProperty("mainGroup", getHash("PBXGroup_CustomTemplate"), "CustomTemplate", SettingsNoValue);
|
||||
project->addProperty("projectDirPath", "", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
project->addProperty("mainGroup", _rootSourceGroup->getHashRef(), "CustomTemplate", SettingsNoValue);
|
||||
project->addProperty("projectDirPath", _projectRoot, "", SettingsNoValue|SettingsQuoteVariable);
|
||||
project->addProperty("projectRoot", "", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
|
||||
// List of targets
|
||||
Property targets;
|
||||
targets.flags = SettingsAsList;
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[0])] = Setting("", _targets[0], SettingsNoValue, 0, 0);
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[1])] = Setting("", _targets[1], SettingsNoValue, 0, 1);
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[2])] = Setting("", _targets[2], SettingsNoValue, 0, 2);
|
||||
#ifdef ENABLE_IOS
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[IOS_TARGET])] = Setting("", _targets[IOS_TARGET], SettingsNoValue, 0, 0);
|
||||
#endif
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[OSX_TARGET])] = Setting("", _targets[OSX_TARGET], SettingsNoValue, 0, 1);
|
||||
#ifdef ENABLE_IOS
|
||||
targets.settings[getHash("PBXNativeTarget_" + _targets[SIM_TARGET])] = Setting("", _targets[SIM_TARGET], SettingsNoValue, 0, 2);
|
||||
#endif
|
||||
project->properties["targets"] = targets;
|
||||
#ifndef ENABLE_IOS
|
||||
// Force list even when there is only a single target
|
||||
project->properties["targets"].flags |= SettingsSingleItem;
|
||||
#endif
|
||||
|
||||
_project.add(project);
|
||||
}
|
||||
|
||||
void XCodeProvider::setupResourcesBuildPhase() {
|
||||
void XcodeProvider::setupResourcesBuildPhase() {
|
||||
_resourcesBuildPhase.comment = "PBXResourcesBuildPhase";
|
||||
|
||||
// Setup resource file properties
|
||||
|
@ -483,7 +646,7 @@ void XCodeProvider::setupResourcesBuildPhase() {
|
|||
ADD_SETTING_ORDER_NOVALUE(files, getHash(id), comment, order++);
|
||||
// TODO Fix crash when adding build file for data
|
||||
//ADD_BUILD_FILE(id, *file, comment);
|
||||
ADD_FILE_REFERENCE(*file, properties[*file]);
|
||||
ADD_FILE_REFERENCE(*file, *file, properties[*file]);
|
||||
}
|
||||
|
||||
// Add custom files depending on the target
|
||||
|
@ -503,12 +666,41 @@ void XCodeProvider::setupResourcesBuildPhase() {
|
|||
}
|
||||
}
|
||||
|
||||
void XCodeProvider::setupSourcesBuildPhase() {
|
||||
// TODO
|
||||
void XcodeProvider::setupSourcesBuildPhase() {
|
||||
_sourcesBuildPhase.comment = "PBXSourcesBuildPhase";
|
||||
|
||||
// Setup source file properties
|
||||
std::map<std::string, FileProperty> properties;
|
||||
|
||||
// Same as for containers: a rule for each native target
|
||||
for (unsigned int i = 0; i < _targets.size(); i++) {
|
||||
Object *source = new Object(this, "PBXSourcesBuildPhase_" + _targets[i], "PBXSourcesBuildPhase", "PBXSourcesBuildPhase", "", "Sources");
|
||||
|
||||
source->addProperty("buildActionMask", "2147483647", "", SettingsNoValue);
|
||||
|
||||
Property files;
|
||||
files.hasOrder = true;
|
||||
files.flags = SettingsAsList;
|
||||
|
||||
int order = 0;
|
||||
for (std::vector<Object*>::iterator file = _buildFile.objects.begin(); file !=_buildFile.objects.end(); ++file) {
|
||||
if (!producesObjectFileOnOSX((*file)->name)) {
|
||||
continue;
|
||||
}
|
||||
std::string comment = (*file)->name + " in Sources";
|
||||
ADD_SETTING_ORDER_NOVALUE(files, getHash((*file)->id), comment, order++);
|
||||
}
|
||||
|
||||
source->properties["files"] = files;
|
||||
|
||||
source->addProperty("runOnlyForDeploymentPostprocessing", "0", "", SettingsNoValue);
|
||||
|
||||
_sourcesBuildPhase.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup all build configurations
|
||||
void XCodeProvider::setupBuildConfiguration() {
|
||||
void XcodeProvider::setupBuildConfiguration() {
|
||||
|
||||
_buildConfiguration.comment = "XCBuildConfiguration";
|
||||
_buildConfiguration.flags = SettingsAsList;
|
||||
|
@ -516,9 +708,9 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
///****************************************
|
||||
// * iPhone
|
||||
// ****************************************/
|
||||
|
||||
#ifdef ENABLE_IOS
|
||||
// Debug
|
||||
Object *iPhone_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Debug", _targets[0] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Object *iPhone_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Debug", _targets[IOS_TARGET] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property iPhone_Debug;
|
||||
ADD_SETTING_QUOTE(iPhone_Debug, "ARCHS", "$(ARCHS_UNIVERSAL_IPHONE_OS)");
|
||||
ADD_SETTING_QUOTE(iPhone_Debug, "CODE_SIGN_IDENTITY", "iPhone Developer");
|
||||
|
@ -539,10 +731,10 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ADD_SETTING(iPhone_Debug, "GCC_THUMB_SUPPORT", "NO");
|
||||
ADD_SETTING(iPhone_Debug, "GCC_UNROLL_LOOPS", "YES");
|
||||
ValueList iPhone_HeaderSearchPaths;
|
||||
iPhone_HeaderSearchPaths.push_back("../../engines/");
|
||||
iPhone_HeaderSearchPaths.push_back("../../");
|
||||
iPhone_HeaderSearchPaths.push_back("$(SRCROOT)/engines/");
|
||||
iPhone_HeaderSearchPaths.push_back("$(SRCROOT)");
|
||||
iPhone_HeaderSearchPaths.push_back("include/");
|
||||
ADD_SETTING_LIST(iPhone_Debug, "HEADER_SEARCH_PATHS", iPhone_HeaderSearchPaths, SettingsAsList|SettingsNoQuote, 5);
|
||||
ADD_SETTING_LIST(iPhone_Debug, "HEADER_SEARCH_PATHS", iPhone_HeaderSearchPaths, SettingsAsList|SettingsQuoteVariable, 5);
|
||||
ADD_SETTING(iPhone_Debug, "INFOPLIST_FILE", "Info.plist");
|
||||
ValueList iPhone_LibPaths;
|
||||
iPhone_LibPaths.push_back("$(inherited)");
|
||||
|
@ -560,7 +752,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
iPhone_Debug_Object->properties["buildSettings"] = iPhone_Debug;
|
||||
|
||||
// Release
|
||||
Object *iPhone_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Release", _targets[0] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Object *iPhone_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Release", _targets[IOS_TARGET] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property iPhone_Release(iPhone_Debug);
|
||||
ADD_SETTING(iPhone_Release, "GCC_OPTIMIZATION_LEVEL", "3");
|
||||
ADD_SETTING(iPhone_Release, "COPY_PHASE_STRIP", "YES");
|
||||
|
@ -572,7 +764,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
|
||||
_buildConfiguration.add(iPhone_Debug_Object);
|
||||
_buildConfiguration.add(iPhone_Release_Object);
|
||||
|
||||
#endif
|
||||
/****************************************
|
||||
* scummvm
|
||||
****************************************/
|
||||
|
@ -581,13 +773,14 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
Object *scummvm_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_NAME "_Debug", PROJECT_NAME, "XCBuildConfiguration", "PBXProject", "Debug");
|
||||
Property scummvm_Debug;
|
||||
ADD_SETTING(scummvm_Debug, "ALWAYS_SEARCH_USER_PATHS", "NO");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "USER_HEADER_SEARCH_PATHS", "$(SRCROOT) $(SRCROOT)/engines");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "ARCHS", "$(ARCHS_STANDARD_32_BIT)");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "CODE_SIGN_IDENTITY", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE_VAR(scummvm_Debug, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "YES");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_INPUT_FILETYPE", "automatic");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
ValueList scummvm_defines(_defines);
|
||||
|
@ -601,15 +794,15 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ADD_SETTING(scummvm_Debug, "GCC_WARN_UNUSED_VARIABLE", "YES");
|
||||
ValueList scummvm_HeaderPaths;
|
||||
scummvm_HeaderPaths.push_back("include/");
|
||||
scummvm_HeaderPaths.push_back("../../engines/");
|
||||
scummvm_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(scummvm_Debug, "HEADER_SEARCH_PATHS", scummvm_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
scummvm_HeaderPaths.push_back("$(SRCROOT)/engines/");
|
||||
scummvm_HeaderPaths.push_back("$(SRCROOT)");
|
||||
ADD_SETTING_LIST(scummvm_Debug, "HEADER_SEARCH_PATHS", scummvm_HeaderPaths, SettingsQuoteVariable|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "LIBRARY_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvm_Debug, "ONLY_ACTIVE_ARCH", "YES");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_CFLAGS", "");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_LDFLAGS", "-lz");
|
||||
ADD_SETTING(scummvm_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "SDKROOT", "macosx10.6");
|
||||
ADD_SETTING(scummvm_Debug, "SDKROOT", "macosx");
|
||||
|
||||
scummvm_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
scummvm_Debug_Object->properties["buildSettings"] = scummvm_Debug;
|
||||
|
@ -633,7 +826,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
****************************************/
|
||||
|
||||
// Debug
|
||||
Object *scummvmOSX_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Debug", _targets[1] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Object *scummvmOSX_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Debug", _targets[OSX_TARGET] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property scummvmOSX_Debug;
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "ARCHS", "$(NATIVE_ARCH)");
|
||||
ADD_SETTING(scummvmOSX_Debug, "COMPRESS_PNG_FILES", "NO");
|
||||
|
@ -642,7 +835,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_RTTI", "YES");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_DYNAMIC_NO_PIC", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_FIX_AND_CONTINUE", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
|
@ -656,11 +849,12 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ValueList scummvmOSX_HeaderPaths;
|
||||
scummvmOSX_HeaderPaths.push_back("/opt/local/include/SDL");
|
||||
scummvmOSX_HeaderPaths.push_back("/opt/local/include");
|
||||
scummvmOSX_HeaderPaths.push_back("/opt/local/include/freetype2");
|
||||
scummvmOSX_HeaderPaths.push_back("include/");
|
||||
scummvmOSX_HeaderPaths.push_back("../../engines/");
|
||||
scummvmOSX_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "HEADER_SEARCH_PATHS", scummvmOSX_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "INFOPLIST_FILE", "$(SRCROOT)/../macosx/Info.plist");
|
||||
scummvmOSX_HeaderPaths.push_back("$(SRCROOT)/engines/");
|
||||
scummvmOSX_HeaderPaths.push_back("$(SRCROOT)");
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "HEADER_SEARCH_PATHS", scummvmOSX_HeaderPaths, SettingsQuoteVariable|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "INFOPLIST_FILE", "$(SRCROOT)/dists/macosx/Info.plist");
|
||||
ValueList scummvmOSX_LibPaths;
|
||||
scummvmOSX_LibPaths.push_back("/sw/lib");
|
||||
scummvmOSX_LibPaths.push_back("/opt/local/lib");
|
||||
|
@ -671,6 +865,10 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ValueList scummvmOSX_LdFlags;
|
||||
scummvmOSX_LdFlags.push_back("-lSDLmain");
|
||||
scummvmOSX_LdFlags.push_back("-logg");
|
||||
scummvmOSX_LdFlags.push_back("-lpng");
|
||||
scummvmOSX_LdFlags.push_back("-ljpeg");
|
||||
scummvmOSX_LdFlags.push_back("-ltheora");
|
||||
scummvmOSX_LdFlags.push_back("-lfreetype");
|
||||
scummvmOSX_LdFlags.push_back("-lvorbisfile");
|
||||
scummvmOSX_LdFlags.push_back("-lvorbis");
|
||||
scummvmOSX_LdFlags.push_back("-lmad");
|
||||
|
@ -685,7 +883,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
scummvmOSX_Debug_Object->properties["buildSettings"] = scummvmOSX_Debug;
|
||||
|
||||
// Release
|
||||
Object *scummvmOSX_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Release", _targets[1] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Object *scummvmOSX_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Release", _targets[OSX_TARGET] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property scummvmOSX_Release(scummvmOSX_Debug);
|
||||
ADD_SETTING(scummvmOSX_Release, "COPY_PHASE_STRIP", "YES");
|
||||
REMOVE_SETTING(scummvmOSX_Release, "GCC_DYNAMIC_NO_PIC");
|
||||
|
@ -697,13 +895,13 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
|
||||
_buildConfiguration.add(scummvmOSX_Debug_Object);
|
||||
_buildConfiguration.add(scummvmOSX_Release_Object);
|
||||
|
||||
#ifdef ENABLE_IOS
|
||||
/****************************************
|
||||
* ScummVM-Simulator
|
||||
****************************************/
|
||||
|
||||
// Debug
|
||||
Object *scummvmSimulator_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-Simulator_Debug", _targets[2] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Object *scummvmSimulator_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-Simulator_Debug", _targets[SIM_TARGET] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property scummvmSimulator_Debug(iPhone_Debug);
|
||||
ADD_SETTING_QUOTE(scummvmSimulator_Debug, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
|
||||
ADD_SETTING_LIST(scummvmSimulator_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvm_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
|
@ -715,7 +913,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
scummvmSimulator_Debug_Object->properties["buildSettings"] = scummvmSimulator_Debug;
|
||||
|
||||
// Release
|
||||
Object *scummvmSimulator_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-Simulator_Release", _targets[2] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Object *scummvmSimulator_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-Simulator_Release", _targets[SIM_TARGET] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property scummvmSimulator_Release(scummvmSimulator_Debug);
|
||||
ADD_SETTING(scummvmSimulator_Release, "COPY_PHASE_STRIP", "YES");
|
||||
ADD_SETTING(scummvmSimulator_Release, "GCC_OPTIMIZATION_LEVEL", "3");
|
||||
|
@ -732,7 +930,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
// Configuration List
|
||||
_configurationList.comment = "XCConfigurationList";
|
||||
_configurationList.flags = SettingsAsList;
|
||||
|
||||
#endif
|
||||
// Warning: This assumes we have all configurations with a Debug & Release pair
|
||||
for (std::vector<Object *>::iterator config = _buildConfiguration.objects.begin(); config != _buildConfiguration.objects.end(); config++) {
|
||||
|
||||
|
@ -758,7 +956,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Setup global defines
|
||||
void XCodeProvider::setupDefines(const BuildSetup &setup) {
|
||||
void XcodeProvider::setupDefines(const BuildSetup &setup) {
|
||||
|
||||
for (StringList::const_iterator i = setup.defines.begin(); i != setup.defines.end(); ++i) {
|
||||
if (*i == "HAVE_NASM") // Not supported on Mac (TODO: change how it's handled in main class or add it only in MSVC/CodeBlocks providers?)
|
||||
|
@ -772,7 +970,6 @@ void XCodeProvider::setupDefines(const BuildSetup &setup) {
|
|||
ADD_DEFINE(_defines, "SCUMM_LITTLE_ENDIAN");
|
||||
ADD_DEFINE(_defines, "UNIX");
|
||||
ADD_DEFINE(_defines, "SCUMMVM");
|
||||
ADD_DEFINE(_defines, "USE_TREMOR");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -780,7 +977,7 @@ void XCodeProvider::setupDefines(const BuildSetup &setup) {
|
|||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO use md5 to compute a file hash (and fall back to standard key generation if not passed a file)
|
||||
std::string XCodeProvider::getHash(std::string key) {
|
||||
std::string XcodeProvider::getHash(std::string key) {
|
||||
|
||||
#if DEBUG_XCODE_HASH
|
||||
return key;
|
||||
|
@ -800,7 +997,7 @@ std::string XCodeProvider::getHash(std::string key) {
|
|||
|
||||
bool isSeparator (char s) { return (s == '-'); }
|
||||
|
||||
std::string XCodeProvider::newHash() const {
|
||||
std::string XcodeProvider::newHash() const {
|
||||
std::string hash = createUUID();
|
||||
|
||||
// Remove { and - from UUID and resize to 96-bits uppercase hex string
|
||||
|
@ -832,7 +1029,7 @@ std::string replace(std::string input, const std::string find, std::string repla
|
|||
return input;
|
||||
}
|
||||
|
||||
std::string XCodeProvider::writeProperty(const std::string &variable, Property &prop, int flags) const {
|
||||
std::string XcodeProvider::writeProperty(const std::string &variable, Property &prop, int flags) const {
|
||||
std::string output;
|
||||
|
||||
output += (flags & SettingsSingleItem ? "" : "\t\t\t") + variable + " = ";
|
||||
|
@ -847,7 +1044,9 @@ std::string XCodeProvider::writeProperty(const std::string &variable, Property &
|
|||
|
||||
output += writeSetting((*setting).first, (*setting).second);
|
||||
|
||||
if ((prop.flags & SettingsAsList) && prop.settings.size() > 1) {
|
||||
// The combination of SettingsAsList, and SettingsSingleItem should use "," and not ";" (i.e children
|
||||
// in PBXGroup, so we special case that case here.
|
||||
if ((prop.flags & SettingsAsList) && (prop.settings.size() > 1 || (prop.flags & SettingsSingleItem))) {
|
||||
output += (prop.settings.size() > 0) ? ",\n" : "\n";
|
||||
} else {
|
||||
output += ";";
|
||||
|
@ -861,13 +1060,13 @@ std::string XCodeProvider::writeProperty(const std::string &variable, Property &
|
|||
return output;
|
||||
}
|
||||
|
||||
std::string XCodeProvider::writeSetting(const std::string &variable, std::string value, std::string comment, int flags, int indent) const {
|
||||
std::string XcodeProvider::writeSetting(const std::string &variable, std::string value, std::string comment, int flags, int indent) const {
|
||||
return writeSetting(variable, Setting(value, comment, flags, indent));
|
||||
}
|
||||
|
||||
// Heavily modified (not in a good way) function, imported from the QMake
|
||||
// XCode project generator pbuilder_pbx.cpp, writeSettings() (under LGPL 2.1)
|
||||
std::string XCodeProvider::writeSetting(const std::string &variable, const Setting &setting) const {
|
||||
std::string XcodeProvider::writeSetting(const std::string &variable, const Setting &setting) const {
|
||||
std::string output;
|
||||
const std::string quote = (setting.flags & SettingsNoQuote) ? "" : "\"";
|
||||
const std::string escape_quote = quote.empty() ? "" : "\\" + quote;
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
|
||||
namespace CreateProjectTool {
|
||||
|
||||
class XCodeProvider : public ProjectProvider {
|
||||
class XcodeProvider : public ProjectProvider {
|
||||
public:
|
||||
XCodeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version = 0);
|
||||
XcodeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version = 0);
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -45,7 +45,6 @@ protected:
|
|||
|
||||
void writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation,
|
||||
const StringList &duplicate, const std::string &objPrefix, const std::string &filePrefix);
|
||||
|
||||
private:
|
||||
enum {
|
||||
SettingsAsList = 0x01,
|
||||
|
@ -169,7 +168,7 @@ private:
|
|||
PropertyList properties; // List of object properties, including output configuration
|
||||
|
||||
// Constructs an object and add a default type property
|
||||
Object(XCodeProvider *objectParent, std::string objectId, std::string objectName, std::string objectType, std::string objectRefType = "", std::string objectComment = "")
|
||||
Object(XcodeProvider *objectParent, std::string objectId, std::string objectName, std::string objectType, std::string objectRefType = "", std::string objectComment = "")
|
||||
: id(objectId), name(objectName), refType(objectRefType), comment(objectComment), parent(objectParent) {
|
||||
assert(objectParent);
|
||||
assert(!objectId.empty());
|
||||
|
@ -210,9 +209,10 @@ private:
|
|||
return output;
|
||||
}
|
||||
|
||||
// Slight hack, to allow Group access to parent.
|
||||
protected:
|
||||
XcodeProvider *parent;
|
||||
private:
|
||||
XCodeProvider *parent;
|
||||
|
||||
// Returns the type property (should always be the first in the properties map)
|
||||
std::string getType() {
|
||||
assert(!properties.empty());
|
||||
|
@ -258,6 +258,36 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
// A class to maintain a folder-reference group-hierarchy, which together with the functionality below
|
||||
// allows for breaking up sub-paths into a chain of groups. This helps with merging engines into the
|
||||
// overall group-layout.
|
||||
class Group : public Object {
|
||||
int _childOrder;
|
||||
std::map<std::string, Group *> _childGroups;
|
||||
std::string _treeName;
|
||||
void addChildInternal(const std::string &id, const std::string &comment);
|
||||
public:
|
||||
Group(XcodeProvider *objectParent, const std::string &groupName, const std::string &uniqueName, const std::string &path);
|
||||
void addChildFile(const std::string &name);
|
||||
void addChildByHash(const std::string &hash, const std::string &name);
|
||||
// Should be passed the hash for the entry
|
||||
void addChildGroup(const Group* group);
|
||||
void ensureChildExists(const std::string &name);
|
||||
Group *getChildGroup(const std::string &name);
|
||||
std::string getHashRef() const { return parent->getHash(id); }
|
||||
};
|
||||
|
||||
// The path used by the root-source group
|
||||
std::string _projectRoot;
|
||||
// The base source group, currently also re-purposed for containing the various support-groups.
|
||||
Group *_rootSourceGroup;
|
||||
// Helper function to create the chain of groups for the various subfolders. Necessary as
|
||||
// create_project likes to start in engines/
|
||||
Group *touchGroupsForPath(const std::string &path);
|
||||
// Functionality for adding file-refs and build-files, as Group-objects need to be able to do this.
|
||||
void addFileReference(const std::string &id, const std::string &name, FileProperty properties);
|
||||
void addProductFileReference(const std::string &id, const std::string &name);
|
||||
void addBuildFile(const std::string &id, const std::string &name, const std::string &fileRefId, const std::string &comment);
|
||||
// All objects
|
||||
std::map<std::string, std::string> _hashDictionnary;
|
||||
ValueList _defines;
|
||||
|
|
|
@ -54,10 +54,11 @@ bool AIFFTrack::openSound(const Common::String &filename, const Common::String &
|
|||
return false;
|
||||
}
|
||||
_soundName = soundName;
|
||||
Audio::SeekableAudioStream *aiffStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
|
||||
Audio::RewindableAudioStream *aiffStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
|
||||
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(aiffStream);
|
||||
_stream = aiffStream;
|
||||
if (start)
|
||||
aiffStream->seek(*start);
|
||||
seekStream->seek(*start);
|
||||
if (!_stream)
|
||||
return false;
|
||||
_handle = new Audio::SoundHandle();
|
||||
|
|
|
@ -1160,9 +1160,9 @@ void LauncherDialog::updateButtons() {
|
|||
_loadButton->setEnabled(en);
|
||||
_loadButton->draw();
|
||||
}
|
||||
switchButtonsText(_addButton, "~A~dd Game...", "Mass Add...");
|
||||
switchButtonsText(_addButton, "~A~dd Game...", _s("Mass Add..."));
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
switchButtonsText(_loadButton, "~L~oad...", "Record...");
|
||||
switchButtonsText(_loadButton, "~L~oad...", _s("Record..."));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -461,11 +461,9 @@ void OptionsDialog::close() {
|
|||
|
||||
if (_oplPopUp) {
|
||||
if (_enableAudioSettings) {
|
||||
const OPL::Config::EmulatorDescription *ed = OPL::Config::getAvailable();
|
||||
while (ed->name && ed->id != (int)_oplPopUp->getSelectedTag())
|
||||
++ed;
|
||||
const OPL::Config::EmulatorDescription *ed = OPL::Config::findDriver(_oplPopUp->getSelectedTag());
|
||||
|
||||
if (ed->name)
|
||||
if (ed)
|
||||
ConfMan.set("opl_driver", ed->name, _domain);
|
||||
else
|
||||
ConfMan.removeKey("opl_driver", _domain);
|
||||
|
|
|
@ -433,10 +433,12 @@ const Graphics::Surface *CinepakDecoder::decodeFrame(Common::SeekableReadStream
|
|||
for (uint16 j = 0; j < 256; j++) {
|
||||
_curFrame.strips[i].v1_codebook[j] = _curFrame.strips[i - 1].v1_codebook[j];
|
||||
_curFrame.strips[i].v4_codebook[j] = _curFrame.strips[i - 1].v4_codebook[j];
|
||||
}
|
||||
|
||||
// Copy the QuickTime dither tables
|
||||
memcpy(_curFrame.strips[i].v1_dither, _curFrame.strips[i - 1].v1_dither, 256 * 4 * 4 * 4);
|
||||
memcpy(_curFrame.strips[i].v4_dither, _curFrame.strips[i - 1].v4_dither, 256 * 4 * 4 * 4);
|
||||
}
|
||||
}
|
||||
|
||||
_curFrame.strips[i].id = stream.readUint16BE();
|
||||
_curFrame.strips[i].length = stream.readUint16BE() - 12; // Subtract the 12 byte header
|
||||
|
|
|
@ -64,6 +64,9 @@ struct CinepakFrame {
|
|||
* Cinepak decoder.
|
||||
*
|
||||
* Used by BMP/AVI and PICT/QuickTime.
|
||||
*
|
||||
* Used in engines:
|
||||
* - sherlock
|
||||
*/
|
||||
class CinepakDecoder : public Codec {
|
||||
public:
|
||||
|
@ -88,7 +91,6 @@ private:
|
|||
|
||||
byte *_ditherPalette;
|
||||
bool _dirtyPalette;
|
||||
byte *_rgbLookup;
|
||||
byte *_colorMap;
|
||||
DitherType _ditherType;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue