From 0e6cdfd67580f75e912c5e92abb26821d032f74b Mon Sep 17 00:00:00 2001 From: dhewg Date: Sun, 20 Mar 2011 21:54:59 +0100 Subject: ANDROID: Experimental MIDI Driver Based on the SONiVOX® Embedded Audio Synthesis (EAS™) library, which is part of the base Android OS. CPU stats (Cortex A8 1GHz, monkey1 intro, peak values): MAME OPL: 30% DosBox OPL: 26% EAS: 19% --- audio/module.mk | 1 + audio/softsynth/eas.cpp | 420 ++++++++++++++++++++++++++++++++++++++++++++++++ base/plugins.cpp | 3 + 3 files changed, 424 insertions(+) create mode 100644 audio/softsynth/eas.cpp diff --git a/audio/module.mk b/audio/module.mk index 661d3ccad4..a9d9bfc869 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -45,6 +45,7 @@ MODULE_OBJS := \ softsynth/ym2612.o \ softsynth/fluidsynth.o \ softsynth/mt32.o \ + softsynth/eas.o \ softsynth/pcspk.o \ softsynth/sid.o \ softsynth/wave6581.o diff --git a/audio/softsynth/eas.cpp b/audio/softsynth/eas.cpp new file mode 100644 index 0000000000..c5c60b253d --- /dev/null +++ b/audio/softsynth/eas.cpp @@ -0,0 +1,420 @@ +/* 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" + +#if defined(__ANDROID__) + +#include + +#include "common/debug.h" +#include "common/endian.h" +#include "common/config-manager.h" +#include "audio/audiostream.h" +#include "audio/mpu401.h" +#include "audio/musicplugin.h" +#include "audio/mixer.h" + +// NOTE: +// EAS's render function *only* accepts one mix buffer size. it's defined at +// compile time of the library and can be retrieved via EASLibConfig.bufSize +// (seen: 128 bytes). +// to avoid local intermediate buffers, this implementation insists on a fixed +// buffer size of the calling rate converter, which in return must be a +// multiple of EAS's. that may change if there're hickups because slower +// devices can't render fast enough + +// from rate_arm.cpp +#define INTERMEDIATE_BUFFER_SIZE 512 + +// so far all android versions have the very same library version +#define EAS_LIBRARY "libsonivox.so" +#define EAS_KNOWNVERSION 0x03060a0e + +#define EAS_REVERB 2 +#define EAS_REVERB_BYPASS 0 +#define EAS_REVERB_PRESET 1 +#define EAS_REVERB_CHAMBER 2 + +class MidiDriver_EAS : public MidiDriver_MPU401, Audio::AudioStream { +public: + MidiDriver_EAS(); + virtual ~MidiDriver_EAS(); + + // MidiDriver + virtual int open(); + virtual bool isOpen() const; + virtual void close(); + virtual void send(uint32 b); + virtual void sysEx(const byte *msg, uint16 length); + virtual void setTimerCallback(void *timerParam, + Common::TimerManager::TimerProc timerProc); + virtual uint32 getBaseTempo(); + + // AudioStream + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const; + virtual int getRate() const; + virtual bool endOfData() const; + +private: + struct EASLibConfig { + uint32 version; + uint32 debug; + int32 voices; + int32 channels; + int32 rate; + int32 bufSize; + uint32 filter; + uint32 timeStamp; + char *GUID; + }; + + typedef void * EASDataHandle; + typedef void * EASHandle; + + typedef EASLibConfig *(*ConfigFunc)(); + typedef int32 (*InitFunc)(EASDataHandle *); + typedef int32 (*ShutdownFunc)(EASDataHandle); + typedef int32 (*SetParameterFunc)(EASDataHandle, int32, int32, int32); + typedef int32 (*OpenStreamFunc)(EASDataHandle, EASHandle *, EASHandle); + typedef int32 (*WriteStreamFunc)(EASDataHandle, EASHandle, byte *, int32); + typedef int32 (*CloseStreamFunc)(EASDataHandle, EASHandle); + typedef int32 (*RenderFunc)(EASDataHandle, int16 *, int32, int32 *); + + template + void sym(T &t, const char *symbol) { + union { + void *v; + T t; + } u; + + assert(sizeof(u.v) == sizeof(u.t)); + + u.v = dlsym(_dlHandle, symbol); + + if (!u.v) + warning("couldn't resolve %s from " EAS_LIBRARY, symbol); + + t = u.t; + } + + void *_dlHandle; + + ConfigFunc _configFunc; + InitFunc _initFunc; + ShutdownFunc _shutdownFunc; + SetParameterFunc _setParameterFunc; + OpenStreamFunc _openStreamFunc; + WriteStreamFunc _writeStreamFunc; + CloseStreamFunc _closeStreamFunc; + RenderFunc _renderFunc; + + const EASLibConfig *_config; + EASDataHandle _EASHandle; + EASHandle _midiStream; + + Common::TimerManager::TimerProc _timerProc; + void *_timerParam; + uint32 _baseTempo; + uint _rounds; + Audio::SoundHandle _soundHandle; +}; + +MidiDriver_EAS::MidiDriver_EAS() : + MidiDriver_MPU401(), + _dlHandle(0), + _configFunc(0), + _initFunc(0), + _shutdownFunc(0), + _setParameterFunc(0), + _openStreamFunc(0), + _writeStreamFunc(0), + _closeStreamFunc(0), + _renderFunc(0), + _config(0), + _EASHandle(0), + _midiStream(0), + _timerProc(0), + _timerParam(0), + _baseTempo(0), + _rounds(0), + _soundHandle() { +} + +MidiDriver_EAS::~MidiDriver_EAS() { +} + +int MidiDriver_EAS::open() { + if (isOpen()) + return MERR_ALREADY_OPEN; + + _dlHandle = dlopen(EAS_LIBRARY, RTLD_LAZY); + if (!_dlHandle) { + warning("error opening " EAS_LIBRARY ": %s", dlerror()); + return MERR_DEVICE_NOT_AVAILABLE; + } + + sym(_configFunc, "EAS_Config"); + if (!_configFunc) { + close(); + return -1; + } + + _config = _configFunc(); + if (!_config) { + close(); + warning("error retrieving EAS library configuration"); + return -1; + } + + if (_config->version != EAS_KNOWNVERSION) { + close(); + warning("unknown EAS library version: 0x%08x", _config->version); + return -1; + } + + if (_config->channels > 2) { + close(); + warning("unsupported number of EAS channels: %d", _config->channels); + return -1; + } + + // see note at top of this file + if (INTERMEDIATE_BUFFER_SIZE % (_config->bufSize * _config->channels)) { + close(); + warning("unsupported EAS buffer size: %d", _config->bufSize); + return -1; + } + + sym(_initFunc, "EAS_Init"); + sym(_shutdownFunc, "EAS_Shutdown"); + sym(_setParameterFunc, "EAS_SetParameter"); + sym(_openStreamFunc, "EAS_OpenMIDIStream"); + sym(_writeStreamFunc, "EAS_WriteMIDIStream"); + sym(_closeStreamFunc, "EAS_CloseMIDIStream"); + sym(_renderFunc, "EAS_Render"); + + if (!_initFunc || !_shutdownFunc || !_setParameterFunc || + !_openStreamFunc || !_writeStreamFunc || !_closeStreamFunc || + !_renderFunc) { + close(); + return -1; + } + + int32 res = _initFunc(&_EASHandle); + if (res) { + close(); + warning("error initializing the EAS library: %d", res); + return -1; + } + + _setParameterFunc(_EASHandle, EAS_REVERB, EAS_REVERB_PRESET, + EAS_REVERB_CHAMBER); + if (res) + warning("error setting reverb preset: %d", res); + + _setParameterFunc(_EASHandle, EAS_REVERB, EAS_REVERB_BYPASS, 0); + if (res) + warning("error disabling reverb bypass: %d", res); + + res = _openStreamFunc(_EASHandle, &_midiStream, 0); + if (res) { + close(); + warning("error opening EAS MIDI stream: %d", res); + return -1; + } + + // set the timer frequency to match a single buffer size + _baseTempo = (1000000 * _config->bufSize) / _config->rate; + + // number of buffer fills per readBuffer() + _rounds = INTERMEDIATE_BUFFER_SIZE / (_config->bufSize * _config->channels); + + debug("EAS initialized (voices:%d channels:%d rate:%d buffer:%d) " + "tempo:%u rounds:%u", _config->voices, _config->channels, + _config->rate, _config->bufSize, _baseTempo, _rounds); + + g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, + &_soundHandle, this, -1, + Audio::Mixer::kMaxChannelVolume, 0, + DisposeAfterUse::NO, true); + + return 0; +} + +bool MidiDriver_EAS::isOpen() const { + return _dlHandle != 0; +} + +void MidiDriver_EAS::close() { + MidiDriver_MPU401::close(); + + if (!isOpen()) + return; + + g_system->getMixer()->stopHandle(_soundHandle); + + // not pretty, but better than a mutex + g_system->delayMillis((_baseTempo * _rounds) / 1000); + + if (_midiStream) { + int32 res = _closeStreamFunc(_EASHandle, _midiStream); + if (res) + warning("error closing EAS MIDI stream: %d", res); + + _midiStream = 0; + } + + if (_EASHandle) { + int32 res = _shutdownFunc(_EASHandle); + if (res) + warning("error shutting down the EAS library: %d", res); + + _EASHandle = 0; + } + + if (dlclose(_dlHandle)) + warning("error closing " EAS_LIBRARY ": %s", dlerror()); + + _dlHandle = 0; +} + +void MidiDriver_EAS::send(uint32 b) { + byte buf[4]; + + WRITE_UINT32(buf, b); + + int32 res = _writeStreamFunc(_EASHandle, _midiStream, buf, 4); + if (res) + warning("error writing to EAS MIDI stream: %d", res); +} + +void MidiDriver_EAS::sysEx(const byte *msg, uint16 length) { + byte buf[266]; + + assert(length + 2 <= ARRAYSIZE(buf)); + + buf[0] = 0xF0; + memcpy(buf + 1, msg, length); + buf[length + 1] = 0xF7; + + int32 res = _writeStreamFunc(_EASHandle, _midiStream, buf, length + 2); + if (res) + warning("error writing to EAS MIDI stream: %d", res); +} + +void MidiDriver_EAS::setTimerCallback(void *timerParam, + Common::TimerManager::TimerProc timerProc) { + _timerParam = timerParam; + _timerProc = timerProc; +} + +uint32 MidiDriver_EAS::getBaseTempo() { + return _baseTempo; +} + +int MidiDriver_EAS::readBuffer(int16 *buffer, const int numSamples) { + // see note at top of this file + assert(numSamples == INTERMEDIATE_BUFFER_SIZE); + + int32 res, c; + + for (uint i = 0; i < _rounds; ++i) { + // pull in MIDI events for exactly one buffer size + if (_timerProc) + (*_timerProc)(_timerParam); + + // if there are no MIDI events, this just renders silence + res = _renderFunc(_EASHandle, buffer, _config->bufSize, &c); + if (res) { + warning("error rendering EAS samples: %d", res); + return -1; + } + + buffer += c * _config->channels; + } + + return numSamples; +} + +bool MidiDriver_EAS::isStereo() const { + return _config->channels == 2; +} + +int MidiDriver_EAS::getRate() const { + return _config->rate; +} + +bool MidiDriver_EAS::endOfData() const { + return false; +} + +class EASMusicPlugin : public MusicPluginObject { +public: + EASMusicPlugin(); + virtual ~EASMusicPlugin(); + + const char *getName() const; + const char *getId() const; + MusicDevices getDevices() const; + Common::Error createInstance(MidiDriver **mididriver, + MidiDriver::DeviceHandle = 0) const; +}; + +EASMusicPlugin::EASMusicPlugin() { +} + +EASMusicPlugin::~EASMusicPlugin() { +} + +const char *EASMusicPlugin::getName() const { + return "Embedded Audio Synthesis"; +} + +const char *EASMusicPlugin::getId() const { + return "eas"; +} + +MusicDevices EASMusicPlugin::getDevices() const { + MusicDevices devices; + devices.push_back(MusicDevice(this, "", MT_GM)); + + return devices; +} + +Common::Error EASMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { + *mididriver = new MidiDriver_EAS(); + + return Common::kNoError; +} + +//#if PLUGIN_ENABLED_DYNAMIC(EAS) + //REGISTER_PLUGIN_DYNAMIC(EAS, PLUGIN_TYPE_MUSIC, EASMusicPlugin); +//#else + REGISTER_PLUGIN_STATIC(EAS, PLUGIN_TYPE_MUSIC, EASMusicPlugin); +//#endif + +#endif + diff --git a/base/plugins.cpp b/base/plugins.cpp index 1db9c0d499..7d0557c474 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -212,6 +212,9 @@ public: #ifdef USE_MT32EMU LINK_PLUGIN(MT32) #endif + #if defined(__ANDROID__) + LINK_PLUGIN(EAS) + #endif LINK_PLUGIN(ADLIB) LINK_PLUGIN(PCSPK) LINK_PLUGIN(PCJR) -- cgit v1.2.3