scummvm/sound/softsynth/mt32.cpp
Jamieson Christian 2469e00248 SysEx data now passed around with const pointers. Permits simplification of some SysEx client code.
Testing on Windows. Developers on other platforms, please verify integrity of music handling in your respective MidiDrivers.

svn-id: r20952
2006-02-27 01:59:07 +00:00

492 lines
12 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001-2006 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*/
#include "common/stdafx.h"
#include "common/scummsys.h"
#ifdef USE_MT32EMU
#include "sound/softsynth/mt32/mt32emu.h"
#include "sound/softsynth/emumidi.h"
#include "sound/mpu401.h"
#include "common/util.h"
#include "common/file.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
class MidiChannel_MT32 : public MidiChannel_MPU401 {
void effectLevel(byte value) { }
void chorusLevel(byte value) { }
};
class MidiDriver_MT32 : public MidiDriver_Emulated {
private:
Audio::SoundHandle _handle;
MidiChannel_MT32 _midiChannels[16];
uint16 _channelMask;
MT32Emu::Synth *_synth;
int _outputRate;
protected:
void generateSamples(int16 *buf, int len);
public:
bool _initialising;
MidiDriver_MT32(Audio::Mixer *mixer);
virtual ~MidiDriver_MT32();
int open();
void close();
void send(uint32 b);
void setPitchBendRange (byte channel, uint range);
void sysEx(const byte *msg, uint16 length);
uint32 property(int prop, uint32 param);
MidiChannel *allocateChannel();
MidiChannel *getPercussionChannel();
// AudioStream API
bool isStereo() const { return true; }
int getRate() const { return _outputRate; }
};
class MT32File: public MT32Emu::File {
Common::File file;
public:
bool open(const char *filename, OpenMode mode) {
Common::File::AccessMode accessMode = mode == OpenMode_read ? Common::File::kFileReadMode : Common::File::kFileWriteMode;
return file.open(filename, accessMode);
}
void close() {
return file.close();
}
size_t read(void *in, size_t size) {
return file.read(in, size);
}
bool readLine(char *in, size_t size) {
return file.readLine(in, size) != NULL;
}
bool readBit8u(MT32Emu::Bit8u *in) {
byte b = file.readByte();
if (file.eof())
return false;
*in = b;
return true;
}
size_t write(const void *in, size_t size) {
return file.write(in, size);
}
bool writeBit8u(MT32Emu::Bit8u out) {
file.writeByte(out);
return !file.ioFailed();
}
bool isEOF() {
return file.eof();
}
};
static int eatSystemEvents() {
OSystem::Event event;
while (g_system->pollEvent(event)) {
switch (event.type) {
case OSystem::EVENT_QUIT:
return 1;
default:
break;
}
}
return 0;
}
static void drawProgress(float progress) {
const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont));
Graphics::Surface surf;
uint32 borderColor = 0x2;
uint32 fillColor = 0x4;
surf.w = g_system->getWidth() / 7 * 5;
surf.h = font.getFontHeight();
int x = g_system->getWidth() / 7;
int y = g_system->getHeight() / 2 - surf.h / 2;
surf.pitch = surf.w;
surf.bytesPerPixel = 1;
surf.pixels = calloc(surf.w, surf.h);
Common::Rect r(surf.w, surf.h);
surf.frameRect(r, borderColor);
r.grow(-1);
r.right = r.left + (uint16)(r.width() * progress);
surf.fillRect(r, fillColor);
g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, x, y, surf.w, surf.h);
g_system->updateScreen();
free(surf.pixels);
}
static void drawMessage(int offset, const Common::String &text) {
const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont));
Graphics::Surface surf;
uint32 color = 0x2;
surf.w = g_system->getWidth();
surf.h = font.getFontHeight();
surf.pitch = surf.w;
surf.bytesPerPixel = 1;
surf.pixels = calloc(surf.w, surf.h);
font.drawString(&surf, text, 0, 0, surf.w, color, Graphics::kTextAlignCenter);
int y = g_system->getHeight() / 2 - font.getFontHeight() / 2 + offset * (font.getFontHeight() + 1);
g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, 0, y, surf.w, surf.h);
g_system->updateScreen();
free(surf.pixels);
}
static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) {
MT32File *file = new MT32File();
if (!file->open(filename, mode)) {
delete file;
return NULL;
}
return file;
}
static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) {
char buf[512];
if (((MidiDriver_MT32 *)userData)->_initialising) {
vsnprintf(buf, 512, fmt, list);
buf[70] = 0; // Truncate to a reasonable length
drawMessage(1, buf);
}
//vdebug(0, fmt, list); // FIXME: Use a higher debug level
}
static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) {
switch(type) {
case MT32Emu::ReportType_lcdMessage:
g_system->displayMessageOnOSD((const char *)reportData);
break;
case MT32Emu::ReportType_errorControlROM:
error("Failed to load MT32_CONTROL.ROM");
break;
case MT32Emu::ReportType_errorPCMROM:
error("Failed to load MT32_PCM.ROM");
break;
case MT32Emu::ReportType_progressInit:
if (((MidiDriver_MT32 *)userData)->_initialising) {
drawProgress(*((const float *)reportData));
return eatSystemEvents();
}
break;
case MT32Emu::ReportType_availableSSE:
debug(1, "MT32emu: SSE is avaliable");
break;
case MT32Emu::ReportType_usingSSE:
debug(1, "MT32emu: using SSE");
break;
case MT32Emu::ReportType_available3DNow:
debug(1, "MT32emu: 3DNow! is avaliable");
break;
case MT32Emu::ReportType_using3DNow:
debug(1, "MT32emu: using 3DNow!");
break;
default:
break;
}
return 0;
}
////////////////////////////////////////
//
// MidiDriver_MT32
//
////////////////////////////////////////
MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) {
_channelMask = 0xFFFF; // Permit all 16 channels by default
uint i;
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
_midiChannels[i].init(this, i);
}
_synth = NULL;
// A higher baseFreq reduces the length used in generateSamples(),
// and means that the timer callback will be called more often.
// That results in more accurate timing.
_baseFreq = 10000;
// Unfortunately bugs in the emulator cause inaccurate tuning
// at rates other than 32KHz, thus we produce data at 32KHz and
// rely on Mixer to convert.
_outputRate = 32000; //_mixer->getOutputRate();
_initialising = false;
}
MidiDriver_MT32::~MidiDriver_MT32() {
if (_synth != NULL)
delete _synth;
}
int MidiDriver_MT32::open() {
MT32Emu::SynthProperties prop;
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
memset(&prop, 0, sizeof(prop));
prop.sampleRate = getRate();
prop.useReverb = true;
prop.useDefaultReverb = false;
prop.reverbType = 0;
prop.reverbTime = 5;
prop.reverbLevel = 3;
prop.userData = this;
prop.printDebug = MT32_PrintDebug;
prop.report = MT32_Report;
prop.openFile = MT32_OpenFile;
_synth = new MT32Emu::Synth();
_initialising = true;
const byte dummy_palette[] = {
0, 0, 0, 0,
0, 0, 171, 0,
0, 171, 0, 0,
0, 171, 171, 0,
171, 0, 0, 0
};
g_system->setPalette(dummy_palette, 0, 5);
drawMessage(-1, "Initialising MT-32 Emulator");
if (!_synth->open(prop))
return MERR_DEVICE_NOT_AVAILABLE;
_initialising = false;
g_system->clearScreen();
g_system->updateScreen();
_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, 255, 0, false, true);
return 0;
}
void MidiDriver_MT32::send(uint32 b) {
_synth->playMsg(b);
}
void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) {
if (range > 24) {
printf("setPitchBendRange() called with range > 24: %d", range);
}
byte benderRangeSysex[9];
benderRangeSysex[0] = 0x41; // Roland
benderRangeSysex[1] = channel;
benderRangeSysex[2] = 0x16; // MT-32
benderRangeSysex[3] = 0x12; // Write
benderRangeSysex[4] = 0x00;
benderRangeSysex[5] = 0x00;
benderRangeSysex[6] = 0x04;
benderRangeSysex[7] = (byte)range;
benderRangeSysex[8] = MT32Emu::Synth::calcSysexChecksum(&benderRangeSysex[4], 4, 0);
sysEx(benderRangeSysex, 9);
}
void MidiDriver_MT32::sysEx(const byte *msg, uint16 length) {
if (msg[0] == 0xf0) {
_synth->playSysex(msg, length);
} else {
_synth->playSysexWithoutFraming(msg, length);
}
}
void MidiDriver_MT32::close() {
if (!_isOpen)
return;
_isOpen = false;
// Detach the player callback handler
setTimerCallback(NULL, NULL);
// Detach the mixer callback handler
_mixer->stopHandle(_handle);
_synth->close();
delete _synth;
_synth = NULL;
}
void MidiDriver_MT32::generateSamples(int16 *data, int len) {
_synth->render(data, len);
}
uint32 MidiDriver_MT32::property(int prop, uint32 param) {
switch (prop) {
case PROP_CHANNEL_MASK:
_channelMask = param & 0xFFFF;
return 1;
}
return 0;
}
MidiChannel *MidiDriver_MT32::allocateChannel() {
MidiChannel_MT32 *chan;
uint i;
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
if (i == 9 || !(_channelMask & (1 << i)))
continue;
chan = &_midiChannels[i];
if (chan->allocate()) {
return chan;
}
}
return NULL;
}
MidiChannel *MidiDriver_MT32::getPercussionChannel() {
return &_midiChannels[9];
}
// This code should be used when calling the timer callback from the mixer thread is undesirable.
// Note that it results in less accurate timing.
#if 0
class MidiEvent_MT32 {
public:
MidiEvent_MT32 *_next;
uint32 _msg; // 0xFFFFFFFF indicates a sysex message
byte *_data;
uint32 _len;
MidiEvent_MT32(uint32 msg, byte *data, uint32 len) {
_msg = msg;
if (len > 0) {
_data = new byte[len];
memcpy(_data, data, len);
}
_len = len;
_next = NULL;
}
MidiEvent_MT32() {
if (_len > 0)
delete _data;
}
};
class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 {
private:
OSystem::Mutex _eventMutex;
MidiEvent_MT32 *_events;
Timer::TimerProc _timer_proc;
void pushMidiEvent(MidiEvent_MT32 *event);
MidiEvent_MT32 *popMidiEvent();
protected:
void send(uint32 b);
void sysEx(const byte *msg, uint16 length);
public:
MidiDriver_ThreadedMT32(Audio::Mixer *mixer);
void onTimer();
void close();
void setTimerCallback(void *timer_param, Timer::TimerProc timer_proc);
};
MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(Audio::Mixer *mixer) : MidiDriver_MT32(mixer) {
_events = NULL;
_timer_proc = NULL;
}
void MidiDriver_ThreadedMT32::close() {
MidiDriver_MT32::close();
while ((popMidiEvent() != NULL)) {
// Just eat any leftover events
}
}
void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, Timer::TimerProc timer_proc) {
if (!_timer_proc || !timer_proc) {
if (_timer_proc)
g_timer->removeTimerProc(_timer_proc);
_timer_proc = timer_proc;
if (timer_proc)
g_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param);
}
}
void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) {
Common::StackLock lock(_eventMutex);
if (_events == NULL) {
_events = event;
} else {
MidiEvent_MT32 *last = _events;
while (last->_next != NULL)
last = last->_next;
last->_next = event;
}
}
MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() {
Common::StackLock lock(_eventMutex);
MidiEvent_MT32 *event;
event = _events;
if (event != NULL)
_events = event->_next;
return event;
}
void MidiDriver_ThreadedMT32::send(uint32 b) {
MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0);
pushMidiEvent(event);
}
void MidiDriver_ThreadedMT32::sysEx(const byte *msg, uint16 length) {
MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length);
pushMidiEvent(event);
}
void MidiDriver_ThreadedMT32::onTimer() {
MidiEvent_MT32 *event;
while ((event = popMidiEvent()) != NULL) {
if (event->_msg == 0xFFFFFFFF) {
MidiDriver_MT32::sysEx(event->_data, event->_len);
} else {
MidiDriver_MT32::send(event->_msg);
}
delete event;
}
}
#endif
////////////////////////////////////////
//
// MidiDriver_MT32 factory
//
////////////////////////////////////////
MidiDriver *MidiDriver_MT32_create(Audio::Mixer *mixer) {
// HACK: It will stay here until engine plugin loader overhaul
if (ConfMan.hasKey("extrapath"))
Common::File::addDefaultDirectory(ConfMan.get("extrapath"));
return new MidiDriver_MT32(mixer);
}
#endif