SCUMM/FM-TOWNS: start rewriting audio code
- Start rewriting audio code for FM-TOWNS versions of Loom, Indy3 and Monkey Island 1 using the recently added code in towns_audio.cpp (Zak should work the same way, but I can't test, since I don't own that one). - All sound types (pcm, euphony and cd audio) now support volume and balance control (e.g. try walking into/out of the kitchen and opening/closing the door in the Scumm Bar in Monkey Island 1 or walking into/out of the circus tent). - Pcm sounds now support proper loop start/end and note offsets (e.g. try out the hammer sound in the forge in LOOM for example). - some other minor improvements - The FM-Towns versions of Indy 4 and Monkey Island 2 are not affected. I don't have Monkey Island 2, but I presume that it will work like Indy 4. Adding support for these will be a separate task, since they work quite differently. svn-id: r52198
This commit is contained in:
parent
c04d3e4689
commit
29a5c6a45b
17 changed files with 771 additions and 331 deletions
|
@ -186,6 +186,7 @@ using Common::GUIO_NONE;
|
|||
using Common::GUIO_NOLAUNCHLOAD;
|
||||
using Common::GUIO_NOMIDI;
|
||||
using Common::GUIO_NOSPEECH;
|
||||
using Common::GUIO_MIDITOWNS;
|
||||
|
||||
// The following table contains information about variants of our various
|
||||
// games. We index into it with help of md5table (from scumm-md5.h), to find
|
||||
|
@ -217,19 +218,19 @@ static const GameSettings gameVariantsTable[] = {
|
|||
|
||||
{"zak", "V1", "v1", GID_ZAK, 1, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"zak", "V2", "v2", GID_ZAK, 2, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"zak", "FM-TOWNS", 0, GID_ZAK, 3, 0, MDT_TOWNS, GF_OLD256 | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"zak", "FM-TOWNS", 0, GID_ZAK, 3, 0, MDT_TOWNS, GF_OLD256 | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS},
|
||||
|
||||
{"indy3", "EGA", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"indy3", "No AdLib", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"indy3", "VGA", "vga", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_OLD256 | GF_FEW_LOCALS, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"indy3", "FM-TOWNS", 0, GID_INDY3, 3, 0, MDT_TOWNS, GF_OLD256 | GF_FEW_LOCALS | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"indy3", "FM-TOWNS", 0, GID_INDY3, 3, 0, MDT_TOWNS, GF_OLD256 | GF_FEW_LOCALS | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS},
|
||||
|
||||
{"loom", "EGA", "ega", GID_LOOM, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
|
||||
{"loom", "No AdLib", "ega", GID_LOOM, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS, 0, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
#ifdef USE_RGB_COLOR
|
||||
{"loom", "PC-Engine", 0, GID_LOOM, 3, 0, MDT_NONE, GF_AUDIOTRACKS | GF_OLD256 | GF_16BIT_COLOR, Common::kPlatformPCEngine, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
#endif
|
||||
{"loom", "FM-TOWNS", 0, GID_LOOM, 3, 0, MDT_TOWNS, GF_AUDIOTRACKS | GF_OLD256, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"loom", "FM-TOWNS", 0, GID_LOOM, 3, 0, MDT_TOWNS, GF_AUDIOTRACKS | GF_OLD256, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS},
|
||||
{"loom", "VGA", "vga", GID_LOOM, 4, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
|
||||
{"pass", 0, 0, GID_PASS, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
|
@ -239,7 +240,7 @@ static const GameSettings gameVariantsTable[] = {
|
|||
{"monkey", "No AdLib", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR, GF_16COLOR, Common::kPlatformAtariST, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"monkey", "Demo", "ega", GID_MONKEY_EGA, 4, 0, MDT_PCSPK | MDT_PCJR | MDT_ADLIB, GF_16COLOR, Common::kPlatformPC, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"monkey", "CD", 0, GID_MONKEY, 5, 0, MDT_ADLIB, GF_AUDIOTRACKS, UNK, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_ADLIB, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
{"monkey", "FM-TOWNS", 0, GID_MONKEY, 5, 0, MDT_TOWNS, GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO_NOSPEECH | GUIO_NOMIDI | GUIO_MIDITOWNS},
|
||||
{"monkey", "SEGA", 0, GID_MONKEY, 5, 0, MDT_NONE, GF_AUDIOTRACKS, Common::kPlatformSegaCD, GUIO_NOSPEECH | GUIO_NOMIDI},
|
||||
|
||||
{"monkey2", 0, 0, GID_MONKEY2, 5, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32, 0, UNK, GUIO_NOSPEECH},
|
||||
|
|
|
@ -47,7 +47,6 @@ namespace Scumm {
|
|||
#define PERCUSSION_CHANNEL 9
|
||||
|
||||
extern MidiParser *MidiParser_createRO();
|
||||
extern MidiParser *MidiParser_createEUP();
|
||||
|
||||
uint16 Player::_active_notes[128];
|
||||
|
||||
|
@ -195,7 +194,11 @@ int Player::start_seq_sound(int sound, bool reset_vars) {
|
|||
_parser = MidiParser_createRO();
|
||||
} else if (!memcmp(ptr, "SO", 2)) {
|
||||
// Euphony (FM-TOWNS) resource
|
||||
_parser = MidiParser_createEUP();
|
||||
|
||||
//////////// REMOVE
|
||||
//_parser = MidiParser_createEUP();
|
||||
///////////
|
||||
|
||||
} else if (!memcmp(ptr, "FORM", 4)) {
|
||||
// Humongous Games XMIDI resource
|
||||
_parser = MidiParser::createParser_XMIDI();
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
/* 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.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "sound/midiparser.h"
|
||||
#include "sound/mididrv.h"
|
||||
#include "common/util.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
/**
|
||||
* The FM-TOWNS Euphony version of MidiParser.
|
||||
*/
|
||||
class MidiParser_EUP : public MidiParser {
|
||||
protected:
|
||||
byte _instruments[6][50]; // Two extra bytes for SysEx ID and channel #
|
||||
byte *_instr_to_channel;
|
||||
struct {
|
||||
byte *enable;
|
||||
int8 *channel;
|
||||
int8 *volume;
|
||||
int8 *transpose;
|
||||
} _presets;
|
||||
bool _loop;
|
||||
byte _presend; // Tracks which startup implied events have been sent.
|
||||
uint32 _base_tick; // Events times are relative to this base.
|
||||
|
||||
protected:
|
||||
void parseNextEvent (EventInfo &info);
|
||||
void resetTracking();
|
||||
|
||||
public:
|
||||
bool loadMusic (byte *data, uint32 size);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// MidiParser_EUP implementation
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
void MidiParser_EUP::parseNextEvent (EventInfo &info) {
|
||||
byte *pos = _position._play_pos;
|
||||
|
||||
// FIXME: The presend is for sending init events
|
||||
// that aren't actually in the stream. This would
|
||||
// be for, e.g., instrument setup. Right now, we
|
||||
// don't actually use the instruments specified
|
||||
// in the music header. We're sending fixed GM
|
||||
// program changes to get a reasonable "one-size-
|
||||
// fits-all" sound until we actually support the
|
||||
// FM synthesis capabilities of FM-TOWNS.
|
||||
for (; _presend < 12; ++_presend) {
|
||||
if (_instr_to_channel[_presend >> 1] >= 16)
|
||||
continue;
|
||||
info.start = pos;
|
||||
info.delta = 0;
|
||||
if (_presend & 1) {
|
||||
byte *data = &_instruments[_presend >> 1][0];
|
||||
data[1] = _instr_to_channel[_presend >> 1];
|
||||
info.event = 0xF0;
|
||||
info.ext.data = data;
|
||||
info.length = 48;
|
||||
} else {
|
||||
info.event = 0xB0 | (_presend >> 1);
|
||||
info.basic.param1 = 121;
|
||||
info.basic.param2 = 0;
|
||||
}
|
||||
++_presend;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
byte cmd = *pos;
|
||||
if ((cmd & 0xF0) == 0x90) {
|
||||
byte preset = pos[1];
|
||||
byte channel = _presets.channel[preset];
|
||||
if (channel >= 16)
|
||||
channel = cmd & 0x0F;
|
||||
uint16 tick = (pos[2] | ((uint16) pos[3] << 7)) + _base_tick;
|
||||
int note = (int) pos[4] + _presets.transpose[preset];
|
||||
int volume = (int) pos[5];
|
||||
// HACK: Loom-Towns distaff tracks seem to
|
||||
// contain zero-volume note events, so change
|
||||
// those to full volume.
|
||||
if (!volume)
|
||||
volume = 127;
|
||||
volume += _presets.volume[preset];
|
||||
if (volume > 127)
|
||||
volume = 127;
|
||||
else if (volume < 0)
|
||||
volume = 0;
|
||||
pos += 6;
|
||||
if (_presets.enable[preset]) {
|
||||
uint16 duration = pos[1] | (pos[2] << 4);
|
||||
info.start = pos;
|
||||
uint32 last = _position._last_event_tick;
|
||||
info.delta = (tick < last) ? 0 : (tick - last);
|
||||
info.event = 0x90 | channel;
|
||||
info.length = duration;
|
||||
info.basic.param1 = note;
|
||||
info.basic.param2 = volume;
|
||||
pos += 6;
|
||||
break;
|
||||
}
|
||||
pos += 6;
|
||||
} else if (cmd == 0xF2) {
|
||||
// This is a "measure marker" of sorts.
|
||||
// It advances the "base time", to which
|
||||
// all event times are relative.
|
||||
_base_tick += (pos[3] << 7) | pos[2];
|
||||
pos += 6;
|
||||
} else if (cmd == 0xF8) {
|
||||
// TODO: Implement this.
|
||||
pos += 6;
|
||||
} else if (cmd == 0xFD || cmd == 0xFE) {
|
||||
// End of track.
|
||||
if (_loop && false) {
|
||||
// TODO: Implement this.
|
||||
} else {
|
||||
info.start = pos;
|
||||
uint32 last = _position._last_event_tick;
|
||||
info.delta = (_base_tick < last) ? 0 : (_base_tick - last);
|
||||
info.event = 0xFF;
|
||||
info.length = 0;
|
||||
info.ext.type = 0x2F;
|
||||
info.ext.data = pos;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
error("Unknown Euphony music event 0x%02X", (int) cmd);
|
||||
memset(&info, 0, sizeof(info));
|
||||
pos = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_position._play_pos = pos;
|
||||
}
|
||||
|
||||
bool MidiParser_EUP::loadMusic (byte *data, uint32 size) {
|
||||
unloadMusic();
|
||||
byte *pos = data;
|
||||
int i;
|
||||
|
||||
if (memcmp(pos, "SO", 2)) {
|
||||
error("'SO' header expected but found '%c%c' instead.", pos[0], pos[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
byte numInstruments = pos[16];
|
||||
pos += 16 + 2;
|
||||
for (i = 0; i < numInstruments; ++i) {
|
||||
_instruments[i][0] = 0x7C;
|
||||
memcpy (&_instruments[i][2], pos, 48);
|
||||
pos += 48;
|
||||
}
|
||||
|
||||
// Load the prest pointers
|
||||
_presets.enable = pos;
|
||||
pos += 32;
|
||||
_presets.channel = (int8 *) pos;
|
||||
pos += 32;
|
||||
_presets.volume = (int8 *) pos;
|
||||
pos += 32;
|
||||
_presets.transpose = (int8 *) pos;
|
||||
pos += 32;
|
||||
|
||||
pos += 8; // Unknown bytes
|
||||
_instr_to_channel = pos; // Instrument-to-channel mapping
|
||||
pos += 6;
|
||||
pos += 4; // Skip the music size for now.
|
||||
pos++; // Unknown byte
|
||||
byte tempo = *pos++;
|
||||
_loop = (*pos++ != 1);
|
||||
pos++; // Unknown byte
|
||||
|
||||
_num_tracks = 1;
|
||||
_ppqn = 120;
|
||||
_tracks[0] = pos;
|
||||
|
||||
// Note that we assume the original data passed in
|
||||
// will persist beyond this call, i.e. we do NOT
|
||||
// copy the data to our own buffer. Take warning....
|
||||
resetTracking();
|
||||
setTempo (1000000 * 60 / tempo);
|
||||
setTrack (0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiParser_EUP::resetTracking() {
|
||||
MidiParser::resetTracking();
|
||||
_presend = 0;
|
||||
_base_tick = 0;
|
||||
}
|
||||
|
||||
MidiParser *MidiParser_createEUP() { return new MidiParser_EUP; }
|
||||
|
||||
} // End of namespace Scumm
|
|
@ -29,7 +29,6 @@ MODULE_OBJS := \
|
|||
imuse/sysex_samnmax.o \
|
||||
imuse/sysex_scumm.o \
|
||||
input.o \
|
||||
midiparser_eup.o \
|
||||
midiparser_ro.o \
|
||||
object.o \
|
||||
palette.o \
|
||||
|
@ -37,6 +36,7 @@ MODULE_OBJS := \
|
|||
player_nes.o \
|
||||
player_pce.o \
|
||||
player_sid.o \
|
||||
player_towns.o \
|
||||
player_v1.o \
|
||||
player_v2.o \
|
||||
player_v2a.o \
|
||||
|
|
557
engines/scumm/player_towns.cpp
Normal file
557
engines/scumm/player_towns.cpp
Normal file
|
@ -0,0 +1,557 @@
|
|||
/* 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.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "scumm/sound.h"
|
||||
#include "scumm/player_towns.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
Player_Towns::Player_Towns(ScummEngine *vm, Audio::Mixer *mixer) : _vm(vm) {
|
||||
_cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0;
|
||||
_cdaForceRestart = 0;
|
||||
memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound));
|
||||
_cdaVolLeft = _cdaVolRight = 0;
|
||||
|
||||
_eupVolLeft = _eupVolRight = 0;
|
||||
memset(&_ovrCur, 0, sizeof(SoundOvrParameters));
|
||||
_soundOverride = 0;
|
||||
|
||||
if (_vm->_game.version == 3) {
|
||||
_soundOverride = new SoundOvrParameters[200];
|
||||
memset(_soundOverride, 0, 200 * sizeof(SoundOvrParameters));
|
||||
}
|
||||
|
||||
_eupLooping = false;
|
||||
_unkFlags = 0x33;
|
||||
|
||||
_driver = new TownsEuphonyDriver(mixer);
|
||||
}
|
||||
|
||||
Player_Towns::~Player_Towns() {
|
||||
delete[] _soundOverride;
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
bool Player_Towns::init() {
|
||||
if (!_driver)
|
||||
return false;
|
||||
|
||||
if (!_driver->init())
|
||||
return false;
|
||||
|
||||
_driver->reserveSoundEffectChannels(8);
|
||||
|
||||
// Treat all 6 fm channels and all 8 pcm channels as sound effect channels
|
||||
// since music seems to exist as CD audio only in the games which use this
|
||||
// MusicEngine implementation.
|
||||
_driver->intf()->setSoundEffectChanMask(-1);
|
||||
|
||||
setVolumeCD(255, 255);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player_Towns::setMusicVolume(int vol) {
|
||||
_driver->setMusicVolume(vol);
|
||||
}
|
||||
|
||||
void Player_Towns::setSfxVolume(int vol) {
|
||||
_driver->setSoundEffectVolume(vol);
|
||||
}
|
||||
|
||||
void Player_Towns::startSound(int sound) {
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, sound);
|
||||
if (_vm->_game.version != 3) {
|
||||
ptr += 2;
|
||||
} else if (_soundOverride && sound > 0 && sound < 200) {
|
||||
memcpy(&_ovrCur, &_soundOverride[sound], sizeof(SoundOvrParameters));
|
||||
memset(&_soundOverride[sound], 0, sizeof(SoundOvrParameters));
|
||||
}
|
||||
|
||||
int type = ptr[13];
|
||||
|
||||
if (type == 0) {
|
||||
playPcmTrack(sound, ptr + 6);
|
||||
} else if (type == 1) {
|
||||
playEuphonyTrack(sound, ptr + 6);
|
||||
} else if (type == 2) {
|
||||
playCdaTrack(sound, ptr + 6);
|
||||
}
|
||||
memset(&_ovrCur, 0, sizeof(SoundOvrParameters));
|
||||
}
|
||||
|
||||
void Player_Towns::stopSound(int sound) {
|
||||
if (sound != 0 && sound == _cdaCurrentSound) {
|
||||
_cdaCurrentSound = 0;
|
||||
_vm->_sound->stopCD();
|
||||
_vm->_sound->stopCDTimer();
|
||||
}
|
||||
|
||||
if (sound != 0 && sound == _eupCurrentSound) {
|
||||
_eupCurrentSound = 0;
|
||||
_eupLooping = false;
|
||||
_driver->stopParser();
|
||||
}
|
||||
|
||||
stopPcmTrack(sound);
|
||||
}
|
||||
|
||||
void Player_Towns::stopAllSounds() {
|
||||
_cdaCurrentSound = 0;
|
||||
_vm->_sound->stopCD();
|
||||
_vm->_sound->stopCDTimer();
|
||||
|
||||
// Loom disasm seems to stop only CD audio and PCM sounds here
|
||||
/*_eupCurrentSound = 0;
|
||||
_eupLooping = false;
|
||||
_driver->stopParser();*/
|
||||
|
||||
stopPcmTrack(0);
|
||||
}
|
||||
|
||||
int Player_Towns::getSoundStatus(int sound) const {
|
||||
if (sound == _cdaCurrentSound)
|
||||
return _vm->_sound->pollCD();
|
||||
if (sound == _eupCurrentSound)
|
||||
return _driver->parserIsPlaying() ? 1 : 0;
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (_pcmCurrentSound[i].index == sound)
|
||||
return _driver->soundEffectIsPlaying(i + 0x3f) ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 Player_Towns::doCommand(int numargs, int args[]) {
|
||||
int32 res = 0;
|
||||
|
||||
switch (args[0]) {
|
||||
case 2:
|
||||
_driver->intf()->callback(73, 0);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
restartLoopingSounds();
|
||||
break;
|
||||
|
||||
case 8:
|
||||
startSound(args[1]);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
_vm->_sound->stopSound(args[1]);
|
||||
break;
|
||||
|
||||
case 11:
|
||||
stopPcmTrack(0);
|
||||
break;
|
||||
|
||||
case 14:
|
||||
startSoundEx(args[1], args[2], args[3], args[4]);
|
||||
break;
|
||||
|
||||
case 15:
|
||||
stopSoundSuspendLooping(args[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
warning("Player_Towns::doCommand: Unknown command %d", args[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void Player_Towns::setVolumeCD(int left, int right) {
|
||||
_cdaVolLeft = left & 0xff;
|
||||
_cdaVolRight = right & 0xff;
|
||||
_driver->setOutputVolume(1, left >> 1, right >> 1);
|
||||
}
|
||||
|
||||
void Player_Towns::setSoundVolume(int sound, int left, int right) {
|
||||
if (_soundOverride && sound > 0 && sound < 200) {
|
||||
_soundOverride[sound].vLeft = left;
|
||||
_soundOverride[sound].vRight = right;
|
||||
}
|
||||
}
|
||||
|
||||
void Player_Towns::setSoundNote(int sound, int note) {
|
||||
if (_soundOverride && sound > 0 && sound < 200)
|
||||
_soundOverride[sound].note = note;
|
||||
}
|
||||
|
||||
void Player_Towns::saveLoadWithSerializer(Serializer *ser) {
|
||||
_cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0;
|
||||
_cdaNumLoopsTemp = _cdaNumLoops & 0xff;
|
||||
|
||||
static const SaveLoadEntry cdEntries[] = {
|
||||
MKLINE(Player_Towns, _cdaCurrentSoundTemp, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _cdaNumLoopsTemp, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _cdaVolLeft, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _cdaVolRight, sleUint8, VER(81)),
|
||||
MKEND()
|
||||
};
|
||||
|
||||
ser->saveLoadEntries(this, cdEntries);
|
||||
|
||||
if (!_eupLooping && !_driver->parserIsPlaying())
|
||||
_eupCurrentSound = 0;
|
||||
|
||||
static const SaveLoadEntry eupEntries[] = {
|
||||
MKLINE(Player_Towns, _eupCurrentSound, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _eupLooping, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _eupVolLeft, sleUint8, VER(81)),
|
||||
MKLINE(Player_Towns, _eupVolRight, sleUint8, VER(81)),
|
||||
MKEND()
|
||||
};
|
||||
|
||||
ser->saveLoadEntries(this, eupEntries);
|
||||
|
||||
static const SaveLoadEntry pcmEntries[] = {
|
||||
MKLINE(PcmCurrentSound, index, sleInt16, VER(81)),
|
||||
MKLINE(PcmCurrentSound, chan, sleInt16, VER(81)),
|
||||
MKLINE(PcmCurrentSound, note, sleUint8, VER(81)),
|
||||
MKLINE(PcmCurrentSound, velo, sleUint8, VER(81)),
|
||||
MKLINE(PcmCurrentSound, pan, sleUint8, VER(81)),
|
||||
MKLINE(PcmCurrentSound, paused, sleUint8, VER(81)),
|
||||
MKLINE(PcmCurrentSound, looping, sleUint8, VER(81)),
|
||||
MKLINE(PcmCurrentSound, priority, sleUint32, VER(81)),
|
||||
MKEND()
|
||||
};
|
||||
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (!_pcmCurrentSound[i].index)
|
||||
continue;
|
||||
|
||||
if (_driver->soundEffectIsPlaying(i + 0x3f))
|
||||
continue;
|
||||
|
||||
_driver->stopSoundEffect(i + 0x3f);
|
||||
|
||||
_pcmCurrentSound[i].index = 0;
|
||||
}
|
||||
|
||||
ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries);
|
||||
}
|
||||
|
||||
void Player_Towns::restoreAfterLoad() {
|
||||
setVolumeCD(_cdaVolLeft, _cdaVolRight);
|
||||
|
||||
if (_cdaCurrentSoundTemp) {
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6;
|
||||
if (_vm->_game.version != 3)
|
||||
ptr += 2;
|
||||
|
||||
if (ptr[7] == 2) {
|
||||
playCdaTrack(_cdaCurrentSoundTemp, ptr, true);
|
||||
_cdaCurrentSound = _cdaCurrentSoundTemp;
|
||||
_cdaNumLoops = _cdaNumLoopsTemp;
|
||||
}
|
||||
}
|
||||
|
||||
if (_eupCurrentSound) {
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6;
|
||||
if (_vm->_game.version != 3)
|
||||
ptr += 2;
|
||||
|
||||
if (ptr[7] == 1) {
|
||||
setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight);
|
||||
playEuphonyTrack(_eupCurrentSound, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (!_pcmCurrentSound[i].index)
|
||||
continue;
|
||||
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
|
||||
if (!ptr)
|
||||
continue;
|
||||
|
||||
if (_vm->_game.version != 3)
|
||||
ptr += 2;
|
||||
|
||||
if (ptr[13])
|
||||
continue;
|
||||
|
||||
playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note);
|
||||
}
|
||||
}
|
||||
|
||||
int Player_Towns::getNextFreePcmChannel(int sound, int sfxChanRelIndex) {
|
||||
int chan = 0;
|
||||
for (int i = 8; i; i--) {
|
||||
if (!_pcmCurrentSound[i].index) {
|
||||
chan = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_driver->soundEffectIsPlaying(i + 0x3f))
|
||||
continue;
|
||||
|
||||
chan = i;
|
||||
_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
|
||||
}
|
||||
|
||||
if (!chan) {
|
||||
uint16 l = 0xffff;
|
||||
uint8 *ptr = 0;
|
||||
for (int i = 8; i; i--) {
|
||||
ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index) + 6;
|
||||
uint16 a = READ_LE_UINT16(ptr + 10);
|
||||
if (a <= l) {
|
||||
chan = i;
|
||||
l = a;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = _vm->getResourceAddress(rtSound, sound) + 6;
|
||||
if (l <= READ_LE_UINT16(ptr + 10))
|
||||
_vm->_sound->stopSound(_pcmCurrentSound[chan].index);
|
||||
else
|
||||
chan = 0;
|
||||
}
|
||||
|
||||
if (chan) {
|
||||
_pcmCurrentSound[chan].index = sound;
|
||||
_pcmCurrentSound[chan].chan = sfxChanRelIndex;
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
void Player_Towns::restartLoopingSounds() {
|
||||
if (_cdaNumLoops && !_cdaForceRestart)
|
||||
_cdaForceRestart = 1;
|
||||
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (!_pcmCurrentSound[i].paused)
|
||||
continue;
|
||||
|
||||
_pcmCurrentSound[i].paused = 0;
|
||||
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index);
|
||||
if (!ptr)
|
||||
continue;
|
||||
ptr += 24;
|
||||
|
||||
int c = 1;
|
||||
while (_pcmCurrentSound[i].chan != c) {
|
||||
ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32;
|
||||
c++;
|
||||
}
|
||||
|
||||
_driver->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr);
|
||||
}
|
||||
|
||||
_driver->intf()->callback(73, 1);
|
||||
}
|
||||
|
||||
void Player_Towns::startSoundEx(int sound, int velo, int pan, int note) {
|
||||
uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2;
|
||||
|
||||
if (pan > 99)
|
||||
pan = 99;
|
||||
|
||||
velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14];
|
||||
velo = CLIP(velo, 1, 255);
|
||||
|
||||
if (ptr[13] == 0) {
|
||||
velo >>= 1;
|
||||
|
||||
if (!velo)
|
||||
velo = 1;
|
||||
|
||||
pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64;
|
||||
|
||||
playPcmTrack(sound, ptr + 6, velo, pan, note);
|
||||
|
||||
} else if (ptr[13] == 2) {
|
||||
int volLeft = velo;
|
||||
int volRight = velo;
|
||||
|
||||
if (pan < 50)
|
||||
volRight = ((pan * 2 + 1) * velo + 50) / 100;
|
||||
else if (pan > 50)
|
||||
volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100;
|
||||
|
||||
setVolumeCD(volLeft, volRight);
|
||||
|
||||
if (!_cdaForceRestart && sound == _cdaCurrentSound)
|
||||
return;
|
||||
|
||||
playCdaTrack(sound, ptr + 6, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Player_Towns::stopSoundSuspendLooping(int sound) {
|
||||
if (!sound) {
|
||||
return;
|
||||
} else if (sound == _cdaCurrentSound) {
|
||||
if (_cdaNumLoops && _cdaForceRestart)
|
||||
_cdaForceRestart = 1;
|
||||
} else {
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (sound == _pcmCurrentSound[i].index) {
|
||||
if (!_driver->soundEffectIsPlaying(i + 0x3f))
|
||||
continue;
|
||||
_driver->stopSoundEffect(i + 0x3f);
|
||||
if (_pcmCurrentSound[i].looping)
|
||||
_pcmCurrentSound[i].paused = 1;
|
||||
else
|
||||
_pcmCurrentSound[i].index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player_Towns::playEuphonyTrack(int sound, const uint8 *data) {
|
||||
const uint8 *pos = data + 16;
|
||||
const uint8 *src = pos + data[14] * 48;
|
||||
const uint8 *trackData = src + 150;
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
_driver->chanEnable(i, *src++);
|
||||
for (int i = 0; i < 32; i++)
|
||||
_driver->chanMode(i, 0xff);
|
||||
for (int i = 0; i < 32; i++)
|
||||
_driver->chanOrdr(i, *src++);
|
||||
for (int i = 0; i < 32; i++)
|
||||
_driver->chanVolumeShift(i, *src++);
|
||||
for (int i = 0; i < 32; i++)
|
||||
_driver->chanNoteShift(i, *src++);
|
||||
|
||||
src += 8;
|
||||
for (int i = 0; i < 6; i++)
|
||||
_driver->assignChannel(i, *src++);
|
||||
|
||||
for (int i = 0; i < data[14]; i++) {
|
||||
_driver->loadInstrument(i, i, pos + i * 48);
|
||||
_driver->intf()->callback(4, i, i);
|
||||
}
|
||||
|
||||
_eupVolLeft = _ovrCur.vLeft;
|
||||
_eupVolRight = _ovrCur.vRight;
|
||||
int lvl = _ovrCur.vLeft + _ovrCur.vRight;
|
||||
if (!lvl)
|
||||
lvl = data[8] + data[9];
|
||||
lvl >>= 2;
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
_driver->chanVolume(i, lvl);
|
||||
|
||||
uint32 trackSize = READ_LE_UINT32(src);
|
||||
src += 4;
|
||||
uint8 startTick = *src++;
|
||||
|
||||
_driver->setMusicTempo(*src++);
|
||||
_driver->startMusicTrack(trackData, trackSize, startTick);
|
||||
|
||||
_eupLooping = (*src != 1) ? 1 : 0;
|
||||
_driver->setMusicLoop(_eupLooping != 0);
|
||||
_driver->continueParsing();
|
||||
_eupCurrentSound = sound;
|
||||
}
|
||||
|
||||
void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note) {
|
||||
const uint8 *ptr = data;
|
||||
const uint8 *sfxData = ptr + 16;
|
||||
|
||||
int note2, velocity;
|
||||
|
||||
if (velo)
|
||||
velocity = velo;
|
||||
else if (_ovrCur.vLeft + _ovrCur.vRight)
|
||||
velocity = (_ovrCur.vLeft + _ovrCur.vRight) >> 2;
|
||||
else
|
||||
velocity = ptr[8] >> 1;
|
||||
|
||||
int numChan = ptr[14];
|
||||
for (int i = 0; i < numChan; i++) {
|
||||
int chan = getNextFreePcmChannel(sound, i);
|
||||
if (!chan)
|
||||
return;
|
||||
|
||||
_driver->intf()->callback(70, _unkFlags);
|
||||
_driver->chanPanPos(chan + 0x3f, pan);
|
||||
|
||||
if (note)
|
||||
note2 = note;
|
||||
else if (_ovrCur.note)
|
||||
note2 = _ovrCur.note;
|
||||
else
|
||||
note2 = sfxData[28];
|
||||
|
||||
_driver->playSoundEffect(chan + 0x3f, note2, velocity, sfxData);
|
||||
|
||||
_pcmCurrentSound[chan].note = note2;
|
||||
_pcmCurrentSound[chan].velo = velocity;
|
||||
_pcmCurrentSound[chan].pan = pan;
|
||||
_pcmCurrentSound[chan].paused = 0;
|
||||
_pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0;
|
||||
|
||||
sfxData += (READ_LE_UINT32(&sfxData[12]) + 32);
|
||||
}
|
||||
}
|
||||
|
||||
void Player_Towns::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) {
|
||||
const uint8 *ptr = data;
|
||||
|
||||
if (!sound)
|
||||
return;
|
||||
|
||||
if (!skipTrackVelo) {
|
||||
if (_ovrCur.vLeft + _ovrCur.vRight)
|
||||
setVolumeCD(_ovrCur.vLeft, _ovrCur.vRight);
|
||||
else
|
||||
setVolumeCD(ptr[8], ptr[9]);
|
||||
}
|
||||
|
||||
if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1)
|
||||
return;
|
||||
|
||||
ptr += 16;
|
||||
|
||||
int track = ptr[0];
|
||||
_cdaNumLoops = ptr[1];
|
||||
int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
|
||||
int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
|
||||
|
||||
_vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start);
|
||||
_cdaForceRestart = 0;
|
||||
_cdaCurrentSound = sound;
|
||||
}
|
||||
|
||||
void Player_Towns::stopPcmTrack(int sound) {
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (sound == _pcmCurrentSound[i].index || !sound) {
|
||||
_driver->stopSoundEffect(i + 0x3f);
|
||||
_pcmCurrentSound[i].index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
120
engines/scumm/player_towns.h
Normal file
120
engines/scumm/player_towns.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
/* 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.
|
||||
*
|
||||
* $URL$
|
||||
* $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCUMM_PLAYER_TOWNS_H
|
||||
#define SCUMM_PLAYER_TOWNS_H
|
||||
|
||||
#include "scumm/scumm.h"
|
||||
#include "scumm/music.h"
|
||||
#include "sound/softsynth/fmtowns_pc98/towns_euphony.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
class Player_Towns : public MusicEngine {
|
||||
public:
|
||||
Player_Towns(ScummEngine *vm, Audio::Mixer *mixer);
|
||||
virtual ~Player_Towns();
|
||||
|
||||
bool init();
|
||||
|
||||
void setMusicVolume(int vol);
|
||||
void setSfxVolume(int vol);
|
||||
void startSound(int sound);
|
||||
void stopSound(int sound);
|
||||
void stopAllSounds();
|
||||
|
||||
int getSoundStatus(int sound) const;
|
||||
int getCurrentCdaSound() { return _cdaCurrentSound; }
|
||||
int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; }
|
||||
|
||||
virtual int32 doCommand(int numargs, int args[]);
|
||||
|
||||
void setVolumeCD(int left, int right);
|
||||
void setSoundVolume(int sound, int left, int right);
|
||||
void setSoundNote(int sound, int note);
|
||||
|
||||
void saveLoadWithSerializer(Serializer *ser);
|
||||
void restoreAfterLoad();
|
||||
|
||||
TownsEuphonyDriver *driver() { return _driver; }
|
||||
|
||||
protected:
|
||||
virtual int getNextFreePcmChannel(int sound, int sfxChanRelIndex);
|
||||
|
||||
private:
|
||||
void restartLoopingSounds();
|
||||
void startSoundEx(int sound, int velo, int pan, int note);
|
||||
void stopSoundSuspendLooping(int sound);
|
||||
|
||||
void playEuphonyTrack(int sound, const uint8 *data);
|
||||
void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0);
|
||||
void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false);
|
||||
|
||||
void stopPcmTrack(int sound);
|
||||
|
||||
uint8 _cdaVolLeft;
|
||||
uint8 _cdaVolRight;
|
||||
|
||||
struct SoundOvrParameters {
|
||||
uint8 vLeft;
|
||||
uint8 vRight;
|
||||
uint8 note;
|
||||
};
|
||||
|
||||
SoundOvrParameters *_soundOverride;
|
||||
SoundOvrParameters _ovrCur;
|
||||
|
||||
uint8 _unkFlags;
|
||||
|
||||
struct PcmCurrentSound {
|
||||
uint16 index;
|
||||
uint16 chan;
|
||||
uint8 note;
|
||||
uint8 velo;
|
||||
uint8 pan;
|
||||
uint8 paused;
|
||||
uint8 looping;
|
||||
uint32 priority;
|
||||
} _pcmCurrentSound[9];
|
||||
|
||||
uint8 _eupCurrentSound;
|
||||
uint8 _eupLooping;
|
||||
uint8 _eupVolLeft;
|
||||
uint8 _eupVolRight;
|
||||
|
||||
uint8 _cdaCurrentSound;
|
||||
uint8 _cdaNumLoops;
|
||||
uint8 _cdaForceRestart;
|
||||
|
||||
uint8 _cdaCurrentSoundTemp;
|
||||
uint8 _cdaNumLoopsTemp;
|
||||
|
||||
TownsEuphonyDriver *_driver;
|
||||
ScummEngine *_vm;
|
||||
};
|
||||
|
||||
} // End of namespace Scumm
|
||||
|
||||
#endif
|
|
@ -32,6 +32,7 @@
|
|||
#include "scumm/charset.h"
|
||||
#include "scumm/imuse_digi/dimuse.h"
|
||||
#include "scumm/imuse/imuse.h"
|
||||
#include "player_towns.h"
|
||||
#include "scumm/he/intern_he.h"
|
||||
#include "scumm/object.h"
|
||||
#include "scumm/resource.h"
|
||||
|
@ -447,6 +448,9 @@ bool ScummEngine::loadState(int slot, bool compat) {
|
|||
// Update volume settings
|
||||
syncSoundSettings();
|
||||
|
||||
if (_townsPlayer && (hdr.ver >= VER(81)))
|
||||
_townsPlayer->restoreAfterLoad();
|
||||
|
||||
// Init NES costume data
|
||||
if (_game.platform == Common::kPlatformNES) {
|
||||
if (hdr.ver < VER(47))
|
||||
|
@ -1394,6 +1398,11 @@ void ScummEngine::saveOrLoad(Serializer *s) {
|
|||
_imuse->save_or_load(s, this);
|
||||
}
|
||||
|
||||
|
||||
// Save/load FM-Towns audio status
|
||||
if (_townsPlayer)
|
||||
_townsPlayer->saveLoadWithSerializer(s);
|
||||
|
||||
//
|
||||
// Save/load the charset renderer state
|
||||
//
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace Scumm {
|
|||
* only saves/loads those which are valid for the version of the savegame
|
||||
* which is being loaded/saved currently.
|
||||
*/
|
||||
#define CURRENT_VER 80
|
||||
#define CURRENT_VER 81
|
||||
|
||||
/**
|
||||
* An auxillary macro, used to specify savegame versions. We use this instead
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "scumm/scumm_v3.h"
|
||||
#include "scumm/scumm_v5.h"
|
||||
#include "scumm/sound.h"
|
||||
#include "scumm/player_towns.h"
|
||||
#include "scumm/util.h"
|
||||
#include "scumm/verbs.h"
|
||||
|
||||
|
@ -1595,21 +1596,18 @@ void ScummEngine_v5::o5_resourceRoutines() {
|
|||
debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number);
|
||||
break;
|
||||
case 35:
|
||||
// TODO: Might be used to set CD volume in FM-TOWNS Loom
|
||||
foo = getVarOrDirectByte(PARAM_2);
|
||||
debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number);
|
||||
if (_townsPlayer)
|
||||
_townsPlayer->setVolumeCD(getVarOrDirectByte(PARAM_2), resid);
|
||||
break;
|
||||
case 36:
|
||||
// TODO: Sets the loudness of a sound resource. Used in Indy3 and Zak.
|
||||
foo = getVarOrDirectByte(PARAM_2);
|
||||
bar = fetchScriptByte();
|
||||
debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number);
|
||||
if (_townsPlayer)
|
||||
_townsPlayer->setSoundVolume(resid, foo, bar);
|
||||
break;
|
||||
case 37:
|
||||
// TODO: Sets the pitch of a sound resource (pitch = foo - center semitones.
|
||||
// "center" is at 0x32 in the sfx resource (always 0x3C in zak256, but sometimes different in Indy3).
|
||||
foo = getVarOrDirectByte(PARAM_2);
|
||||
debug(0, "o5_resourceRoutines %d not yet handled (script %d)", op, vm.slot[_currentScript].number);
|
||||
if (_townsPlayer)
|
||||
_townsPlayer->setSoundNote(resid, getVarOrDirectByte(PARAM_2));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1981,6 +1979,7 @@ void ScummEngine_v5::o5_startMusic() {
|
|||
break;
|
||||
case 0xFF:
|
||||
// TODO: Might return current CD volume in FM-TOWNS Loom. See also bug #805691.
|
||||
result = _townsPlayer->getCurrentCdaVolume();
|
||||
break;
|
||||
default:
|
||||
// TODO: return track length in seconds. We'll have to extend Sound and OSystem for this.
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "scumm/imuse_digi/dimuse.h"
|
||||
#include "scumm/smush/smush_mixer.h"
|
||||
#include "scumm/smush/smush_player.h"
|
||||
#include "scumm/player_towns.h"
|
||||
#include "scumm/insane/insane.h"
|
||||
#include "scumm/he/animation_he.h"
|
||||
#include "scumm/he/intern_he.h"
|
||||
|
@ -146,6 +147,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)
|
|||
_imuse = NULL;
|
||||
_imuseDigital = NULL;
|
||||
_musicEngine = NULL;
|
||||
_townsPlayer = NULL;
|
||||
_verbs = NULL;
|
||||
_objs = NULL;
|
||||
_sound = NULL;
|
||||
|
@ -1757,6 +1759,10 @@ void ScummEngine::setupMusic(int midi) {
|
|||
_musicEngine = new Player_V2CMS(this, _mixer);
|
||||
} else if (_game.platform == Common::kPlatform3DO && _game.heversion <= 62) {
|
||||
// 3DO versions use digital music and sound samples.
|
||||
} else if (_game.platform == Common::kPlatformFMTowns && (_game.version == 3 || _game.id == GID_MONKEY)) {
|
||||
_musicEngine = _townsPlayer = new Player_Towns(this, _mixer);
|
||||
if (!_townsPlayer->init())
|
||||
error("Failed to initialize FM-Towns audio driver.");
|
||||
} else if (_game.version >= 3 && _game.heversion <= 62) {
|
||||
MidiDriver *nativeMidiDriver = 0;
|
||||
MidiDriver *adlibMidiDriver = 0;
|
||||
|
@ -1806,6 +1812,10 @@ void ScummEngine::syncSoundSettings() {
|
|||
_musicEngine->setMusicVolume(soundVolumeMusic);
|
||||
}
|
||||
|
||||
if (_townsPlayer) {
|
||||
_townsPlayer->setSfxVolume(soundVolumeSfx);
|
||||
}
|
||||
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolumeSfx);
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, soundVolumeSpeech);
|
||||
|
|
|
@ -70,6 +70,7 @@ class CharsetRenderer;
|
|||
class IMuse;
|
||||
class IMuseDigital;
|
||||
class MusicEngine;
|
||||
class Player_Towns;
|
||||
class ScummEngine;
|
||||
class ScummDebugger;
|
||||
class Serializer;
|
||||
|
@ -426,6 +427,7 @@ public:
|
|||
IMuse *_imuse;
|
||||
IMuseDigital *_imuseDigital;
|
||||
MusicEngine *_musicEngine;
|
||||
Player_Towns *_townsPlayer;
|
||||
Sound *_sound;
|
||||
|
||||
VerbSlot *_verbs;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "scumm/file.h"
|
||||
#include "scumm/imuse/imuse.h"
|
||||
#include "scumm/imuse_digi/dimuse.h"
|
||||
#include "scumm/player_towns.h"
|
||||
#include "scumm/scumm.h"
|
||||
#include "scumm/sound.h"
|
||||
#include "scumm/util.h"
|
||||
|
@ -150,9 +151,10 @@ void Sound::processSoundQueues() {
|
|||
data[0] >> 8, data[0] & 0xFF,
|
||||
data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
||||
|
||||
if (_vm->_imuse) {
|
||||
if (_vm->_townsPlayer)
|
||||
_vm->VAR(_vm->VAR_SOUNDRESULT) = (short)_vm->_townsPlayer->doCommand(num, data);
|
||||
else if (_vm->_imuse)
|
||||
_vm->VAR(_vm->VAR_SOUNDRESULT) = (short)_vm->_imuse->doCommand(num, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
_soundQuePos = 0;
|
||||
|
@ -312,91 +314,6 @@ void Sound::playSound(int soundID) {
|
|||
stream = Audio::makeRawStream(sound, size, rate, Audio::FLAG_UNSIGNED);
|
||||
_mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID);
|
||||
}
|
||||
else if ((_vm->_game.platform == Common::kPlatformFMTowns && _vm->_game.version == 3) || READ_BE_UINT32(ptr) == MKID_BE('SOUN') || READ_BE_UINT32(ptr) == MKID_BE('TOWS')) {
|
||||
|
||||
bool tows = READ_BE_UINT32(ptr) == MKID_BE('TOWS');
|
||||
if (_vm->_game.version == 3) {
|
||||
size = READ_LE_UINT32(ptr);
|
||||
} else {
|
||||
size = READ_BE_UINT32(ptr + 4) - 2;
|
||||
if (tows)
|
||||
size += 8;
|
||||
ptr += 2;
|
||||
}
|
||||
|
||||
rate = 11025;
|
||||
int type = *(ptr + 0x0D);
|
||||
int numInstruments;
|
||||
|
||||
if (tows)
|
||||
type = 0;
|
||||
|
||||
switch (type) {
|
||||
case 0: // Sound effect
|
||||
numInstruments = *(ptr + 0x14);
|
||||
if (tows)
|
||||
numInstruments = 1;
|
||||
ptr += 0x16;
|
||||
size -= 0x16;
|
||||
|
||||
while (numInstruments--) {
|
||||
int waveSize = READ_LE_UINT32(ptr + 0x0C);
|
||||
int loopStart = READ_LE_UINT32(ptr + 0x10) * 2;
|
||||
int loopEnd = READ_LE_UINT32(ptr + 0x14) - 1;
|
||||
rate = READ_LE_UINT32(ptr + 0x18) * 1000 / 0x62;
|
||||
ptr += 0x20;
|
||||
size -= 0x20;
|
||||
if (size < waveSize) {
|
||||
warning("Wrong wave size in sound #%i: %i", soundID, waveSize);
|
||||
waveSize = size;
|
||||
}
|
||||
sound = (byte *)malloc(waveSize);
|
||||
for (int x = 0; x < waveSize; x++) {
|
||||
byte b = *ptr++;
|
||||
if (b < 0x80)
|
||||
sound[x] = 0x7F - b;
|
||||
else
|
||||
sound[x] = b;
|
||||
}
|
||||
size -= waveSize;
|
||||
|
||||
if (loopEnd > 0) {
|
||||
Audio::SeekableAudioStream *s = Audio::makeRawStream(sound, waveSize, rate, Audio::FLAG_UNSIGNED);
|
||||
stream = new Audio::SubLoopingAudioStream(s, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate));
|
||||
} else {
|
||||
stream = Audio::makeRawStream(sound, waveSize, rate, Audio::FLAG_UNSIGNED);
|
||||
}
|
||||
_mixer->playStream(Audio::Mixer::kSFXSoundType, NULL, stream, soundID, 255, 0);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// Music (Euphony format)
|
||||
if (_vm->_musicEngine)
|
||||
_vm->_musicEngine->startSound(soundID);
|
||||
break;
|
||||
case 2: // CD track resource
|
||||
ptr += 0x16;
|
||||
|
||||
if (soundID == _currentCDSound && pollCD() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int track = ptr[0];
|
||||
int loops = ptr[1];
|
||||
int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4];
|
||||
int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7];
|
||||
|
||||
playCDTrack(track, loops == 0xff ? -1 : loops, start, end <= start ? 0 : end - start);
|
||||
}
|
||||
|
||||
_currentCDSound = soundID;
|
||||
break;
|
||||
default:
|
||||
// All other sound types are ignored
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) {
|
||||
// Mac version of Loom uses yet another sound format
|
||||
/*
|
||||
|
@ -480,6 +397,9 @@ void Sound::playSound(int soundID) {
|
|||
if (_vm->_musicEngine) {
|
||||
_vm->_musicEngine->startSound(soundID);
|
||||
}
|
||||
|
||||
if (_vm->_townsPlayer)
|
||||
_currentCDSound = _vm->_townsPlayer->getCurrentCdaSound();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfac
|
|||
INTCB(notImpl),
|
||||
// 72
|
||||
INTCB(notImpl),
|
||||
INTCB(notImpl),
|
||||
INTCB(cdaToggle),
|
||||
INTCB(notImpl),
|
||||
INTCB(notImpl),
|
||||
// 76
|
||||
|
@ -222,9 +222,19 @@ TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfac
|
|||
|
||||
_timerBase = (uint32)(_baserate * 1000000.0f);
|
||||
_tickLength = 2 * _timerBase;
|
||||
|
||||
setTimerCallbackA((ChipTimerProc)&TownsAudioInterface::timerCallbackA);
|
||||
setTimerCallbackB((ChipTimerProc)&TownsAudioInterface::timerCallbackB);
|
||||
}
|
||||
|
||||
TownsAudioInterface::~TownsAudioInterface() {
|
||||
Common::StackLock lock(_mutex);
|
||||
reset();
|
||||
_ready = false;
|
||||
|
||||
setTimerCallbackA();
|
||||
setTimerCallbackB();
|
||||
|
||||
delete[] _fmSaveReg[0];
|
||||
delete[] _fmSaveReg[1];
|
||||
delete[] _fmInstruments;
|
||||
|
@ -759,6 +769,12 @@ int TownsAudioInterface::intf_updateOutputVolume(va_list &args) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int TownsAudioInterface::intf_cdaToggle(va_list &args) {
|
||||
//int mode = va_arg(args, int);
|
||||
//_unkMask = mode ? 0x7f : 0x3f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int TownsAudioInterface::intf_pcmUpdateEnvelopeGenerator(va_list &args) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
pcmUpdateEnvelopeGenerator(i);
|
||||
|
|
|
@ -95,6 +95,7 @@ private:
|
|||
int intf_setOutputVolume(va_list &args);
|
||||
int intf_resetOutputVolume(va_list &args);
|
||||
int intf_updateOutputVolume(va_list &args);
|
||||
int intf_cdaToggle(va_list &args);
|
||||
int intf_pcmUpdateEnvelopeGenerator(va_list &args);
|
||||
|
||||
int intf_notImpl(va_list &args);
|
||||
|
|
|
@ -1037,11 +1037,21 @@ TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type)
|
|||
_musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) {
|
||||
|
||||
_sfxOffsets[0] = _sfxOffsets[1] = 0;
|
||||
|
||||
setTimerCallbackA((ChipTimerProc)&TownsPC98_AudioDriver::timerCallbackA);
|
||||
setTimerCallbackB((ChipTimerProc)&TownsPC98_AudioDriver::timerCallbackB);
|
||||
}
|
||||
|
||||
TownsPC98_AudioDriver::~TownsPC98_AudioDriver() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
reset();
|
||||
|
||||
_ready = false;
|
||||
|
||||
setTimerCallbackA();
|
||||
setTimerCallbackB();
|
||||
|
||||
if (_channels) {
|
||||
for (int i = 0; i < _numChan; i++)
|
||||
delete _channels[i];
|
||||
|
|
|
@ -835,6 +835,7 @@ TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) :
|
|||
|
||||
memset(&_timers[0], 0, sizeof(ChipTimer));
|
||||
memset(&_timers[1], 0, sizeof(ChipTimer));
|
||||
|
||||
_timers[0].cb = &TownsPC98_FmSynth::timerCallbackA;
|
||||
_timers[1].cb = &TownsPC98_FmSynth::timerCallbackB;
|
||||
_timerbase = (uint32)(_baserate * 1000000.0f);
|
||||
|
@ -842,6 +843,9 @@ TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) :
|
|||
|
||||
TownsPC98_FmSynth::~TownsPC98_FmSynth() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
_ready = false;
|
||||
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
delete _ssg;
|
||||
delete _prc;
|
||||
|
@ -1154,6 +1158,14 @@ int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) {
|
|||
return numSamples;
|
||||
}
|
||||
|
||||
void TownsPC98_FmSynth::setTimerCallbackA(ChipTimerProc proc) {
|
||||
_timers[0].cb = proc;
|
||||
}
|
||||
|
||||
void TownsPC98_FmSynth::setTimerCallbackB(ChipTimerProc proc) {
|
||||
_timers[1].cb = proc;
|
||||
}
|
||||
|
||||
uint8 TownsPC98_FmSynth::readSSGStatus() {
|
||||
return _ssg->chanEnable();
|
||||
}
|
||||
|
|
|
@ -71,6 +71,10 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
typedef void (TownsPC98_FmSynth::*ChipTimerProc)();
|
||||
void setTimerCallbackA(ChipTimerProc proc = &TownsPC98_FmSynth::timerCallbackA);
|
||||
void setTimerCallbackB(ChipTimerProc proc = &TownsPC98_FmSynth::timerCallbackB);
|
||||
|
||||
// Implement this in your inherited class if your driver generates
|
||||
// additional output that has to be inserted into the buffer.
|
||||
virtual void nextTickEx(int32 *buffer, uint32 bufferSize) {}
|
||||
|
@ -80,8 +84,8 @@ protected:
|
|||
}
|
||||
uint8 readSSGStatus();
|
||||
|
||||
virtual void timerCallbackA() = 0;
|
||||
virtual void timerCallbackB() = 0;
|
||||
virtual void timerCallbackA() {}
|
||||
virtual void timerCallbackB() {}
|
||||
|
||||
// The audio driver can store and apply two different audio settings
|
||||
// (usually for music and sound effects). The channel mask will determine
|
||||
|
@ -139,8 +143,6 @@ private:
|
|||
|
||||
bool _regProtectionFlag;
|
||||
|
||||
typedef void (TownsPC98_FmSynth::*ChipTimerProc)();
|
||||
|
||||
struct ChipTimer {
|
||||
bool enabled;
|
||||
uint16 value;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue