diff options
author | Max Horn | 2011-02-09 01:09:01 +0000 |
---|---|---|
committer | Max Horn | 2011-02-09 01:09:01 +0000 |
commit | 42ab839dd6c8a1570b232101eb97f4e54de57935 (patch) | |
tree | 3b763d8913a87482b793e0348c88b9a5f40eecc9 /audio/softsynth/mt32.cpp | |
parent | 386203a3d6ce1abf457c9110d695408ec5f01b85 (diff) | |
download | scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.gz scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.bz2 scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.zip |
AUDIO: Rename sound/ dir to audio/
svn-id: r55850
Diffstat (limited to 'audio/softsynth/mt32.cpp')
-rw-r--r-- | audio/softsynth/mt32.cpp | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/audio/softsynth/mt32.cpp b/audio/softsynth/mt32.cpp new file mode 100644 index 0000000000..3a3958d494 --- /dev/null +++ b/audio/softsynth/mt32.cpp @@ -0,0 +1,573 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/scummsys.h" + +#ifdef USE_MT32EMU + +#include "audio/softsynth/mt32/mt32emu.h" + +#include "audio/softsynth/emumidi.h" +#include "audio/musicplugin.h" +#include "audio/mpu401.h" + +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/events.h" +#include "common/file.h" +#include "common/system.h" +#include "common/util.h" +#include "common/archive.h" +#include "common/translation.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 _in; + Common::DumpFile _out; +public: + bool open(const char *filename, OpenMode mode) { + if (mode == OpenMode_read) + return _in.open(filename); + else + return _out.open(filename); + } + void close() { + _in.close(); + _out.close(); + } + size_t read(void *in, size_t size) { + return _in.read(in, size); + } + bool readBit8u(MT32Emu::Bit8u *in) { + byte b = _in.readByte(); + if (_in.eos()) + return false; + *in = b; + return true; + } + size_t write(const void *in, size_t size) { + return _out.write(in, size); + } + bool writeBit8u(MT32Emu::Bit8u out) { + _out.writeByte(out); + return !_out.err(); + } + bool isEOF() { + return _in.isOpen() && _in.eos(); + } +}; + +static int eatSystemEvents() { + Common::Event event; + Common::EventManager *eventMan = g_system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + return 1; + default: + break; + } + } + return 0; +} + +static void drawProgress(float progress) { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); + Graphics::Surface *screen = g_system->lockScreen(); + + assert(screen); + assert(screen->pixels); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + int16 w = g_system->getWidth() / 7 * 5; + int16 h = font.getFontHeight(); + int16 x = g_system->getWidth() / 7; + int16 y = g_system->getHeight() / 2 - h / 2; + + Common::Rect r(x, y, x + w, y + h); + + uint32 col; + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 171, 0); + else + col = 1; + + screen->frameRect(r, col); + + r.grow(-1); + r.setWidth(uint16(progress * w)); + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(171, 0, 0); + else + col = 2; + + screen->fillRect(r, col); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +static void drawMessage(int offset, const Common::String &text) { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kOSDFont)); + Graphics::Surface *screen = g_system->lockScreen(); + + assert(screen); + assert(screen->pixels); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + uint16 h = font.getFontHeight(); + uint16 y = g_system->getHeight() / 2 - h / 2 + offset * (h + 1); + + uint32 col; + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 0, 0); + else + col = 0; + + Common::Rect r(0, y, screen->w, y + h); + screen->fillRect(r, col); + + if (screenFormat.bytesPerPixel > 1) + col = screenFormat.RGBToColor(0, 171, 0); + else + col = 1; + + font.drawString(screen, text, 0, y, screen->w, col, Graphics::kTextAlignCenter); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +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) { + if (((MidiDriver_MT32 *)userData)->_initialising) { + char buf[512]; + + 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 available"); + break; + case MT32Emu::ReportType_usingSSE: + debug(1, "MT32emu: using SSE"); + break; + case MT32Emu::ReportType_available3DNow: + debug(1, "MT32emu: 3DNow! is available"); + 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() { + 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(); + + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + + if (screenFormat.bytesPerPixel == 1) { + const byte dummy_palette[] = { + 0, 0, 0, 0, // background + 0, 171, 0, 0, // border, font + 171, 0, 0, 0 // fill + }; + + g_system->getPaletteManager()->setPalette(dummy_palette, 0, 3); + } + + _initialising = true; + drawMessage(-1, _s("Initialising MT-32 Emulator")); + if (!_synth->open(prop)) + return MERR_DEVICE_NOT_AVAILABLE; + _initialising = false; + + if (screenFormat.bytesPerPixel > 1) + g_system->fillScreen(screenFormat.RGBToColor(0, 0, 0)); + else + g_system->fillScreen(0); + + g_system->updateScreen(); + + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, 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; + TimerManager::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, TimerManager::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, TimerManager::TimerProc timer_proc) { + if (!_timer_proc || !timer_proc) { + if (_timer_proc) + _vm->_timer->removeTimerProc(_timer_proc); + _timer_proc = timer_proc; + if (timer_proc) + _vm->_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 + + +// Plugin interface + +class MT32EmuMusicPlugin : public MusicPluginObject { +public: + const char *getName() const { + return _s("MT-32 Emulator"); + } + + const char *getId() const { + return "mt32"; + } + + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const; +}; + +MusicDevices MT32EmuMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_MT32)); + return devices; +} + +Common::Error MT32EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + if (ConfMan.hasKey("extrapath")) + SearchMan.addDirectory("extrapath", ConfMan.get("extrapath")); + + *mididriver = new MidiDriver_MT32(g_system->getMixer()); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(MT32) + //REGISTER_PLUGIN_DYNAMIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin); +//#endif + +#endif |