Initial commit
This commit is contained in:
77
engines/ultima/nuvie/sound/adlib_sfx_manager.cpp
Normal file
77
engines/ultima/nuvie/sound/adlib_sfx_manager.cpp
Normal 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
|
||||
49
engines/ultima/nuvie/sound/adlib_sfx_manager.h
Normal file
49
engines/ultima/nuvie/sound/adlib_sfx_manager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
71
engines/ultima/nuvie/sound/adplug/adplug_player.cpp
Normal file
71
engines/ultima/nuvie/sound/adplug/adplug_player.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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
|
||||
99
engines/ultima/nuvie/sound/adplug/adplug_player.h
Normal file
99
engines/ultima/nuvie/sound/adplug/adplug_player.h
Normal 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
|
||||
76
engines/ultima/nuvie/sound/adplug/emu_opl.cpp
Normal file
76
engines/ultima/nuvie/sound/adplug/emu_opl.cpp
Normal 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
|
||||
54
engines/ultima/nuvie/sound/adplug/emu_opl.h
Normal file
54
engines/ultima/nuvie/sound/adplug/emu_opl.h
Normal 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
|
||||
2401
engines/ultima/nuvie/sound/adplug/fm_opl.cpp
Normal file
2401
engines/ultima/nuvie/sound/adplug/fm_opl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
137
engines/ultima/nuvie/sound/adplug/fm_opl.h
Normal file
137
engines/ultima/nuvie/sound/adplug/fm_opl.h
Normal 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
|
||||
572
engines/ultima/nuvie/sound/adplug/mid.cpp
Normal file
572
engines/ultima/nuvie/sound/adplug/mid.cpp
Normal 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
|
||||
115
engines/ultima/nuvie/sound/adplug/mid.h
Normal file
115
engines/ultima/nuvie/sound/adplug/mid.h
Normal 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
|
||||
42
engines/ultima/nuvie/sound/adplug/opl.h
Normal file
42
engines/ultima/nuvie/sound/adplug/opl.h
Normal 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
|
||||
1630
engines/ultima/nuvie/sound/adplug/opl_class.cpp
Normal file
1630
engines/ultima/nuvie/sound/adplug/opl_class.cpp
Normal file
File diff suppressed because it is too large
Load Diff
253
engines/ultima/nuvie/sound/adplug/opl_class.h
Normal file
253
engines/ultima/nuvie/sound/adplug/opl_class.h
Normal 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
|
||||
34
engines/ultima/nuvie/sound/adplug/silent_opl.h
Normal file
34
engines/ultima/nuvie/sound/adplug/silent_opl.h
Normal 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
|
||||
629
engines/ultima/nuvie/sound/adplug/u6m.cpp
Normal file
629
engines/ultima/nuvie/sound/adplug/u6m.cpp
Normal 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 ¶m) {
|
||||
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
|
||||
137
engines/ultima/nuvie/sound/adplug/u6m.h
Normal file
137
engines/ultima/nuvie/sound/adplug/u6m.h
Normal 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
|
||||
124
engines/ultima/nuvie/sound/custom_sfx_manager.cpp
Normal file
124
engines/ultima/nuvie/sound/custom_sfx_manager.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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
|
||||
57
engines/ultima/nuvie/sound/custom_sfx_manager.h
Normal file
57
engines/ultima/nuvie/sound/custom_sfx_manager.h
Normal 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
|
||||
89
engines/ultima/nuvie/sound/decoder/adlib_sfx_stream.cpp
Normal file
89
engines/ultima/nuvie/sound/decoder/adlib_sfx_stream.cpp
Normal 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
|
||||
94
engines/ultima/nuvie/sound/decoder/adlib_sfx_stream.h
Normal file
94
engines/ultima/nuvie/sound/decoder/adlib_sfx_stream.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
101
engines/ultima/nuvie/sound/decoder/fm_towns_decoder_stream.cpp
Normal file
101
engines/ultima/nuvie/sound/decoder/fm_towns_decoder_stream.cpp
Normal 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
|
||||
86
engines/ultima/nuvie/sound/decoder/fm_towns_decoder_stream.h
Normal file
86
engines/ultima/nuvie/sound/decoder/fm_towns_decoder_stream.h
Normal 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
|
||||
150
engines/ultima/nuvie/sound/decoder/pc_speaker.cpp
Normal file
150
engines/ultima/nuvie/sound/decoder/pc_speaker.cpp
Normal 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
|
||||
|
||||
62
engines/ultima/nuvie/sound/decoder/pc_speaker.h
Normal file
62
engines/ultima/nuvie/sound/decoder/pc_speaker.h
Normal 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
|
||||
427
engines/ultima/nuvie/sound/decoder/pc_speaker_stream.cpp
Normal file
427
engines/ultima/nuvie/sound/decoder/pc_speaker_stream.cpp
Normal 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
|
||||
186
engines/ultima/nuvie/sound/decoder/pc_speaker_stream.h
Normal file
186
engines/ultima/nuvie/sound/decoder/pc_speaker_stream.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
128
engines/ultima/nuvie/sound/decoder/u6_adplug_decoder_stream.cpp
Normal file
128
engines/ultima/nuvie/sound/decoder/u6_adplug_decoder_stream.cpp
Normal 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
|
||||
@@ -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
|
||||
459
engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
Normal file
459
engines/ultima/nuvie/sound/mididrv_m_adlib.cpp
Normal 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
|
||||
103
engines/ultima/nuvie/sound/mididrv_m_adlib.h
Normal file
103
engines/ultima/nuvie/sound/mididrv_m_adlib.h
Normal 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
|
||||
205
engines/ultima/nuvie/sound/mididrv_m_mt32.cpp
Normal file
205
engines/ultima/nuvie/sound/mididrv_m_mt32.cpp
Normal 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
|
||||
98
engines/ultima/nuvie/sound/mididrv_m_mt32.h
Normal file
98
engines/ultima/nuvie/sound/mididrv_m_mt32.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
265
engines/ultima/nuvie/sound/midiparser_m.cpp
Normal file
265
engines/ultima/nuvie/sound/midiparser_m.cpp
Normal 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
|
||||
85
engines/ultima/nuvie/sound/midiparser_m.h
Normal file
85
engines/ultima/nuvie/sound/midiparser_m.h
Normal 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
|
||||
533
engines/ultima/nuvie/sound/origin_fx_adib_driver.cpp
Normal file
533
engines/ultima/nuvie/sound/origin_fx_adib_driver.cpp
Normal 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
|
||||
97
engines/ultima/nuvie/sound/origin_fx_adib_driver.h
Normal file
97
engines/ultima/nuvie/sound/origin_fx_adib_driver.h
Normal 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
|
||||
121
engines/ultima/nuvie/sound/pc_speaker_sfx_manager.cpp
Normal file
121
engines/ultima/nuvie/sound/pc_speaker_sfx_manager.cpp
Normal 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
|
||||
50
engines/ultima/nuvie/sound/pc_speaker_sfx_manager.h
Normal file
50
engines/ultima/nuvie/sound/pc_speaker_sfx_manager.h
Normal 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
|
||||
80
engines/ultima/nuvie/sound/sfx.h
Normal file
80
engines/ultima/nuvie/sound/sfx.h
Normal 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
|
||||
57
engines/ultima/nuvie/sound/sfx_manager.h
Normal file
57
engines/ultima/nuvie/sound/sfx_manager.h
Normal 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
|
||||
41
engines/ultima/nuvie/sound/song.cpp
Normal file
41
engines/ultima/nuvie/sound/song.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
62
engines/ultima/nuvie/sound/song.h
Normal file
62
engines/ultima/nuvie/sound/song.h
Normal 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
|
||||
79
engines/ultima/nuvie/sound/song_adplug.cpp
Normal file
79
engines/ultima/nuvie/sound/song_adplug.cpp
Normal 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
|
||||
66
engines/ultima/nuvie/sound/song_adplug.h
Normal file
66
engines/ultima/nuvie/sound/song_adplug.h
Normal 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
|
||||
66
engines/ultima/nuvie/sound/song_filename.cpp
Normal file
66
engines/ultima/nuvie/sound/song_filename.cpp
Normal 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
|
||||
44
engines/ultima/nuvie/sound/song_filename.h
Normal file
44
engines/ultima/nuvie/sound/song_filename.h
Normal 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
|
||||
72
engines/ultima/nuvie/sound/sound.h
Normal file
72
engines/ultima/nuvie/sound/sound.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
902
engines/ultima/nuvie/sound/sound_manager.cpp
Normal file
902
engines/ultima/nuvie/sound/sound_manager.cpp
Normal 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
|
||||
195
engines/ultima/nuvie/sound/sound_manager.h
Normal file
195
engines/ultima/nuvie/sound/sound_manager.h
Normal 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
|
||||
153
engines/ultima/nuvie/sound/towns_sfx_manager.cpp
Normal file
153
engines/ultima/nuvie/sound/towns_sfx_manager.cpp
Normal 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
|
||||
62
engines/ultima/nuvie/sound/towns_sfx_manager.h
Normal file
62
engines/ultima/nuvie/sound/towns_sfx_manager.h
Normal 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
|
||||
Reference in New Issue
Block a user