Initial commit

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

View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/decoder/adlib_sfx_stream.h"
#include "ultima/nuvie/sound/adlib_sfx_manager.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
AdLibSfxManager::AdLibSfxManager(const Configuration *cfg, Audio::Mixer *m) : SfxManager(cfg, m) {
}
AdLibSfxManager::~AdLibSfxManager() {
}
bool AdLibSfxManager::playSfx(SfxIdType sfx_id, uint8 volume) {
return playSfxLooping(sfx_id, nullptr, volume);
}
bool AdLibSfxManager::playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) {
AdLibSfxStream *stream = nullptr;
if (sfx_id == NUVIE_SFX_SE_TICK) {
stream = new AdLibSfxStream(config, mixer->getOutputRate(), 17, 0x30, 0x60, 0xff, 22050);
} else if (sfx_id == NUVIE_SFX_EXPLOSION) {
stream = new AdLibSfxStream(config, mixer->getOutputRate(), 8, 0x40, 0x40, 0x7f, 22050);
}
if (stream) {
sfx_duration = stream->getLengthInMsec();
playSoundSample(stream, handle, volume);
return true;
}
return false;
}
void AdLibSfxManager::playSoundSample(Audio::AudioStream *stream, Audio::SoundHandle *looping_handle, uint8 volume) {
Audio::SoundHandle handle;
if (looping_handle) {
Audio::RewindableAudioStream *audioStream = dynamic_cast<Audio::RewindableAudioStream *>(stream);
assert(audioStream);
Audio::LoopingAudioStream *looping_stream = new Audio::LoopingAudioStream(audioStream, 0);
mixer->playStream(Audio::Mixer::kPlainSoundType, looping_handle, looping_stream, -1, volume);
} else {
mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, stream, -1, volume);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,49 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADLIB_SFX_MANAGER_H
#define NUVIE_SOUND_ADLIB_SFX_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "audio/mixer.h"
#include "ultima/nuvie/sound/sfx_manager.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
class AdLibSfxManager : public SfxManager {
public:
AdLibSfxManager(const Configuration *cfg, Audio::Mixer *m);
~AdLibSfxManager() override;
bool playSfx(SfxIdType sfx_id, uint8 volume) override;
bool playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) override;
private:
void playSoundSample(Audio::AudioStream *stream, Audio::SoundHandle *looping_handle, uint8 volume);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/sound/adplug/adplug_player.h"
#include "ultima/nuvie/sound/adplug/silent_opl.h"
namespace Ultima {
namespace Nuvie {
/***** CPlayer *****/
const unsigned short CPlayer::note_table[12] =
{363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647, 686};
const unsigned char CPlayer::op_table[9] =
{0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12};
CPlayer::CPlayer(Copl *newopl)
: opl(newopl) {
}
CPlayer::~CPlayer() {
}
unsigned long CPlayer::songlength(int subsong) {
CSilentopl tempopl;
Copl *saveopl = opl;
float slength = 0.0f;
// save original OPL from being overwritten
opl = &tempopl;
// get song length
rewind(subsong);
while (update() && slength < 600000) // song length limit: 10 minutes
slength += 1000.0f / getrefresh();
rewind(subsong);
// restore original OPL and return
opl = saveopl;
return (unsigned long)slength;
}
void CPlayer::seek(unsigned long ms) {
float pos = 0.0f;
rewind();
while (pos < ms && update()) // seek to new position
pos += 1000 / getrefresh();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_ADPLUG_PLAYER
#define NUVIE_SOUND_ADPLUG_ADPLUG_PLAYER
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/sound/adplug/opl.h"
namespace Common {
class Path;
}
namespace Ultima {
namespace Nuvie {
class CPlayer {
public:
CPlayer(Copl *newopl);
virtual ~CPlayer();
/***** Operational methods *****/
void seek(unsigned long ms);
virtual bool load(const Common::Path &filename) = 0; // loads file
virtual bool update() = 0; // executes replay code for 1 tick
virtual void rewind(int subsong = -1) = 0; // rewinds to specified subsong
virtual float getrefresh() = 0; // returns needed timer refresh rate
/***** Informational methods *****/
unsigned long songlength(int subsong = -1);
virtual Std::string gettype() = 0; // returns file type
virtual Std::string gettitle() { // returns song title
return Std::string();
}
virtual Std::string getauthor() { // returns song author name
return Std::string();
}
virtual Std::string getdesc() { // returns song description
return Std::string();
}
virtual unsigned int getpatterns() { // returns number of patterns
return 0;
}
virtual unsigned int getpattern() { // returns currently playing pattern
return 0;
}
virtual unsigned int getorders() { // returns size of orderlist
return 0;
}
virtual unsigned int getorder() { // returns currently playing song position
return 0;
}
virtual unsigned int getrow() { // returns currently playing row
return 0;
}
virtual unsigned int getspeed() { // returns current song speed
return 0;
}
virtual unsigned int getsubsongs() { // returns number of subsongs
return 1;
}
virtual unsigned int getinstruments() { // returns number of instruments
return 0;
}
virtual Std::string getinstrument(unsigned int n) { // returns n-th instrument name
return Std::string();
}
protected:
Copl *opl; // our OPL chip
static const unsigned short note_table[12]; // standard adlib note table
static const unsigned char op_table[9]; // the 9 operators as expected by the OPL2
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,76 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/adplug/fm_opl.h"
namespace Ultima {
namespace Nuvie {
CEmuopl::CEmuopl(int rate, bool bit16, bool usestereo)
: use16bit(bit16), stereo(usestereo), oplRate(rate) {
YM3812Init(1, 3579545, rate);
}
CEmuopl::~CEmuopl() {
YM3812Shutdown();
}
void CEmuopl::update(short *buf, int samples) {
int i;
if (use16bit) {
YM3812UpdateOne(0, buf, samples);
if (stereo)
for (i = samples - 1; i >= 0; i--) {
buf[i * 2] = buf[i];
buf[i * 2 + 1] = buf[i];
}
} else {
short *tempbuf = new short[stereo ? samples * 2 : samples];
YM3812UpdateOne(0, tempbuf, samples);
if (stereo)
for (i = samples - 1; i >= 0; i--) {
tempbuf[i * 2] = tempbuf[i];
tempbuf[i * 2 + 1] = tempbuf[i];
}
for (i = 0; i < (stereo ? samples * 2 : samples); i++)
((char *)buf)[i] = (tempbuf[i] >> 8) ^ 0x80;
delete [] tempbuf;
}
}
void CEmuopl::write(int reg, int val) {
YM3812Write(0, 0, reg);
YM3812Write(0, 1, val);
}
void CEmuopl::init() {
YM3812ResetChip(0);
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_EMU_OPL_H
#define NUVIE_SOUND_ADPLUG_EMU_OPL_H
#include "ultima/nuvie/sound/adplug/opl.h"
#include "audio/fmopl.h"
namespace Ultima {
namespace Nuvie {
class CEmuopl: public Copl {
public:
CEmuopl(int rate, bool bit16, bool usestereo); // rate = sample rate
~CEmuopl() override;
int getRate() {
return oplRate;
}
void update(short *buf, int samples); // fill buffer
// template methods
void write(int reg, int val) override;
void init() override;
private:
bool use16bit, stereo;
int oplRate;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_FM_OPL_H
#define NUVIE_SOUND_ADPLUG_FM_OPL_H
#include "common/scummsys.h"
namespace Ultima {
namespace Nuvie {
#define HAS_YM3812 1
#define HAS_YM3526 0
#define HAS_Y8950 0
/* --- select emulation chips --- */
#define BUILD_YM3812 (HAS_YM3812)
#define BUILD_YM3526 (HAS_YM3526)
#define BUILD_Y8950 (HAS_Y8950)
/* select output bits size of output : 8 or 16 */
#define OPL_SAMPLE_BITS 16
#if (OPL_SAMPLE_BITS==16)
typedef int16 OPLSAMPLE;
#endif
#if (OPL_SAMPLE_BITS==8)
typedef int8 OPLSAMPLE;
#endif
typedef void (*OPL_TIMERHANDLER)(int channel, double interval_Sec);
typedef void (*OPL_IRQHANDLER)(int param, int irq);
typedef void (*OPL_UPDATEHANDLER)(int param, int min_interval_us);
typedef void (*OPL_PORTHANDLER_W)(int param, unsigned char data);
typedef unsigned char (*OPL_PORTHANDLER_R)(int param);
#if BUILD_YM3812
int YM3812Init(int num, int clock, int rate);
void YM3812Shutdown(void);
void YM3812ResetChip(int which);
int YM3812Write(int which, int a, int v);
unsigned char YM3812Read(int which, int a);
int YM3812TimerOver(int which, int c);
void YM3812UpdateOne(int which, int16 *buffer, int length);
void YM3812SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void YM3812SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param);
void YM3812SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param);
#endif
#if BUILD_YM3526
/*
** Initialize YM3526 emulator(s).
**
** 'num' is the number of virtual YM3526's to allocate
** 'clock' is the chip clock in Hz
** 'rate' is sampling rate
*/
int YM3526Init(int num, int clock, int rate);
/* shutdown the YM3526 emulators*/
void YM3526Shutdown(void);
void YM3526ResetChip(int which);
int YM3526Write(int which, int a, int v);
unsigned char YM3526Read(int which, int a);
int YM3526TimerOver(int which, int c);
/*
** Generate samples for one of the YM3526's
**
** 'which' is the virtual YM3526 number
** '*buffer' is the output buffer pointer
** 'length' is the number of samples that should be generated
*/
void YM3526UpdateOne(int which, int16 *buffer, int length);
void YM3526SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void YM3526SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param);
void YM3526SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param);
#endif
} // End of namespace Nuvie
} // End of namespace Ultima
#if BUILD_Y8950
#include "ymdeltat.h"
namespace Ultima {
namespace Nuvie {
/* Y8950 port handlers */
void Y8950SetPortHandler(int which, OPL_PORTHANDLER_W PortHandler_w, OPL_PORTHANDLER_R PortHandler_r, int param);
void Y8950SetKeyboardHandler(int which, OPL_PORTHANDLER_W KeyboardHandler_w, OPL_PORTHANDLER_R KeyboardHandler_r, int param);
void Y8950SetDeltaTMemory(int which, void *deltat_rom, int deltat_rom_size);
int Y8950Init(int num, int clock, int rate);
void Y8950Shutdown(void);
void Y8950ResetChip(int which);
int Y8950Write(int which, int a, int v);
unsigned char Y8950Read(int which, int a);
int Y8950TimerOver(int which, int c);
void Y8950UpdateOne(int which, int16 *buffer, int length);
void Y8950SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void Y8950SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param);
void Y8950SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param);
} // End of namespace Nuvie
} // End of namespace Ultima
#endif
#endif

View File

@@ -0,0 +1,572 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/sound/origin_fx_adib_driver.h"
#include "ultima/nuvie/sound/adplug/mid.h"
namespace Ultima {
namespace Nuvie {
//#define TESTING
#ifdef TESTING
#define midiprintf debug
#else
void CmidPlayer::midiprintf(const char *format, ...) {
}
#endif
#define LUCAS_STYLE 1
#define CMF_STYLE 2
#define MIDI_STYLE 4
#define SIERRA_STYLE 8
// AdLib melodic and rhythm mode defines
#define ADLIB_MELODIC 1
#define ADLIB_RYTHM 0
// File types
#define FILE_LUCAS 1
#define FILE_MIDI 2
#define FILE_CMF 3
#define FILE_SIERRA 4
#define FILE_ADVSIERRA 5
#define FILE_OLDLUCAS 6
CPlayer *CmidPlayer::factory(Copl *newopl) {
return new CmidPlayer(newopl);
}
CmidPlayer::CmidPlayer(Copl *newopl)
: CPlayer(newopl), author(&emptystr), title(&emptystr), remarks(&emptystr),
emptystr('\0'), flen(0), data(0) {
origin_fx_driver = new OriginFXAdLibDriver(Game::get_game()->get_config(), newopl);
}
CmidPlayer::~CmidPlayer() {
if (data)
delete [] data;
delete origin_fx_driver;
}
unsigned char CmidPlayer::datalook(long pos_) {
if (pos_ < 0 || pos_ >= flen) return 0;
return data[pos_];
}
unsigned long CmidPlayer::getnexti(unsigned long num) {
unsigned long v = 0;
unsigned long i;
for (i = 0; i < num; i++) {
v += (datalook(pos) << (8 * i));
pos++;
}
return v;
}
unsigned long CmidPlayer::getnext(unsigned long num) {
unsigned long v = 0;
unsigned long i;
for (i = 0; i < num; i++) {
v <<= 8;
v += datalook(pos);
pos++;
}
return v;
}
unsigned long CmidPlayer::getval() {
int v = 0;
unsigned char b;
b = (unsigned char)getnext(1);
v = b & 0x7f;
while ((b & 0x80) != 0) {
b = (unsigned char)getnext(1);
v = (v << 7) + (b & 0x7F);
}
return v;
}
bool CmidPlayer::load(const Common::Path &filename) {
return false;
}
bool CmidPlayer::load(const Common::Path &filename, int song_index) {
U6Lib_n f;
f.open(filename, 4, NUVIE_GAME_MD);
//binistream *f = fp.open(filename); if(!f) return false;
int good;
flen = f.get_item_size(song_index);
data = new unsigned char [flen];
f.get_item(song_index, data);
//f->readString((char *)data, flen);
//f->readString((char *)s, 6);
good = 0;
subsongs = 0;
switch (data[0]) {
case 'A':
if (data[1] == 'D' && data[2] == 'L') good = FILE_LUCAS;
break;
case 'M':
if (data[1] == 'T' && data[2] == 'h' && data[3] == 'd') good = FILE_MIDI;
break;
case 'C':
if (data[1] == 'T' && data[2] == 'M' && data[3] == 'F') good = FILE_CMF;
break;
break;
default:
if (data[4] == 'A' && data[5] == 'D') good = FILE_OLDLUCAS;
break;
}
if (good != 0)
subsongs = 1;
else {
delete [] data;
data = nullptr;
return false;
}
type = good;
//f->seek(0);
rewind(0);
return true;
}
void CmidPlayer::interrupt_vector() {
origin_fx_driver->interrupt_vector();
}
bool CmidPlayer::update() {
const uint8 adlib_chan_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,
10, 18, 11, 0, 12, 13, 17, 13, 16, 13, 14, 13, 13, 15,
13, 19, 0, 0, 0, 0, 21, 0, 0, 0, 26, 26, 25, 20, 20,
0, 0, 21, 21, 22, 23, 0, 0, 24, 0, 20, 0
};
const uint8 adlib_note_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48,
48, 48, 48, 0, 48, 42, 71, 42, 71, 47, 71, 47, 52, 79,
52, 77, 0, 0, 0, 0, 71, 0, 0, 0, 72, 79, 79, 64, 58,
0, 0, 89, 84, 48, 72, 0, 0, 36, 0, 96, 0
};
//long w,v,note,vel,ctrl,nv,x,l,lnum;
long w, v, note, vel, ctrl, x, l, lnum;
int i = 0, j, c;
//int on,onl,numchan;
int ret;
int current_status[16];
for (i = 0; i < 16; i++)
current_status[i] = 0;
if (doing == 1) {
// just get the first wait and ignore it :>
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on) {
pos = track[curtrack].pos;
if (type != FILE_SIERRA && type != FILE_ADVSIERRA)
track[curtrack].iwait += getval();
else
track[curtrack].iwait += getnext(1);
track[curtrack].pos = pos;
}
doing = 0;
}
iwait = 0;
ret = 1;
while (iwait == 0 && ret == 1) {
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on && track[curtrack].iwait == 0 &&
track[curtrack].pos < track[curtrack].tend) {
pos = track[curtrack].pos;
v = getnext(1);
// This is to do implied MIDI events. aka 'Running Status'
if (v < 0x80) {
v = track[curtrack].pv;
debug("Running status [%2X]", (unsigned int)v);
pos--;
} else {
if (v >= 0xf0 && v < 0xf9) {
track[curtrack].pv = 0; //reset running status.
} else if (v < 0xf0) {
track[curtrack].pv = (unsigned char)v;
}
// if v > 0xf9 then current running status is maintained.
}
c = v & 0x0f;
midiprintf("[%2X]", (unsigned int)v);
if (v == 0xfe)
midiprintf("pos=%d", (int)pos);
current_status[curtrack] = v;
switch (v & 0xf0) {
case 0x80: /*note off*/
midiprintf("Trk%02d: Note Off\n", curtrack);
note = getnext(1);
vel = getnext(1);
origin_fx_driver->play_note(c, note, 0);
break;
case 0x90: /*note on*/
// doing=0;
midiprintf("Trk%02d: Note On\n", curtrack);
note = getnext(1);
vel = getnext(1);
if (c == 9) {
if (adlib_chan_tbl[note] != 0) {
origin_fx_driver->play_note(adlib_chan_tbl[note] - 1, adlib_note_tbl[note], vel);
}
} else {
origin_fx_driver->play_note(c, note, vel);
}
break;
case 0xa0: /*key after touch */
note = getnext(1);
vel = getnext(1);
break;
case 0xb0: /*control change .. pitch bend? */
ctrl = getnext(1);
vel = getnext(1);
origin_fx_driver->control_mode_change(c, ctrl, vel);
break;
case 0xc0: /*patch change*/
x = getnext(1);
origin_fx_driver->program_change(c, x);
break;
case 0xd0: /*chanel touch*/
x = getnext(1);
break;
case 0xe0: /*pitch wheel*/
x = getnext(1);
l = getnext(1);
origin_fx_driver->pitch_bend(c, x, l);
break;
case 0xf0:
switch (v) {
case 0xf0:
case 0xf7: /*sysex*/
l = getval();
if (datalook(pos + l) == 0xf7)
i = 1;
midiprintf("{%d}", (int)l);
midiprintf("\n");
if (datalook(pos) == 0x7d &&
datalook(pos + 1) == 0x10 &&
datalook(pos + 2) < 16) {
adlib_style = LUCAS_STYLE | MIDI_STYLE;
for (i = 0; i < l; i++) {
midiprintf("%x ", datalook(pos + i));
if ((i - 3) % 10 == 0) midiprintf("\n");
}
midiprintf("\n");
getnext(1);
getnext(1);
c = getnext(1);
getnext(1);
// getnext(22); //temp
i = (getnext(1) << 4) + getnext(1);
//if ((i&1)==1) ch[c].ins[10]=1;
midiprintf("\n%d: ", c);
getnext(l - 26);
} else {
midiprintf("\n");
for (j = 0; j < l; j++)
midiprintf("%2X ", (unsigned int)getnext(1));
}
midiprintf("\n");
if (i == 1)
getnext(1);
break;
case 0xf1:
break;
case 0xf2:
getnext(2);
break;
case 0xf3:
getnext(1);
break;
case 0xf4:
break;
case 0xf5:
break;
case 0xf6: /*something*/
case 0xf8:
case 0xfa:
case 0xfb:
case 0xfc:
break;
case 0xfe:
i = getnext(1);
//debug("FE %02X pos=%d\n",i, (int)pos);//(unsigned int)getnext(1),(unsigned int)getnext(1));
getnext(2);
if (i == 0) {
//debug(" %02X",(unsigned int)getnext(1));
//getnext(1);
}
//debug("\n");
if (i != 3) {
origin_fx_driver->control_mode_change(c, 0x7b, 0);
}
break;
case 0xfd:
break;
case 0xff:
v = getnext(1);
l = getval();
midiprintf("\n");
midiprintf("{%X_%X}", (unsigned int)v, (int)l);
if (v == 0x51) {
lnum = getnext(l);
msqtr = lnum; /*set tempo*/
midiprintf("Set Tempo (qtr=%ld)", msqtr);
} else if (v == 0x3) {
midiprintf("Track Name: ");
for (i = 0; i < l; i++)
midiprintf("%c", (unsigned char)getnext(1));
} else if (v == 0x6) {
debugN("Marker: ");
for (i = 0; i < l; i++) {
//midiprintf ("%c",(unsigned char)getnext(1));
debugN("%c", (unsigned char)getnext(1));
}
debug("%s", "");
} else {
for (i = 0; i < l; i++)
midiprintf("%2X ", (unsigned int)getnext(1));
}
break;
}
break;
default:
midiprintf("! v = %d", (int)v); /* if we get down here, a error occurred */
break;
}
if (pos < track[curtrack].tend) {
if (type != FILE_SIERRA && type != FILE_ADVSIERRA)
w = getval();
else
w = getnext(1);
track[curtrack].iwait = w;
/*
if (w!=0)
{
midiprintf("\n<%d>",w);
f =
((float)w/(float)deltas)*((float)msqtr/(float)1000000);
if (doing==1) f=0; //not playing yet. don't wait yet
}
*/
} else
track[curtrack].iwait = 0;
track[curtrack].pos = pos;
}
/*
for(i=0;i<16;i++)
{
if(current_status[i] == 0)
debug("--");
else
debug("%02X", current_status[i]);
debug(" ");
}
debug("\n");
*/
ret = 0; //end of song.
iwait = 0;
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on == 1 &&
track[curtrack].pos < track[curtrack].tend)
ret = 1; //not yet..
if (ret == 1) {
iwait = 0xffffff; // bigger than any wait can be!
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on == 1 &&
track[curtrack].pos < track[curtrack].tend &&
track[curtrack].iwait < iwait)
iwait = track[curtrack].iwait;
}
}
if (iwait != 0 && ret == 1) {
for (curtrack = 0; curtrack < 16; curtrack++)
if (track[curtrack].on)
track[curtrack].iwait -= iwait;
fwait = 1.0f / (((float)iwait / (float)deltas) * ((float)msqtr / (float)1000000));
} else
fwait = 50; // 1/50th of a second
midiprintf("\n");
for (i = 0; i < 16; i++)
if (track[i].on) {
if (track[i].pos < track[i].tend)
;//midiprintf ("<%d:%d>",(int)i,(int)track[i].iwait);
else
midiprintf("stop");
}
// FIXME: current_status is unused?
(void)current_status[0];
if (ret)
return true;
else
return false;
}
float CmidPlayer::getrefresh() {
return (fwait > 0.01f ? fwait : 0.01f);
}
void CmidPlayer::rewind(int subsong) {
long i;
pos = 0;
tins = 0;
adlib_style = MIDI_STYLE | CMF_STYLE;
adlib_mode = ADLIB_MELODIC;
/* General init */
for (i = 0; i < 9; i++) {
chp[i][0] = -1;
chp[i][2] = 0;
}
deltas = 250; // just a number, not a standard
msqtr = 500000;
fwait = 123; // gotta be a small thing.. sorta like nothing
iwait = 0;
subsongs = 1;
for (i = 0; i < 16; i++) {
track[i].tend = 0;
track[i].spos = 0;
track[i].pos = 0;
track[i].iwait = 0;
track[i].on = 0;
track[i].pv = 0;
}
curtrack = 0;
/* specific to file-type init */
pos = 0;
i = getnext(1);
switch (type) {
case FILE_MIDI:
if (type != FILE_LUCAS)
tins = 128;
getnext(9); /*skip header*/
track_count = getnext(2); //total number of tracks.
deltas = getnext(2);
midiprintf("deltas:%ld\n", deltas);
load_ultima_midi_tracks();
break;
}
/* Common::sprintf_s(info,"%s\r\nTicks/Quarter Note: %ld\r\n",info,deltas);
Common::sprintf_s(info,"%sms/Quarter Note: %ld",info,msqtr); */
for (i = 0; i < 16; i++)
if (track[i].on) {
track[i].pos = track[i].spos;
track[i].pv = 0;
track[i].iwait = 0;
}
doing = 1;
origin_fx_driver->init();
}
void CmidPlayer::load_ultima_midi_tracks() {
for (curtrack = 0; curtrack < track_count; curtrack++) {
getnext(4); //skip MTrk
track[curtrack].on = 1;
track[curtrack].tend = getnext(4);
track[curtrack].tend += pos;
track[curtrack].spos = pos;
pos = track[curtrack].tend;
midiprintf("tracklen:%ld\n", track[curtrack].tend - track[curtrack].spos);
}
}
Std::string CmidPlayer::gettype() {
switch (type) {
case FILE_LUCAS:
return Std::string("LucasArts AdLib MIDI");
case FILE_MIDI:
return Std::string("General MIDI");
case FILE_CMF:
return Std::string("Creative Music Format (CMF MIDI)");
case FILE_OLDLUCAS:
return Std::string("Lucasfilm Adlib MIDI");
case FILE_ADVSIERRA:
return Std::string("Sierra On-Line VGA MIDI");
case FILE_SIERRA:
return Std::string("Sierra On-Line EGA MIDI");
default:
return Std::string("MIDI unknown");
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,115 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/sound/adplug/adplug_player.h"
namespace Ultima {
namespace Nuvie {
class OriginFXAdLibDriver;
class CmidPlayer: public CPlayer {
public:
static CPlayer *factory(Copl *newopl);
CmidPlayer(Copl *newopl);
~CmidPlayer() override;
bool load(const Common::Path &filename) override;
bool load(const Common::Path &filename, int song_index);
//bool load(const Std::string &filename, const CFileProvider &fp);
bool update() override;
void rewind(int subsong) override;
float getrefresh() override;
Std::string gettype() override;
Std::string gettitle() override {
return Std::string(title);
}
Std::string getauthor() override {
return Std::string(author);
}
Std::string getdesc() override {
return Std::string(remarks);
}
unsigned int getinstruments() override {
return tins;
}
unsigned int getsubsongs() override {
return subsongs;
}
protected:
static const unsigned char adlib_opadd[];
static const int ops[], map_chan[], fnums[], percussion_map[];
struct midi_track {
unsigned long tend;
unsigned long spos;
unsigned long pos;
unsigned long iwait;
int on;
unsigned char pv;
};
char *author, *title, *remarks, emptystr;
long flen;
unsigned long pos;
int subsongs;
unsigned char *data;
int adlib_style;
int adlib_mode;
int chp[18][3];
long deltas;
long msqtr;
midi_track track[16];
unsigned int curtrack;
unsigned int track_count;
float fwait;
unsigned long iwait;
int doing;
int type, tins, stins;
OriginFXAdLibDriver *origin_fx_driver;
private:
void load_ultima_midi_tracks();
void midiprintf(const char *format, ...);
unsigned char datalook(long pos);
unsigned long getnexti(unsigned long num);
unsigned long getnext(unsigned long num);
unsigned long getval();
public:
void interrupt_vector();
};
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,42 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_OPL
#define NUVIE_SOUND_ADPLUG_OPL
#include "common/scummsys.h"
namespace Ultima {
namespace Nuvie {
class Copl {
public:
virtual ~Copl() {
return;
}
virtual void write(int reg, int val) = 0; // combined register select + data write
virtual void init(void) = 0; // reinitialize OPL chip
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_OPL_CLASS_H
#define NUVIE_SOUND_ADPLUG_OPL_CLASS_H
#include "ultima/nuvie/sound/adplug/opl.h"
namespace Ultima {
namespace Nuvie {
#define HAS_YM3812 1
#define HAS_YM3526 0
#define HAS_Y8950 0
/* --- select emulation chips --- */
#define BUILD_YM3812 (HAS_YM3812)
#define BUILD_YM3526 (HAS_YM3526)
#define BUILD_Y8950 (HAS_Y8950)
/* select output bits size of output : 8 or 16 */
#define OPL_SAMPLE_BITS 16
#if (OPL_SAMPLE_BITS==16)
typedef int16 OPLSAMPLE;
#endif
#if (OPL_SAMPLE_BITS==8)
typedef int8 OPLSAMPLE;
#endif
typedef void (*OPL_TIMERHANDLER)(int channel, double interval_Sec);
typedef void (*OPL_IRQHANDLER)(int param, int irq);
typedef void (*OPL_UPDATEHANDLER)(int param, int min_interval_us);
typedef void (*OPL_PORTHANDLER_W)(int param, unsigned char data);
typedef unsigned char (*OPL_PORTHANDLER_R)(int param);
/* Saving is necessary for member of the 'R' mark for suspend/resume */
typedef struct {
uint32 ar; /* attack rate: AR<<2 */
uint32 dr; /* decay rate: DR<<2 */
uint32 rr; /* release rate:RR<<2 */
uint8 KSR; /* key scale rate */
uint8 ksl; /* keyscale level */
uint8 ksr; /* key scale rate: kcode>>KSR */
uint8 mul; /* multiple: mul_tab[ML] */
/* Phase Generator */
uint32 Cnt; /* frequency counter */
uint32 Incr; /* frequency counter step */
uint8 FB; /* feedback shift value */
signed int *connect1; /* slot1 output pointer */
int32 op1_out[2]; /* slot1 output for feedback */
uint8 CON; /* connection (algorithm) type */
/* Envelope Generator */
uint8 eg_type; /* percussive/non-percussive mode */
uint8 state; /* phase type */
uint32 TL; /* total level: TL << 2 */
int32 TLL; /* adjusted now TL */
int32 volume; /* envelope counter */
uint32 sl; /* sustain level: sl_tab[SL] */
uint8 eg_sh_ar; /* (attack state) */
uint8 eg_sel_ar; /* (attack state) */
uint8 eg_sh_dr; /* (decay state) */
uint8 eg_sel_dr; /* (decay state) */
uint8 eg_sh_rr; /* (release state) */
uint8 eg_sel_rr; /* (release state) */
uint32 key; /* 0 = KEY OFF, >0 = KEY ON */
/* LFO */
uint32 AMmask; /* LFO Amplitude Modulation enable mask */
uint8 vib; /* LFO Phase Modulation enable flag (active high)*/
/* waveform select */
unsigned int wavetable;
} OPL_SLOT;
typedef struct {
OPL_SLOT SLOT[2];
/* phase generator state */
uint32 block_fnum; /* block+fnum */
uint32 fc; /* Freq. Increment base */
uint32 ksl_base; /* KeyScaleLevel Base step */
uint8 kcode; /* key code (for key scaling) */
} OPL_CH;
/* OPL state */
typedef struct fm_opl_f {
/* FM channel slots */
OPL_CH P_CH[9]; /* OPL/OPL2 chips have 9 channels*/
uint32 eg_cnt; /* global envelope generator counter */
uint32 eg_timer; /* global envelope generator counter works at frequency = chipclock/72 */
uint32 eg_timer_add; /* step of eg_timer */
uint32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */
uint8 rhythm; /* Rhythm mode */
uint32 fn_tab[1024]; /* fnumber->increment counter */
/* LFO */
uint8 lfo_am_depth;
uint8 lfo_pm_depth_range;
uint32 lfo_am_cnt;
uint32 lfo_am_inc;
uint32 lfo_pm_cnt;
uint32 lfo_pm_inc;
uint32 noise_rng; /* 23 bit noise shift register */
uint32 noise_p; /* current noise 'phase' */
uint32 noise_f; /* current noise period */
uint8 wavesel; /* waveform select enable flag */
int T[2]; /* timer counters */
uint8 st[2]; /* timer enable */
/* external event callback handlers */
OPL_TIMERHANDLER TimerHandler; /* TIMER handler */
int TimerParam; /* TIMER parameter */
OPL_IRQHANDLER IRQHandler; /* IRQ handler */
int IRQParam; /* IRQ parameter */
OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */
int UpdateParam; /* stream update parameter */
uint8 type; /* chip type */
uint8 address; /* address register */
uint8 status; /* status flag */
uint8 statusmask; /* status mask */
uint8 mode; /* Reg.08 : CSM,notesel,etc. */
int clock; /* master clock (Hz) */
int rate; /* sampling rate (Hz) */
double freqbase; /* frequency base */
double TimerBase; /* Timer base time (==sampling time)*/
} FM_OPL;
#define MAX_OPL_CHIPS 2
/* sinwave entries */
#define SIN_BITS 10
#define SIN_LEN (1<<SIN_BITS)
#define SIN_MASK (SIN_LEN-1)
#define TL_RES_LEN (256) /* 8 bits addressing (real chip) */
/* TL_TAB_LEN is calculated as:
* 12 - sinus amplitude bits (Y axis)
* 2 - sinus sign bit (Y axis)
* TL_RES_LEN - sinus resolution (X axis)
*/
#define TL_TAB_LEN (12*2*TL_RES_LEN)
class OplClass: public Copl {
private:
FM_OPL *OPL_YM3812[MAX_OPL_CHIPS]; /* array of pointers to the YM3812's */
int YM3812NumChips; /* number of chips */
signed int tl_tab[TL_TAB_LEN];
/* sin waveform table in 'decibel' scale */
/* four waveforms on OPL2 type chips */
unsigned int sin_tab[SIN_LEN * 4];
/* lock level of common table */
int num_lock;
/* work table */
void *cur_chip; /* current chip point */
OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2;
signed int phase_modulation; /* phase modulation input (SLOT 2) */
signed int output[1];
uint32 LFO_AM;
int32 LFO_PM;
bool use16bit, stereo;
int oplRate;
public:
OplClass(int rate, bool bit16, bool usestereo); // rate = sample rate
~OplClass() override {
YM3812Shutdown();
}
int getRate() {
return oplRate;
}
void update(short *buf, int samples); // fill buffer
// template methods
void write(int reg, int val) override;
void init() override;
private:
int YM3812Init(int num, int clock, int rate);
void YM3812Shutdown(void);
void YM3812ResetChip(int which);
int YM3812Write(int which, int a, int v);
unsigned char YM3812Read(int which, int a);
int YM3812TimerOver(int which, int c);
void YM3812UpdateOne(int which, int16 *buffer, int length);
void YM3812SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void YM3812SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param);
void YM3812SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param);
int init_tables(void);
void OPLWriteReg(FM_OPL *OPL, int r, int v);
void OPLResetChip(FM_OPL *OPL);
int OPL_LockTable(void);
FM_OPL *OPLCreate(int type, int clock, int rate);
void OPL_UnLockTable(void);
void OPLDestroy(FM_OPL *OPL);
int OPLWrite(FM_OPL *OPL, int a, int v);
inline void advance_lfo(FM_OPL *OPL);
inline void advancex(FM_OPL *OPL);
inline signed int op_calc(uint32 phase, unsigned int env, signed int pm, unsigned int wave_tab);
inline signed int op_calc1(uint32 phase, unsigned int env, signed int pm, unsigned int wave_tab);
inline void OPL_CALC_CH(OPL_CH *CH);
inline void OPL_CALC_RH(OPL_CH *CH, unsigned int noise);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,34 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/sound/adplug/opl.h"
namespace Ultima {
namespace Nuvie {
class CSilentopl : public Copl {
public:
void write(int reg, int val) override { };
void init() override { };
};
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,629 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/sound/adplug/u6m.h"
namespace Ultima {
namespace Nuvie {
Cu6mPlayer::~Cu6mPlayer() {
if (song_data)
free(song_data);
}
CPlayer *Cu6mPlayer::factory(Copl *newopl) {
return new Cu6mPlayer(newopl);
}
bool Cu6mPlayer::load(const Common::Path &filename) {
uint32 decompressed_filesize;
U6Lzw lzw;
song_data = lzw.decompress_file(filename, decompressed_filesize);
rewind(0);
return true;
}
bool Cu6mPlayer::update() {
if (!driver_active) {
driver_active = true;
dec_clip(read_delay);
if (read_delay == 0) {
command_loop();
}
// on all Adlib channels: freq slide/vibrato, mute factor slide
for (int i = 0; i < 9; i++) {
if (channel_freq_signed_delta[i] != 0)
// frequency slide + mute factor slide
{
// freq slide
freq_slide(i);
// mute factor slide
if (carrier_mf_signed_delta[i] != 0) {
mf_slide(i);
}
} else
// vibrato + mute factor slide
{
// vibrato
if ((vb_multiplier[i] != 0) && ((channel_freq[i].hi & 0x20) == 0x20)) {
vibrato(i);
}
// mute factor slide
if (carrier_mf_signed_delta[i] != 0) {
mf_slide(i);
}
}
}
driver_active = false;
}
return !songend;
}
void Cu6mPlayer::rewind(int subsong) {
played_ticks = 0;
songend = false;
// set the driver's internal variables
byte_pair freq_word = {0, 0};
driver_active = false;
song_pos = 0;
loop_position = 0; // position of the loop point
read_delay = 0; // delay (in timer ticks) before further song data is read
for (int i = 0; i < 9; i++) {
// frequency
channel_freq_signed_delta[i] = 0;
channel_freq[i] = freq_word; // Adlib freq settings for each channel
// vibrato ("vb")
vb_current_value[i] = 0;
vb_double_amplitude[i] = 0;
vb_multiplier[i] = 0;
vb_direction_flag[i] = 0;
// mute factor ("mf") == ~(volume)
carrier_mf[i] = 0;
carrier_mf_signed_delta[i] = 0;
carrier_mf_mod_delay_backup[i] = 0;
carrier_mf_mod_delay[i] = 0;
}
while (!subsong_stack.empty()) // empty subsong stack
subsong_stack.pop();
opl->init();
out_adlib(1, 32); // go to OPL2 mode
}
float Cu6mPlayer::getrefresh() {
return ((float)60); // the Ultima 6 music driver expects to be called at 60 Hz
}
// ============================================================================================
//
//
// Functions called by update()
//
//
// ============================================================================================
// This function reads the song data and executes the embedded commands.
void Cu6mPlayer::command_loop() {
unsigned char command_byte; // current command byte
int command_nibble_hi; // command byte, bits 4-7
int command_nibble_lo; // command byte, bite 0-3
bool repeat_loop = true; //
do {
// extract low and high command nibbles
command_byte = read_song_byte(); // implicitly increments song_pos
command_nibble_hi = command_byte >> 4;
command_nibble_lo = command_byte & 0xf;
switch (command_nibble_hi) {
case 0x0:
command_0(command_nibble_lo);
break;
case 0x1:
command_1(command_nibble_lo);
break;
case 0x2:
command_2(command_nibble_lo);
break;
case 0x3:
command_3(command_nibble_lo);
break;
case 0x4:
command_4(command_nibble_lo);
break;
case 0x5:
command_5(command_nibble_lo);
break;
case 0x6:
command_6(command_nibble_lo);
break;
case 0x7:
command_7(command_nibble_lo);
break;
case 0x8:
switch (command_nibble_lo) {
case 1:
command_81();
break;
case 2:
command_82();
repeat_loop = false;
break;
case 3:
command_83();
break;
case 5:
command_85();
break;
case 6:
command_86();
break;
default:
break; // maybe generate an error?
}
break;
case 0xE:
command_E();
break;
case 0xF:
command_F();
break;
default:
break; // maybe generate an error?
}
} while (repeat_loop);
}
// --------------------------------------------------------
// The commands supported by the U6 music file format
// --------------------------------------------------------
// ----------------------------------------
// Set octave and frequency, note off
// Format: 0c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_0(int channel) {
unsigned char freq_byte;
byte_pair freq_word;
freq_byte = read_song_byte();
freq_word = expand_freq_byte(freq_byte);
set_adlib_freq(channel, freq_word);
}
// ---------------------------------------------------
// Set octave and frequency, old note off, new note on
// Format: 1c nn
// c = channel, nn = packed Adlib frequency
// ---------------------------------------------------
void Cu6mPlayer::command_1(int channel) {
unsigned char freq_byte;
byte_pair freq_word;
vb_direction_flag[channel] = 0;
vb_current_value[channel] = 0;
freq_byte = read_song_byte();
freq_word = expand_freq_byte(freq_byte);
set_adlib_freq(channel, freq_word);
freq_word.hi = freq_word.hi | 0x20; // note on
set_adlib_freq(channel, freq_word);
}
// ----------------------------------------
// Set octave and frequency, note on
// Format: 2c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_2(int channel) {
unsigned char freq_byte;
byte_pair freq_word;
freq_byte = read_song_byte();
freq_word = expand_freq_byte(freq_byte);
freq_word.hi = freq_word.hi | 0x20; // note on
set_adlib_freq(channel, freq_word);
}
// --------------------------------------
// Set "carrier mute factor"==not(volume)
// Format: 3c nn
// c = channel, nn = mute factor
// --------------------------------------
void Cu6mPlayer::command_3(int channel) {
unsigned char mf_byte;
carrier_mf_signed_delta[channel] = 0;
mf_byte = read_song_byte();
set_carrier_mf(channel, mf_byte);
}
// ----------------------------------------
// set "modulator mute factor"==not(volume)
// Format: 4c nn
// c = channel, nn = mute factor
// ----------------------------------------
void Cu6mPlayer::command_4(int channel) {
unsigned char mf_byte;
mf_byte = read_song_byte();
set_modulator_mf(channel, mf_byte);
}
// --------------------------------------------
// Set portamento (pitch slide)
// Format: 5c nn
// c = channel, nn = signed channel pitch delta
// --------------------------------------------
void Cu6mPlayer::command_5(int channel) {
channel_freq_signed_delta[channel] = read_signed_song_byte();
}
// --------------------------------------------
// Set vibrato parameters
// Format: 6c mn
// c = channel
// m = vibrato double amplitude
// n = vibrato multiplier
// --------------------------------------------
void Cu6mPlayer::command_6(int channel) {
unsigned char vb_parameters;
vb_parameters = read_song_byte();
vb_double_amplitude[channel] = vb_parameters >> 4; // high nibble
vb_multiplier[channel] = vb_parameters & 0xF; // low nibble
}
// ----------------------------------------
// Assign Adlib instrument to Adlib channel
// Format: 7c nn
// c = channel, nn = instrument number
// ----------------------------------------
void Cu6mPlayer::command_7(int channel) {
int instrument_offset = instrument_offsets[read_song_byte()];
out_adlib_opcell(channel, false, 0x20, *(song_data + instrument_offset + 0));
out_adlib_opcell(channel, false, 0x40, *(song_data + instrument_offset + 1));
out_adlib_opcell(channel, false, 0x60, *(song_data + instrument_offset + 2));
out_adlib_opcell(channel, false, 0x80, *(song_data + instrument_offset + 3));
out_adlib_opcell(channel, false, 0xE0, *(song_data + instrument_offset + 4));
out_adlib_opcell(channel, true, 0x20, *(song_data + instrument_offset + 5));
out_adlib_opcell(channel, true, 0x40, *(song_data + instrument_offset + 6));
out_adlib_opcell(channel, true, 0x60, *(song_data + instrument_offset + 7));
out_adlib_opcell(channel, true, 0x80, *(song_data + instrument_offset + 8));
out_adlib_opcell(channel, true, 0xE0, *(song_data + instrument_offset + 9));
out_adlib(0xC0 + channel, *(song_data + instrument_offset + 10));
}
// -------------------------------------------
// Branch to a new subsong
// Format: 81 nn aa bb
// nn == number of times to repeat the subsong
// aa == subsong offset (low byte)
// bb == subsong offset (high byte)
// -------------------------------------------
void Cu6mPlayer::command_81() {
subsong_info new_ss_info;
new_ss_info.subsong_repetitions = read_song_byte();
new_ss_info.subsong_start = read_song_byte();
new_ss_info.subsong_start += read_song_byte() << 8;
new_ss_info.continue_pos = song_pos;
subsong_stack.push(new_ss_info);
song_pos = new_ss_info.subsong_start;
}
// ------------------------------------------------------------
// Stop interpreting commands for this timer tick
// Format: 82 nn
// nn == delay (in timer ticks) until further data will be read
// ------------------------------------------------------------
void Cu6mPlayer::command_82() {
read_delay = read_song_byte();
}
// -----------------------------
// Adlib instrument data follows
// Format: 83 nn <11 bytes>
// nn == instrument number
// -----------------------------
void Cu6mPlayer::command_83() {
unsigned char instrument_number = read_song_byte();
instrument_offsets[instrument_number] = song_pos;
song_pos += 11;
}
// ----------------------------------------------
// Set -1 mute factor slide (upward volume slide)
// Format: 85 cn
// c == channel
// n == slide delay
// ----------------------------------------------
void Cu6mPlayer::command_85() {
unsigned char data_byte = read_song_byte();
int channel = data_byte >> 4; // high nibble
unsigned char slide_delay = data_byte & 0xF; // low nibble
carrier_mf_signed_delta[channel] = +1;
carrier_mf_mod_delay[channel] = slide_delay + 1;
carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
}
// ------------------------------------------------
// Set +1 mute factor slide (downward volume slide)
// Format: 86 cn
// c == channel
// n == slide speed
// ------------------------------------------------
void Cu6mPlayer::command_86() {
unsigned char data_byte = read_song_byte();
int channel = data_byte >> 4; // high nibble
unsigned char slide_delay = data_byte & 0xF; // low nibble
carrier_mf_signed_delta[channel] = -1;
carrier_mf_mod_delay[channel] = slide_delay + 1;
carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
}
// --------------
// Set loop point
// Format: E?
// --------------
void Cu6mPlayer::command_E() {
loop_position = song_pos;
}
// ---------------------------
// Return from current subsong
// Format: F?
// ---------------------------
void Cu6mPlayer::command_F() {
if (!subsong_stack.empty()) {
subsong_info temp = subsong_stack.top();
subsong_stack.pop();
temp.subsong_repetitions--;
if (temp.subsong_repetitions == 0) {
song_pos = temp.continue_pos;
} else {
song_pos = temp.subsong_start;
subsong_stack.push(temp);
}
} else {
song_pos = loop_position;
songend = true;
}
}
// --------------------
// Additional functions
// --------------------
// This function decrements its argument, without allowing it to become negative.
void Cu6mPlayer::dec_clip(int &param) {
param--;
if (param < 0) {
param = 0;
}
}
// Returns the byte at the current song position.
// Side effect: increments song_pos.
unsigned char Cu6mPlayer::read_song_byte() {
unsigned char song_byte;
song_byte = song_data[song_pos];
song_pos++;
return song_byte;
}
// Same as read_song_byte(), except that it returns a signed byte
signed char Cu6mPlayer::read_signed_song_byte() {
unsigned char song_byte;
int signed_value;
song_byte = *(song_data + song_pos);
song_pos++;
if (song_byte <= 127) {
signed_value = song_byte;
} else {
signed_value = (int)song_byte - 0x100;
}
return ((signed char)signed_value);
}
Cu6mPlayer::byte_pair Cu6mPlayer::expand_freq_byte(unsigned char freq_byte) {
const byte_pair freq_table[24] = {
{0x00, 0x00}, {0x58, 0x01}, {0x82, 0x01}, {0xB0, 0x01},
{0xCC, 0x01}, {0x03, 0x02}, {0x41, 0x02}, {0x86, 0x02},
{0x00, 0x00}, {0x6A, 0x01}, {0x96, 0x01}, {0xC7, 0x01},
{0xE4, 0x01}, {0x1E, 0x02}, {0x5F, 0x02}, {0xA8, 0x02},
{0x00, 0x00}, {0x47, 0x01}, {0x6E, 0x01}, {0x9A, 0x01},
{0xB5, 0x01}, {0xE9, 0x01}, {0x24, 0x02}, {0x66, 0x02}
};
int packed_freq;
int octave;
byte_pair freq_word;
packed_freq = freq_byte & 0x1F;
octave = freq_byte >> 5;
// range check (not present in the original U6 music driver)
if (packed_freq >= 24) {
packed_freq = 0;
}
freq_word.hi = freq_table[packed_freq].hi + (octave << 2);
freq_word.lo = freq_table[packed_freq].lo;
return freq_word;
}
void Cu6mPlayer::set_adlib_freq(int channel, Cu6mPlayer::byte_pair freq_word) {
out_adlib(0xA0 + channel, freq_word.lo);
out_adlib(0xB0 + channel, freq_word.hi);
// update the Adlib register backups
channel_freq[channel] = freq_word;
}
// this function sets the Adlib frequency, but does not update the register backups
void Cu6mPlayer::set_adlib_freq_no_update(int channel, Cu6mPlayer::byte_pair freq_word) {
out_adlib(0xA0 + channel, freq_word.lo);
out_adlib(0xB0 + channel, freq_word.hi);
}
void Cu6mPlayer::set_carrier_mf(int channel, unsigned char mute_factor) {
out_adlib_opcell(channel, true, 0x40, mute_factor);
carrier_mf[channel] = mute_factor;
}
void Cu6mPlayer::set_modulator_mf(int channel, unsigned char mute_factor) {
out_adlib_opcell(channel, false, 0x40, mute_factor);
}
void Cu6mPlayer::freq_slide(int channel) {
byte_pair freq = channel_freq[channel];
long freq_word = freq.lo + (freq.hi << 8) + channel_freq_signed_delta[channel];
if (freq_word < 0) {
freq_word += 0x10000;
}
if (freq_word > 0xFFFF) {
freq_word -= 0x10000;
}
freq.lo = freq_word & 0xFF;
freq.hi = (freq_word >> 8) & 0xFF;
set_adlib_freq(channel, freq);
}
void Cu6mPlayer::vibrato(int channel) {
byte_pair freq;
if (vb_current_value[channel] >= vb_double_amplitude[channel]) {
vb_direction_flag[channel] = 1;
} else if (vb_current_value[channel] <= 0) {
vb_direction_flag[channel] = 0;
}
if (vb_direction_flag[channel] == 0) {
vb_current_value[channel]++;
} else {
vb_current_value[channel]--;
}
long freq_word = channel_freq[channel].lo + (channel_freq[channel].hi << 8);
freq_word += (vb_current_value[channel] - (vb_double_amplitude[channel] >> 1))
* vb_multiplier[channel];
if (freq_word < 0) {
freq_word += 0x10000;
}
if (freq_word > 0xFFFF) {
freq_word -= 0x10000;
}
freq.lo = freq_word & 0xFF;
freq.hi = (freq_word >> 8) & 0xFF;
set_adlib_freq_no_update(channel, freq);
}
void Cu6mPlayer::mf_slide(int channel) {
carrier_mf_mod_delay[channel]--;
if (carrier_mf_mod_delay[channel] == 0) {
carrier_mf_mod_delay[channel] = carrier_mf_mod_delay_backup[channel];
int current_mf = carrier_mf[channel] + carrier_mf_signed_delta[channel];
if (current_mf > 0x3F) {
current_mf = 0x3F;
carrier_mf_signed_delta[channel] = 0;
} else if (current_mf < 0) {
current_mf = 0;
carrier_mf_signed_delta[channel] = 0;
}
set_carrier_mf(channel, (unsigned char)current_mf);
}
}
void Cu6mPlayer::out_adlib(unsigned char adlib_register, unsigned char adlib_data) {
opl->write(adlib_register, adlib_data);
}
void Cu6mPlayer::out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte) {
const unsigned char adlib_channel_to_carrier_offset[9] =
{0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15};
const unsigned char adlib_channel_to_modulator_offset[9] =
{0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
if (carrier) {
out_adlib(adlib_register + adlib_channel_to_carrier_offset[channel], out_byte);
} else {
out_adlib(adlib_register + adlib_channel_to_modulator_offset[channel], out_byte);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,137 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_U6M
#define NUVIE_SOUND_ADPLUG_U6M
#include "ultima/nuvie/sound/adplug/adplug_player.h"
#include "common/stack.h"
namespace Ultima {
namespace Nuvie {
class Cu6mPlayer: public CPlayer {
public:
static CPlayer *factory(Copl *newopl);
Cu6mPlayer(Copl *newopl) : CPlayer(newopl), song_data(0), driver_active(false),
songend(false), song_pos(0), loop_position(0), read_delay(0), played_ticks(0) {
ARRAYCLEAR(channel_freq);
}
~Cu6mPlayer() override;
bool load(const Common::Path &filename) override;
bool update() override;
void rewind(int subsong) override;
float getrefresh() override;
Std::string gettype() override {
return Std::string("Ultima 6 Music");
};
protected:
struct byte_pair {
unsigned char lo;
unsigned char hi;
};
struct subsong_info { // information about a subsong
int continue_pos;
int subsong_repetitions;
int subsong_start;
};
struct data_block { //
long size;
unsigned char *data;
};
// class variables
long played_ticks;
unsigned char *song_data; // the uncompressed .m file (the "song")
bool driver_active; // flag to prevent reentrancy
bool songend; // indicates song end
int song_pos; // current offset within the song
int loop_position; // position of the loop point
int read_delay; // delay (in timer ticks) before further song data is read
Common::Stack<subsong_info> subsong_stack;
int instrument_offsets[9]; // offsets of the adlib instrument data
// vibrato ("vb")
unsigned char vb_current_value[9];
unsigned char vb_double_amplitude[9];
unsigned char vb_multiplier[9];
unsigned char vb_direction_flag[9];
// mute factor ("mf") = not(volume)
unsigned char carrier_mf[9];
signed char carrier_mf_signed_delta[9];
unsigned char carrier_mf_mod_delay_backup[9];
unsigned char carrier_mf_mod_delay[9];
// frequency
byte_pair channel_freq[9]; // adlib freq settings for each channel
signed char channel_freq_signed_delta[9];
// protected functions used by update()
void command_loop();
unsigned char read_song_byte();
signed char read_signed_song_byte();
void dec_clip(int &);
byte_pair expand_freq_byte(unsigned char);
void set_adlib_freq(int channel, byte_pair freq_word);
void set_adlib_freq_no_update(int channel, byte_pair freq_word);
void set_carrier_mf(int channel, unsigned char mute_factor);
void set_modulator_mf(int channel, unsigned char mute_factor);
void freq_slide(int channel);
void vibrato(int channel);
void mf_slide(int channel);
void command_0(int channel);
void command_1(int channel);
void command_2(int channel);
void command_3(int channel);
void command_4(int channel);
void command_5(int channel);
void command_6(int channel);
void command_7(int channel);
void command_81();
void command_82();
void command_83();
void command_85();
void command_86();
void command_E();
void command_F();
void out_adlib(unsigned char adlib_register, unsigned char adlib_data);
void out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,124 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "audio/mixer.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/sound/custom_sfx_manager.h"
#include "common/file.h"
namespace Ultima {
namespace Nuvie {
CustomSfxManager::CustomSfxManager(const Configuration *cfg, Audio::Mixer *m) : SfxManager(cfg, m) {
Common::Path cfg_filename;
config->pathFromValue("config/ultima6/sfxdir", "", custom_filepath);
build_path(custom_filepath, "sfx_map.cfg", cfg_filename);
loadSfxMapFile(cfg_filename);
}
CustomSfxManager::~CustomSfxManager() {
}
bool CustomSfxManager::loadSfxMapFile(const Common::Path &cfg_filename) {
const char seps[] = ";\r\n";
const char *token1;
const char *token2;
NuvieIOFileRead niof;
if (niof.open(cfg_filename) == false) {
DEBUG(0, LEVEL_ERROR, "Failed to open '%s'", cfg_filename.toString().c_str());
return false;
}
char *sz = (char *)niof.readAll();
token1 = strtok(sz, seps);
while ((token1 != nullptr) && ((token2 = strtok(nullptr, seps)) != nullptr)) {
SfxIdType sfx_id = (SfxIdType)atoi(token1);
int custom_wave_id = atoi(token2);
DEBUG(0, LEVEL_DEBUGGING, "%d : %d.wav\n", sfx_id, custom_wave_id);
sfx_map[sfx_id] = custom_wave_id;
token1 = strtok(nullptr, seps);
}
return true;
}
bool CustomSfxManager::playSfx(SfxIdType sfx_id, uint8 volume) {
return playSfxLooping(sfx_id, nullptr, volume);
}
bool CustomSfxManager::playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) {
Common::HashMap < uint16, uint16 >::iterator it;
it = sfx_map.find((uint16)sfx_id);
if (it != sfx_map.end()) {
playSoundSample((*it)._value, handle, volume);
return true;
}
return false;
}
void CustomSfxManager::playSoundSample(uint16 sample_num, Audio::SoundHandle *looping_handle, uint8 volume) {
Audio::AudioStream *stream = nullptr;
Audio::SoundHandle handle;
Common::Path filename;
char wavefile[10]; // "nnnnn.wav\0"
Common::sprintf_s(wavefile, "%d.wav", sample_num);
build_path(custom_filepath, wavefile, filename);
Common::File *readStream = new Common::File();
if (!readStream->open(filename)) {
DEBUG(0, LEVEL_ERROR, "Failed to open '%s'", filename.toString().c_str());
delete readStream;
return;
}
stream = Audio::makeWAVStream(readStream, DisposeAfterUse::YES);
if (looping_handle) {
Audio::RewindableAudioStream *rwStream = dynamic_cast<Audio::RewindableAudioStream *>(stream);
Audio::LoopingAudioStream *looping_stream = new Audio::LoopingAudioStream(rwStream, 0);
mixer->playStream(Audio::Mixer::kPlainSoundType, looping_handle, looping_stream, -1, volume);
} else {
mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, stream, -1, volume);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_CUSTOMS_FX_MANAGER_H
#define NUVIE_SOUND_CUSTOMS_FX_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/sound/sfx_manager.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/wave.h"
namespace Ultima {
namespace Nuvie {
class CustomSfxManager : public SfxManager {
public:
CustomSfxManager(const Configuration *cfg, Audio::Mixer *m);
~CustomSfxManager() override;
bool playSfx(SfxIdType sfx_id, uint8 volume) override;
bool playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) override;
void playSoundSample(uint16 sample_num, Audio::SoundHandle *looping_handle, uint8 volume);
private:
bool loadSfxMapFile(const Common::Path &cfg_filename);
private:
Common::Path custom_filepath;
Common::HashMap<uint16, uint16> sfx_map;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,89 @@
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/adplug/opl_class.h"
#include "ultima/nuvie/sound/origin_fx_adib_driver.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/sound/decoder/adlib_sfx_stream.h"
namespace Ultima {
namespace Nuvie {
AdLibSfxStream::AdLibSfxStream(const Configuration *cfg, int rate, uint8 channel, sint8 note, uint8 velocity, uint8 program_number, uint32 d) {
interrupt_samples_left = 0;
opl = new OplClass(rate, true, true); // 16bit stereo
driver = new OriginFXAdLibDriver(cfg, opl);
if (program_number != 0xff) {
driver->program_change(channel, program_number);
}
driver->control_mode_change(channel, 0x7, 0x7f);
driver->play_note(channel, note, velocity);
duration = d;
interrupt_rate = (int)(opl->getRate() / 60);
total_samples_played = 0;
}
AdLibSfxStream::~AdLibSfxStream() {
delete driver;
delete opl;
}
int AdLibSfxStream::readBuffer(sint16 *buffer, const int numSamples) {
//sint32 i, j;
short *data = (short *)buffer;
int len = numSamples / 2;
total_samples_played += numSamples;
if (interrupt_samples_left > 0) {
if (interrupt_samples_left > len) {
opl->update(data, len);
interrupt_samples_left -= len;
return numSamples;
}
opl->update(data, interrupt_samples_left);
data += interrupt_samples_left * 2;
len -= interrupt_samples_left;
interrupt_samples_left = 0;
}
for (int i = len; i > 0;) {
driver->interrupt_vector();
int j = interrupt_rate;
if (j > i) {
interrupt_samples_left = j - i;
j = i;
}
opl->update(data, j);
data += j * 2;
i -= j;
}
//driver->play_note(8, 0x40, 0);
return numSamples;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,94 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIXER_DECODER_ADLIB_SFX_STREAM_H
#define NUVIE_SOUND_MIXER_DECODER_ADLIB_SFX_STREAM_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/sound/adplug/opl_class.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
class OriginFXAdLibDriver;
class Configuration;
using Std::string;
class AdLibSfxStream : public Audio::RewindableAudioStream {
public:
AdLibSfxStream() {
opl = nullptr;
duration = 0;
}
AdLibSfxStream(const Configuration *cfg, int rate, uint8 channel, sint8 note, uint8 velocity, uint8 program_number, uint32 d);
~AdLibSfxStream() override;
int readBuffer(sint16 *buffer, const int numSamples) override;
/** Is this a stereo stream? */
bool isStereo() const override {
return true;
}
/** Sample rate of the stream. */
int getRate() const override {
return opl->getRate();
}
bool rewind() override {
return false;
}
/**
* End of data reached? If this returns true, it means that at this
* time there is no data available in the stream. However there may be
* more data in the future.
* This is used by e.g. a rate converter to decide whether to keep on
* converting data or stop.
*/
bool endOfData() const override {
if (total_samples_played >= duration) {
return true;
}
return false;
}
uint32 getLengthInMsec() {
return (uint32)(duration / (getRate() / 1000.0f));
}
protected:
OplClass *opl;
OriginFXAdLibDriver *driver;
uint32 duration;
int interrupt_samples_left;
int interrupt_rate;
uint32 total_samples_played;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,101 @@
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/sound/decoder/fm_towns_decoder_stream.h"
namespace Ultima {
namespace Nuvie {
inline sint16 convert_sample(uint16 raw_sample);
FMtownsDecoderStream::FMtownsDecoderStream(unsigned char *buf, uint32 len) {
raw_audio_buf = buf;
buf_len = len;
buf_pos = 0;
should_free_raw_data = false;
}
FMtownsDecoderStream::FMtownsDecoderStream(const Common::Path &filename, uint16 sample_num, bool isCompressed) {
unsigned char *item_data;
uint32 decomp_size;
U6Lib_n sam_file;
U6Lzw lzw;
sam_file.open(filename, 4);
item_data = sam_file.get_item(sample_num, nullptr);
if (isCompressed) {
raw_audio_buf = lzw.decompress_buffer(item_data, sam_file.get_item_size(sample_num), decomp_size);
free(item_data);
buf_len = decomp_size;
} else {
raw_audio_buf = item_data;
buf_len = sam_file.get_item_size(sample_num);
}
buf_pos = 0;
should_free_raw_data = true;
}
FMtownsDecoderStream::~FMtownsDecoderStream() {
if (raw_audio_buf && should_free_raw_data)
free(raw_audio_buf);
}
uint32 FMtownsDecoderStream::getLengthInMsec() {
return (uint32)(buf_len / (getRate() / 1000.0f));
}
int FMtownsDecoderStream::readBuffer(sint16 *buffer, const int numSamples) {
int j = 0;
uint32 i = buf_pos;
//DEBUG(0,LEVEL_INFORMATIONAL, "numSamples = %d. buf_pos = %d, buf_len = %d\n", numSamples, buf_pos, buf_len);
for (; j < numSamples && i < buf_len;) {
buffer[j] = convertSample(static_cast<uint16>(raw_audio_buf[i]));
j++;
i++;
}
buf_pos += j;
//DEBUG(0,LEVEL_INFORMATIONAL, "read %d samples.\n", j);
return j;
}
inline sint16 FMtownsDecoderStream::convertSample(uint16 rawSample) const {
sint16 sample;
if (rawSample & 128)
sample = ((sint16)(ABS(128 - rawSample) * 256) ^ 0xffff) + 1;
else
sample = rawSample * 256;
return sample;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,86 @@
#ifndef __FMtownsDecoderStream_h__
#define __FMtownsDecoderStream_h__
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/sound/decoder/random_collection_audio_stream.h"
namespace Ultima {
namespace Nuvie {
class U6Lib_n;
class U6Lzw;
class NuvieIOBuffer;
class FMtownsDecoderStream : public Audio::RewindableAudioStream {
public:
FMtownsDecoderStream() {
should_free_raw_data = false;
raw_audio_buf = nullptr;
}
FMtownsDecoderStream(unsigned char *buf, uint32 len);
FMtownsDecoderStream(const Common::Path &filename, uint16 sample_num, bool isCompressed = true);
~FMtownsDecoderStream() override;
uint32 getLengthInMsec();
int readBuffer(sint16 *buffer, const int numSamples) override;
/** Is this a stereo stream? */
bool isStereo() const override {
return false;
}
/** Sample rate of the stream. */
int getRate() const override {
return 14700;
}
/**
* End of data reached? If this returns true, it means that at this
* time there is no data available in the stream. However there may be
* more data in the future.
* This is used by e.g. a rate converter to decide whether to keep on
* converting data or stop.
*/
bool endOfData() const override {
return buf_pos >= buf_len;
}
bool rewind() override {
buf_pos = 0;
return true;
}
protected:
bool should_free_raw_data;
unsigned char *raw_audio_buf;
uint32 buf_len;
uint32 buf_pos;
private:
inline sint16 convertSample(uint16 rawSample) const;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,150 @@
/*
* PCSpeaker.cpp
* Nuvie
*
* Created by Eric Fry on Sun Feb 13 2011.
* Copyright (c) 2011. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/decoder/pc_speaker.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
#define SPKR_VOLUME 5000
//#define SPKR_SHIFT 8
#define SPKR_SPEED (float)((SPKR_VOLUME*2)/0.070f)
#define PIT_TICK_RATE 1193182
void PCSpeaker::SetOn() {
wav_length = 0;
/*
//PCSPEAKER_SetType(3);
dataFile.open("/Users/efry/pcspeaker.wav");
dataFile.writeBuf((const unsigned char*)"RIFF", 4);
dataFile.write4(36 + wav_length * 2); //length of RIFF chunk
dataFile.writeBuf((const unsigned char*)"WAVE", 4);
dataFile.writeBuf((const unsigned char*)"fmt ", 4);
dataFile.write4(16); // length of format chunk
dataFile.write2(1); // PCM encoding
dataFile.write2(1); // mono
dataFile.write4(rate); // sample frequency 16KHz
dataFile.write4(rate * 2); // sample rate
dataFile.write2(2); // BlockAlign
dataFile.write2(16); // Bits per sample
dataFile.writeBuf((const unsigned char*)"data", 4);
dataFile.write4(wav_length * 2); // length of data chunk
*/
time_left = 0.0;
want_vol = -SPKR_VOLUME;
}
void PCSpeaker::SetOff() {
want_vol = 0;
time_left = 0.0;
/*
//PCSPEAKER_SetType(0);
dataFile.seek(4);
dataFile.write4(36 + wav_length * 2); //length of RIFF chunk
dataFile.seek(40);
dataFile.write4(wav_length * 2); // length of data chunk
dataFile.close();
*/
}
void PCSpeaker::SetFrequency(uint16 freq, float offset) {
//PCSPEAKER_SetCounter(PIT_TICK_RATE/freq, PIT_MODE_3_SQUAREWAVE);
if (frequency == freq)
return;
frequency = freq;
osc_length = rate / frequency;
osc_samples = 0;
half_period = ((float)rate / (float)frequency) / 2;
//want_vol = SPKR_VOLUME;
//DEBUG(0, LEVEL_DEBUGGING, "new_freq = %d half_period = %f time_left = %f", freq, half_period, time_left);
//time_left = offset; //half_period;
}
PCSpeaker::PCSpeaker(uint32 mixer_rate) : rate(mixer_rate), cur_vol(0.0f), want_vol(0.0f),
frequency(0), half_period(0.0f), time_left(0.0f), osc_length(0),
osc_samples(0), wav_length(0) {
}
void PCSpeaker::PCSPEAKER_CallBack(sint16 *stream, const uint32 len) {
uint32 i;
for (i = 0; i < len; i++) {
//float new_time_left = time_left - 1;
if (cur_vol != want_vol) {
if (time_left < 1.0f)
cur_vol = cur_vol + ((want_vol * 8.3502) * time_left) / 2;
else
cur_vol = cur_vol + ((want_vol * 8.3502) / 2);
if (cur_vol > SPKR_VOLUME || cur_vol < -SPKR_VOLUME) { //limit the volume to our max speaker range.
cur_vol = want_vol;
}
}
if (time_left <= 1.0f) { //we change wave direction in this time slice.
//change wave edge.
if (want_vol < 0)
want_vol = SPKR_VOLUME;
else
want_vol = -SPKR_VOLUME;
float remainder = (1.0f - time_left);
if (remainder != 0.0f) { //calculate new current volume position
cur_vol = cur_vol + ((want_vol * 8.3502) * remainder) / 2;
if (cur_vol > SPKR_VOLUME || cur_vol < -SPKR_VOLUME) { //limit the volume to our max speaker range.
cur_vol = want_vol;
}
}
time_left = half_period - remainder;
//DEBUG(0, LEVEL_DEBUGGING, "remainder = %f, time_left = %f\n", remainder, time_left);
} else
time_left = time_left - 1.0f;
stream[i] = (sint16)cur_vol;
//dataFile.write2(stream[i]);
//wav_length++;
}
return;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIXER_DECODER_PC_SPEAKER_H
#define NUVIE_SOUND_MIXER_DECODER_PC_SPEAKER_H
#include "ultima/nuvie/files/nuvie_io_file.h"
namespace Ultima {
namespace Nuvie {
#define SPKR_OUTPUT_RATE 22050 //11025 //FIXME may need to fiddle with this.
class PCSpeaker {
private:
uint32 rate;
uint16 frequency;
float half_period;
float cur_vol;
float want_vol;
float time_left;
uint32 osc_length;
uint32 osc_samples;
private:
NuvieIOFileWrite dataFile;
uint32 wav_length;
public:
PCSpeaker(uint32 mixer_rate);
~PCSpeaker() { }
void SetOn();
void SetOff();
void SetFrequency(uint16 freq, float offset = 0.0f);
void PCSPEAKER_CallBack(sint16 *stream, const uint32 len);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,427 @@
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/decoder/pc_speaker_stream.h"
#include "ultima/nuvie/core/game.h"
namespace Ultima {
namespace Nuvie {
PCSpeakerFreqStream::PCSpeakerFreqStream(uint freq, uint16 d) {
frequency = freq;
duration = d * (SPKR_OUTPUT_RATE / 1255);
if (freq != 0) {
pcspkr->SetOn();
pcspkr->SetFrequency(frequency);
}
total_samples_played = 0;
}
PCSpeakerFreqStream::~PCSpeakerFreqStream() {
}
uint32 PCSpeakerFreqStream::getLengthInMsec() {
return (uint32)(duration / (getRate() / 1000.0f));
}
int PCSpeakerFreqStream::readBuffer(sint16 *buffer, const int numSamples) {
uint32 samples = (uint32)numSamples;
if (total_samples_played >= duration)
return 0;
if (total_samples_played + samples > duration)
samples = duration - total_samples_played;
if (frequency != 0)
pcspkr->PCSPEAKER_CallBack(buffer, samples);
else
memset(buffer, 0, sizeof(sint16)*numSamples);
total_samples_played += samples;
if (total_samples_played >= duration) {
finished = true;
pcspkr->SetOff();
}
return samples;
}
//******** PCSpeakerSweepFreqStream
PCSpeakerSweepFreqStream::PCSpeakerSweepFreqStream(uint start, uint end, uint16 d, uint16 s) {
start_freq = start;
finish_freq = end;
cur_freq = start_freq;
num_steps = d / s;
freq_step = ((finish_freq - start_freq) * s) / d;
stepping = s;
duration = d * (SPKR_OUTPUT_RATE / 1255);
samples_per_step = (float)s * (SPKR_OUTPUT_RATE * 0.000879533f); //(2 * (uint32)(SPKR_OUTPUT_RATE / start_freq)); //duration / num_steps;
sample_pos = 0.0f;
pcspkr->SetOn();
pcspkr->SetFrequency(start_freq);
total_samples_played = 0;
cur_step = 0;
DEBUG(0, LEVEL_DEBUGGING, "num_steps = %d freq_step = %d samples_per_step = %f\n", num_steps, freq_step, samples_per_step);
}
PCSpeakerSweepFreqStream::~PCSpeakerSweepFreqStream() {
}
uint32 PCSpeakerSweepFreqStream::getLengthInMsec() {
return (uint32)((num_steps * samples_per_step) / (getRate() / 1000.0f));
}
int PCSpeakerSweepFreqStream::readBuffer(sint16 *buffer, const int numSamples) {
uint32 samples = (uint32)numSamples;
uint32 i;
//if(total_samples_played >= duration)
// return 0;
//if(total_samples_played + samples > duration)
// samples = duration - total_samples_played;
for (i = 0; i < samples && cur_step < num_steps;) {
//DEBUG(0, LEVEL_DEBUGGING, "sample_pos = %f\n", sample_pos);
float n = samples_per_step - sample_pos;
if ((float)i + n > (float)samples)
n = (float)(samples - i);
float remainder = n - floor(n);
n = floor(n);
pcspkr->PCSPEAKER_CallBack(&buffer[i], (uint32)n);
sample_pos += n;
i += (uint32)n;
//DEBUG(0, LEVEL_DEBUGGING, "sample_pos = %f remainder = %f\n", sample_pos, remainder);
if (sample_pos + remainder >= samples_per_step) {
cur_freq += freq_step;
pcspkr->SetFrequency(cur_freq, remainder);
if (remainder != 0.0f) {
sample_pos = 1.0f - remainder;
pcspkr->PCSPEAKER_CallBack(&buffer[i], 1);
i++;
} else {
sample_pos = 0;
}
cur_step++;
}
}
total_samples_played += i;
if (cur_step >= num_steps) { //total_samples_played >= duration)
DEBUG(0, LEVEL_DEBUGGING, "total_samples_played = %d cur_freq = %d\n", total_samples_played, cur_freq);
finished = true;
pcspkr->SetOff();
}
return i;
}
//**************** PCSpeakerRandomStream
PCSpeakerRandomStream::PCSpeakerRandomStream(uint freq, uint16 d, uint16 s)
: rand_value(0x7664), base_val(freq), duration(0), stepping(0),
cur_step(0), sample_pos(0), num_steps(d / s),
samples_per_step(s * (SPKR_OUTPUT_RATE / 20 / 800)),
total_samples_played(0) {
/*
frequency = freq;
duration = d * (SPKR_OUTPUT_RATE / 1255);
pcspkr->SetFrequency(frequency);
pcspkr->SetOn();
total_samples_played = 0;
*/
pcspkr->SetOn();
pcspkr->SetFrequency(getNextFreqValue());
DEBUG(0, LEVEL_DEBUGGING, "num_steps = %d samples_per_step = %d\n", num_steps, samples_per_step);
}
PCSpeakerRandomStream::~PCSpeakerRandomStream() {
}
uint32 PCSpeakerRandomStream::getLengthInMsec() {
return (uint32)((num_steps * samples_per_step) / (getRate() / 1000.0f));
}
uint16 PCSpeakerRandomStream::getNextFreqValue() {
rand_value += 0x9248;
rand_value = rand_value & 0xffff; //clamp_max(rand_value, 65535);
uint16 bits = rand_value & 0x7;
rand_value = (rand_value >> 3) + (bits << 13); //rotate rand_value right (ror) by 3 bits
rand_value = rand_value ^ 0x9248;
rand_value += 0x11;
rand_value = rand_value & 0xffff; //clamp_max(rand_value, 65535);
uint16 freq = base_val - 0x64 + 1;
uint16 tmp = rand_value;
freq = tmp - floor(tmp / freq) * freq;
freq += 0x64;
return freq;
}
int PCSpeakerRandomStream::readBuffer(sint16 *buffer, const int numSamples) {
uint32 samples = (uint32)numSamples;
uint32 s = 0;
//if(total_samples_played >= duration)
// return 0;
//if(total_samples_played + samples > duration)
// samples = duration - total_samples_played;
for (uint32 i = 0; i < samples && cur_step <= num_steps;) {
uint32 n = samples_per_step - sample_pos;
if (i + n > samples)
n = samples - i;
pcspkr->PCSPEAKER_CallBack(&buffer[i], n);
sample_pos += n;
i += n;
// DEBUG(0, LEVEL_DEBUGGING, "n = %d\n", n);
if (sample_pos >= samples_per_step) {
//DEBUG(0, LEVEL_DEBUGGING, "samples_per_step = %d period = %d\n", samples_per_step, period);
pcspkr->SetFrequency(getNextFreqValue());
sample_pos = 0;
cur_step++;
}
s += n;
}
total_samples_played += s;
if (cur_step >= num_steps) { //total_samples_played >= duration)
DEBUG(0, LEVEL_DEBUGGING, "total_samples_played = %d\n", total_samples_played);
finished = true;
pcspkr->SetOff();
}
return s;
}
//**************** PCSpeakerStutterStream
PCSpeakerStutterStream::PCSpeakerStutterStream(sint16 a0, uint16 a2, uint16 a4, uint16 a6, uint16 a8) {
arg_0 = a0;
arg_2 = a2;
arg_4 = a4;
arg_6 = a6;
arg_8 = a8;
cx = arg_4;
dx = 0;
pcspkr->SetOn();
pcspkr->SetFrequency(22096);
delay = (SPKR_OUTPUT_RATE / 22050) * arg_6; //FIXME this needs to be refined.
delay_remaining = 0;
//samples_per_step = s * (SPKR_OUTPUT_RATE / 20 / 800); //1255);
//total_samples_played = 0;
//DEBUG(0, LEVEL_DEBUGGING, "num_steps = %d samples_per_step = %d\n", num_steps, samples_per_step);
}
PCSpeakerStutterStream::~PCSpeakerStutterStream() {
}
uint32 PCSpeakerStutterStream::getLengthInMsec() {
return (uint32)((arg_4 * delay) / (getRate() / 1000.0f));
}
int PCSpeakerStutterStream::readBuffer(sint16 *buffer, const int numSamples) {
uint32 s = 0;
for (; cx > 0 && s < (uint32)numSamples; cx--) {
uint32 n = (uint32)floor(delay_remaining);
if (n > 0) {
pcspkr->PCSPEAKER_CallBack(&buffer[s], n);
delay_remaining -= n;
s += n;
}
dx = (dx + arg_8) & 0xffff;
if (dx > arg_2) {
pcspkr->SetOn();
} else {
pcspkr->SetOff();
}
arg_2 += arg_0;
/*
for(int i = arg_6; i > 0 ; i--)
{
for(int j = counter;j > 0;)
{
j--;
}
}
*/
n = (uint32)floor(delay);
if (s + n > (uint32)numSamples)
n = numSamples - s;
pcspkr->PCSPEAKER_CallBack(&buffer[s], n);
delay_remaining = delay - n;
s += n;
}
if (cx <= 0) {
//DEBUG(0, LEVEL_DEBUGGING, "total_samples_played = %d\n", total_samples_played);
finished = true;
pcspkr->SetOff();
}
return s;
}
Audio::AudioStream *makePCSpeakerSlugDissolveSfxStream(uint /*rate*/) {
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint16 i = 0; i < 20; i++) {
stream->queueAudioStream(new PCSpeakerRandomStream((NUVIE_RAND() % 0x1068) + 0x258, 0x15e, 1), DisposeAfterUse::YES);
}
return stream;
}
Audio::AudioStream *makePCSpeakerGlassSfxStream(uint /*rate*/) {
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint16 i = 0x7d0; i < 0x4e20; i += 0x3e8) {
stream->queueAudioStream(new PCSpeakerRandomStream(i, 0x78, 0x28), DisposeAfterUse::YES);
}
return stream;
}
Audio::AudioStream *makePCSpeakerMagicCastingP1SfxStream(uint /*rate*/, uint8 magic_circle) {
//Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(rate, false);
return new PCSpeakerRandomStream(0x2bc, 0x640 * magic_circle + 0x1f40, 0x320);
//return stream;
}
Audio::AudioStream *makePCSpeakerMagicCastingP2SfxStream(uint /*rate*/, uint8 magic_circle) {
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
const sint16 word_30188[] = {3, 2, 2, 2, 1, 1, 1, 1, 1};
const uint16 word_30164[] = {0xA8C, 0xBB8, 0x3E8, 0x64, 0x1388, 0xFA0, 0x9C4, 0x3E8, 1};
const uint16 word_30176[] = {0x7FBC, 0x7918, 0x9088, 0xAFC8, 0x7918, 0x84D0, 0x8E94, 0x9858, 0xA410};
const uint16 word_30152[] = {0x226A, 0x1E96, 0x1B94, 0x1996, 0x173E, 0x15C2, 0x143C, 0x12D4, 0x1180};
stream->queueAudioStream(new PCSpeakerStutterStream(word_30188[magic_circle], word_30164[magic_circle], (magic_circle * 0xfa0) + 0x2710, 1, word_30152[magic_circle]));
stream->queueAudioStream(new PCSpeakerStutterStream(-word_30188[magic_circle], word_30176[magic_circle], (magic_circle * 0xfa0) + 0x2710, 1, word_30152[magic_circle]));
return stream;
}
Audio::AudioStream *makePCSpeakerAvatarDeathSfxStream(uint /*rate*/) {
const uint16 avatar_death_tune[] = {0x12C, 0x119, 0x12C, 0xFA, 0x119, 0xDE, 0xFA, 0xFA};
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint8 i = 0; i < 8; i++) {
stream->queueAudioStream(new PCSpeakerStutterStream(3, 1, 0x4e20, 1, avatar_death_tune[i]));
}
return stream;
}
Audio::AudioStream *makePCSpeakerKalLorSfxStream(uint /*rate*/) {
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint8 i = 0; i < 0x32; i++) {
stream->queueAudioStream(new PCSpeakerStutterStream((0x32 - i) << 2, 0x2710 - (i << 6), 0x3e8, 1, (i << 4) + 0x320));
}
stream->queueAudioStream(new PCSpeakerStutterStream(8, 0, 0x1f40, 1, 0x640));
return stream;
}
Audio::AudioStream *makePCSpeakerHailStoneSfxStream(uint /*rate*/) {
//FIXME This doesn't sound right. It should probably use a single
// pcspkr object. The original also plays the hailstones
// individually, not all at once like we do. :(
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint16 i = 0; i < 0x28; i++) {
stream->queueAudioStream(new PCSpeakerFreqStream((NUVIE_RAND() % 0x28) + 0x20, 8), DisposeAfterUse::YES);
}
/* The original logic looks something like this. But this doesn't sound right.
uint16 base_freq = (NUVIE_RAND()%0x64)+0x190;
for(uint16 i=0;i<0x28;i++)
{
if(NUVIE_RAND()%7==0)
stream->queueAudioStream(new PCSpeakerFreqStream(base_freq + (NUVIE_RAND()%0x28), 8), DisposeAfterUse::YES);
else
stream->queueAudioStream(new PCSpeakerFreqStream(0, 8), DisposeAfterUse::YES);
}
*/
return stream;
}
Audio::AudioStream *makePCSpeakerEarthQuakeSfxStream(uint /*rate*/) {
Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SPKR_OUTPUT_RATE, false);
for (uint16 i = 0; i < 0x28; i++) {
stream->queueAudioStream(new PCSpeakerFreqStream((NUVIE_RAND() % 0xb5) + 0x13, 8), DisposeAfterUse::YES);
}
return stream;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,186 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIXER_DECODER_PC_SPEAKER_STREAM_H
#define NUVIE_SOUND_MIXER_DECODER_PC_SPEAKER_STREAM_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/sound/decoder/pc_speaker.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
class PCSpeakerStream : public Audio::RewindableAudioStream {
public:
PCSpeakerStream() {
pcspkr = new PCSpeaker(SPKR_OUTPUT_RATE);
finished = false;
}
~PCSpeakerStream() override {
delete pcspkr;
}
/** Is this a stereo stream? */
bool isStereo() const override {
return false;
}
/** Sample rate of the stream. */
int getRate() const override {
return SPKR_OUTPUT_RATE;
}
/**
* End of data reached? If this returns true, it means that at this
* time there is no data available in the stream. However there may be
* more data in the future.
* This is used by e.g. a rate converter to decide whether to keep on
* converting data or stop.
*/
bool endOfData() const override {
return finished;
}
bool rewind() override {
return false;
}
protected:
PCSpeaker *pcspkr;
bool finished;
};
class PCSpeakerFreqStream : public PCSpeakerStream {
public:
PCSpeakerFreqStream() {
}
PCSpeakerFreqStream(uint start, uint16 d);
~PCSpeakerFreqStream() override;
uint32 getLengthInMsec();
int readBuffer(sint16 *buffer, const int numSamples) override;
protected:
uint32 frequency;
uint32 duration;
uint32 total_samples_played;
};
class PCSpeakerSweepFreqStream : public PCSpeakerStream {
public:
PCSpeakerSweepFreqStream() {
}
PCSpeakerSweepFreqStream(uint start, uint end, uint16 d, uint16 s);
~PCSpeakerSweepFreqStream() override;
uint32 getLengthInMsec();
int readBuffer(sint16 *buffer, const int numSamples) override;
protected:
uint32 start_freq;
uint32 finish_freq;
uint32 cur_freq;
uint16 duration;
uint16 stepping;
uint32 freq_step;
float samples_per_step;
float sample_pos;
uint32 total_samples_played;
uint32 num_steps;
uint32 cur_step;
};
class PCSpeakerRandomStream : public PCSpeakerStream {
public:
PCSpeakerRandomStream() {
}
PCSpeakerRandomStream(uint start, uint16 d, uint16 s);
~PCSpeakerRandomStream() override;
uint32 getLengthInMsec();
uint16 getNextFreqValue();
int readBuffer(sint16 *buffer, const int numSamples) override;
protected:
uint16 base_val;
uint16 duration;
uint16 stepping;
uint32 rand_value;
uint32 sample_pos;
uint32 total_samples_played;
uint32 samples_per_step;
uint32 num_steps;
uint32 cur_step;
};
class PCSpeakerStutterStream : public PCSpeakerStream {
public:
PCSpeakerStutterStream() {
}
PCSpeakerStutterStream(sint16 a0, uint16 a2, uint16 a4, uint16 a6, uint16 a8);
~PCSpeakerStutterStream() override;
uint32 getLengthInMsec();
int readBuffer(sint16 *buffer, const int numSamples) override;
protected:
sint16 arg_0;
uint16 arg_2;
uint16 arg_4;
uint16 arg_6;
uint16 arg_8;
uint16 dx;
uint16 cx;
float delay;
float delay_remaining;
};
Audio::AudioStream *makePCSpeakerSlugDissolveSfxStream(uint rate);
Audio::AudioStream *makePCSpeakerGlassSfxStream(uint rate);
Audio::AudioStream *makePCSpeakerMagicCastingP1SfxStream(uint rate, uint8 magic_circle);
Audio::AudioStream *makePCSpeakerMagicCastingP2SfxStream(uint rate, uint8 magic_circle);
Audio::AudioStream *makePCSpeakerAvatarDeathSfxStream(uint rate);
Audio::AudioStream *makePCSpeakerKalLorSfxStream(uint rate);
Audio::AudioStream *makePCSpeakerHailStoneSfxStream(uint rate);
Audio::AudioStream *makePCSpeakerEarthQuakeSfxStream(uint rate);
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,134 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/sound/decoder/random_collection_audio_stream.h"
#include "ultima/nuvie/core/game.h"
#include "audio/audiostream.h"
#include "common/mutex.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
namespace U6Audio {
#pragma mark -
#pragma mark --- random collection audio stream ---
#pragma mark -
class RandomCollectionAudioStreamImpl : public RandomCollectionAudioStream {
private:
/**
* The sampling rate of this audio stream.
*/
const int _rate;
/**
* Whether this audio stream is mono (=false) or stereo (=true).
*/
const int _stereo;
/**
* This flag is set by the finish() method only. See there for more details.
*/
bool _finished;
/**
* An array of audio streams.
*/
Std::vector<Audio::RewindableAudioStream *> _streams;
DisposeAfterUse::Flag _disposeAfterUse;
Audio::RewindableAudioStream *_currentStream;
public:
RandomCollectionAudioStreamImpl(int rate, bool stereo, const Std::vector<Audio::RewindableAudioStream *> &streams, DisposeAfterUse::Flag disposeAfterUse)
: _rate(rate), _stereo(stereo), _finished(false), _streams(streams), _disposeAfterUse(disposeAfterUse) {
if (_streams.size() > 0)
_currentStream = _streams[NUVIE_RAND() % _streams.size()];
else
_currentStream = nullptr;
}
~RandomCollectionAudioStreamImpl() override;
// Implement the AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override {
return _stereo;
}
int getRate() const override {
return _rate;
}
bool endOfData() const override {
return false;
}
bool endOfStream() const override {
return _finished;
}
void finish() override {
_finished = true;
}
};
RandomCollectionAudioStreamImpl::~RandomCollectionAudioStreamImpl() {
if (_disposeAfterUse == DisposeAfterUse::YES) {
while (!_streams.empty()) {
delete _streams.back();
_streams.pop_back();
}
}
}
int RandomCollectionAudioStreamImpl::readBuffer(int16 *buffer, const int numSamples) {
int samplesDecoded = 0;
if (_currentStream) {
while (samplesDecoded < numSamples) {
samplesDecoded += _currentStream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);
if (_currentStream->endOfData()) {
_currentStream->rewind();
//pseudo random we don't want to play the same stream twice in a row.
int32 idx = NUVIE_RAND() % _streams.size();
Audio::RewindableAudioStream *tmp = _streams[idx];
if (_currentStream == tmp) {
idx = (idx + (NUVIE_RAND() % 1 == 1 ? 1 : _streams.size() - 1)) % _streams.size();
_currentStream = _streams[idx];
} else
_currentStream = tmp;
//DEBUG(0, LEVEL_INFORMATIONAL, "new sample_num = %d\n", idx);
}
}
}
return samplesDecoded;
}
RandomCollectionAudioStream *makeRandomCollectionAudioStream(int rate, bool stereo,
Std::vector<Audio::RewindableAudioStream *> streams, DisposeAfterUse::Flag disposeAfterUse) {
return new RandomCollectionAudioStreamImpl(rate, stereo, streams, disposeAfterUse);
}
} // End of namespace U6Audio
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIXER_RANDOM_COLLECTION_AUDIO_STREAM_H
#define NUVIE_SOUND_MIXER_RANDOM_COLLECTION_AUDIO_STREAM_H
#include "ultima/shared/std/containers.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
namespace U6Audio {
class RandomCollectionAudioStream : public Audio::AudioStream {
public:
/**
* Mark this stream as finished. That is, signal that no further data
* will be queued to it. Only after this has been done can this
* stream ever 'end'.
*/
virtual void finish() = 0;
};
/**
* Factory function for a QueuingAudioStream.
*/
RandomCollectionAudioStream *makeRandomCollectionAudioStream(int rate, bool stereo, Std::vector<Audio::RewindableAudioStream *>streams, DisposeAfterUse::Flag disposeAfterUse);
} // End of namespace U6Audio
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,128 @@
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/sound/decoder/u6_adplug_decoder_stream.h"
namespace Ultima {
namespace Nuvie {
U6AdPlugDecoderStream::U6AdPlugDecoderStream(CEmuopl *o, const Common::Path &filename, uint16 song_num) {
is_midi_track = false;
opl = o;
samples_left = 0;
if (has_file_extension(filename.baseName().c_str(), ".lzc")) {
player = new CmidPlayer(opl);
((CmidPlayer *)player)->load(filename, song_num);
is_midi_track = true;
} else {
player = new Cu6mPlayer(opl);
player->load(filename);
}
player_refresh_count = (int)(opl->getRate() / player->getrefresh());
interrupt_rate = (int)(opl->getRate() / 60);
interrupt_samples_left = interrupt_rate;
}
U6AdPlugDecoderStream::~U6AdPlugDecoderStream() {
}
int U6AdPlugDecoderStream::readBuffer(sint16 *buffer, const int numSamples) {
sint32 i, j;
short *data = (short *)buffer;
int len = numSamples / 2;
//DEBUG(0, LEVEL_INFORMATIONAL, "Get here. numSamples = %d player refreshrate = %f refresh_count = %d\n", numSamples, player->getrefresh(), (int)(opl->getRate() / player->getrefresh()));
if (samples_left > 0) {
if (samples_left > len) {
update_opl(data, len);
samples_left -= len;
return numSamples;
}
update_opl(data, samples_left);
data += samples_left * 2;
len -= samples_left;
samples_left = 0;
}
for (i = len; i > 0;) {
if (!player->update()) {
player->rewind();
//SoundManager::g_MusicFinished = true;
DEBUG(0, LEVEL_DEBUGGING, "Music Finished!\n");
}
j = (int)(opl->getRate() / player->getrefresh());
if (j > i) {
samples_left = j - i;
j = i;
}
update_opl(data, j);
data += j * 2;
i -= j;
}
return numSamples;
}
void U6AdPlugDecoderStream::update_opl(short *data, int len) {
if (is_midi_track) {
if (interrupt_samples_left > 0) {
if (interrupt_samples_left > len) {
opl->update(data, len);
interrupt_samples_left -= len;
return;
}
opl->update(data, interrupt_samples_left);
data += interrupt_samples_left * 2;
len -= interrupt_samples_left;
interrupt_samples_left = 0;
}
for (int i = len; i > 0;) {
((CmidPlayer *)player)->interrupt_vector();
int j = interrupt_rate;
if (j > i) {
interrupt_samples_left = j - i;
j = i;
}
opl->update(data, j);
data += j * 2;
i -= j;
}
} else {
opl->update(data, len);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,95 @@
/* Created by Eric Fry
* Copyright (C) 2011 The Nuvie Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#ifndef NUVIE_SOUND_ADPLUG_ADPLUG_DECODER_STREAM_H
#define NUVIE_SOUND_ADPLUG_ADPLUG_DECODER_STREAM_H
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/adplug/opl.h"
#include "ultima/nuvie/sound/adplug/u6m.h"
#include "ultima/nuvie/sound/adplug/mid.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
class U6Lib_n;
class U6Lzw;
class NuvieIOBuffer;
using Std::string;
class U6AdPlugDecoderStream : public Audio::RewindableAudioStream {
public:
U6AdPlugDecoderStream() {
opl = nullptr;
player = nullptr;
player_refresh_count = 0;
}
U6AdPlugDecoderStream(CEmuopl *o, const Common::Path &filename, uint16 song_num);
~U6AdPlugDecoderStream() override;
int readBuffer(sint16 *buffer, const int numSamples) override;
/** Is this a stereo stream? */
bool isStereo() const override {
return true;
}
/** Sample rate of the stream. */
int getRate() const override {
return opl->getRate();
}
bool rewind() override {
if (player) {
player->rewind(); //FIXME this would need to be locked if called outside mixer thread.
return true;
}
return false;
}
/**
* End of data reached? If this returns true, it means that at this
* time there is no data available in the stream. However there may be
* more data in the future.
* This is used by e.g. a rate converter to decide whether to keep on
* converting data or stop.
*/
bool endOfData() const override {
return false;
}
private:
void update_opl(short *data, int num_samples);
protected:
uint16 samples_left;
CEmuopl *opl;
CPlayer *player;
int player_refresh_count;
int interrupt_rate;
int interrupt_samples_left;
bool is_midi_track;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,459 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mididrv_m_adlib.h"
namespace Ultima {
namespace Nuvie {
const uint16 MidiDriver_M_AdLib::FNUM_VALUES[24] = {
0x0, 0x158, 0x182, 0x1B0, 0x1CC, 0x203, 0x241, 0x286,
0x0, 0x16A, 0x196, 0x1C7, 0x1E4, 0x21E, 0x25F, 0x2A8,
0x0, 0x147, 0x16E, 0x19A, 0x1B5, 0x1E9, 0x224, 0x266
};
MidiDriver_M_AdLib::MidiDriver_M_AdLib() : MidiDriver_ADLIB_Multisource(OPL::Config::kOpl2, 60) {
_modulationDepth = MODULATION_DEPTH_LOW;
_vibratoDepth = VIBRATO_DEPTH_LOW;
_allocationMode = ALLOCATION_MODE_STATIC;
_instrumentWriteMode = INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE;
Common::fill(_slideValues, _slideValues + ARRAYSIZE(_slideValues), 0);
Common::fill(_vibratoDepths, _vibratoDepths + ARRAYSIZE(_vibratoDepths), 0);
Common::fill(_vibratoFactors, _vibratoFactors + ARRAYSIZE(_vibratoFactors), 0);
Common::fill(_vibratoCurrentDepths, _vibratoCurrentDepths + ARRAYSIZE(_vibratoCurrentDepths), 0);
Common::fill(_vibratoDirections, _vibratoDirections + ARRAYSIZE(_vibratoDirections), VIBRATO_DIRECTION_RISING);
Common::fill(_fadeDirections, _fadeDirections + ARRAYSIZE(_fadeDirections), FADE_DIRECTION_NONE);
Common::fill(_fadeStepDelays, _fadeStepDelays + ARRAYSIZE(_fadeStepDelays), 0);
Common::fill(_fadeCurrentDelays, _fadeCurrentDelays + ARRAYSIZE(_fadeCurrentDelays), 0);
_instrumentBank = new OplInstrumentDefinition[16];
}
MidiDriver_M_AdLib::~MidiDriver_M_AdLib() {
delete[] _instrumentBank;
}
void MidiDriver_M_AdLib::send(int8 source, uint32 b) {
byte command = b & 0xF0;
byte channel = b & 0x0F;
byte data = (b >> 8) & 0xFF;
ActiveNote *activeNote;
//uint16 channelOffset;
//uint16 frequency;
switch (command) {
case 0x00: // Note off
// The original driver always writes both F-num registers with the
// supplied note value; it does not check what the active note value
// is. The standard noteOff implementation checks if the active note
// value matches an active note on the data channel. If the note off
// does not match the active note, this could cause a hanging note.
// None of the Ultima 6 tracks seem to have this problem however.
/* DEBUG: Write Ax register
// Calculate the frequency.
channelOffset = determineChannelRegisterOffset(channel);
frequency = calculateFrequency(channel, source, data);
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
*/
noteOff(channel, data, 0, source);
break;
case 0x10: // Note on
// Stop vibrato (if active)
_vibratoDirections[channel] = VIBRATO_DIRECTION_RISING;
_vibratoCurrentDepths[channel] = 0;
// The original driver always writes a note off before a note on, even
// if there is no note active. The standard noteOn implementation only
// writes a note off if a note is active. This causes no audible
// difference however.
/* DEBUG: Write note off
_activeNotesMutex.lock();
// Melodic instrument.
activeNote = &_activeNotes[channel];
// Calculate the frequency.
channelOffset = determineChannelRegisterOffset(channel);
frequency = calculateFrequency(channel, source, data);
activeNote->oplFrequency = frequency;
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset, frequency >> 8);
// Update the active note data.
activeNote->noteActive = false;
activeNote->noteSustained = false;
// Register the current note counter value when turning off a note.
activeNote->noteCounterValue = _noteCounter;
_activeNotesMutex.unlock();
*/
noteOn(channel, data, 0x7F, source);
break;
case 0x20: // Set pitch
// If a note is already active on this channel, this will just update
// the pitch. Otherwise it is the same as a Note on.
_activeNotesMutex.lock();
// Determine the OPL channel to use and the active note data to update.
uint8 oplChannel;
oplChannel = 0xFF;
activeNote = nullptr;
// Allocate a melodic OPL channel.
InstrumentInfo instrumentInfo;
instrumentInfo = { };
oplChannel = allocateOplChannel(channel, source, instrumentInfo);
if (oplChannel != 0xFF)
activeNote = &_activeNotes[oplChannel];
if (activeNote != nullptr) {
if (!activeNote->noteActive) {
// If there is no note active currently, treat this as a
// regular note on.
noteOn(channel, data, 0x7F, source);
} else {
// If there is a note active, only update the frequency.
activeNote->note = data;
activeNote->oplNote = data;
// Calculate and write frequency and block and write key on bit.
writeFrequency(oplChannel);
}
}
_activeNotesMutex.unlock();
break;
case 0x30: // Set level
// This directly writes the OPL level register of the carrier operator.
// This can also write the key scale level bits.
// Note that the control data volume field is used for an OPL level
// value, not for a MIDI channel volume value as usual.
// Stop fade (if active)
_fadeDirections[channel] = FADE_DIRECTION_NONE;
_controlData[source][channel].volume = data;
if (_activeNotes[channel].instrumentDef)
writeVolume(channel, 1);
break;
case 0x40: // Set modulation
modulation(channel, data, source);
break;
case 0x50: // Set slide
// Start or stop a pitch slide. The slide is processed by onTimer.
_slideValues[channel] = (int8)data;
break;
case 0x60: // Set vibrato
// Turns vibrato on or off or modifies the parameters. High nibble
// is the vibrato depth, low nibble is the vibrato factor. The vibrato
// is processed by onTimer.
_vibratoDepths[channel] = data >> 4;
_vibratoFactors[channel] = data & 0xF;
break;
case 0x70: // Program change
programChange(channel, data, source);
break;
case 0x80: // Subcommand
uint8 subcommand;
subcommand = channel;
switch (subcommand) {
case 0x1: // Call subroutine
case 0x2: // Delay
// These are handled by the parser.
break;
case 0x3: // Load instrument
// This should be sent to the driver as a meta event.
warning("MidiDriver_M_AdLib::send - Received load instrument as command");
break;
case 0x5: // Fade out
case 0x6: // Fade in
// Starts a volume fade in or out. The high nibble of the data byte
// is the channel, the low nibble is the fade delay. The fade is
// processed by onTimer.
channel = data >> 4;
_fadeDirections[channel] = (subcommand == 0x5 ? FADE_DIRECTION_FADE_OUT : FADE_DIRECTION_FADE_IN);
uint8 delay;
delay = (data & 0xF) + 1;
_fadeStepDelays[channel] = _fadeCurrentDelays[channel] = delay;
break;
default: // Unknown subcommand
break;
}
break;
case 0xE0: // Set loop point
case 0xF0: // Return
// These are handled by the parser.
break;
default: // Unknown command
break;
}
}
void MidiDriver_M_AdLib::metaEvent(int8 source, byte type, const byte* data, uint16 length) {
if (type == 0x3) {
// Load instrument
// This loads an OPL instrument definition into the bank. The first
// byte is the instrument bank number. The next 11 bytes contain the
// instrument parameters.
if (length < 12) {
warning("Received a load instrument event with insufficient data length");
return;
}
byte instrumentNumber = data[0];
assert(instrumentNumber < 16);
// This was allocated in the constructor so it's not really const
OplInstrumentDefinition *instrument = const_cast<OplInstrumentDefinition *>(&_instrumentBank[instrumentNumber]);
instrument->fourOperator = false;
instrument->rhythmType = RHYTHM_TYPE_UNDEFINED;
instrument->operator0.freqMultMisc = data[1];
instrument->operator0.level = data[2];
instrument->operator0.decayAttack = data[3];
instrument->operator0.releaseSustain = data[4];
instrument->operator0.waveformSelect = data[5];
instrument->operator1.freqMultMisc = data[6];
instrument->operator1.level = data[7];
instrument->operator1.decayAttack = data[8];
instrument->operator1.releaseSustain = data[9];
instrument->operator1.waveformSelect = data[10];
instrument->connectionFeedback0 = data[11];
instrument->transpose = 0;
}
}
void MidiDriver_M_AdLib::programChange(uint8 channel, uint8 program, uint8 source) {
assert(program < 16);
// Changing the instrument overwrites the current volume and modulation
// settings.
_controlData[source][channel].volume = _instrumentBank[program].operator1.level;
_controlData[source][channel].modulation = _instrumentBank[program].operator0.level;
// Note that this will turn off an active note on the channel if there is
// one. The original driver does not do this. Changing the instrument while
// a note is playing would be strange though; none of the tracks in
// Ultima 6 seem to do this.
MidiDriver_ADLIB_Multisource::programChange(channel, program, source);
}
void MidiDriver_M_AdLib::modulation(uint8 channel, uint8 modulation, uint8 source) {
// This directly writes the OPL level register of the modulator
// operator.
// Note that the control data modulation field is used for an OPL level
// value, not for a MIDI channel modulation value as usual.
_controlData[source][channel].modulation = modulation;
uint16 registerOffset = determineOperatorRegisterOffset(channel, 0);
writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, modulation);
}
uint8 MidiDriver_M_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
// Allocation of M data channels to OPL output channels is simply 1 on 1.
return channel;
}
void MidiDriver_M_AdLib::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
Common::StackLock lock(_activeNotesMutex);
ActiveNote *activeNote = &_activeNotes[oplChannel];
uint8 mNote = activeNote->oplNote;
// Calculate the frequency.
uint16 channelOffset = determineChannelRegisterOffset(oplChannel, activeNote->instrumentDef->fourOperator);
uint16 frequency = calculateFrequency(activeNote->channel, activeNote->source, mNote);
activeNote->oplFrequency = frequency;
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
}
uint16 MidiDriver_M_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
// An M note value consist of a note lookup value in the low 5 bits and
// a block (octave) value in the high 3 bits.
uint8 fnumIndex = note & 0x1F;
assert(fnumIndex < 24);
uint16 oplFrequency = FNUM_VALUES[fnumIndex];
uint8 block = note >> 5;
return oplFrequency | (block << 10);
}
uint8 MidiDriver_M_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition& instrumentDef, uint8 operatorNum) {
// M directy uses OPL level values, so no calculation is necessary.
return _controlData[source][channel].volume & OPL_MASK_LEVEL;
}
void MidiDriver_M_AdLib::writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType) {
ActiveNote *activeNote = (rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[rhythmType - 1]);
// Calculate operator volume.
uint16 registerOffset = determineOperatorRegisterOffset(
oplChannel, operatorNum, rhythmType, activeNote->instrumentDef->fourOperator);
uint8 level = calculateVolume(activeNote->channel, activeNote->source, activeNote->velocity,
*activeNote->instrumentDef, operatorNum);
// Add key scaling level from the last written volume or modulation value
// to the calculated level.
MidiChannelControlData *controlData = &_controlData[activeNote->source][activeNote->channel];
uint8 ksl = (operatorNum == 0 ? controlData->modulation : controlData->volume) & ~OPL_MASK_LEVEL;
writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, level | ksl);
}
void MidiDriver_M_AdLib::deinitSource(uint8 source) {
// Reset effects status.
Common::fill(_slideValues, _slideValues + ARRAYSIZE(_slideValues), 0);
Common::fill(_vibratoFactors, _vibratoFactors + ARRAYSIZE(_vibratoFactors), 0);
Common::fill(_vibratoCurrentDepths, _vibratoCurrentDepths + ARRAYSIZE(_vibratoCurrentDepths), 0);
Common::fill(_vibratoDirections, _vibratoDirections + ARRAYSIZE(_vibratoDirections), VIBRATO_DIRECTION_RISING);
Common::fill(_fadeDirections, _fadeDirections + ARRAYSIZE(_fadeDirections), FADE_DIRECTION_NONE);
MidiDriver_ADLIB_Multisource::deinitSource(source);
}
void MidiDriver_M_AdLib::onTimer() {
MidiDriver_ADLIB_Multisource::onTimer();
_activeNotesMutex.lock();
// Process effects.
for (int i = 8; i >= 0; i--) {
ActiveNote *activeNote = &_activeNotes[i];
if (_slideValues[i] != 0) {
// Process slide. A slide continually increases or decreases the
// note frequency until it is turned off.
// Increase or decrease the OPL frequency by the slide value.
// Note that this can potentially over- or underflow the OPL
// frequency, but there is no bounds checking in the original
// driver either.
activeNote->oplFrequency += _slideValues[i];
// Write the low 8 frequency bits.
uint16 channelOffset = determineChannelRegisterOffset(i);
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, activeNote->oplFrequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(activeNote->oplFrequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
} else if (_vibratoFactors[i] > 0 && activeNote->noteActive) {
// Process vibrato. Vibrato will alternately increase and decrease
// the frequency up to the maximum depth.
// The depth is the difference between the minimum and maximum
// frequency change, so a positive number, twice the amplitude.
// The current depth is converted to the actual frequency offset by
// subtracting half the total depth. The offset is then multiplied
// by the vibrato factor.
// Note that current depth starts at 0, so minimum depth, rather
// than at neutral (half depth).
// Flip vibrato direction if the maximum or minimum depth has been reached.
if (_vibratoCurrentDepths[i] >= _vibratoDepths[i]) {
_vibratoDirections[i] = VIBRATO_DIRECTION_FALLING;
} else if (_vibratoCurrentDepths[i] == 0) {
_vibratoDirections[i] = VIBRATO_DIRECTION_RISING;
}
// Update current depth.
if (_vibratoDirections[i] == VIBRATO_DIRECTION_FALLING) {
_vibratoCurrentDepths[i]--;
} else {
_vibratoCurrentDepths[i]++;
}
// Convert the depth to an OPL frequency offset.
int vibratoOffset = _vibratoCurrentDepths[i] - (_vibratoDepths[i] >> 1);
vibratoOffset *= _vibratoFactors[i];
uint16 frequency = activeNote->oplFrequency + vibratoOffset;
// Write the low 8 frequency bits.
uint16 channelOffset = determineChannelRegisterOffset(i);
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
}
if (_fadeDirections[i] != FADE_DIRECTION_NONE && --_fadeCurrentDelays[i] == 0) {
// Process fade. A fade will continually increase or decrease the
// level (volume) until the maximum or minimum volume is reached.
// Then the fade is stopped. A delay determines the speed of the
// fade by increasing the number of ticks between each increase or
// decrease.
// Reset delay.
_fadeCurrentDelays[i] = _fadeStepDelays[i];
// Calculate new channel level.
int newChannelLevel = _controlData[activeNote->source][i].volume + (_fadeDirections[i] == FADE_DIRECTION_FADE_IN ? -1 : 1);
if (newChannelLevel < 0 || newChannelLevel > 0x3F) {
// Minimum or maximum level reached. Stop the fade.
newChannelLevel = (newChannelLevel < 0) ? 0 : 0x3F;
_fadeDirections[i] = FADE_DIRECTION_NONE;
}
// Apply the new volume.
_controlData[activeNote->source][i].volume = newChannelLevel;
writeVolume(i, 1);
}
}
_activeNotesMutex.unlock();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,103 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIDIDRV_M_ADLIB_H
#define NUVIE_SOUND_MIDIDRV_M_ADLIB_H
#include "audio/adlib_ms.h"
namespace Ultima {
namespace Nuvie {
/**
* M driver for AdLib (OPL2).
* This driver supports several effects by adjusting OPL frequency and level
* values based on timer ticks: slide, vibrato and fade in/out. Only vibrato is
* used by the tracks in Ultima 6.
*/
class MidiDriver_M_AdLib : public MidiDriver_ADLIB_Multisource {
protected:
// The direction of a volume fade: in (increase) or out (decrease).
enum FadeDirection {
FADE_DIRECTION_NONE,
FADE_DIRECTION_FADE_IN,
FADE_DIRECTION_FADE_OUT
};
// The current direction of vibrato pitch change.
enum VibratoDirection {
VIBRATO_DIRECTION_RISING,
VIBRATO_DIRECTION_FALLING
};
// Converts M note values to OPL frequency (F-num) values.
static const uint16 FNUM_VALUES[24];
public:
MidiDriver_M_AdLib();
~MidiDriver_M_AdLib();
using MidiDriver_Multisource::send;
void send(int8 source, uint32 b) override;
void metaEvent(int8 source, byte type, const byte *data, uint16 length) override;
protected:
void programChange(uint8 channel, uint8 program, uint8 source) override;
void modulation(uint8 channel, uint8 modulation, uint8 source) override;
uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
void writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
void deinitSource(uint8 source) override;
void onTimer() override;
// Number of F-num units each channel will increase/decrease each tick.
int8 _slideValues[9];
// Maximum number of F-num units the frequency will be changed by vibrato,
// before applying the factor, for each channel. This is the difference
// between the lowest and highest value (so twice the amplitude).
uint8 _vibratoDepths[9];
// Multiplication factor for vibrato F-num values for each channel.
uint8 _vibratoFactors[9];
// The current "progression" of vibrato through the cycle for each channel.
// This is before the vibrato factor is applied.
uint8 _vibratoCurrentDepths[9];
// The current direction in which the vibrato is progressing for each
// channel (rising or falling frequency).
VibratoDirection _vibratoDirections[9];
// The direction of the fade currently active on each channel (in or out).
// NONE indicates no fade is active.
FadeDirection _fadeDirections[9];
// The delay in ticks between each level increase or decrease for each
// channel.
uint8 _fadeStepDelays[9];
// The current fade delay counter value for each channel.
uint8 _fadeCurrentDelays[9];
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,205 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mididrv_m_mt32.h"
namespace Ultima {
namespace Nuvie {
const uint8 MidiDriver_M_MT32::MIDI_NOTE_MAP[] = {
0x00, 0x0C, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17,
0x00, 0x0D, 0x0F, 0x11, 0x12, 0x14, 0x16, 0x18,
0x00, 0x0B, 0x0D, 0x0F, 0x10, 0x12, 0x14, 0x16
};
MidiDriver_M_MT32::MidiDriver_M_MT32() : MidiDriver_MT32GM(MT_MT32) {
Common::fill(_mInstrumentMidiChannels, _mInstrumentMidiChannels + sizeof(_mInstrumentMidiChannels), 1);
Common::fill(_mInstrumentMapping, _mInstrumentMapping + sizeof(_mInstrumentMapping), 0);
}
MidiDriver_M_MT32::~MidiDriver_M_MT32() { }
int MidiDriver_M_MT32::open(MidiDriver *driver, bool nativeMT32) {
int result = MidiDriver_MT32GM::open(driver, nativeMT32);
if (result == 0)
setInstrumentRemapping(_mInstrumentMapping);
return result;
}
void MidiDriver_M_MT32::send(int8 source, uint32 b) {
if (!_isOpen) {
// During the opening of the driver, some MIDI commands are sent to
// initialize the device. These are not M commands so they are sent
// straight to the device.
MidiDriver_MT32GM::send(source, b);
return;
}
byte mCommand = b & 0xF0;
if (mCommand >= 0x80) {
// These commands are either handled by the parser (call, return,
// set loop point, delay) or are not implemented for MT-32
// (load instrument, fade). Not all of them have the channel in the
// low nibble, so they are filtered out here.
return;
}
byte dataChannel = b & 0x0F;
byte data = (b >> 8) & 0xFF;
MChannelData &mChannelData = _mChannelData[dataChannel];
// Get the MIDI output channel assigned to this M data channel.
int8 outputChannel = source < 0 ? dataChannel : mapSourceChannel(source, dataChannel);
if (outputChannel < 0) {
warning("MidiDriver_M_MT32::send - Could not map data channel %i to an output channel", dataChannel);
return;
}
MidiChannelControlData &controlData = *_controlData[outputChannel];
byte midiNote;
byte mNote;
// Convert M to MIDI events
switch (mCommand) {
case 0x00: // Note off
mNote = data & 0x1F;
assert(mNote < 24);
midiNote = MIDI_NOTE_MAP[mNote] + ((data >> 5) * 12);
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, midiNote, mChannelData.velocity, source, controlData);
mChannelData.activeNote = -1;
break;
case 0x10: // Note on
case 0x20: // Set pitch
// In the original driver, for Note on events, Note off is explicitly
// called first to turn off the previous note. However, the Note off
// event is not sent if there is no note active. For Set pitch,
// Note off is not explicitly called; Note on is called directly.
// However, Note on turns off any active notes first before sending the
// Note on event. So despite the different code paths, these events
// effectively do the same thing: turn off the currently active note on
// the channel, if there is one, then play the new note on the next
// tick.
if (mChannelData.activeNote >= 0) {
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, mChannelData.activeNote, mChannelData.velocity, source, controlData);
mChannelData.activeNote = -1;
}
mNote = data & 0x1F;
assert(mNote < 24);
midiNote = MIDI_NOTE_MAP[mNote] + ((data >> 5) * 12);
// The new note is queued for playback on the next timer tick
// (see onTimer).
if (mChannelData.queuedNote >= 0) {
warning("MidiDriver_M_MT32::send - Note on on channel %i while a note is already queued", dataChannel);
}
mChannelData.queuedNote = midiNote;
break;
case 0x30: // Set level
// The OPL level is converted to a MIDI note velocity, which is used
// for notes subsequently played on the M channel. The active note is
// not affected.
mChannelData.velocity = (0x3F - (data & 0x3F)) * 1.5;
break;
case 0x70: // Program change
// When instrument assignments are set on the driver, each M instrument
// is assigned to a fixed MIDI output channel. When a program change
// event is encountered on an M channel, the MIDI output channel of
// that M channel is changed to the MIDI channel assigned to the new M
// instrument.
int8 newOutputChannel;
assert(data < 16);
newOutputChannel = _mInstrumentMidiChannels[data];
if (newOutputChannel < 0) {
warning("MidiDriver_M_MT32::send - Received program change for unmapped instrument %i", data);
break;
}
if (newOutputChannel != outputChannel && mChannelData.activeNote >= 0) {
// Turn off the active note.
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, mChannelData.activeNote, mChannelData.velocity, source, controlData);
mChannelData.activeNote = -1;
}
_channelMap[source][dataChannel] = newOutputChannel;
// Because the assignment of instruments to output channels is fixed,
// a program change for each channel could be sent once when setting
// instrument assignments. However, the original driver sends a program
// change every time the instrument on an M channel is changed.
programChange(newOutputChannel, data, source, controlData);
break;
default:
// Modulation, slide and vibrato are not implemented for MT-32.
break;
}
}
void MidiDriver_M_MT32::metaEvent(int8 source, byte type, const byte *data, uint16 length) {
// Load instrument is ignored for MT-32; instruments are set using
// setInstrumentAssignments.
}
void MidiDriver_M_MT32::setInstrumentAssignments(const MInstrumentAssignment *assignments) {
// Each M instrument used in the played track (up to 16) should get a MIDI
// output channel and a MIDI instrument assigned to it. The MIDI instrument
// is set on the output channel and when an M data channel switches to the
// corresponding M instrument, the data channel is mapped to that output
// channel.
for (int i = 0; i < 16; i++) {
if (assignments[i].midiChannel < 0)
break;
_mInstrumentMidiChannels[i] = assignments[i].midiChannel;
_mInstrumentMapping[i] = assignments[i].midiInstrument;
}
}
void MidiDriver_M_MT32::stopAllNotes(bool stopSustainedNotes) {
MidiDriver_MT32GM::stopAllNotes();
// Clear active and queued notes.
for (int i = 0; i < 9; i++) {
_mChannelData[i].activeNote = -1;
_mChannelData[i].queuedNote = -1;
}
}
void MidiDriver_M_MT32::onTimer() {
// Play the queued notes for each M channel.
for (int i = 0; i < 9; i++) {
if (_mChannelData[i].queuedNote >= 0) {
int8 outputChannel = mapSourceChannel(0, i);
if (outputChannel < 0) {
warning("MidiDriver_M_MT32::onTimer - Could not map data channel %i to an output channel", i);
continue;
}
MidiChannelControlData &controlData = *_controlData[outputChannel];
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_ON, _mChannelData[i].queuedNote, _mChannelData[i].velocity, 0, controlData);
_mChannelData[i].activeNote = _mChannelData[i].queuedNote;
_mChannelData[i].queuedNote = -1;
}
}
MidiDriver_MT32GM::onTimer();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,98 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIDIDRV_M_MT32_H
#define NUVIE_SOUND_MIDIDRV_M_MT32_H
#include "audio/mt32gm.h"
namespace Ultima {
namespace Nuvie {
// An assignment of a MIDI instrument to a MIDI output channel. M data channels
// using this instrument will be mapped to the specified MIDI channel.
struct MInstrumentAssignment {
int8 midiChannel;
uint8 midiInstrument;
};
/**
* M driver for the Roland MT-32.
* The M format is focused on OPL2 and conversion to MIDI is rudimentary. Only
* note on/off, channel volume (using note velocity) and program change are
* implemented by the original driver.
* A mapping of M instruments to MIDI instruments must be set using
* setInstrumentAssignments before starting playback of a track.
*/
class MidiDriver_M_MT32 : public MidiDriver_MT32GM {
protected:
/**
* Playback status information for an M channel.
* Note that although this data applies to an M data channel, the values
* are MIDI note and velocity values.
*/
struct MChannelData {
// The MIDI note currently played on this channel.
int8 activeNote = -1;
// The MIDI note velocity currently used on this channel.
uint8 velocity = 0;
// The MIDI note queued for playback on this channel.
int8 queuedNote = -1;
};
// Converts M note values to MIDI notes.
static const uint8 MIDI_NOTE_MAP[24];
public:
MidiDriver_M_MT32();
~MidiDriver_M_MT32();
using MidiDriver_MT32GM::open;
int open(MidiDriver *driver, bool nativeMT32) override;
using MidiDriver_MT32GM::send;
void send(int8 source, uint32 b) override;
void metaEvent(int8 source, byte type, const byte *data, uint16 length) override;
/**
* Sets the assignments of the 16 M instruments to the MIDI instruments and
* MIDI output channels they should use.
*
* @param assignments An instrument assignment array of length 16
*/
void setInstrumentAssignments(const MInstrumentAssignment *assignments);
void stopAllNotes(bool stopSustainedNotes) override;
protected:
void onTimer() override;
MChannelData _mChannelData[9];
// Mapping of M instrument numbers to MIDI output channels
int8 _mInstrumentMidiChannels[16];
// Mapping of M instrument numbers to MIDI instrument numbers
uint8 _mInstrumentMapping[16];
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,265 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "midiparser_m.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
namespace Ultima {
namespace Nuvie {
MidiParser_M::MidiParser_M(int8 source) : MidiParser(source) {
// M uses a fixed timer frequency of 60 Hz, or 16.667 ms per tick.
_psecPerTick = 16667;
_trackLength = 0;
_loopPoint = nullptr;
_loopStack = new Common::FixedStack<LoopData, 16>();
}
MidiParser_M::~MidiParser_M() {
delete _loopStack;
}
bool MidiParser_M::loadMusic(const byte* data, uint32 size) {
unloadMusic();
// M uses only 1 track.
_tracks[0][0] = data;
_numTracks = 1;
_numSubtracks[0] = 1;
_trackLength = size;
// The global loop defaults to the start of the M data.
_loopPoint = data;
resetTracking();
setTrack(0);
return true;
}
void MidiParser_M::unloadMusic() {
MidiParser::unloadMusic();
_trackLength = 0;
_loopPoint = nullptr;
_loopStack->clear();
}
// MidiParser::onTimer does some checks based on MIDI note on/off command bytes
// which have a different meaning in M, so those checks are removed here.
void MidiParser_M::onTimer() {
uint32 endTime;
uint32 eventTime;
uint32 eventTick;
if (!_position.isTracking() || !_driver || !_doParse || _pause || !_driver->isReady(_source))
return;
_abortParse = false;
endTime = _position._playTime + _timerRate;
bool loopEvent = false;
while (!_abortParse) {
EventInfo &info = *_nextEvent;
eventTick = _position._subtracks[0]._lastEventTick + info.delta;
eventTime = _position._lastEventTime + (eventTick - _position._lastEventTick) * _psecPerTick;
if (eventTime > endTime)
break;
if (!info.noop) {
// Process the next info.
bool ret = processEvent(info);
if (!ret)
return;
}
loopEvent |= info.loop;
if (!_abortParse) {
_position._playTime = eventTime;
_position._lastEventTime = eventTime;
_position._subtracks[0]._lastEventTime = eventTime;
_position._playTick = eventTick;
_position._lastEventTick = eventTick;
_position._subtracks[0]._lastEventTick = eventTick;
if (_position.isTracking(0)) {
parseNextEvent(_nextSubtrackEvents[0]);
}
determineNextEvent();
}
}
if (!_abortParse) {
_position._playTime = endTime;
_position._playTick = (endTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
if (loopEvent) {
// One of the processed events has looped (part of) the MIDI data.
// Infinite looping will cause the tracker to overflow eventually.
// Reset the tracker positions to prevent this from happening.
rebaseTracking();
}
}
}
bool MidiParser_M::processEvent(const EventInfo& info, bool fireEvents) {
const byte *playPos = _position._subtracks[0]._playPos;
if (info.command() == 0x8 && info.channel() == 0x1) {
// Call subroutine
LoopData loopData { };
loopData.returnPos = playPos;
loopData.numLoops = info.ext.data[0];
uint16 startOffset = READ_LE_UINT16(info.ext.data + 1);
assert(startOffset < _trackLength);
loopData.startPos = _tracks[0][0] + startOffset;
_loopStack->push(loopData);
playPos = loopData.startPos;
} else if (info.command() == 0xE) {
// Set loop point
_loopPoint = playPos;
} else if (info.command() == 0xF) {
// Return
if (_loopStack->empty()) {
// Global loop: return to the global loop point
playPos = _loopPoint;
} else {
// Subroutine loop
LoopData *loopData = &_loopStack->top();
if (loopData->numLoops > 1) {
// Return to the start of the subroutine data
loopData->numLoops--;
playPos = loopData->startPos;
} else {
// Return to the call position
playPos = loopData->returnPos;
_loopStack->pop();
}
}
} else if (info.command() == 0x8 && info.channel() == 0x3) {
// Load instrument
if (fireEvents) {
// Send the instrument data as a meta event
sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
}
} else if (fireEvents) {
// Other events are handled by the driver
sendToDriver(info.event, info.basic.param1, info.basic.param2);
}
_position._subtracks[0]._playPos = playPos;
return true;
}
void MidiParser_M::parseNextEvent(EventInfo &info) {
const byte *playPos = _position._subtracks[0]._playPos;
assert(playPos >= _tracks[0][0]);
assert(playPos - _tracks[0][0] < (int)_trackLength);
info.start = playPos;
info.event = *(playPos++);
info.delta = 0;
info.basic.param1 = 0;
info.basic.param2 = 0;
info.noop = false;
info.loop = false;
switch (info.command()) {
case 0x0: // Note off
case 0x1: // Note on
case 0x2: // Set pitch
case 0x3: // Set level
case 0x4: // Set modulation
case 0x5: // Set slide
case 0x6: // Set vibrato
case 0x7: // Program change
// These commands all have 1 data byte.
info.basic.param1 = *(playPos++);
break;
case 0x8: // Subcommand
switch (info.channel()) {
case 0x1: // Call subroutine
// This command specifies the number of loops (1 byte) and an
// offset in the M data to jump to (2 bytes).
info.ext.type = info.channel();
info.length = 3;
info.ext.data = playPos;
playPos += info.length;
break;
case 0x2: // Delay
// This command is used to specify a delta time between the
// previous and the next event. It does nothing otherwise.
info.delta = *(playPos++);
info.noop = true;
break;
case 0x3: // Load instrument
// This command specifies the instrument bank slot in which the
// instrument should be loaded (1 byte) plus an OPL instrument
// definition (11 bytes).
info.ext.type = info.channel();
info.length = 12;
info.ext.data = playPos;
playPos += info.length;
break;
case 0x5: // Fade out
case 0x6: // Fade in
// These commands have 1 data byte.
info.basic.param1 = *(playPos++);
break;
default:
info.noop = true;
break;
}
break;
case 0xE: // Set loop point
// This command does not have any data bytes.
break;
case 0xF: // Return
// This command does not have any data bytes.
info.loop = true;
break;
default:
info.noop = true;
break;
}
_position._subtracks[0]._playPos = playPos;
}
void MidiParser_M::allNotesOff() {
if (_driver) {
_driver->stopAllNotes();
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_MIDIPARSER_M_H
#define NUVIE_SOUND_MIDIPARSER_M_H
#include "audio/midiparser.h"
#include "common/stack.h"
#include "common/util.h"
namespace Ultima {
namespace Nuvie {
/**
* A parser for the music format M, used by Times Of Lore, Bad Blood and
* Ultima 6.
* This format is not really a MIDI format; it targets the OPL2 chip. However,
* it has several things in common with MIDI: it is a stream of events, it has
* note on and note off events and events similar to MIDI controllers, the high
* nibble of the first event byte is the command while the low nibble is
* usually the channel.
* The commands are different. M does not use the status byte / data byte
* convention and delta times are specified using a wait command. It uses
* channels 0-8, corresponding to the 9 OPL2 channels. OPL rhythm mode is not
* used.
*/
class MidiParser_M : public MidiParser {
protected:
struct LoopData {
byte numLoops;
const byte *startPos;
const byte *returnPos;
};
public:
MidiParser_M(int8 source = -1);
~MidiParser_M();
bool loadMusic(const byte *data, uint32 size) override;
void unloadMusic() override;
void onTimer() override;
protected:
bool processEvent(const EventInfo &info, bool fireEvents = true) override;
void parseNextEvent(EventInfo &info) override;
void allNotesOff() override;
uint32 _trackLength;
// The point in the MIDI data where the global loop (not using the stack)
// has started and will return.
const byte *_loopPoint;
// A stack of nested loops, similar to a call stack. A call command will
// specify an offset where the parser should jump to (startPus), plus a
// number of times the data from this offset up to the return command should
// be repeated (numLoops). Then the parser resumes with the command after
// the call command (returnPos). A maximum depth of 16 levels is supported.
Common::FixedStack<LoopData, 16> *_loopStack;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,533 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/sound/adplug/opl.h"
#include "ultima/nuvie/sound/origin_fx_adib_driver.h"
namespace Ultima {
namespace Nuvie {
const uint8 adlib_BD_cmd_tbl[] = { 0, 1, 0, 1, 0, 1, 16, 8, 4, 2, 1 };
OriginFXAdLibDriver::OriginFXAdLibDriver(const Configuration *cfg, Copl *newopl) {
const uint8 byte_73_init[] = {1, 2, 3, 4, 5, 6, 7, 8, 0xB, 0xFF, 0xFF, 0, 0xC};
config = cfg;
opl = newopl;
adlib_tim_data = nullptr;
adlib_num_active_channels = 9;
memset(midi_chan_tim_ptr, 0, sizeof(midi_chan_tim_ptr));
memset(midi_chan_pitch, 0, sizeof(midi_chan_pitch));
memset(midi_chan_tim_off_10, 0, sizeof(midi_chan_tim_off_10));
memset(midi_chan_tim_off_11, 0, sizeof(midi_chan_tim_off_11));
adlib_bd_status = 0;
memcpy(byte_73, byte_73_init, sizeof(byte_73_init));
for (int i = 0; i < 29; i++) {
midi_chan_volume[i] = 0x100;
}
memset(adlib_ins, 0, sizeof(adlib_ins));
for (int i = 0; i < 11; i++) {
adlib_ins[i].note = -1;
adlib_ins[i].channel = -1;
adlib_ins[i].byte_68 = 1;
}
load_tim_file();
init();
}
OriginFXAdLibDriver::~OriginFXAdLibDriver() {
if (adlib_tim_data)
delete [] adlib_tim_data;
}
void OriginFXAdLibDriver::init() {
opl->init();
for (int i = 0; i < 256; i++) {
midi_write_adlib(i, 0);
}
midi_write_adlib(0x01, 0x20);
midi_write_adlib(0xBD, 0);
midi_write_adlib(0x8, 0);
}
sint16 OriginFXAdLibDriver::read_sint16(unsigned char *buf) {
return (buf[1] << 8) | buf[0];
}
void OriginFXAdLibDriver::load_tim_file() {
U6Lib_n f;
Common::Path filename;
nuvie_game_t game_type = get_game_type(config);
if (game_type == NUVIE_GAME_SE) {
config_get_path(config, "savage.tim", filename);
} else { // game_type == NUVIE_GAME_MD
config_get_path(config, "md.tim", filename);
}
f.open(filename, 4, game_type);
unsigned char *buf = f.get_item(1);
adlib_tim_data = new unsigned char [f.get_item_size(1) - 1];
num_tim_records = buf[0];
memcpy(adlib_tim_data, &buf[1], f.get_item_size(1) - 1);
free(buf);
for (int i = 0; i < 32; i++) {
midi_chan_tim_ptr[i] = adlib_tim_data;
}
program_change(0x9 , 0x80);
program_change(0xa , 0x72);
program_change(0xb , 0x83);
program_change(0xc , 0x71);
program_change(0xd , 0x86);
program_change(0xe , 0x87);
program_change(0xf , 0x85);
program_change(0x10 , 0x84);
program_change(0x11 , 0x81);
program_change(0x12 , 0x88);
program_change(0x13 , 0x8D);
program_change(0x14 , 0x8F);
program_change(0x15 , 0x90);
program_change(0x16 , 0x91);
program_change(0x17 , 0x93);
program_change(0x18 , 0x8C);
program_change(0x19 , 0x8B);
}
unsigned char *OriginFXAdLibDriver::get_tim_data(uint8 program_number) {
for (int i = 0; i < num_tim_records; i++) {
if (adlib_tim_data[i * 48 + 0x2f] == program_number) {
return &adlib_tim_data[i * 48];
}
}
return adlib_tim_data;
}
void OriginFXAdLibDriver::midi_write_adlib(unsigned int r, unsigned char v) {
opl->write(r, v);
}
void OriginFXAdLibDriver::program_change(sint8 channel, uint8 program_number) {
unsigned char *tim_data = get_tim_data(program_number);
int i, j;
debug("Program change channel: %d program: %d", channel, program_number);
for (i = 0; i < 11; i++) {
if (adlib_ins[i].channel == channel) {
play_note(channel, adlib_ins[i].note, 0); //note off.
adlib_ins[i].channel = -1;
adlib_ins[i].tim_data = nullptr;
}
}
midi_chan_tim_ptr[channel] = tim_data;
midi_chan_tim_off_10[channel] = tim_data[0x10];
midi_chan_tim_off_11[channel] = tim_data[0x11];
if (tim_data[0xb] != 0 && adlib_num_active_channels == 9) {
midi_write_adlib(0xa6, 0);
midi_write_adlib(0xb6, 0);
midi_write_adlib(0xa7, 0);
midi_write_adlib(0xb7, 0xa);
midi_write_adlib(0xa8, 0x54);
midi_write_adlib(0xb8, 0x9);
adlib_num_active_channels = 6;
for (i = 6; i < 9; i++) {
for (j = 0; j < 13; j++) {
if (byte_73[j] == i) {
byte_73[j] = byte_73[i];
byte_73[i] = (uint8)-1;
break;
}
}
}
adlib_bd_status = 0x20;
midi_write_adlib(0xbd, adlib_bd_status);
}
}
void OriginFXAdLibDriver::pitch_bend(uint8 channel, uint8 pitch_lsb, uint8 pitch_msb) {
unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];
midi_chan_pitch[channel] = ((sint16)((pitch_msb << 7) + pitch_lsb - 8192) * cur_tim_ptr[0xe]) / 256;
debug("pitch_bend: c=%d, pitch=%d %d,%d,%d", channel, midi_chan_pitch[channel], pitch_msb, pitch_lsb, cur_tim_ptr[0xe]);
for (int i = 0; i < adlib_num_active_channels; i++) {
if (adlib_ins[i].byte_68 > 1 && adlib_ins[i].channel == channel) {
sint16 var_4 = 0;
if (adlib_ins[i].tim_data != nullptr) {
var_4 = read_sint16(&adlib_ins[i].tim_data[0x24]);
}
uint16 var_2 = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_4);
var_2 += 0x2000;
midi_write_adlib(0xa0 + i, var_2 & 0xff);
midi_write_adlib(0xb0 + i, var_2 >> 8);
}
}
}
void OriginFXAdLibDriver::control_mode_change(uint8 channel, uint8 function, uint8 value) {
uint8 c = channel;
debug("control_mode_change: c=%d, func=%2x, value=%d", channel, function, value);
if (c == 9) {
c++;
do {
control_mode_change(c, function, value);
c++;
} while (c <= 25);
c = 9;
}
if (function == 1) {
midi_chan_tim_off_11[channel] = ((((sint16)midi_chan_tim_ptr[channel][0xf]) * value) / 128) + (sint16)midi_chan_tim_ptr[channel][0x11];
} else if (function == 7) {
midi_chan_volume[c] = value + 128;
} else if (function == 0x7b) {
bool var_6 = false;
for (int i = 0; i < 0xb; i++) {
if (adlib_ins[i].byte_68 > 1) {
if (adlib_ins[i].channel == channel) {
play_note(channel, adlib_ins[i].note, 0); //note off
} else if (i >= adlib_num_active_channels) {
var_6 = true;
}
}
}
if (var_6 && adlib_num_active_channels < 9) {
midi_write_adlib(0xbd, 0);
adlib_num_active_channels = 9;
byte_73[6] = 7;
byte_73[7] = 8;
byte_73[8] = byte_73[0xb];
byte_73[0xb] = 6;
}
} else if (function == 0x79) {
control_mode_change(channel, 1, 0);
control_mode_change(channel, 7, 0x7f);
pitch_bend(channel, 0, 0x40);
}
}
void OriginFXAdLibDriver::play_note(uint8 channel, sint8 note, uint8 velocity) {
unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];
for (; cur_tim_ptr != nullptr; cur_tim_ptr += 48) {
sint8 voice = sub_4BF(channel, note, velocity, cur_tim_ptr);
sint16 var_4 = voice;
if (voice > 8) {
var_4 = 0x11 - voice;
}
if (voice >= 0) {
sint16 var_a = read_sint16(&cur_tim_ptr[0x24]);
if (velocity != 0) {
adlib_ins[voice].word_121 = 0;
adlib_ins[voice].byte_137 = 0;
adlib_ins[voice].word_cb = read_sint16(&cur_tim_ptr[0x12]);
}
sint8 cl = cur_tim_ptr[0x27];
if (cl < 0) {
adlib_ins[voice].word_3c = (-((sint16)(note - 60) * 256) / (1 << -(cl + 1))) + 0x3c00;
} else {
adlib_ins[voice].word_3c = (((sint16)(note - 60) * 256) / (1 << cl)) + 0x3c00;
}
uint16 var_2 = sub_60D(adlib_ins[voice].word_3c + midi_chan_pitch[channel] + adlib_ins[voice].word_cb + adlib_ins[voice].word_121 + var_a);
if (velocity == 0) {
if (voice < adlib_num_active_channels || voice <= 6) {
midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
midi_write_adlib(0xb0 + var_4, var_2 >> 8);
} else {
adlib_bd_status &= ~adlib_BD_cmd_tbl[voice];
}
} else {
uint16 var_6 = cur_tim_ptr[6];
if (cur_tim_ptr[0xc] != 0 || midi_chan_volume[channel] < 0x100) {
sint16 di = 0x3f - ((midi_chan_volume[channel] * (0x3f - (((sint16)(63 - velocity) / (sint16)(1 << (7 - cur_tim_ptr[0xc]))) + (var_6 & 0x3f)))) >> 8); //fixme this was 0x14 in dosbox var_6 = 8
di = 63 - velocity;
di = di / (1 << (7 - cur_tim_ptr[0xc]));
di += var_6 & 0x3f;
sint16 ax = (0x3f - di) * midi_chan_volume[channel];
ax = ax / 256;
di = 0x3f - ax;
if (di > 0x3f) {
di = 0x3f;
}
if (di < 0) {
di = 0;
}
midi_write_adlib(0x40 + adlib_voice_op1(voice), (var_6 & 0xc0) + di);
}
var_6 = cur_tim_ptr[1];
if (cur_tim_ptr[0xd] != 0) {
sint16 di = (0x3f - velocity) / (sint16)(1 << (7 - cur_tim_ptr[0xd])) + (var_6 & 0x3f);
if (di > 0x3f) {
di = 0x3f;
}
if (di < 0) {
di = 0;
}
midi_write_adlib(0x40 + adlib_voice_op(voice), (var_6 & 0xc0) + di);
}
if (cur_tim_ptr[0xb] == 0 || voice == 6) {
if (cur_tim_ptr[0xb] == 0) {
var_2 += 0x2000;
}
midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
midi_write_adlib(0xb0 + var_4, var_2 >> 8);
}
if (cur_tim_ptr[0xb] != 0) {
adlib_bd_status |= adlib_BD_cmd_tbl[voice];
}
}
if (cur_tim_ptr[0xb] != 0) {
midi_write_adlib(0xbd, adlib_bd_status);
}
}
if (cur_tim_ptr[0x26] == 0)
break;
}
}
uint16 OriginFXAdLibDriver::sub_60D(sint16 val) {
static const uint16 word_20f[] = {0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2AE, 0x2D7, 0x302, 0x330, 0x360, 0x393, 0x3CA};
sint16 var_2 = val / 256;
sint16 si = ((var_2 + 6) / 0xc) - 2;
if (si > 7) {
si = 7;
}
if (si < 0) {
si = 0;
}
uint16 di = word_20f[(var_2 + 6) % 0xc];
if ((val & 0xff) != 0) {
int offset = ((var_2 - 18) % 0xc) + 1;
// FIXME: This offset is negative near the end of the Savage Empire Origin FX
// intro.. what should it do?
if (offset >= 0)
di += ((word_20f[offset] - di) * (val & 0xff)) / 256;
}
return (si << 10) + di;
}
uint16 OriginFXAdLibDriver::sub_4BF(uint8 channel, uint8 note, uint8 velocity, unsigned char *cur_tim_ptr) {
sint16 si = -1;
if (adlib_num_active_channels >= 9 || cur_tim_ptr[0xb] == 0) {
if (velocity == 0) {
for (si = 0; si < adlib_num_active_channels; si++) {
if (adlib_ins[si].byte_68 > 1 && adlib_ins[si].note == note && adlib_ins[si].channel == channel && adlib_ins[si].tim_data == cur_tim_ptr) {
adlib_ins[si].byte_68 = 0;
sub_45E(si);
sub_48E(si, 0xb);
break;
}
}
if (si == adlib_num_active_channels) {
si = -1;
}
} else {
if (byte_73[0xb] == 0xb) {
if (midi_chan_tim_ptr[channel] == cur_tim_ptr) {
si = byte_73[0xc];
byte_73[0xc] = byte_73[si];
sub_48E(si, 0xc);
midi_write_adlib(0xa0 + si, 0);
midi_write_adlib(0xb0 + si, 0);
}
} else {
si = byte_73[0xb];
byte_73[0xb] = byte_73[si];
sub_48E(si, 0xc);
}
if (si >= 0) {
adlib_ins[si].byte_68 = 2;
adlib_ins[si].note = note;
}
}
} else {
si = cur_tim_ptr[0xb];
adlib_bd_status &= ~adlib_BD_cmd_tbl[cur_tim_ptr[0xb]];
midi_write_adlib(0xbd, adlib_bd_status);
}
if (si >= 0) {
if (adlib_ins[si].channel != channel || adlib_ins[si].tim_data != cur_tim_ptr) { //changing instruments
write_adlib_instrument(si, cur_tim_ptr);
adlib_ins[si].channel = channel;
adlib_ins[si].tim_data = cur_tim_ptr;
}
}
return si;
}
void OriginFXAdLibDriver::sub_45E(sint16 voice) {
for (int i = 0; i < 0xd; i++) {
if (byte_73[i] == voice) {
byte_73[i] = byte_73[voice];
byte_73[voice] = voice;
}
}
}
void OriginFXAdLibDriver::sub_48E(sint16 voice, uint8 val) {
for (int i = 0; i < 0xd; i++) {
if (byte_73[i] == val) {
byte_73[i] = voice;
byte_73[voice] = val;
break;
}
}
}
uint8 OriginFXAdLibDriver::adlib_voice_op(sint8 voice) {
const uint8 opp_tbl[] = {0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12, 0, 1, 2, 8, 9, 0xA, 0x10, 0x14, 0x12, 0x15, 0x11};
return opp_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}
uint8 OriginFXAdLibDriver::adlib_voice_op1(sint8 voice) {
const uint8 opp1_tbl[] = {3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15, 3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x12, 0x15, 0x11};
return opp1_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}
void OriginFXAdLibDriver::write_adlib_instrument(sint8 voice, unsigned char *tim_data) {
uint8 opadd = adlib_voice_op(voice);
uint8 opadd1 = adlib_voice_op1(voice);
unsigned char *cur_tim_ptr = tim_data;
midi_write_adlib(0x20 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x40 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x60 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x80 + opadd, *cur_tim_ptr++);
midi_write_adlib(0xe0 + opadd, *cur_tim_ptr++);
if (adlib_num_active_channels == 9 || tim_data[0xb] < 7) {
midi_write_adlib(0x20 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x40 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x60 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x80 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0xe0 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0xc0 + voice, *cur_tim_ptr++);
}
}
void OriginFXAdLibDriver::interrupt_vector() {
const uint8 byte_229[] = {24, 0, 18, 20, 22, 0, 0, 0};
for (int i = 0; i < adlib_num_active_channels; i++) {
unsigned char *cur_tim_data = nullptr;
bool update_adlib = false;
sint8 channel = adlib_ins[i].channel;
if (channel < 0 || channel >= 32) {
continue;
}
uint8 var_8 = byte_229[adlib_ins[i].byte_68];
sint16 var_10 = 0;
if (adlib_ins[i].tim_data == nullptr) {
cur_tim_data = adlib_tim_data;
} else {
cur_tim_data = adlib_ins[i].tim_data;
var_10 = read_sint16(&cur_tim_data[0x24]);
}
if (var_8 != 0) {
sint16 var_a = read_sint16(&cur_tim_data[var_8 * 2 - 16]);
sint16 var_c = read_sint16(&cur_tim_data[(var_8 + 1) * 2 - 16]);
sint16 tmp = (var_c > adlib_ins[i].word_cb) ? var_c - adlib_ins[i].word_cb : adlib_ins[i].word_cb - var_c;
if (tmp >= var_a) {
if (adlib_ins[i].word_cb >= var_c) {
adlib_ins[i].word_cb -= var_a;
} else {
adlib_ins[i].word_cb += var_a;
}
} else {
adlib_ins[i].word_cb = var_c;
adlib_ins[i].byte_68++;
}
update_adlib = true;
}
if (midi_chan_tim_off_10[channel] != 0) {
adlib_ins[i].byte_137 += midi_chan_tim_off_10[channel];
sint8 var_11 = adlib_ins[i].byte_137;
if (var_11 > 63 || var_11 < -64) {
var_11 = -128 - var_11;
}
adlib_ins[i].word_121 = (midi_chan_tim_off_11[channel] * var_11) / 16;
update_adlib = true;
}
if (update_adlib || var_10 != 0) {
uint16 adlib_cmd_data = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_10);
if (adlib_ins[i].byte_68 > 1) {
adlib_cmd_data += 0x2000;
}
midi_write_adlib(0xa0 + i, adlib_cmd_data & 0xff);
midi_write_adlib(0xb0 + i, adlib_cmd_data >> 8);
}
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,97 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_ORIGIN_FX_ADLIB_DRIVER_H
#define NUVIE_SOUND_ORIGIN_FX_ADLIB_DRIVER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
namespace Ultima {
namespace Nuvie {
class Copl;
class Configuration;
class OriginFXAdLibDriver {
public:
OriginFXAdLibDriver(const Configuration *cfg, Copl *newopl);
~OriginFXAdLibDriver();
private:
const Configuration *config;
Copl *opl;
unsigned char num_tim_records;
unsigned char *adlib_tim_data;
struct adlib_instrument {
sint8 channel;
sint8 note;
uint8 byte_68;
sint16 word_121;
uint8 byte_137;
sint16 word_cb;
sint16 word_3c;
unsigned char *tim_data;
};
adlib_instrument adlib_ins[11];
int adlib_num_active_channels; //either 6 or 9.
unsigned char *midi_chan_tim_ptr[32];
uint8 midi_chan_tim_off_10[32];
sint16 midi_chan_tim_off_11[32];
sint16 midi_chan_pitch[32];
sint16 midi_chan_volume[29];
uint8 byte_73[13];
uint8 adlib_bd_status;
public:
void init();
void play_note(uint8 channel, sint8 note, uint8 velocity);
void control_mode_change(uint8 channel, uint8 function, uint8 value);
void program_change(sint8 channel, uint8 program_number);
void pitch_bend(uint8 channel, uint8 pitch_lsb, uint8 pitch_msb);
void interrupt_vector();
private:
sint16 read_sint16(unsigned char *buf);
void midi_write_adlib(unsigned int r, unsigned char v);
void load_tim_file();
unsigned char *get_tim_data(uint8 program_number);
uint8 adlib_voice_op(sint8 voice);
uint8 adlib_voice_op1(sint8 voice);
uint16 sub_60D(sint16 val);
uint16 sub_4BF(uint8 channel, uint8 note, uint8 velocity, unsigned char *cur_tim_ptr);
void sub_45E(sint16 voice);
void sub_48E(sint16 voice, uint8 val);
void write_adlib_instrument(sint8 voice, unsigned char *tim_data);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,121 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/pc_speaker_sfx_manager.h"
#include "ultima/nuvie/sound/decoder/adlib_sfx_stream.h"
#include "ultima/nuvie/sound/decoder/fm_towns_decoder_stream.h"
#include "ultima/nuvie/sound/decoder/pc_speaker_stream.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
PCSpeakerSfxManager::PCSpeakerSfxManager(const Configuration *cfg, Audio::Mixer *m) : SfxManager(cfg, m) {
}
PCSpeakerSfxManager::~PCSpeakerSfxManager() {
}
bool PCSpeakerSfxManager::playSfx(SfxIdType sfx_id, uint8 volume) {
return playSfxLooping(sfx_id, nullptr, volume);
}
bool PCSpeakerSfxManager::playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) {
Audio::AudioStream *stream = nullptr;
if (sfx_id == NUVIE_SFX_BLOCKED) {
stream = new PCSpeakerFreqStream(311, 0xa);
} else if (sfx_id == NUVIE_SFX_SUCCESS) {
stream = new PCSpeakerFreqStream(2000, 0xa);
} else if (sfx_id == NUVIE_SFX_FAILURE) {
stream = new PCSpeakerSweepFreqStream(800, 2000, 50, 1);
} else if (sfx_id == NUVIE_SFX_ATTACK_SWING) {
stream = new PCSpeakerSweepFreqStream(400, 750, 150, 5);
} else if (sfx_id == NUVIE_SFX_RUBBER_DUCK) {
stream = new PCSpeakerSweepFreqStream(5000, 8000, 50, 1);
} else if (sfx_id == NUVIE_SFX_HIT) {
stream = new PCSpeakerRandomStream(0x2710, 0x320, 1);
} else if (sfx_id == NUVIE_SFX_BROKEN_GLASS) {
stream = makePCSpeakerGlassSfxStream(mixer->getOutputRate());
} else if (sfx_id == NUVIE_SFX_CORPSER_DRAGGED_UNDER) {
stream = new PCSpeakerSweepFreqStream(1200, 2000, 40, 1);
} else if (sfx_id == NUVIE_SFX_CORPSER_REGURGITATE) {
stream = new PCSpeakerRandomStream(0x258, 0x1b58, 1);
} else if (sfx_id >= NUVIE_SFX_CASTING_MAGIC_P1 && sfx_id <= NUVIE_SFX_CASTING_MAGIC_P1_8) {
uint8 magic_circle = sfx_id - NUVIE_SFX_CASTING_MAGIC_P1 + 1;
stream = makePCSpeakerMagicCastingP1SfxStream(mixer->getOutputRate(), magic_circle);
} else if (sfx_id >= NUVIE_SFX_CASTING_MAGIC_P2 && sfx_id <= NUVIE_SFX_CASTING_MAGIC_P2_8) {
uint8 magic_circle = sfx_id - NUVIE_SFX_CASTING_MAGIC_P2 + 1;
stream = makePCSpeakerMagicCastingP2SfxStream(mixer->getOutputRate(), magic_circle);
} else if (sfx_id == NUVIE_SFX_BELL) {
stream = new PCSpeakerStutterStream(-1, 0x4e20, 0x3e80, 1, 0x7d0);
} else if (sfx_id == NUVIE_SFX_AVATAR_DEATH) {
stream = makePCSpeakerAvatarDeathSfxStream(mixer->getOutputRate());
} else if (sfx_id == NUVIE_SFX_KAL_LOR) {
stream = makePCSpeakerKalLorSfxStream(mixer->getOutputRate());
} else if (sfx_id == NUVIE_SFX_SLUG_DISSOLVE) {
stream = makePCSpeakerSlugDissolveSfxStream(mixer->getOutputRate());
} else if (sfx_id == NUVIE_SFX_HAIL_STONE) {
stream = makePCSpeakerHailStoneSfxStream(mixer->getOutputRate());
} else if (sfx_id == NUVIE_SFX_EARTH_QUAKE) {
stream = makePCSpeakerEarthQuakeSfxStream(mixer->getOutputRate());
}
if (stream) {
sfx_duration = 0;
AdLibSfxStream *adlibStream = dynamic_cast<AdLibSfxStream *>(stream);
if (adlibStream)
sfx_duration = adlibStream->getLengthInMsec();
FMtownsDecoderStream *fmStream = dynamic_cast<FMtownsDecoderStream *>(stream);
if (fmStream)
sfx_duration = fmStream->getLengthInMsec();
PCSpeakerFreqStream *pcStream = dynamic_cast<PCSpeakerFreqStream *>(stream);
if (pcStream)
sfx_duration = pcStream->getLengthInMsec();
playSoundSample(stream, handle, volume);
return true;
}
return false;
}
void PCSpeakerSfxManager::playSoundSample(Audio::AudioStream *stream, Audio::SoundHandle *looping_handle, uint8 volume) {
Audio::SoundHandle handle;
if (looping_handle) {
Audio::RewindableAudioStream *rwStream = dynamic_cast<Audio::RewindableAudioStream *>(stream);
Audio::LoopingAudioStream *looping_stream = new Audio::LoopingAudioStream(rwStream, 0);
mixer->playStream(Audio::Mixer::kPlainSoundType, looping_handle, looping_stream, -1, volume);
} else {
mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, stream, -1, volume);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,50 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_PC_SPEAKER_SFX_MANAGER_H
#define NUVIE_SOUND_PC_SPEAKER_SFX_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "audio/mixer.h"
#include "ultima/nuvie/sound/sfx_manager.h"
#include "audio/audiostream.h"
namespace Ultima {
namespace Nuvie {
class PCSpeakerSfxManager : public SfxManager {
public:
PCSpeakerSfxManager(const Configuration *cfg, Audio::Mixer *m);
~PCSpeakerSfxManager() override;
bool playSfx(SfxIdType sfx_id, uint8 volume) override;
bool playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) override;
private:
void playSoundSample(Audio::AudioStream *stream, Audio::SoundHandle *looping_handle, uint8 volume);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,80 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SFX_H
#define NUVIE_SOUND_SFX_H
namespace Ultima {
namespace Nuvie {
typedef uint16 SfxIdType;
#define NUVIE_SFX_NONE 65535
#define NUVIE_SFX_BLOCKED 0
#define NUVIE_SFX_HIT 1
#define NUVIE_SFX_FOUNTAIN 2
#define NUVIE_SFX_DEATH 3
#define NUVIE_SFX_RUBBER_DUCK 4
#define NUVIE_SFX_BROKEN_GLASS 5
#define NUVIE_SFX_BELL 6
#define NUVIE_SFX_FIRE 7
#define NUVIE_SFX_CLOCK 8
#define NUVIE_SFX_PROTECTION_FIELD 9
#define NUVIE_SFX_WATER_WHEEL 10
#define NUVIE_SFX_MISSLE 11
#define NUVIE_SFX_EXPLOSION 12
#define NUVIE_SFX_ATTACK_SWING 13
#define NUVIE_SFX_SUCCESS 14
#define NUVIE_SFX_FAILURE 15
#define NUVIE_SFX_CORPSER_DRAGGED_UNDER 16
#define NUVIE_SFX_CORPSER_REGURGITATE 17
#define NUVIE_SFX_CASTING_MAGIC_P1 18
#define NUVIE_SFX_CASTING_MAGIC_P1_2 19
#define NUVIE_SFX_CASTING_MAGIC_P1_3 20
#define NUVIE_SFX_CASTING_MAGIC_P1_4 21
#define NUVIE_SFX_CASTING_MAGIC_P1_5 22
#define NUVIE_SFX_CASTING_MAGIC_P1_6 23
#define NUVIE_SFX_CASTING_MAGIC_P1_7 24
#define NUVIE_SFX_CASTING_MAGIC_P1_8 25
#define NUVIE_SFX_CASTING_MAGIC_P2 26
#define NUVIE_SFX_CASTING_MAGIC_P2_2 27
#define NUVIE_SFX_CASTING_MAGIC_P2_3 28
#define NUVIE_SFX_CASTING_MAGIC_P2_4 29
#define NUVIE_SFX_CASTING_MAGIC_P2_5 30
#define NUVIE_SFX_CASTING_MAGIC_P2_6 31
#define NUVIE_SFX_CASTING_MAGIC_P2_7 32
#define NUVIE_SFX_CASTING_MAGIC_P2_8 33
#define NUVIE_SFX_AVATAR_DEATH 34
#define NUVIE_SFX_KAL_LOR 35
#define NUVIE_SFX_SLUG_DISSOLVE 36
#define NUVIE_SFX_HAIL_STONE 37
#define NUVIE_SFX_SE_TICK 38
#define NUVIE_SFX_EARTH_QUAKE 39
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SFX_MANAGER_H
#define NUVIE_SOUND_SFX_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "audio/mixer.h"
#include "ultima/nuvie/sound/sfx.h"
namespace Ultima {
namespace Nuvie {
class SfxManager {
public:
SfxManager(const Configuration *cfg, Audio::Mixer *m) : config(cfg), mixer(m) {
sfx_duration = 0;
};
virtual ~SfxManager() {};
virtual bool playSfx(SfxIdType sfx_id, uint8 volume) = 0;
virtual bool playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) = 0;
uint32 getLastSfxDuration() {
return sfx_duration;
}
protected:
const Configuration *config;
Audio::Mixer *mixer;
uint32 sfx_duration; //duration of the last sfx played in milliseconds.
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//Mix_HookMusicFinished
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/song.h"
/*
Song::Song() {
m_Filename="";
m_pMusic=nullptr;
// m_Paused=false;
}
Song::~Song() {
if (m_pMusic!=nullptr) {
Mix_HaltMusic();
Mix_FreeMusic(m_pMusic);
m_pMusic=nullptr;
}
}
*/

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SONG_H
#define NUVIE_SOUND_SONG_H
#include "ultima/nuvie/sound/sound.h"
namespace Ultima {
namespace Nuvie {
class Song : public Sound {
public:
virtual bool Init(const Common::Path &path, const char *fileId) {
return false;
}
virtual bool Init(const Common::Path &path, const char *fileId, uint16 songnum) {
return false;
}
bool Play(bool looping = false) override {
return false;
}
bool Stop() override {
return false;
}
bool SetVolume(uint8 volume) override {
return false;
}
bool FadeOut(float seconds) override {
return false;
}
void SetTitle(const char *title) {
if (title) m_Title = title; // SB-X
}
private:
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,79 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//Mix_HookMusicFinished
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/adplug/u6m.h"
#include "ultima/nuvie/sound/song_adplug.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/nuvie.h"
namespace Ultima {
namespace Nuvie {
SongAdPlug::SongAdPlug(Audio::Mixer *m, CEmuopl *o) {
mixer = m;
opl = o;
samples_left = 0;
stream = nullptr;
}
SongAdPlug::~SongAdPlug() {
}
bool SongAdPlug::Init(const Common::Path &filename, const char *fileId, uint16 song_num) {
if (filename == nullptr)
return false;
m_Filename = filename.toString('/'); // SB-X
stream = new U6AdPlugDecoderStream(opl, filename, song_num);
return true;
}
bool SongAdPlug::Play(bool looping) {
// Just in case song is already playing, stop it
Stop();
// Tell the mixer to play the stream
if (stream) {
byte volume = g_engine->getSoundManager()->get_music_volume();
mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, stream, -1, volume, 0, DisposeAfterUse::NO);
}
return true;
}
bool SongAdPlug::Stop() {
mixer->stopHandle(handle);
stream->rewind();
return true;
}
bool SongAdPlug::SetVolume(uint8 volume) {
mixer->setChannelVolume(handle, volume);
return true;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,66 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SONG_ADPLUG_H
#define NUVIE_SOUND_SONG_ADPLUG_H
#include "audio/mixer.h"
#include "ultima/nuvie/sound/song.h"
#include "ultima/nuvie/sound/decoder/u6_adplug_decoder_stream.h"
namespace Ultima {
namespace Nuvie {
class CEmuopl;
class SongAdPlug : public Song {
public:
uint16 samples_left;
SongAdPlug(Audio::Mixer *m, CEmuopl *o);
~SongAdPlug() override;
bool Init(const Common::Path &path, const char *fileId) override {
return Init(path, fileId, 0);
}
bool Init(const Common::Path &path, const char *fileId, uint16 song_num) override;
bool Play(bool looping = false) override;
bool Stop() override;
bool SetVolume(uint8 volume) override;
bool FadeOut(float seconds) override {
return false;
}
CEmuopl *get_opl() {
return opl;
};
private:
Audio::Mixer *mixer;
CEmuopl *opl;
U6AdPlugDecoderStream *stream;
Audio::SoundHandle handle;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,66 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/adplug/u6m.h"
#include "ultima/nuvie/sound/song_filename.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/nuvie.h"
namespace Ultima {
namespace Nuvie {
SongFilename::~SongFilename() {
}
bool SongFilename::Init(const Common::Path &path, const char *fileId) {
return Init(path, fileId, 0);
}
bool SongFilename::Init(const Common::Path &filename, const char *fileId, uint16 song_num) {
if (filename.empty())
return false;
m_Filename = filename.toString(); // SB-X
m_FileId = fileId;
return true;
}
bool SongFilename::Play(bool looping) {
return true;
}
bool SongFilename::Stop() {
return true;
}
bool SongFilename::SetVolume(uint8 volume) {
return true;
}
bool SongFilename::FadeOut(float seconds) {
return false;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,44 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SONG_FILENAME_H
#define NUVIE_SOUND_SONG_FILENAME_H
#include "ultima/nuvie/sound/song.h"
namespace Ultima {
namespace Nuvie {
class SongFilename : public Song {
public:
~SongFilename() override;
bool Init(const Common::Path &path, const char *fileId) override;
bool Init(const Common::Path &path, const char *fileId, uint16 song_num) override;
bool Play(bool looping = false) override;
bool Stop() override;
bool SetVolume(uint8 volume) override;
bool FadeOut(float seconds) override;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,72 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SOUND_H
#define NUVIE_SOUND_SOUND_H
#include "ultima/nuvie/core/game.h"
#include "ultima/shared/std/string.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Nuvie {
using Std::string;
using Std::list;
using Std::vector;
class Sound {
public:
virtual ~Sound() {};
virtual bool Play(bool looping = false) = 0;
virtual bool Stop() = 0;
virtual bool FadeOut(float seconds) = 0;
virtual bool SetVolume(uint8 volume) = 0; //range 0..255
const string &GetName() const {
return m_Filename;
}
string GetTitle() {
return m_Title;
}
string GetId() {
return m_FileId;
}
protected:
// TODO: determine if filename should be a Common::Path
string m_Filename;
string m_Title;
string m_FileId;
// static SoundManager *gpSM;
};
class SoundCollection {
public:
Sound *Select() {
int i = NUVIE_RAND() % m_Sounds.size();
return m_Sounds[i];
}; //randomly select one from the list
vector<Sound *> m_Sounds;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,902 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/core/u6_objects.h"
#include "ultima/nuvie/sound/sound_manager.h"
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/song_filename.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/player.h"
#include "ultima/nuvie/gui/widgets/map_window.h"
#include "ultima/nuvie/core/effect.h"
#include "ultima/nuvie/sound/adplug/emu_opl.h"
#include "ultima/nuvie/sound/adlib_sfx_manager.h"
#include "ultima/nuvie/sound/pc_speaker_sfx_manager.h"
#include "ultima/nuvie/sound/towns_sfx_manager.h"
#include "ultima/nuvie/sound/custom_sfx_manager.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "audio/mixer.h"
#include "common/algorithm.h"
#include "common/config-manager.h"
namespace Ultima {
namespace Nuvie {
struct ObjSfxLookup { // obj sfx lookup
uint16 obj_n;
SfxIdType sfx_id;
};
#define SOUNDMANANGER_OBJSFX_TBL_SIZE 5
static const ObjSfxLookup u6_obj_lookup_tbl[] = {
{OBJ_U6_FOUNTAIN, NUVIE_SFX_FOUNTAIN},
{OBJ_U6_FIREPLACE, NUVIE_SFX_FIRE},
{OBJ_U6_CLOCK, NUVIE_SFX_CLOCK},
{OBJ_U6_PROTECTION_FIELD, NUVIE_SFX_PROTECTION_FIELD},
{OBJ_U6_WATER_WHEEL, NUVIE_SFX_WATER_WHEEL}
};
// This is the default MT-32 instrument mapping specified in MIDI.DAT
const SoundManager::SongMT32InstrumentMapping SoundManager::DEFAULT_MT32_INSTRUMENT_MAPPING[12] = {
{'1', "ultima.m", {{1, 25}, {2, 50}, {3, 24}, {4, 27}, {-1, 0}}},
{'2', "bootup.m", {{1, 37}, {2, 38}, {-1, 0}}},
{'3', "intro.m", {{1, 61}, {2, 60}, {3, 55}, {4, 117}, {5, 117}, {-1, 0}}},
{'4', "create.m", {{1, 6}, {2, 1}, {3, 33}, {-1, 0}}},
{'5', "forest.m", {{1, 59}, {2, 60}, {-1, 0}}},
{'6', "hornpipe.m", {{1, 87}, {2, 60}, {3, 59}, {-1, 0}}},
{'7', "engage.m", {{1, 49}, {2, 26}, {3, 18}, {4, 16}, {-1, 0}}},
{'8', "stones.m", {{1, 6}, {2, 32}, {-1, 0}}},
{'9', "dungeon.m", {{1, 37}, {2, 113}, {3, 55}, {-1, 0}}},
{'0', "brit.m", {{1, 12}, {-1, 0}}},
{'-', "gargoyle.m", {{1, 38}, {2, 5}, {-1, 0}}},
{'=', "end.m", {{1, 38}, {2, 12}, {3, 50}, {4, 94}, {-1, 0}}}
};
bool SoundManager::g_MusicFinished;
void musicFinished() {
SoundManager::g_MusicFinished = true;
}
SoundManager::SoundManager(Audio::Mixer *mixer) : _mixer(mixer),
m_pCurrentSong(nullptr), audio_enabled(false), music_enabled(false),
sfx_enabled(false), m_Config(nullptr), m_SfxManager(nullptr), opl(nullptr),
stop_music_on_group_change(true), speech_enabled(true), music_volume(0),
sfx_volume(0), game_type(NUVIE_GAME_NONE), _midiDriver(nullptr),
_mt32MidiDriver(nullptr), _midiParser(nullptr), _deviceType(MT_NULL),
_musicData(nullptr), _mt32InstrumentMapping(nullptr) {
m_CurrentGroup = "";
g_MusicFinished = true;
}
SoundManager::~SoundManager() {
// Stop all mixing
_mixer->stopAll();
musicPause();
if (_midiDriver) {
_midiDriver->setTimerCallback(nullptr, nullptr);
_midiDriver->close();
}
Common::StackLock lock(_musicMutex);
if (_midiParser) {
delete _midiParser;
}
if (_midiDriver) {
delete _midiDriver;
}
//thanks to wjp for this one
while (!m_Songs.empty()) {
delete *(m_Songs.begin());
m_Songs.erase(m_Songs.begin());
}
while (!m_Samples.empty()) {
delete *(m_Samples.begin());
m_Samples.erase(m_Samples.begin());
}
delete opl;
for (auto &i : m_ObjectSampleMap)
delete i._value;
for (auto &i : m_TileSampleMap)
delete i._value;
for (auto &i : m_MusicMap)
delete i._value;
delete m_SfxManager;
}
bool SoundManager::nuvieStartup(const Configuration *config) {
Std::string config_key;
Std::string music_style;
Std::string music_cfg_file; //full path and filename to music.cfg
Std::string sound_dir;
Std::string sfx_style;
m_Config = config;
m_Config->value("config/GameType", game_type);
m_Config->value("config/audio/stop_music_on_group_change", stop_music_on_group_change, true);
config_key = config_get_game_key(config);
config_key.append("/music");
config->value(config_key, music_style, "native");
config_key = config_get_game_key(config);
config_key.append("/sfx");
config->value(config_key, sfx_style, "native");
config_key = config_get_game_key(config);
config_key.append("/sounddir");
config->value(config_key, sound_dir, "");
if (!initAudio()) {
return false;
}
if (music_style == "native") {
if (game_type == NUVIE_GAME_U6)
LoadNativeU6Songs(); //FIX need to handle MD & SE music too.
} else if (music_style == "custom")
LoadCustomSongs(Common::Path(sound_dir));
else
DEBUG(0, LEVEL_WARNING, "Unknown music style '%s'\n", music_style.c_str());
musicPlayFrom("random");
//LoadObjectSamples(sound_dir);
//LoadTileSamples(sound_dir);
LoadSfxManager(sfx_style);
return true;
}
bool SoundManager::initAudio() {
assert(!_midiDriver);
int devFlags = MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32;
if (game_type == NUVIE_GAME_U6)
// CMS, Tandy and SSI are only supported by Ultima 6 DOS.
devFlags |= MDT_CMS | MDT_PCJR | MDT_C64;
// Check the type of device that the user has configured.
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(devFlags);
_deviceType = MidiDriver::getMusicType(dev);
if (_deviceType == MT_GM && ConfMan.getBool("native_mt32"))
_deviceType = MT_MT32;
switch (_deviceType) {
case MT_ADLIB:
_midiDriver = new MidiDriver_M_AdLib();
break;
case MT_MT32:
case MT_GM:
_midiDriver = _mt32MidiDriver = new MidiDriver_M_MT32();
// TODO Parse MIDI.DAT
_mt32InstrumentMapping = DEFAULT_MT32_INSTRUMENT_MAPPING;
break;
case MT_CMS:
case MT_PCJR:
case MT_C64:
default:
// TODO Implement these
_midiDriver = new MidiDriver_NULL_Multisource();
break;
}
_midiDriver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
// TODO Only Ultima 6 M format is supported.
_midiParser = new MidiParser_M(0);
_midiParser->property(MidiParser::mpDisableAutoStartPlayback, true);
// Open the MIDI driver(s).
int returnCode = _midiDriver->open();
if (returnCode != 0) {
warning("SoundManager::initAudio - Failed to open M music driver - error code %d.", returnCode);
return false;
}
syncSoundSettings();
// Connect the driver and the parser.
_midiParser->setMidiDriver(_midiDriver);
_midiParser->setTimerRate(_midiDriver->getBaseTempo());
_midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback);
//opl = new CEmuopl(_mixer->getOutputRate(), true, true);
return true;
}
bool SoundManager::LoadNativeU6Songs() {
Song *song;
Common::Path filename;
string fileId;
fileId = "brit.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
// loadSong(song, filename);
loadSong(song, filename, fileId.c_str(), "Rule Britannia");
groupAddSong("random", song);
fileId = "forest.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Wanderer (Forest)");
groupAddSong("random", song);
fileId = "stones.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Stones");
groupAddSong("random", song);
fileId = "ultima.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Ultima VI Theme");
groupAddSong("random", song);
fileId = "engage.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Engagement and Melee");
groupAddSong("combat", song);
fileId = "hornpipe.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Captain Johne's Hornpipe");
groupAddSong("boat", song);
fileId = "gargoyle.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Audchar Gargl Zenmur");
groupAddSong("gargoyle", song);
fileId = "dungeon.m";
config_get_path(m_Config, fileId, filename);
song = new SongFilename();
loadSong(song, filename, fileId.c_str(), "Dungeon");
groupAddSong("dungeon", song);
return true;
}
bool SoundManager::LoadCustomSongs(const Common::Path &sound_dir) {
char seps[] = ";\r\n";
char *token1;
char *token2;
char *sz;
NuvieIOFileRead niof;
Song *song;
Common::Path scriptname;
Common::Path filename;
build_path(sound_dir, "music.cfg", scriptname);
if (niof.open(scriptname) == false)
return false;
sz = (char *)niof.readAll();
if (sz == nullptr)
return false;
token1 = strtok(sz, seps);
for (; (token1 != nullptr) && ((token2 = strtok(nullptr, seps)) != nullptr) ; token1 = strtok(nullptr, seps)) {
build_path(sound_dir, token2, filename);
song = (Song *)SongExists(token2);
if (song == nullptr) {
// Note: the base class Song does not have an implementation for
// Init, so loading custom songs does not work.
song = new Song;
if (!loadSong(song, filename, token2))
continue; //error loading song
}
if (groupAddSong(token1, song))
DEBUG(0, LEVEL_DEBUGGING, "%s : %s\n", token1, token2);
}
free(sz);
return true;
}
bool SoundManager::loadSong(Song *song, const Common::Path &filename, const char *fileId) {
if (song->Init(filename, fileId)) {
m_Songs.push_back(song); //add it to our global list
return true;
} else {
DEBUG(0, LEVEL_ERROR, "could not load %s\n", filename.toString().c_str());
}
return false;
}
// (SB-X)
bool SoundManager::loadSong(Song *song, const Common::Path &filename, const char *fileId, const char *title) {
if (loadSong(song, filename, fileId) == true) {
song->SetTitle(title);
return true;
}
return false;
}
bool SoundManager::groupAddSong(const char *group, Song *song) {
if (song != nullptr) {
//we have a valid song
SoundCollection *psc;
Common::HashMap <Common::String, SoundCollection * >::iterator it;
it = m_MusicMap.find(group);
if (it == m_MusicMap.end()) {
//is there already a collection for this entry?
psc = new SoundCollection(); // no, create a new sound collection
psc->m_Sounds.push_back(song); // add this sound to the collection
m_MusicMap[group] = psc; // insert this pair into the map
} else {
psc = (*it)._value; // yes, get the existing
psc->m_Sounds.push_back(song); // add this sound to the collection
}
}
return true;
}
/*
bool SoundManager::LoadObjectSamples (string sound_dir)
{
char seps[] = ";\r\n";
char *token1;
char *token2;
NuvieIOFileRead niof;
char *sz;
string samplename;
string scriptname;
build_path(sound_dir, "obj_samples.cfg", scriptname);
if(niof.open (scriptname) == false)
return false;
sz = (char *) niof.readAll ();
token1 = strtok (sz, seps);
while ((token1 != nullptr) && ((token2 = strtok (nullptr, seps)) != nullptr))
{
int id = atoi (token1);
DEBUG(0,LEVEL_DEBUGGING,"%d : %s\n", id, token2);
Sound *ps;
ps = SampleExists (token2);
if (ps == nullptr)
{
Sample *s;
s = new Sample;
build_path(sound_dir, token2, samplename);
if (!s->Init (samplename.c_str ()))
{
DEBUG(0,LEVEL_ERROR,"could not load %s\n", samplename.c_str ());
}
ps = s;
m_Samples.push_back (ps); //add it to our global list
}
if (ps != nullptr)
{ //we have a valid sound
SoundCollection *psc;
Common::HashMap < int, SoundCollection * >::iterator it;
it = m_ObjectSampleMap.find (id);
if (it == m_ObjectSampleMap.end ())
{ //is there already a collection for this entry?
psc = new SoundCollection; //no, create a new sound collection
psc->m_Sounds.push_back (ps); //add this sound to the collection
m_ObjectSampleMap.insert (Std::make_pair (id, psc)); //insert this pair into the map
}
else
{
psc = (*it).second; //yes, get the existing
psc->m_Sounds.push_back (ps); //add this sound to the collection
}
}
token1 = strtok (nullptr, seps);
}
return true;
};
bool SoundManager::LoadTileSamples (string sound_dir)
{
char seps[] = ";\r\n";
char *token1;
char *token2;
NuvieIOFileRead niof;
char *sz;
string samplename;
string scriptname;
build_path(sound_dir, "tile_samples.cfg", scriptname);
if(niof.open (scriptname) == false)
{
DEBUG(0,LEVEL_ERROR,"opening %s\n",scriptname.c_str());
return false;
}
sz = (char *) niof.readAll ();
token1 = strtok (sz, seps);
while ((token1 != nullptr) && ((token2 = strtok (nullptr, seps)) != nullptr))
{
int id = atoi (token1);
DEBUG(0,LEVEL_DEBUGGING,"%d : %s\n", id, token2);
Sound *ps;
ps = SampleExists (token2);
if (ps == nullptr)
{
Sample *s;
s = new Sample;
build_path(sound_dir, token2, samplename);
if (!s->Init (samplename.c_str ()))
{
DEBUG(0,LEVEL_ERROR,"could not load %s\n", samplename.c_str ());
}
ps = s;
m_Samples.push_back (ps); //add it to our global list
}
if (ps != nullptr)
{ //we have a valid sound
SoundCollection *psc;
Common::HashMap < int, SoundCollection * >::iterator it;
it = m_TileSampleMap.find (id);
if (it == m_TileSampleMap.end ())
{ //is there already a collection for this entry?
psc = new SoundCollection; //no, create a new sound collection
psc->m_Sounds.push_back (ps); //add this sound to the collection
m_TileSampleMap.insert (Std::make_pair (id, psc)); //insert this pair into the map
}
else
{
psc = (*it).second; //yes, get the existing
psc->m_Sounds.push_back (ps); //add this sound to the collection
}
}
token1 = strtok (nullptr, seps);
}
return true;
};
*/
bool SoundManager::LoadSfxManager(string sfx_style) {
if (m_SfxManager != nullptr) {
return false;
}
if (sfx_style == "native") {
switch (game_type) {
case NUVIE_GAME_U6 :
if (has_fmtowns_support(m_Config)) {
sfx_style = "towns";
} else {
sfx_style = "pcspeaker";
}
break;
case NUVIE_GAME_MD :
case NUVIE_GAME_SE :
sfx_style = "adlib";
break;
}
}
if (sfx_style == "pcspeaker") {
m_SfxManager = new PCSpeakerSfxManager(m_Config, _mixer);
}
if (sfx_style == "adlib") {
m_SfxManager = new AdLibSfxManager(m_Config, _mixer);
} else if (sfx_style == "towns") {
m_SfxManager = new TownsSfxManager(m_Config, _mixer);
} else if (sfx_style == "custom") {
m_SfxManager = new CustomSfxManager(m_Config, _mixer);
}
//FIXME what to do if unknown sfx_style is entered in config file.
return true;
}
void SoundManager::musicPlayFrom(string group) {
Common::StackLock lock(_musicMutex);
if (!music_enabled || !audio_enabled)
return;
if (m_CurrentGroup != group) {
if (stop_music_on_group_change)
g_MusicFinished = true;
m_CurrentGroup = group;
}
}
void SoundManager::musicPause() {
Common::StackLock lock(_musicMutex);
if (m_pCurrentSong != nullptr && _midiParser->isPlaying()) {
_midiParser->stopPlaying();
}
}
/* don't call if audio or music is disabled */
void SoundManager::musicPlay() {
Common::StackLock lock(_musicMutex);
if (m_pCurrentSong != nullptr && _midiParser->isPlaying()) {
// Already playing a song.
return;
}
// (SB-X) Get a new song if stopped.
if (m_pCurrentSong == nullptr)
m_pCurrentSong = RequestSong(m_CurrentGroup);
if (m_pCurrentSong != nullptr) {
DEBUG(0, LEVEL_INFORMATIONAL, "assigning new song! '%s'\n", m_pCurrentSong->GetName().c_str());
// TODO Only Ultima 6 LZW format is supported.
uint32 decompressed_filesize;
U6Lzw lzw;
_musicData = lzw.decompress_file(Common::Path(m_pCurrentSong->GetName()), decompressed_filesize);
bool result = _midiParser->loadMusic(_musicData, decompressed_filesize);
if (result) {
_midiDriver->deinitSource(0);
if (_mt32MidiDriver) {
for (int i = 0; i < 12; i++) {
if (!strcmp(m_pCurrentSong->GetId().c_str(), DEFAULT_MT32_INSTRUMENT_MAPPING[i].filename)) {
_mt32MidiDriver->setInstrumentAssignments(DEFAULT_MT32_INSTRUMENT_MAPPING[i].instrumentMapping);
break;
}
}
}
result = _midiParser->startPlaying();
g_MusicFinished = false;
}
if (!result) {
DEBUG(0, LEVEL_ERROR, "play failed!\n");
}
}
}
void SoundManager::musicPlay(const char *filename, uint16 song_num) {
Common::Path path;
if (!music_enabled || !audio_enabled)
return;
config_get_path(m_Config, filename, path);
SongFilename *song = new SongFilename();
song->Init(path, filename, song_num);
Common::StackLock lock(_musicMutex);
musicStop();
m_pCurrentSong = song;
m_CurrentGroup = "";
musicPlay();
}
// (SB-X) Stop the current song so a new song will play when resumed.
void SoundManager::musicStop() {
Common::StackLock lock(_musicMutex);
musicPause();
m_pCurrentSong = nullptr;
if (_musicData) {
free(_musicData);
_musicData = nullptr;
}
}
Std::list < SoundManagerSfx >::iterator SoundManagerSfx_find(Std::list < SoundManagerSfx >::iterator first, Std::list < SoundManagerSfx >::iterator last, const SfxIdType &value) {
for (; first != last; first++) {
if ((*first).sfx_id == value)
break;
}
return first;
}
void SoundManager::update_map_sfx() {
unsigned int i;
uint16 x, y;
uint8 l;
if (sfx_enabled == false)
return;
string next_group = "";
Player *p = Game::get_game()->get_player();
MapWindow *mw = Game::get_game()->get_map_window();
vector < SfxIdType >currentlyActiveSounds;
Common::HashMap < SfxIdType, float >volumeLevels;
p->get_location(&x, &y, &l);
//m_ViewableTiles
//get a list of all the sounds
for (const Obj *obj : mw->m_ViewableObjects) {
//DEBUG(0,LEVEL_DEBUGGING,"%d %s",obj->obj_n,Game::get_game()->get_obj_manager()->get_obj_name(obj));
SfxIdType sfx_id = RequestObjectSfxId(obj->obj_n); //does this object have an associated sound?
if (sfx_id != NUVIE_SFX_NONE) {
//calculate the volume
uint16 ox = obj->x;
uint16 oy = obj->y;
float dist = sqrtf((float)(x - ox) * (x - ox) + (float)(y - oy) * (y - oy));
float vol = (8.0f - dist) / 8.0f;
if (vol < 0)
vol = 0;
//sp->SetVolume(vol);
//need the map to adjust volume according to number of active elements
Common::HashMap < SfxIdType, float >::iterator it;
it = volumeLevels.find(sfx_id);
if (it != volumeLevels.end()) {
if (volumeLevels[sfx_id] < vol)
volumeLevels[sfx_id] = vol;
} else {
volumeLevels[sfx_id] = vol;
}
//add to currently active list
currentlyActiveSounds.push_back(sfx_id);
}
}
/*
for (i = 0; i < mw->m_ViewableTiles.size(); i++)
{
Sound *sp = RequestTileSound (mw->m_ViewableTiles[i].t->tile_num); //does this object have an associated sound?
if (sp != nullptr)
{
//calculate the volume
short ox = mw->m_ViewableTiles[i].x - 5;
short oy = mw->m_ViewableTiles[i].y - 5;
// DEBUG(0,LEVEL_DEBUGGING,"%d %d\n",ox,oy);
float dist = sqrtf ((float) (ox) * (ox) + (float) (oy) * (oy));
// DEBUG(0,LEVEL_DEBUGGING,"%s %f\n",sp->GetName().c_str(),dist);
float vol = (7.0f - (dist - 1)) / 7.0f;
if (vol < 0)
vol = 0;
//sp->SetVolume(vol);
//need the map to adjust volume according to number of active elements
Common::HashMap < Sound *, float >::iterator it;
it = volumeLevels.find (sp);
if (it != volumeLevels.end ())
{
float old = volumeLevels[sp];
// DEBUG(0,LEVEL_DEBUGGING,"old:%f new:%f\n",old,vol);
if (old < vol)
{
volumeLevels[sp] = vol;
}
}
else
{
volumeLevels.insert (Std::make_pair (sp, vol));
}
//add to currently active list
currentlyActiveSounds.push_back (sp);
}
}
*/
//DEBUG(1,LEVEL_DEBUGGING,"\n");
//is this sound new? - activate it.
for (i = 0; i < currentlyActiveSounds.size(); i++) {
Std::list < SoundManagerSfx >::iterator it;
it = SoundManagerSfx_find(m_ActiveSounds.begin(), m_ActiveSounds.end(), currentlyActiveSounds[i]); //is the sound already active?
if (it == m_ActiveSounds.end()) {
//this is a new sound, add it to the active list
//currentlyActiveSounds[i]->Play (true);
//currentlyActiveSounds[i]->SetVolume (0);
SoundManagerSfx sfx;
sfx.sfx_id = currentlyActiveSounds[i];
if (m_SfxManager->playSfxLooping(sfx.sfx_id, &sfx.handle, 0)) {
m_ActiveSounds.push_back(sfx);//currentlyActiveSounds[i]);
}
}
}
//is this sound old? - deactivate it
Std::list < SoundManagerSfx >::iterator it;
it = m_ActiveSounds.begin();
while (it != m_ActiveSounds.end()) {
Std::vector<SfxIdType>::iterator fit;
SoundManagerSfx sfx = (*it);
fit = Common::find(currentlyActiveSounds.begin(), currentlyActiveSounds.end(), sfx.sfx_id); //is the sound in the new active list?
if (fit == currentlyActiveSounds.end()) {
//its not, stop this sound from playing.
//sfx_id->Stop ();
_mixer->stopHandle(sfx.handle);
it = m_ActiveSounds.erase(it);
} else {
_mixer->setChannelVolume(sfx.handle, (uint8)(volumeLevels[sfx.sfx_id] * (sfx_volume / 255.0f) * 255.0f));
it++;
}
}
}
void SoundManager::update() {
if (music_enabled && audio_enabled && g_MusicFinished) {
Common::StackLock lock(_musicMutex);
musicStop();
musicPlay();
}
}
Sound *SoundManager::SongExists(const string &name) {
for (Sound *song : m_Songs) {
if (song->GetName() == name)
return song;
}
return nullptr;
}
Sound *SoundManager::SampleExists(const string &name) {
for (Sound *sample : m_Samples) {
if (sample->GetName() == name)
return sample;
}
return nullptr;
}
Sound *SoundManager::RequestTileSound(int id) {
Common::HashMap < int, SoundCollection * >::iterator it;
it = m_TileSampleMap.find(id);
if (it != m_TileSampleMap.end()) {
SoundCollection *psc;
psc = (*it)._value;
return psc->Select();
}
return nullptr;
}
Sound *SoundManager::RequestObjectSound(int id) {
Common::HashMap < int, SoundCollection * >::iterator it;
it = m_ObjectSampleMap.find(id);
if (it != m_ObjectSampleMap.end()) {
SoundCollection *psc;
psc = (*it)._value;
return psc->Select();
}
return nullptr;
}
uint16 SoundManager::RequestObjectSfxId(uint16 obj_n) {
uint16 i = 0;
for (i = 0; i < SOUNDMANANGER_OBJSFX_TBL_SIZE; i++) {
if (u6_obj_lookup_tbl[i].obj_n == obj_n) {
return u6_obj_lookup_tbl[i].sfx_id;
}
}
return NUVIE_SFX_NONE;
}
Sound *SoundManager::RequestSong(const string &group) {
Common::HashMap<Common::String, SoundCollection * >::iterator it;
it = m_MusicMap.find(group);
if (it != m_MusicMap.end()) {
SoundCollection *psc;
psc = (*it)._value;
return psc->Select();
}
return nullptr;
}
Audio::SoundHandle SoundManager::playTownsSound(const Common::Path &filename, uint16 sample_num) {
FMtownsDecoderStream *stream = new FMtownsDecoderStream(filename, sample_num);
Audio::SoundHandle handle;
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &handle, stream, -1);
return handle;
}
bool SoundManager::isSoundPLaying(Audio::SoundHandle handle) {
return _mixer->isSoundHandleActive(handle);
}
bool SoundManager::playSfx(uint16 sfx_id, bool async) {
if (m_SfxManager == nullptr || audio_enabled == false || sfx_enabled == false)
return false;
if (async) {
if (m_SfxManager->playSfx(sfx_id, sfx_volume)) {
uint32 duration = m_SfxManager->getLastSfxDuration();
TimedEffect *timer = new TimedEffect();
AsyncEffect *e = new AsyncEffect(timer);
timer->start_timer(duration);
e->run();
return true;
}
} else {
return m_SfxManager->playSfx(sfx_id, sfx_volume);
}
return false;
}
void SoundManager::syncSoundSettings() {
set_audio_enabled(
!ConfMan.hasKey("mute") || !ConfMan.getBool("mute"));
set_sfx_enabled(
!ConfMan.hasKey("sfx_mute") || !ConfMan.getBool("sfx_mute"));
// TODO Music is disabled for SE and MD for now
set_music_enabled(game_type == NUVIE_GAME_U6 &&
(!ConfMan.hasKey("music_mute") || !ConfMan.getBool("music_mute")));
set_speech_enabled(game_type == NUVIE_GAME_U6 &&
(!ConfMan.hasKey("speech_mute") || !ConfMan.getBool("speech_mute")));
set_sfx_volume(ConfMan.hasKey("sfx_volume") ? clamp(ConfMan.getInt("sfx_volume"), 0, 255) : 255);
set_music_volume(ConfMan.hasKey("music_volume") ? clamp(ConfMan.getInt("music_volume"), 0, 255) : 255);
if (_midiDriver)
_midiDriver->syncSoundSettings();
}
void SoundManager::set_audio_enabled(bool val) {
audio_enabled = val;
if (audio_enabled && music_enabled)
musicPlay();
else
musicStop();
}
void SoundManager::set_music_enabled(bool val) {
music_enabled = val;
if (audio_enabled && music_enabled)
musicPlay();
else
musicStop();
}
void SoundManager::set_speech_enabled(bool val) {
speech_enabled = val;
// FIXME - stop speech
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,195 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_SOUND_MANAGER_H
#define NUVIE_SOUND_SOUND_MANAGER_H
//priorities:
//todo:
//-sample loading partially implemented, will do later (now is 21/01/04)
//-make songs fade in & out - add query/callback for end of song so that they can cycle
//-make samples sound from mapwindow
//-make samples fade in & out according to distance
//-try and use original .m files
#include "mididrv_m_adlib.h"
#include "mididrv_m_mt32.h"
#include "midiparser_m.h"
#include "ultima/nuvie/sound/sound.h"
#include "ultima/nuvie/sound/song.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/sound/sfx.h"
#include "audio/mixer.h"
#include "audio/mididrv.h"
#include "common/mutex.h"
namespace Ultima {
namespace Nuvie {
#define SFX_PLAY_ASYNC true
#define SFX_PLAY_SYNC false
class SfxManager;
class CEmuopl;
struct SoundManagerSfx {
SfxIdType sfx_id;
Audio::SoundHandle handle;
} ;
class SoundManager {
private:
struct SongMT32InstrumentMapping {
char midiDatId;
const char *filename;
MInstrumentAssignment instrumentMapping[16];
};
const static SongMT32InstrumentMapping DEFAULT_MT32_INSTRUMENT_MAPPING[12];
public:
SoundManager(Audio::Mixer *mixer);
~SoundManager();
bool nuvieStartup(const Configuration *config);
bool initAudio();
void update_map_sfx(); //updates the active sounds
void update(); // at the moment this just changes songs if required
void musicPlayFrom(string group);
void musicPause();
void musicPlay();
void musicPlay(const char *filename, uint16 song_num = 0);
void musicStop(); // SB-X
Audio::SoundHandle playTownsSound(const Common::Path &filename, uint16 sample_num);
bool isSoundPLaying(Audio::SoundHandle handle);
bool playSfx(uint16 sfx_id, bool async = false);
void syncSoundSettings();
bool is_audio_enabled() {
return audio_enabled;
}
void set_audio_enabled(bool val);
bool is_music_enabled() {
return music_enabled;
}
void set_music_enabled(bool val);
bool is_speech_enabled() {
return speech_enabled;
}
void set_speech_enabled(bool val);
bool is_sfx_enabled() {
return sfx_enabled;
}
void set_sfx_enabled(bool val) {
sfx_enabled = val;
}
uint8 get_sfx_volume() {
return sfx_volume;
}
void set_sfx_volume(uint8 val) {
sfx_volume = val;
}
uint8 get_music_volume() {
return music_volume;
}
void set_music_volume(uint8 val) {
music_volume = val;
}
Sound *get_m_pCurrentSong() {
return m_pCurrentSong;
}
bool stop_music_on_group_change;
private:
bool LoadCustomSongs(const Common::Path &scriptname);
bool LoadNativeU6Songs();
bool loadSong(Song *song, const Common::Path &filename, const char *fileId);
bool loadSong(Song *song, const Common::Path &filename, const char *fileId, const char *title);
bool groupAddSong(const char *group, Song *song);
//bool LoadObjectSamples(string sound_dir);
//bool LoadTileSamples(string sound_dir);
bool LoadSfxManager(string sfx_style);
Sound *SongExists(const string &name); //have we loaded this sound before?
Sound *SampleExists(const string &name); //have we loaded this sound before?
Sound *RequestTileSound(int id);
Sound *RequestObjectSound(int id);
Sound *RequestSong(const string &group); //request a song from this group
uint16 RequestObjectSfxId(uint16 obj_n);
typedef Common::HashMap<int, SoundCollection *> IntCollectionMap;
typedef Common::HashMap<Common::String, SoundCollection *> StringCollectionMap;
IntCollectionMap m_TileSampleMap;
IntCollectionMap m_ObjectSampleMap;
StringCollectionMap m_MusicMap;
list<Sound *> m_Songs;
list<Sound *> m_Samples;
const Configuration *m_Config;
//state info:
string m_CurrentGroup;
Sound *m_pCurrentSong;
list<SoundManagerSfx> m_ActiveSounds;
bool audio_enabled;
bool music_enabled;
bool speech_enabled;
bool sfx_enabled;
uint8 music_volume;
uint8 sfx_volume;
Audio::Mixer *_mixer;
SfxManager *m_SfxManager;
CEmuopl *opl;
MidiDriver_Multisource *_midiDriver;
MidiDriver_M_MT32 *_mt32MidiDriver;
MidiParser_M *_midiParser;
MusicType _deviceType;
byte *_musicData;
const SongMT32InstrumentMapping *_mt32InstrumentMapping;
Common::Mutex _musicMutex;
int game_type; //FIXME there's a nuvie_game_t, but almost everything uses int game_type (or gametype)
public:
static bool g_MusicFinished;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,153 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/string.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "audio/mixer.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/files/nuvie_io.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/sound/towns_sfx_manager.h"
namespace Ultima {
namespace Nuvie {
typedef struct { // sfx lookup
uint16 sfx_id;
uint8 towns_sample_num;
} TownsSfxLookup;
#define TOWNS_SFX_TBL_SIZE 12
//15 hail effect
//16 explosion
//17 level not high enough, no effect etc.
//18 cast magic sound
//19 resurrection tune
static const TownsSfxLookup sfx_lookup_tbl[] = {
{NUVIE_SFX_BLOCKED, 0},
{NUVIE_SFX_HIT, 4},
{NUVIE_SFX_BROKEN_GLASS, 12},
{NUVIE_SFX_BELL, 13},
{NUVIE_SFX_FOUNTAIN, 46},
{NUVIE_SFX_PROTECTION_FIELD, 47},
//FIXME {NUVIE_SFX_CLOCK, 1},
{NUVIE_SFX_FIRE, 6},
{NUVIE_SFX_RUBBER_DUCK, 3},
{NUVIE_SFX_WATER_WHEEL, 48},
{NUVIE_SFX_MISSLE, 9},
{NUVIE_SFX_EXPLOSION, 16},
{NUVIE_SFX_ATTACK_SWING, 2}
};
TownsSfxManager::TownsSfxManager(const Configuration *cfg, Audio::Mixer *m) : SfxManager(cfg, m),
fireStream(nullptr) {
config->pathFromValue("config/townsdir", "sounds2.dat", sounds2dat_filepath);
loadSound1Dat();
}
TownsSfxManager::~TownsSfxManager() {
//FIXME how do we make sure no sfxs are playing before deleting our resources?
//delete fireStream;
//free sounds1_dat[] buffers
}
void TownsSfxManager::loadSound1Dat() {
Common::Path filename;
U6Lzw decompressor;
U6Lib_n lib;
NuvieIOBuffer iobuf;
uint32 slib32_len = 0;
config->pathFromValue("config/townsdir", "sounds1.dat", filename);
unsigned char *slib32_data = decompressor.decompress_file(filename, slib32_len);
if (slib32_len == 0) {
error("Failed to load FM-Towns sound file %s", filename.toString().c_str());
return;
}
iobuf.open(slib32_data, slib32_len);
free(slib32_data);
if (!lib.open(&iobuf, 4)) {
error("Failed to load FM-Towns sound file %s", filename.toString().c_str());
return;
}
for (int i = 0; i < TOWNS_SFX_SOUNDS1_SIZE; i++) {
sounds1_dat[i].buf = lib.get_item(i);
sounds1_dat[i].len = lib.get_item_size(i);
}
// Fire SFX is made up of three individual samples played in a random sequence
Std::vector<Audio::RewindableAudioStream *> streams;
streams.push_back(new FMtownsDecoderStream(sounds1_dat[6].buf, sounds1_dat[6].len));
streams.push_back(new FMtownsDecoderStream(sounds1_dat[7].buf, sounds1_dat[7].len));
streams.push_back(new FMtownsDecoderStream(sounds1_dat[8].buf, sounds1_dat[8].len));
fireStream = U6Audio::makeRandomCollectionAudioStream(mixer->getOutputRate(), false, streams, DisposeAfterUse::NO);
}
bool TownsSfxManager::playSfx(SfxIdType sfx_id, uint8 volume) {
return playSfxLooping(sfx_id, nullptr, volume);
}
bool TownsSfxManager::playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) {
uint16 i = 0;
for (i = 0; i < TOWNS_SFX_TBL_SIZE; i++) {
if (sfx_lookup_tbl[i].sfx_id == sfx_id) {
playSoundSample(sfx_lookup_tbl[i].towns_sample_num, handle, volume);
return true;
}
}
return false;
}
void TownsSfxManager::playSoundSample(uint8 sample_num, Audio::SoundHandle *looping_handle, uint8 volume) {
Audio::AudioStream *stream = nullptr;
Audio::SoundHandle handle;
if (sample_num > 5 && sample_num < 9) {
// Fire samples
mixer->playStream(Audio::Mixer::kPlainSoundType, looping_handle ? looping_handle : &handle, fireStream, -1, volume, 0, DisposeAfterUse::NO);
return;
}
if (sample_num < TOWNS_SFX_SOUNDS1_SIZE) {
stream = new FMtownsDecoderStream(sounds1_dat[sample_num].buf, sounds1_dat[sample_num].len);
} else {
stream = new FMtownsDecoderStream(sounds2dat_filepath, sample_num - TOWNS_SFX_SOUNDS1_SIZE, false); //not compressed
}
if (looping_handle) {
Audio::RewindableAudioStream *rwStream = dynamic_cast<Audio::RewindableAudioStream *>(stream);
Audio::LoopingAudioStream *looping_stream = new Audio::LoopingAudioStream(rwStream, 0);
mixer->playStream(Audio::Mixer::kPlainSoundType, looping_handle, looping_stream, -1, volume);
} else {
mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, stream, -1, volume);
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_SOUND_TOWNS_SFX_MANAGER_H
#define NUVIE_SOUND_TOWNS_SFX_MANAGER_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/sound/decoder/fm_towns_decoder_stream.h"
#include "ultima/nuvie/sound/sfx_manager.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Nuvie {
#define TOWNS_SFX_SOUNDS1_SIZE 12
typedef struct {
unsigned char *buf;
uint32 len;
} TownsSampleData;
class TownsSfxManager : public SfxManager {
public:
TownsSfxManager(const Configuration *cfg, Audio::Mixer *m);
~TownsSfxManager() override;
bool playSfx(SfxIdType sfx_id, uint8 volume) override;
bool playSfxLooping(SfxIdType sfx_id, Audio::SoundHandle *handle, uint8 volume) override;
private:
Common::Path sounds2dat_filepath;
TownsSampleData sounds1_dat[TOWNS_SFX_SOUNDS1_SIZE];
U6Audio::RandomCollectionAudioStream *fireStream;
void loadSound1Dat();
void playSoundSample(uint8 sample_num, Audio::SoundHandle *looping_handle, uint8 volume);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif