Import 0.5.1

This commit is contained in:
Gregory Montoir 2023-04-15 08:44:11 +08:00
parent 1f86fdea2d
commit 100218a3c2
23 changed files with 295 additions and 175 deletions

View File

@ -1,3 +1,8 @@
* release 0.5.1
- added looping for DOS .prf music
- changed audio mixer to stereo
- updated timings for cutscenes
* release 0.5.0
- added CD-i widescreen mode (flashp*bob)
- added support for DOS .prf music (Adlib, MT32)

View File

@ -1,6 +1,6 @@
REminiscence README
Release version: 0.5.0
Release version: 0.5.1
-------------------------------------------------------------------------------

View File

@ -120,7 +120,8 @@ bool CpcPlayer::mix(int16_t *buf, int len) {
for (int i = 0; i < len; ++i) {
_sampleL = decodeSDX2(_sampleL, readSampleData());
_sampleR = decodeSDX2(_sampleR, readSampleData());
*buf++ = (_sampleL + _sampleR) / 2;
*buf++ = _sampleL;
*buf++ = _sampleR;
}
return true;
}

View File

@ -35,15 +35,16 @@ const uint8_t *Cutscene::getPolygonData() const {
return _res->_pol;
}
void Cutscene::sync() {
void Cutscene::sync(int frameDelay) {
if (_stub->_pi.quit) {
return;
}
if (_stub->_pi.dbgMask & PlayerInput::DF_FASTMODE) {
return;
}
static const int frameHz = 60;
const int32_t delay = _stub->getTimeStamp() - _tstamp;
const int32_t pause = _frameDelay * TIMER_SLICE - delay;
const int32_t pause = frameDelay * (1000 / frameHz) - delay;
if (pause > 0) {
_stub->sleep(pause);
}
@ -72,7 +73,7 @@ void Cutscene::updatePalette() {
}
void Cutscene::updateScreen() {
sync();
sync(_frameDelay - 1);
updatePalette();
SWAP(_frontPage, _backPage);
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _frontPage, _vid->_w);
@ -343,7 +344,7 @@ void Cutscene::op_waitForSync() {
_creditsSlowText = false;
} else {
_frameDelay = fetchNextCmdByte() * 4;
sync(); // XXX handle input
sync(_frameDelay);
}
}
@ -470,7 +471,7 @@ void Cutscene::op_drawCaptionText() {
} else if (_id == kCineEspions) {
// cutscene relies on drawCaptionText opcodes for timing
_frameDelay = 100;
sync();
sync(_frameDelay);
}
}
}
@ -1257,7 +1258,7 @@ void Cutscene::playText(const char *str) {
_stub->_pi.backspace = false;
break;
}
_stub->sleep(TIMER_SLICE);
_stub->sleep(30);
}
}
@ -1467,7 +1468,7 @@ void Cutscene::playSet(const uint8_t *p, int offset) {
_stub->copyRect(0, 0, _vid->_w, _vid->_h, _backPage, _vid->_w);
_stub->updateScreen(0);
const int diff = 6 * TIMER_SLICE - (_stub->getTimeStamp() - timestamp);
const int diff = 90 - (_stub->getTimeStamp() - timestamp);
_stub->sleep((diff < 16) ? 16 : diff);
_stub->processEvents();
if (_stub->_pi.backspace) {

View File

@ -19,8 +19,7 @@ struct Cutscene {
enum {
MAX_VERTICES = 128,
NUM_OPCODES = 15,
TIMER_SLICE = 15
NUM_OPCODES = 15
};
enum {
@ -58,7 +57,8 @@ struct Cutscene {
static const uint8_t _creditsDataDOS[];
static const uint8_t _creditsDataAmiga[];
static const uint16_t _creditsCutSeq[];
static const uint8_t _musicTable[];
static const uint8_t _musicTableDOS[];
static const uint8_t _musicTableAmiga[];
static const uint8_t _protectionShapeData[];
static const Text _frTextsTable[];
static const Text _enTextsTable[];
@ -122,7 +122,7 @@ struct Cutscene {
const uint8_t *getCommandData() const;
const uint8_t *getPolygonData() const;
void sync();
void sync(int delay);
void copyPalette(const uint8_t *pal, uint16_t num);
void updatePalette();
void updateScreen();

View File

@ -133,7 +133,6 @@ void Game::run() {
_skillLevel = _menu._skill;
_currentLevel = _menu._level;
}
_mix.stopMusic();
break;
case kResourceTypeAmiga:
displayTitleScreenAmiga();
@ -143,6 +142,7 @@ void Game::run() {
displayTitleScreenMac(Menu::kMacTitleScreen_Flashback);
break;
}
_mix.stopMusic();
}
if (_stub->_pi.quit) {
break;
@ -544,7 +544,18 @@ void Game::playCutscene(int id) {
}
}
}
_mix.playMusic(Cutscene::_musicTable[_cut._id]);
if (_res.isAmiga()) {
const int num = Cutscene::_musicTableAmiga[_cut._id * 2];
if (num != 0xFF) {
const int bpm = Cutscene::_musicTableAmiga[_cut._id * 2 + 1];
_mix.playMusic(num, bpm);
}
} else {
const int num = Cutscene::_musicTableDOS[_cut._id];
if (num != 0xFF) {
_mix.playMusic(num);
}
}
_cut.play();
if (id == 0xD && !_cut._interrupted) {
if (!_res.isAmiga()) {
@ -1713,6 +1724,18 @@ void Game::loadLevelData() {
char name[64];
snprintf(name, sizeof(name), "DUMP/level%d_room%02d.bmp", _currentLevel, i);
saveBMP(name, _vid._backLayer, palette, _vid._w, _vid._h);
memcpy(_vid._frontLayer, _vid._backLayer, _vid._layerSize);
for (int y = 0; y < 7; ++y) {
for (int x = 0; x < 16; ++x) {
const uint8_t num = _res._ctData[0x100 + _currentRoom * 0x70 + y * 16 + x];
char buf[16];
snprintf(buf, sizeof(buf), "%d", num);
_vid.drawString(buf, x * 16, y * 36 + 8, 0xE7);
}
}
snprintf(name, sizeof(name), "DUMP/level%d_room%02d_grid.bmp", _currentLevel, i);
saveBMP(name, _vid._frontLayer, palette, _vid._w, _vid._h);
}
}
}

View File

@ -52,8 +52,7 @@ inline int16_t S8_to_S16(int a) {
} else if (a > 127) {
return 32767;
} else {
const uint8_t u8 = (a ^ 0x80);
return ((u8 << 8) | u8) - 32768;
return ((uint8_t)a) * 257;
}
}

View File

@ -260,12 +260,16 @@ int main(int argc, char *argv[]) {
{ LANG_JP, "JP" },
{ -1, 0 }
};
for (int i = 0; languages[i].str; ++i) {
int i = 0;
for (; languages[i].str; ++i) {
if (strcasecmp(languages[i].str, optarg) == 0) {
forcedLanguage = languages[i].lang;
break;
}
}
if (!languages[i].str) {
warning("Invalid language '%s'", optarg);
}
}
break;
case 7:
@ -286,12 +290,16 @@ int main(int argc, char *argv[]) {
{ MODE_MT32, "mt32" },
{ -1, 0 }
};
for (int i = 0; drivers[i].str; ++i) {
int i = 0;
for (; drivers[i].str; ++i) {
if (strcasecmp(drivers[i].str, optarg) == 0) {
midiDriver = drivers[i].mode;
break;
}
}
if (!drivers[i].str) {
warning("Invalid MIDI driver '%s'", optarg);
}
}
break;
default:

View File

@ -6,10 +6,15 @@
#include "midi_parser.h"
#include "util.h"
static uint32_t readVLQ(File *f) {
void MidiTrack::rewind() {
endOfTrack = false;
offset = 0;
}
static uint32_t readVLQ(const uint8_t *data, uint32_t& offset) {
uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
const uint8_t b = f->readByte();
const uint8_t b = data[offset++];
value = (value << 7) | (b & 0x7F);
if ((b & 0x80) == 0) {
break;
@ -18,39 +23,42 @@ static uint32_t readVLQ(File *f) {
return value;
}
static void loadTrk(File *f, int len, MidiTrack &track) {
const uint32_t end = f->tell() + len;
while (f->tell() < end) {
MidiEvent ev;
ev.timestamp = readVLQ(f);
ev.command = f->readByte();
const MidiEvent *MidiTrack::nextEvent() {
while (offset < size) {
MidiEvent &ev = event;
ev.timestamp = readVLQ(data, offset);
ev.command = data[offset++];
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF:
case MIDI_COMMAND_NOTE_ON:
ev.param1 = f->readByte();
ev.param2 = f->readByte();
//fprintf(stdout, "\t Note %s: note %d vel %d timestamp %d\n", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Note %s: note %d vel %d timestamp %d", (((ev.command & 0xF0) == MIDI_COMMAND_NOTE_OFF) ? "Off" : "On"), ev.param1, ev.param2, ev.timestamp);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
ev.param1 = f->readByte();
//fprintf(stdout, "\t Program %d timestamp %d\n", ev.param1, ev.timestamp);
ev.param1 = data[offset++];
debug(DBG_MIDI, "Program %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_CHANNEL_PRESSURE:
ev.param1 = f->readByte();
//fprintf(stdout, "\t Channel pressure %d timestamp %d\n", ev.param1, ev.timestamp);
ev.param1 = data[offset++];
debug(DBG_MIDI, "Channel pressure %d timestamp %d", ev.param1, ev.timestamp);
break;
case MIDI_COMMAND_PITCH_BEND:
ev.param1 = f->readByte();
ev.param2 = f->readByte();
//fprintf(stdout, "\t Pitch Bend %d,%d\n", ev.param1, ev.param2);
ev.param1 = data[offset++];
ev.param2 = data[offset++];
debug(DBG_MIDI, "Pitch Bend %d,%d", ev.param1, ev.param2);
break;
case MIDI_COMMAND_SYSEX:
if (ev.command == 0xFF) {
f->readByte(); /* type */
const int len = readVLQ(f);
f->seek(f->tell() + len);
//fprintf(stdout, "\t SysEx timestamp %d\n", ev.timestamp);
const int num = data[offset++];
const int len = readVLQ(data, offset);
offset += len;
debug(DBG_MIDI, "SysEx timestamp %d", ev.timestamp);
assert(ev.timestamp == 0);
if (num == MIDI_SYSEX_END_OF_TRACK) {
endOfTrack = true;
return 0;
}
continue;
}
/* fall-through */
@ -58,14 +66,22 @@ static void loadTrk(File *f, int len, MidiTrack &track) {
warning("Unhandled MIDI command 0x%x", ev.command);
break;
}
track.events.push(ev);
return &event;
}
return 0;
}
MidiParser::MidiParser() {
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
}
void MidiParser::loadMid(File *f) {
for (int i = 0; i < MAX_TRACKS; ++i) {
_tracks[i].events = std::queue<MidiEvent>();
free(_tracks[i].data);
_tracks[i].data = 0;
}
memset(_tracks, 0, sizeof(_tracks));
_tracksCount = 0;
char tag[4];
f->read(tag, 4);
@ -73,18 +89,26 @@ void MidiParser::loadMid(File *f) {
const uint32_t len = f->readUint32BE();
assert(len == 6);
f->readByte();
/* const int type = */ f->readByte();
const int type = f->readByte();
const int tracksCount = f->readUint16BE();
assert(tracksCount <= MAX_TRACKS);
/* const int ppqn = */ f->readUint16BE();
//fprintf(stdout, "MThd type %d tracks %d ppqn %d len %d\n", type, tracksCount, ppqn, len);
const int ppqn = f->readUint16BE();
debug(DBG_MIDI, "MThd type %d tracks %d ppqn %d len %d", type, tracksCount, ppqn, len);
for (int i = 0; i < tracksCount; ++i) {
f->read(tag, 4);
assert(memcmp(tag, "MTrk", 4) == 0);
const uint32_t len = f->readUint32BE();
//fprintf(stdout, "Track #%d len %d\n", i, len);
loadTrk(f, len, _tracks[i]);
//fprintf(stdout, "Track #%d events %ld\n", i, track.events.size());
debug(DBG_MIDI, "Track #%d len %d", i, len);
MidiTrack *track = &_tracks[i];
track->data = (uint8_t *)malloc(len);
if (!track->data) {
warning("Failed to allocate %d bytes for MIDI track %d", len, i);
} else {
track->endOfTrack = false;
track->offset = 0;
track->size = len;
f->read(track->data, len);
}
}
_tracksCount = tracksCount;
}

View File

@ -2,7 +2,6 @@
#ifndef MIDI_PARSER_H__
#define MIDI_PARSER_H__
#include <queue>
#include <stdint.h>
#define MIDI_COMMAND_NOTE_OFF 0x80
@ -12,6 +11,8 @@
#define MIDI_COMMAND_PITCH_BEND 0xE0
#define MIDI_COMMAND_SYSEX 0xF0 /* unused */
#define MIDI_SYSEX_END_OF_TRACK 0x2F
struct MidiEvent {
uint32_t timestamp;
uint8_t command;
@ -20,7 +21,13 @@ struct MidiEvent {
};
struct MidiTrack {
std::queue<MidiEvent> events;
bool endOfTrack;
uint8_t *data;
uint32_t offset, size;
MidiEvent event;
void rewind();
const MidiEvent *nextEvent();
};
struct File;
@ -28,6 +35,9 @@ struct File;
#define MAX_TRACKS 16
struct MidiParser {
MidiParser();
void loadMid(File *f);
int _tracksCount;

View File

@ -90,8 +90,8 @@ static bool isMusicSfx(int num) {
return (num >= 68 && num <= 75);
}
void Mixer::playMusic(int num) {
debug(DBG_SND, "Mixer::playMusic(%d)", num);
void Mixer::playMusic(int num, int tempo) {
debug(DBG_SND, "Mixer::playMusic(%d, %d)", num, tempo);
int trackNum = -1;
if (num == 1) { // menu screen
trackNum = 2;
@ -119,7 +119,7 @@ void Mixer::playMusic(int num) {
_musicType = MT_SFX;
}
} else { // cutscene
_mod.play(num);
_mod.play(num, tempo);
if (_mod._playing) {
_musicType = MT_MOD;
return;
@ -156,7 +156,7 @@ void Mixer::stopMusic() {
break;
}
_musicType = MT_NONE;
if (_musicTrack != -1) {
if (_musicTrack > 2) { // do not resume menu music
switch (_backgroundMusicType) {
case MT_OGG:
_ogg.resumeTrack();
@ -194,18 +194,21 @@ void Mixer::mix(int16_t *out, int len) {
MixerChannel *ch = &_channels[i];
if (ch->active) {
for (int pos = 0; pos < len; ++pos) {
if ((ch->chunkPos >> FRAC_BITS) >= (ch->chunk.len - 1)) {
const uint32_t cpos = ch->chunkPos >> FRAC_BITS;
if (cpos >= ch->chunk.len) {
ch->active = false;
break;
}
const int sample = ch->chunk.getPCM(ch->chunkPos >> FRAC_BITS) * ch->volume / Mixer::MAX_VOLUME;
out[pos] = ADDC_S16(out[pos], S8_to_S16(sample));
const int sample8 = ch->chunk.getPCM(cpos) * ch->volume / Mixer::MAX_VOLUME;
const int sample16 = S8_to_S16(sample8);
out[2 * pos] = ADDC_S16(out[2 * pos], sample16);
out[2 * pos + 1] = ADDC_S16(out[2 * pos + 1], sample16);
ch->chunkPos += ch->chunkInc;
}
}
}
if (kUseNr) {
nr(out, len);
nr(out, len * 2); // stereo
}
}

View File

@ -84,7 +84,7 @@ struct Mixer {
bool isPlaying(const uint8_t *data) const;
uint32_t getSampleRate() const;
void stopAll();
void playMusic(int num);
void playMusic(int num, int tempo = 0);
void stopMusic();
void mix(int16_t *buf, int len);

View File

@ -16,6 +16,7 @@ struct ModPlayer_impl {
ModPlugFile *_mf;
ModPlug_Settings _settings;
int _songTempo;
bool _repeatIntro;
ModPlayer_impl()
@ -26,7 +27,7 @@ struct ModPlayer_impl {
memset(&_settings, 0, sizeof(_settings));
ModPlug_GetSettings(&_settings);
_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
_settings.mChannels = 1;
_settings.mChannels = 2;
_settings.mBits = 16;
_settings.mFrequency = rate;
_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
@ -59,7 +60,7 @@ struct ModPlayer_impl {
ModPlug_SeekOrder(_mf, 1);
_repeatIntro = false;
}
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t));
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t) * 2);
// setting mLoopCount to non-zero does not trigger any looping in
// my test and ModPlug_Read returns 0.
// looking at the libmodplug-0.8.8 tarball, it seems the variable
@ -82,6 +83,7 @@ struct ModPlayer_impl {
NUM_TRACKS = 4,
NUM_PATTERNS = 128,
FRAC_BITS = 12,
BASE_TEMPO = 125,
PAULA_FREQ = 3546897
};
@ -234,7 +236,7 @@ bool ModPlayer_impl::load(File *f) {
_currentTick = 0;
_patternDelay = 0;
_songSpeed = 6;
_songTempo = 125;
_songTempo = BASE_TEMPO;
_patternLoopPos = 0;
_patternLoopCount = -1;
_samplesLeft = 0;
@ -506,6 +508,7 @@ void ModPlayer_impl::handleEffect(int trackNum, bool tick) {
tk->volume = 0;
}
}
break;
case 0xD: // delay sample
if (!tick) {
tk->delayCounter = effectY;
@ -616,8 +619,11 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
*mixbuf = ADDC_S16(*mixbuf, S8_to_S16(out));
const int sample8 = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -628,9 +634,9 @@ void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
}
bool ModPlayer_impl::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
const int samplesPerTick = _mixingRate / (50 * _songTempo / BASE_TEMPO);
while (len != 0) {
if (_samplesLeft == 0) {
handleTick();
@ -643,7 +649,7 @@ bool ModPlayer_impl::mix(int16_t *buf, int len) {
_samplesLeft -= count;
len -= count;
mixSamples(buf, count);
buf += count;
buf += count * 2; // stereo
}
}
return _playing;
@ -659,20 +665,24 @@ ModPlayer::~ModPlayer() {
delete _impl;
}
void ModPlayer::play(int num) {
if (num < _modulesFilesCount) {
void ModPlayer::play(int num, int tempo) {
if (num * 2 < _namesCount) {
File f;
for (uint8_t i = 0; i < ARRAYSIZE(_modulesFiles[num]); ++i) {
if (f.open(_modulesFiles[num][i], "rb", _fs)) {
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
if (!f.open(_names[num * 2], "rb", _fs)) {
const char *p = _names[num * 2 + 1];
char name[32];
snprintf(name, sizeof(name), "mod.flashback-%s", p ? p : _names[num * 2]);
if (!f.open(name, "rb", _fs)) {
return;
}
}
_impl->init(_mix->getSampleRate());
if (_impl->load(&f)) {
_impl->_songTempo = tempo;
_impl->_repeatIntro = (num == 0) && !_isAmiga;
_mix->setPremixHook(mixCallback, _impl);
_playing = true;
}
}
}

View File

@ -16,8 +16,8 @@ struct ModPlayer_impl;
struct ModPlayer {
static const uint16_t _periodTable[];
static const char *_modulesFiles[][2];
static const int _modulesFilesCount;
static const char *_names[];
static const int _namesCount;
bool _isAmiga;
bool _playing;
@ -28,7 +28,7 @@ struct ModPlayer {
ModPlayer(Mixer *mixer, FileSystem *fs);
~ModPlayer();
void play(int num);
void play(int num, int tempo);
void stop();
static bool mixCallback(void *param, int16_t *buf, int len);

View File

@ -119,8 +119,9 @@ struct OggDecoder_impl {
case 2:
assert((len & 3) == 0);
for (int i = 0; i < len / 2; i += 2) {
const int16_t s16 = (_readBuf[i] + _readBuf[i + 1]) / 2;
*dst = ADDC_S16(*dst, s16);
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i + 1]);
++dst;
}
break;
@ -128,6 +129,8 @@ struct OggDecoder_impl {
for (int i = 0; i < len / 2; ++i) {
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
*dst = ADDC_S16(*dst, _readBuf[i]);
++dst;
}
break;
}
@ -187,8 +190,9 @@ struct OggDecoder_impl {
if (_decodedSamplesLen != 0) {
const int len = MIN(_decodedSamplesLen, samples);
for (int i = 0; i < len; ++i) {
const int sample = (_decodedSamples[0][i] + _decodedSamples[1][i]) / 2;
*dst = ADDC_S16(*dst, ((sample * kMusicVolume) >> 8));
*dst = ADDC_S16(*dst, ((_decodedSamples[0][i] * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((_decodedSamples[1][i] * kMusicVolume) >> 8));
++dst;
}
total += len;
@ -229,8 +233,9 @@ struct OggDecoder_impl {
for (int i = 0; i < len; ++i) {
const int l = int(outputs[0][i] * 32768 + .5);
const int r = int(outputs[1][i] * 32768 + .5);
const int sample = (l + r) / 2;
*dst = ADDC_S16(*dst, ((sample * kMusicVolume) >> 8));
*dst = ADDC_S16(*dst, ((l * kMusicVolume) >> 8));
++dst;
*dst = ADDC_S16(*dst, ((r * kMusicVolume) >> 8));
++dst;
}
if (count > remain) {
@ -270,12 +275,15 @@ OggPlayer::~OggPlayer() {
_impl = 0;
}
// https://www.amigaremix.com/remix/191
static const char *kMenuThemeRemix = "deadly_cookie_-_flashback.ogg";
bool OggPlayer::playTrack(int num) {
stopTrack();
char buf[16];
snprintf(buf, sizeof(buf), "track%02d.ogg", num);
if (_impl->load(buf, _fs, _mix->getSampleRate())) {
debug(DBG_INFO, "Playing '%s'", buf);
if (_impl->load(buf, _fs, _mix->getSampleRate())
|| (num == 2 && _impl->load(kMenuThemeRemix, _fs, _mix->getSampleRate()))) {
_mix->setPremixHook(mixCallback, this);
return true;
}

View File

@ -1641,21 +1641,24 @@ int Game::pge_o_unk0x71(ObjectOpcodeArgs *args) {
}
int Game::pge_o_unk0x72(ObjectOpcodeArgs *args) {
int8_t *var4 = &_res._ctData[0x100] + args->pge->room_location * 0x70;
var4 += (((args->pge->pos_y / 36) & ~1) + args->a) * 16 + (args->pge->pos_x + 8) / 16;
int8_t *grid_data = &_res._ctData[0x100] + args->pge->room_location * 0x70;
int16_t pge_pos_y = ((args->pge->pos_y / 36) & ~1) + args->a;
int16_t pge_pos_x = (args->pge->pos_x + 8) >> 4;
grid_data += pge_pos_y * 16 + pge_pos_x;
CollisionSlot2 *_di = _col_slots2Next;
int _cx = 0x100;
while (_di && _cx != 0) {
if (_di->unk2 != var4) {
int count = 256; // ARRAYSIZE(_col_slots2)
while (_di && count != 0) {
if (_di->unk2 != grid_data) {
_di = _di->next_slot;
--_cx;
--count;
} else {
memcpy(_di->unk2, _di->data_buf, _di->data_size + 1);
break;
}
}
return 0xFFFF; // XXX var4;
// original returns the pointer to ctData
return 0xFFFF;
}
int Game::pge_o_unk0x73(ObjectOpcodeArgs *args) {
@ -2079,7 +2082,21 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value)
memset(grid_data, value, pge_collision_data_len);
return 1;
} else {
// the increment looks wrong but matches the DOS disassembly
//
// seg000:667B inc cx
// seg000:667C mov si, bx
// seg000:667E mov bx, [bx+t_collision_slot2.next_slot]
// seg000:6680 loop loc_0_1665B
//
// interestingly Amiga does not have it
//
// CODE:000042BA movea.l a4,a5
// CODE:000042BC movea.l 0(a4),a4
// CODE:000042C0 dbf d0,loc_4290
++i;
slot1 = slot1->next_slot;
if (--i == 0) {
break;

View File

@ -167,8 +167,10 @@ void PrfPlayer::play() {
for (int i = 0; i < _parser._tracksCount; ++i) {
PrfTrack *current_track = &_tracks[i];
MidiTrack *track = &_parser._tracks[i];
MidiEvent ev = track->events.front();
current_track->counter = ev.timestamp;
track->rewind();
const MidiEvent *ev = track->nextEvent();
current_track->counter = ev ? ev->timestamp : 0;
current_track->counter2 = current_track->counter % _prfData.timerMod;
}
_timerTick = _musicTick = 0;
_samplesLeft = 0;
@ -233,7 +235,7 @@ void PrfPlayer::adlibNoteOff(int track, int note, int velocity) {
void PrfPlayer::handleTick() {
for (int i = 0; i < _parser._tracksCount; ++i) {
MidiTrack *track = &_parser._tracks[i];
if (track->events.size() == 0) {
if (track->endOfTrack) {
continue;
}
const int track_index = i;
@ -242,12 +244,8 @@ void PrfPlayer::handleTick() {
--current_track->counter;
continue;
}
next_event:
MidiEvent ev = track->events.front();
track->events.pop();
if (current_track->loop_flag) {
track->events.push(ev);
}
while (1) {
const MidiEvent &ev = track->event;
switch (ev.command & 0xF0) {
case MIDI_COMMAND_NOTE_OFF: {
int note = ev.param1;
@ -358,14 +356,25 @@ next_event:
warning("Unhandled MIDI event command 0x%x", ev.command);
break;
}
if (track->events.size() != 0) {
ev = track->events.front();
current_track->counter = ev.timestamp;
if (current_track->counter == 0) {
goto next_event;
const MidiEvent *next = track->nextEvent();
if (!next) {
if (current_track->loop_flag == 0) {
track->rewind();
const MidiEvent *next = track->nextEvent();
if (next) {
current_track->counter = _prfData.timerMod - current_track->counter2 + next->timestamp - 1;
current_track->counter2 = next->timestamp % _prfData.timerMod;
}
}
--current_track->counter;
break;
}
current_track->counter = next->timestamp;
current_track->counter2 = (current_track->counter + current_track->counter2) % _prfData.timerMod;
if (current_track->counter != 0) {
--current_track->counter;
break;
}
}
}
}
@ -377,19 +386,13 @@ int PrfPlayer::readSamples(int16_t *samples, int count) {
const int total = count;
while (count != 0) {
if (_samplesLeft == 0) {
//++_timerTick;
//if (_timerTick == _prfData.timerTicks) {
//fprintf(stdout, "musicTick #%d of %d\n", _musicTick, _prfData.totalDurationTicks);
handleTick();
//_timerTick = 0;
if (_prfData.totalDurationTicks != 0) {
++_musicTick;
if (_musicTick == _prfData.totalDurationTicks + 1) {
debug(DBG_PRF, "End of music");
//break;
}
handleTick();
if (_prfData.totalDurationTicks != 0) {
++_musicTick;
if (_musicTick == _prfData.totalDurationTicks + 1) {
debug(DBG_PRF, "End of music");
}
//}
}
_samplesLeft = _samplesPerTick * _prfData.timerTicks;
}
const int len = (count < _samplesLeft) ? count : _samplesLeft;
@ -406,16 +409,6 @@ bool PrfPlayer::mixCallback(void *param, int16_t *buf, int len) {
}
bool PrfPlayer::mix(int16_t *buf, int len) {
int16_t *p = (int16_t *)alloca(len * sizeof(int16_t) * 2);
if (p) {
const int count = readSamples(p, len);
/* stereo to mono */
for (int i = 0; i < count; ++i) {
const int16_t l = *p++;
const int16_t r = *p++;
buf[i] = CLIP((l + r) / 2, -32768, 32767);
}
return count != 0;
}
return false;
const int count = readSamples(buf, len);
return count != 0;
}

View File

@ -32,6 +32,7 @@ struct PrfData {
struct PrfTrack {
uint8_t instrument_num;
uint32_t counter;
uint32_t counter2;
uint8_t hw_channel_num;
uint8_t mt32_program_num;
uint8_t loop_flag;

View File

@ -334,8 +334,12 @@ bool SeqPlayer::mix(int16_t *buf, int samples) {
}
while (_soundQueue && samples > 0) {
const int count = MIN(samples, _soundQueue->size - _soundQueue->read);
memcpy(buf, _soundQueue->data + _soundQueue->read, count * sizeof(int16_t));
buf += count;
const int16_t *src = (const int16_t *)(_soundQueue->data + _soundQueue->read);
for (int i = 0; i < count; ++i) {
const int16_t sample = *src++;
*buf++ = sample;
*buf++ = sample;
}
_soundQueue->read += count;
if (_soundQueue->read == _soundQueue->size) {
SoundBufferQueue *next = _soundQueue->next;

View File

@ -13,7 +13,7 @@
static const int kMasterVolume = 64 * 3;
// 12 dB/oct Butterworth low-pass filter at 3.3 kHz
static const bool kLowPassFilter = true;
static const bool kLowPassFilter = false;
#define NZEROS 2
#define NPOLES 2
@ -151,8 +151,11 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
curLen = 0;
}
while (count--) {
const int out = si->getPCM(pos >> FRAC_BITS) * si->vol / kMasterVolume;
*mixbuf = ADDC_S16(*mixbuf, S8_to_S16(out));
const int sample8 = si->getPCM(pos >> FRAC_BITS) * si->vol / kMasterVolume;
const int sample16 = S8_to_S16(sample8);
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
*mixbuf = ADDC_S16(*mixbuf, sample16);
++mixbuf;
pos += deltaPos;
}
@ -163,7 +166,7 @@ void SfxPlayer::mixSamples(int16_t *buf, int samplesLen) {
}
bool SfxPlayer::mix(int16_t *buf, int len) {
memset(buf, 0, sizeof(int16_t) * len);
memset(buf, 0, sizeof(int16_t) * len * 2); // stereo
if (_playing) {
const int samplesPerTick = _mix->getSampleRate() / 50;
while (len != 0) {
@ -179,9 +182,9 @@ bool SfxPlayer::mix(int16_t *buf, int len) {
len -= count;
mixSamples(buf, count);
if (kLowPassFilter) {
butterworth(buf, count);
butterworth(buf, count * 2); // stereo
}
buf += count;
buf += count * 2; // stereo
}
}
return _playing;

View File

@ -537,7 +537,7 @@ const uint16_t Cutscene::_creditsCutSeq[] = {
0x00, 0x05, 0x2F, 0x32, 0x36, 0x3E, 0x30, 0x39, 0x3F, 0x14, 0x34, 0xFFFF
};
const uint8_t Cutscene::_musicTable[] = {
const uint8_t Cutscene::_musicTableDOS[] = {
0x10, 0x15, 0x15, 0xFF, 0x15, 0x19, 0x0F, 0xFF, 0x15, 0x04, 0x15, 0xFF, 0xFF, 0x00, 0x19, 0x15,
0x15, 0x0D, 0x15, 0x0D, 0x18, 0x13, 0xFF, 0xFF, 0xFF, 0x14, 0x14, 0x14, 0x14, 0x14, 0xFF, 0xFF,
0x13, 0x13, 0x13, 0x13, 0x15, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x13, 0x13, 0x11, 0xFF, 0x03,
@ -545,6 +545,19 @@ const uint8_t Cutscene::_musicTable[] = {
0x0B, 0x0C, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xFF, 0xFF, 0xFF
};
const uint8_t Cutscene::_musicTableAmiga[] = {
0x10, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x7D, 0xFF, 0xFF,
0xFF, 0xFF, 0x04, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x6B, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x0D, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x13, 0x7D, 0x13, 0x7D, 0x13, 0x7D, 0x13, 0x7D, 0xFF, 0xFF, 0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D,
0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D, 0x13, 0x9B, 0x13, 0x9B, 0x11, 0x7D, 0xFF, 0xFF, 0x03, 0x78,
0x0E, 0x7D, 0x13, 0x7D, 0x12, 0x6E, 0xFF, 0xFF, 0x06, 0x87, 0x07, 0x7D, 0x0A, 0x7D, 0x0A, 0x7D,
0xFF, 0xFF, 0x05, 0x7D, 0x13, 0x7D, 0x02, 0x7D, 0xFF, 0xFF, 0x09, 0x7D, 0xFF, 0xFF, 0x08, 0x7D,
0x0B, 0x7D, 0x0C, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x3D, 0x25, 0x27, 0x28
};
const uint8_t Cutscene::_protectionShapeData[] = {
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x06, 0xD4, 0x00, 0x00, 0x00, 0x92,
0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x28, 0x00, 0x54, 0x00, 0x8C, 0x00, 0xB2,
@ -4419,36 +4432,31 @@ const uint16_t ModPlayer::_periodTable[] = {
216, 203, 192, 181, 171, 161, 152, 144, 136, 128, 121, 114 // C-3 to B-3 Finetune -1
};
const char *ModPlayer::_modulesFiles[][2] = {
{ "intro", "mod.flashback-introb" }, // introl3
{ "options", "mod.flashback-options2" }, // option3
{ "journal", "mod.flashback-options1" }, // journal3
{ "ceinture", "mod.flashback-ceinturea" }, // chute3
{ "desinteg", "mod.flashback-desintegr" }, // desinte3
{ "reunion", "mod.flashback-reunion" }, // capture3
{ "voyage", "mod.flashback-voyage" }, // voyage3
{ "level4", "mod.flashback-teleporta" }, // telepor3
{ "planetexplo", "mod.flashback-teleport2" }, // planexp3
{ "fin", "mod.flashback-fin" }, // end31
{ "ascenseur", "mod.flashback-ascenseur" }, // lift3
{ "logo", "mod.flashback-logo" }, // present3
{ "game_over", "mod.flashback-game_over" }, // gameove3
{ "holocube", "mod.flashback-holocube" }, // holo3
{ "memoire", "mod.flashback-memoire" }, // memory3
{ "chute", "mod.flashback-chute" }, // chutevi3
{ "debut", "mod.flashback-jungle" }, // reveil3
{ "missions", "mod.flashback-missionca" }, // misvali3
{ "taxi", "mod.flashback-taxi" }, // taxi3
{ "donneobj", "mod.flashback-donneobjt" }, // donner3
{ "missions2", "mod.flashback-fin2" } // mission3
// { 0, 0, }, // objet3
// { 0, 0, }, // recharg3
// { 0, 0, }, // generat3
// { 0, 0, }, // pont3
// { 0, 0, } // rechage3
const char *ModPlayer::_names[] = {
"intro", "introb",
"options", "options2",
"journal", "options1",
"ceinture", "ceinturea",
"desinteg", "desintegr",
"reunion", 0,
"voyage", 0,
"level4", "teleporta",
"planetexplo", "teleport2",
"fin", 0,
"ascenseur", 0,
"logo", 0,
"game_over", 0,
"holocube", 0,
"memoire", 0,
"chute", 0,
"debut", "jungle",
"missions", "missionca",
"taxi", 0,
"donneobj", "donneobjt",
"missions2", "fin2",
};
const int ModPlayer::_modulesFilesCount = ARRAYSIZE(_modulesFiles);
const int ModPlayer::_namesCount = ARRAYSIZE(_names);
const char *SeqPlayer::_namesTable[] = {
/* 0x00 */

View File

@ -928,7 +928,8 @@ uint32_t SystemStub_SDL::getTimeStamp() {
static void mixAudioS16(void *param, uint8_t *buf, int len) {
SystemStub_SDL *stub = (SystemStub_SDL *)param;
memset(buf, 0, len);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / 2);
assert((len & 3) == 0);
stub->_audioCbProc(stub->_audioCbData, (int16_t *)buf, len / (sizeof(int16_t) * 2));
}
void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
@ -936,7 +937,7 @@ void SystemStub_SDL::startAudio(AudioCallback callback, void *param) {
memset(&desired, 0, sizeof(desired));
desired.freq = kAudioHz;
desired.format = AUDIO_S16SYS;
desired.channels = 1;
desired.channels = 2;
desired.samples = 2048;
desired.callback = mixAudioS16;
desired.userdata = this;

3
util.h
View File

@ -24,7 +24,8 @@ enum {
DBG_SFX = 1 << 11,
DBG_FILE = 1 << 12,
DBG_DEMO = 1 << 13,
DBG_PRF = 1 << 14
DBG_PRF = 1 << 14,
DBG_MIDI = 1 << 15
};
extern uint16_t g_debugMask;