aboutsummaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
authorMax Horn2011-02-09 01:09:01 +0000
committerMax Horn2011-02-09 01:09:01 +0000
commit42ab839dd6c8a1570b232101eb97f4e54de57935 (patch)
tree3b763d8913a87482b793e0348c88b9a5f40eecc9 /audio
parent386203a3d6ce1abf457c9110d695408ec5f01b85 (diff)
downloadscummvm-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')
-rw-r--r--audio/audiostream.cpp407
-rw-r--r--audio/audiostream.h371
-rw-r--r--audio/decoders/adpcm.cpp851
-rw-r--r--audio/decoders/adpcm.h90
-rw-r--r--audio/decoders/aiff.cpp186
-rw-r--r--audio/decoders/aiff.h71
-rw-r--r--audio/decoders/flac.cpp745
-rw-r--r--audio/decoders/flac.h75
-rw-r--r--audio/decoders/iff_sound.cpp130
-rw-r--r--audio/decoders/iff_sound.h47
-rw-r--r--audio/decoders/mac_snd.cpp116
-rw-r--r--audio/decoders/mac_snd.h58
-rw-r--r--audio/decoders/mp3.cpp375
-rw-r--r--audio/decoders/mp3.h76
-rw-r--r--audio/decoders/raw.cpp356
-rw-r--r--audio/decoders/raw.h153
-rw-r--r--audio/decoders/vag.cpp150
-rw-r--r--audio/decoders/vag.h60
-rw-r--r--audio/decoders/voc.cpp403
-rw-r--r--audio/decoders/voc.h107
-rw-r--r--audio/decoders/vorbis.cpp262
-rw-r--r--audio/decoders/vorbis.h75
-rw-r--r--audio/decoders/wave.cpp194
-rw-r--r--audio/decoders/wave.h84
-rw-r--r--audio/fmopl.cpp197
-rw-r--r--audio/fmopl.h179
-rw-r--r--audio/mididrv.cpp326
-rw-r--r--audio/mididrv.h288
-rw-r--r--audio/midiparser.cpp467
-rw-r--r--audio/midiparser.h404
-rw-r--r--audio/midiparser_smf.cpp384
-rw-r--r--audio/midiparser_xmidi.cpp375
-rw-r--r--audio/mixer.cpp556
-rw-r--r--audio/mixer.h265
-rw-r--r--audio/mixer_intern.h135
-rw-r--r--audio/mods/infogrames.cpp470
-rw-r--r--audio/mods/infogrames.h148
-rw-r--r--audio/mods/maxtrax.cpp1040
-rw-r--r--audio/mods/maxtrax.h225
-rw-r--r--audio/mods/module.cpp252
-rw-r--r--audio/mods/module.h90
-rw-r--r--audio/mods/paula.cpp212
-rw-r--r--audio/mods/paula.h210
-rw-r--r--audio/mods/protracker.cpp466
-rw-r--r--audio/mods/protracker.h57
-rw-r--r--audio/mods/rjp1.cpp582
-rw-r--r--audio/mods/rjp1.h50
-rw-r--r--audio/mods/soundfx.cpp275
-rw-r--r--audio/mods/soundfx.h53
-rw-r--r--audio/mods/tfmx.cpp1193
-rw-r--r--audio/mods/tfmx.h284
-rw-r--r--audio/module.mk61
-rw-r--r--audio/mpu401.cpp145
-rw-r--r--audio/mpu401.h92
-rw-r--r--audio/musicplugin.cpp64
-rw-r--r--audio/musicplugin.h125
-rw-r--r--audio/null.cpp62
-rw-r--r--audio/null.h56
-rw-r--r--audio/rate.cpp358
-rw-r--r--audio/rate.h90
-rw-r--r--audio/rate_arm.cpp465
-rw-r--r--audio/rate_arm_asm.s687
-rw-r--r--audio/softsynth/adlib.cpp1617
-rw-r--r--audio/softsynth/appleiigs.cpp57
-rw-r--r--audio/softsynth/cms.cpp376
-rw-r--r--audio/softsynth/cms.h92
-rw-r--r--audio/softsynth/emumidi.h116
-rw-r--r--audio/softsynth/fluidsynth.cpp254
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_audio.cpp1583
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_audio.h179
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_euphony.cpp905
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_euphony.h187
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp1428
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_driver.h135
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp1548
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h196
-rw-r--r--audio/softsynth/mt32.cpp573
-rw-r--r--audio/softsynth/mt32/freeverb.cpp310
-rw-r--r--audio/softsynth/mt32/freeverb.h244
-rw-r--r--audio/softsynth/mt32/i386.cpp849
-rw-r--r--audio/softsynth/mt32/i386.h49
-rw-r--r--audio/softsynth/mt32/module.mk14
-rw-r--r--audio/softsynth/mt32/mt32_file.cpp70
-rw-r--r--audio/softsynth/mt32/mt32_file.h52
-rw-r--r--audio/softsynth/mt32/mt32emu.h70
-rw-r--r--audio/softsynth/mt32/part.cpp633
-rw-r--r--audio/softsynth/mt32/part.h113
-rw-r--r--audio/softsynth/mt32/partial.cpp968
-rw-r--r--audio/softsynth/mt32/partial.h148
-rw-r--r--audio/softsynth/mt32/partialManager.cpp272
-rw-r--r--audio/softsynth/mt32/partialManager.h56
-rw-r--r--audio/softsynth/mt32/structures.h284
-rw-r--r--audio/softsynth/mt32/synth.cpp1198
-rw-r--r--audio/softsynth/mt32/synth.h300
-rw-r--r--audio/softsynth/mt32/tables.cpp757
-rw-r--r--audio/softsynth/mt32/tables.h116
-rw-r--r--audio/softsynth/opl/dbopl.cpp1536
-rw-r--r--audio/softsynth/opl/dbopl.h283
-rw-r--r--audio/softsynth/opl/dosbox.cpp335
-rw-r--r--audio/softsynth/opl/dosbox.h110
-rw-r--r--audio/softsynth/opl/mame.cpp1234
-rw-r--r--audio/softsynth/opl/mame.h202
-rw-r--r--audio/softsynth/pcspk.cpp187
-rw-r--r--audio/softsynth/pcspk.h88
-rw-r--r--audio/softsynth/sid.cpp1456
-rw-r--r--audio/softsynth/sid.h348
-rw-r--r--audio/softsynth/wave6581.cpp2098
-rw-r--r--audio/softsynth/ym2612.cpp789
-rw-r--r--audio/softsynth/ym2612.h179
-rw-r--r--audio/timestamp.cpp212
-rw-r--r--audio/timestamp.h251
111 files changed, 41887 insertions, 0 deletions
diff --git a/audio/audiostream.cpp b/audio/audiostream.cpp
new file mode 100644
index 0000000000..46b2846137
--- /dev/null
+++ b/audio/audiostream.cpp
@@ -0,0 +1,407 @@
+/* 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/debug.h"
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/queue.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/flac.h"
+#include "audio/mixer.h"
+#include "audio/decoders/mp3.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/vorbis.h"
+
+
+namespace Audio {
+
+struct StreamFileFormat {
+ /** Decodername */
+ const char *decoderName;
+ const char *fileExtension;
+ /**
+ * Pointer to a function which tries to open a file of type StreamFormat.
+ * Return NULL in case of an error (invalid/nonexisting file).
+ */
+ SeekableAudioStream *(*openStreamFile)(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+};
+
+static const StreamFileFormat STREAM_FILEFORMATS[] = {
+ /* decoderName, fileExt, openStreamFuntion */
+#ifdef USE_FLAC
+ { "FLAC", ".flac", makeFLACStream },
+ { "FLAC", ".fla", makeFLACStream },
+#endif
+#ifdef USE_VORBIS
+ { "Ogg Vorbis", ".ogg", makeVorbisStream },
+#endif
+#ifdef USE_MAD
+ { "MPEG Layer 3", ".mp3", makeMP3Stream },
+#endif
+
+ { NULL, NULL, NULL } // Terminator
+};
+
+SeekableAudioStream *SeekableAudioStream::openStreamFile(const Common::String &basename) {
+ SeekableAudioStream *stream = NULL;
+ Common::File *fileHandle = new Common::File();
+
+ for (int i = 0; i < ARRAYSIZE(STREAM_FILEFORMATS)-1 && stream == NULL; ++i) {
+ Common::String filename = basename + STREAM_FILEFORMATS[i].fileExtension;
+ fileHandle->open(filename);
+ if (fileHandle->isOpen()) {
+ // Create the stream object
+ stream = STREAM_FILEFORMATS[i].openStreamFile(fileHandle, DisposeAfterUse::YES);
+ fileHandle = 0;
+ break;
+ }
+ }
+
+ delete fileHandle;
+
+ if (stream == NULL)
+ debug(1, "SeekableAudioStream::openStreamFile: Could not open compressed AudioFile %s", basename.c_str());
+
+ return stream;
+}
+
+#pragma mark -
+#pragma mark --- LoopingAudioStream ---
+#pragma mark -
+
+LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse)
+ : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops), _completeIterations(0) {
+ assert(stream);
+
+ if (!stream->rewind()) {
+ // TODO: Properly indicate error
+ _loops = _completeIterations = 1;
+ }
+}
+
+LoopingAudioStream::~LoopingAudioStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _parent;
+}
+
+int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
+ if ((_loops && _completeIterations == _loops) || !numSamples)
+ return 0;
+
+ int samplesRead = _parent->readBuffer(buffer, numSamples);
+
+ if (_parent->endOfStream()) {
+ ++_completeIterations;
+ if (_completeIterations == _loops)
+ return samplesRead;
+
+ const int remainingSamples = numSamples - samplesRead;
+
+ if (!_parent->rewind()) {
+ // TODO: Properly indicate error
+ _loops = _completeIterations = 1;
+ return samplesRead;
+ }
+
+ return samplesRead + readBuffer(buffer + samplesRead, remainingSamples);
+ }
+
+ return samplesRead;
+}
+
+bool LoopingAudioStream::endOfData() const {
+ return (_loops != 0 && (_completeIterations == _loops));
+}
+
+AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops) {
+ if (loops != 1)
+ return new LoopingAudioStream(stream, loops);
+ else
+ return stream;
+}
+
+AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops) {
+ if (!start.totalNumberOfFrames() && (!end.totalNumberOfFrames() || end == stream->getLength())) {
+ return makeLoopingAudioStream(stream, loops);
+ } else {
+ if (!end.totalNumberOfFrames())
+ end = stream->getLength();
+
+ if (start >= end) {
+ warning("makeLoopingAudioStream: start (%d) >= end (%d)", start.msecs(), end.msecs());
+ delete stream;
+ return 0;
+ }
+
+ return makeLoopingAudioStream(new SubSeekableAudioStream(stream, start, end), loops);
+ }
+}
+
+#pragma mark -
+#pragma mark --- SubLoopingAudioStream ---
+#pragma mark -
+
+SubLoopingAudioStream::SubLoopingAudioStream(SeekableAudioStream *stream,
+ uint loops,
+ const Timestamp loopStart,
+ const Timestamp loopEnd,
+ DisposeAfterUse::Flag disposeAfterUse)
+ : _parent(stream), _disposeAfterUse(disposeAfterUse), _loops(loops),
+ _pos(0, getRate() * (isStereo() ? 2 : 1)),
+ _loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())),
+ _loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())),
+ _done(false) {
+ assert(loopStart < loopEnd);
+
+ if (!_parent->rewind())
+ _done = true;
+}
+
+SubLoopingAudioStream::~SubLoopingAudioStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _parent;
+}
+
+int SubLoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) {
+ if (_done)
+ return 0;
+
+ int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples);
+ int framesRead = _parent->readBuffer(buffer, framesLeft);
+ _pos = _pos.addFrames(framesRead);
+
+ if (framesRead < framesLeft && _parent->endOfData()) {
+ // TODO: Proper error indication.
+ _done = true;
+ return framesRead;
+ } else if (_pos == _loopEnd) {
+ if (_loops != 0) {
+ --_loops;
+ if (!_loops) {
+ _done = true;
+ return framesRead;
+ }
+ }
+
+ if (!_parent->seek(_loopStart)) {
+ // TODO: Proper error indication.
+ _done = true;
+ return framesRead;
+ }
+
+ _pos = _loopStart;
+ framesLeft = numSamples - framesLeft;
+ return framesRead + readBuffer(buffer + framesRead, framesLeft);
+ } else {
+ return framesRead;
+ }
+}
+
+#pragma mark -
+#pragma mark --- SubSeekableAudioStream ---
+#pragma mark -
+
+SubSeekableAudioStream::SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse)
+ : _parent(parent), _disposeAfterUse(disposeAfterUse),
+ _start(convertTimeToStreamPos(start, getRate(), isStereo())),
+ _pos(0, getRate() * (isStereo() ? 2 : 1)),
+ _length(convertTimeToStreamPos(end - start, getRate(), isStereo())) {
+
+ assert(_length.totalNumberOfFrames() % (isStereo() ? 2 : 1) == 0);
+ _parent->seek(_start);
+}
+
+SubSeekableAudioStream::~SubSeekableAudioStream() {
+ if (_disposeAfterUse)
+ delete _parent;
+}
+
+int SubSeekableAudioStream::readBuffer(int16 *buffer, const int numSamples) {
+ int framesLeft = MIN(_length.frameDiff(_pos), numSamples);
+ int framesRead = _parent->readBuffer(buffer, framesLeft);
+ _pos = _pos.addFrames(framesRead);
+ return framesRead;
+}
+
+bool SubSeekableAudioStream::seek(const Timestamp &where) {
+ _pos = convertTimeToStreamPos(where, getRate(), isStereo());
+ if (_pos > _length) {
+ _pos = _length;
+ return false;
+ }
+
+ if (_parent->seek(_pos + _start)) {
+ return true;
+ } else {
+ _pos = _length;
+ return false;
+ }
+}
+
+#pragma mark -
+#pragma mark --- Queueing audio stream ---
+#pragma mark -
+
+
+void QueuingAudioStream::queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags) {
+ AudioStream *stream = makeRawStream(data, size, getRate(), flags, disposeAfterUse);
+ queueAudioStream(stream, DisposeAfterUse::YES);
+}
+
+
+class QueuingAudioStreamImpl : public QueuingAudioStream {
+private:
+ /**
+ * We queue a number of (pointers to) audio stream objects.
+ * In addition, we need to remember for each stream whether
+ * to dispose it after all data has been read from it.
+ * Hence, we don't store pointers to stream objects directly,
+ * but rather StreamHolder structs.
+ */
+ struct StreamHolder {
+ AudioStream *_stream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+ StreamHolder(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse)
+ : _stream(stream),
+ _disposeAfterUse(disposeAfterUse) {}
+ };
+
+ /**
+ * 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;
+
+ /**
+ * A mutex to avoid access problems (causing e.g. corruption of
+ * the linked list) in thread aware environments.
+ */
+ Common::Mutex _mutex;
+
+ /**
+ * The queue of audio streams.
+ */
+ Common::Queue<StreamHolder> _queue;
+
+public:
+ QueuingAudioStreamImpl(int rate, bool stereo)
+ : _rate(rate), _stereo(stereo), _finished(false) {}
+ ~QueuingAudioStreamImpl();
+
+ // Implement the AudioStream API
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+ virtual bool isStereo() const { return _stereo; }
+ virtual int getRate() const { return _rate; }
+ virtual bool endOfData() const {
+ //Common::StackLock lock(_mutex);
+ return _queue.empty();
+ }
+ virtual bool endOfStream() const { return _finished && _queue.empty(); }
+
+ // Implement the QueuingAudioStream API
+ virtual void queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+ virtual void finish() { _finished = true; }
+
+ uint32 numQueuedStreams() const {
+ //Common::StackLock lock(_mutex);
+ return _queue.size();
+ }
+};
+
+QueuingAudioStreamImpl::~QueuingAudioStreamImpl() {
+ while (!_queue.empty()) {
+ StreamHolder tmp = _queue.pop();
+ if (tmp._disposeAfterUse == DisposeAfterUse::YES)
+ delete tmp._stream;
+ }
+}
+
+void QueuingAudioStreamImpl::queueAudioStream(AudioStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ assert(!_finished);
+ if ((stream->getRate() != getRate()) || (stream->isStereo() != isStereo()))
+ error("QueuingAudioStreamImpl::queueAudioStream: stream has mismatched parameters");
+
+ Common::StackLock lock(_mutex);
+ _queue.push(StreamHolder(stream, disposeAfterUse));
+}
+
+int QueuingAudioStreamImpl::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+ int samplesDecoded = 0;
+
+ while (samplesDecoded < numSamples && !_queue.empty()) {
+ AudioStream *stream = _queue.front()._stream;
+ samplesDecoded += stream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);
+
+ if (stream->endOfData()) {
+ StreamHolder tmp = _queue.pop();
+ if (tmp._disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ }
+ }
+
+ return samplesDecoded;
+}
+
+QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo) {
+ return new QueuingAudioStreamImpl(rate, stereo);
+}
+
+Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo) {
+ Timestamp result(where.convertToFramerate(rate * (isStereo ? 2 : 1)));
+
+ // When the Stream is a stereo stream, we have to assure
+ // that the sample position is an even number.
+ if (isStereo && (result.totalNumberOfFrames() & 1))
+ result = result.addFrames(-1); // We cut off one sample here.
+
+ // Since Timestamp allows sub-frame-precision it might lead to odd behaviors
+ // when we would just return result.
+ //
+ // An example is when converting the timestamp 500ms to a 11025 Hz based
+ // stream. It would have an internal frame counter of 5512.5. Now when
+ // doing calculations at frame precision, this might lead to unexpected
+ // results: The frame difference between a timestamp 1000ms and the above
+ // mentioned timestamp (both with 11025 as framerate) would be 5512,
+ // instead of 5513, which is what a frame-precision based code would expect.
+ //
+ // By creating a new Timestamp with the given parameters, we create a
+ // Timestamp with frame-precision, which just drops a sub-frame-precision
+ // information (i.e. rounds down).
+ return Timestamp(result.secs(), result.numberOfFrames(), result.framerate());
+}
+
+} // End of namespace Audio
diff --git a/audio/audiostream.h b/audio/audiostream.h
new file mode 100644
index 0000000000..cd6456cc70
--- /dev/null
+++ b/audio/audiostream.h
@@ -0,0 +1,371 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_AUDIOSTREAM_H
+#define SOUND_AUDIOSTREAM_H
+
+#include "common/util.h"
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#include "audio/timestamp.h"
+
+namespace Audio {
+
+class SeekableAudioStream;
+
+/**
+ * Generic audio input stream. Subclasses of this are used to feed arbitrary
+ * sampled audio data into ScummVM's audio mixer.
+ */
+class AudioStream {
+public:
+ virtual ~AudioStream() {}
+
+ /**
+ * Fill the given buffer with up to numSamples samples. Returns the actual
+ * number of samples read, or -1 if a critical error occurred (note: you
+ * *must* check if this value is less than what you requested, this can
+ * happen when the stream is fully used up).
+ *
+ * Data has to be in native endianess, 16 bit per sample, signed. For stereo
+ * stream, buffer will be filled with interleaved left and right channel
+ * samples, starting with a left sample. Furthermore, the samples in the
+ * left and right are summed up. So if you request 4 samples from a stereo
+ * stream, you will get a total of two left channel and two right channel
+ * samples.
+ */
+ virtual int readBuffer(int16 *buffer, const int numSamples) = 0;
+
+ /** Is this a stereo stream? */
+ virtual bool isStereo() const = 0;
+
+ /** Sample rate of the stream. */
+ virtual int getRate() const = 0;
+
+ /**
+ * 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.
+ */
+ virtual bool endOfData() const = 0;
+
+ /**
+ * End of stream reached? If this returns true, it means that all data
+ * in this stream is used up and no additional data will appear in it
+ * in the future.
+ * This is used by the mixer to decide whether a given stream shall be
+ * removed from the list of active streams (and thus be destroyed).
+ * By default this maps to endOfData()
+ */
+ virtual bool endOfStream() const { return endOfData(); }
+};
+
+/**
+ * A rewindable audio stream. This allows for reseting the AudioStream
+ * to its initial state. Note that rewinding itself is not required to
+ * be working when the stream is being played by Mixer!
+ */
+class RewindableAudioStream : public AudioStream {
+public:
+ /**
+ * Rewinds the stream to its start.
+ *
+ * @return true on success, false otherwise.
+ */
+ virtual bool rewind() = 0;
+};
+
+/**
+ * A looping audio stream. This object does nothing besides using
+ * a RewindableAudioStream to play a stream in a loop.
+ */
+class LoopingAudioStream : public AudioStream {
+public:
+ /**
+ * Creates a looping audio stream object.
+ *
+ * Note that on creation of the LoopingAudioStream object
+ * the underlying stream will be rewound.
+ *
+ * @see makeLoopingAudioStream
+ *
+ * @param stream Stream to loop
+ * @param loops How often to loop (0 = infinite)
+ * @param disposeAfterUse Destroy the stream after the LoopingAudioStream has finished playback.
+ */
+ LoopingAudioStream(RewindableAudioStream *stream, uint loops, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+ ~LoopingAudioStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool endOfData() const;
+
+ bool isStereo() const { return _parent->isStereo(); }
+ int getRate() const { return _parent->getRate(); }
+
+ /**
+ * Returns number of loops the stream has played.
+ *
+ * @param numLoops number of loops to play, 0 - infinite
+ */
+ uint getCompleteIterations() const { return _completeIterations; }
+private:
+ RewindableAudioStream *_parent;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ uint _loops;
+ uint _completeIterations;
+};
+
+/**
+ * Wrapper functionality to efficiently create a stream, which might be looped.
+ *
+ * Note that this function does not return a LoopingAudioStream, because it does
+ * not create one when the loop count is "1". This allows to keep the runtime
+ * overhead down, when the code does not require any functionality only offered
+ * by LoopingAudioStream.
+ *
+ * @param stream Stream to loop (will be automatically destroyed, when the looping is done)
+ * @param loops How often to loop (0 = infinite)
+ * @return A new AudioStream, which offers the desired functionality.
+ */
+AudioStream *makeLoopingAudioStream(RewindableAudioStream *stream, uint loops);
+
+/**
+ * A seekable audio stream. Subclasses of this class implement an
+ * interface for seeking. The seeking itself is not required to be
+ * working while the stream is being played by Mixer!
+ */
+class SeekableAudioStream : public RewindableAudioStream {
+public:
+ /**
+ * Tries to load a file by trying all available formats.
+ * In case of an error, the file handle will be closed, but deleting
+ * it is still the responsibility of the caller.
+ *
+ * @param basename a filename without an extension
+ * @return an SeekableAudioStream ready to use in case of success;
+ * NULL in case of an error (e.g. invalid/nonexisting file)
+ */
+ static SeekableAudioStream *openStreamFile(const Common::String &basename);
+
+ /**
+ * Seeks to a given offset in the stream.
+ *
+ * @param where offset in milliseconds
+ * @return true on success, false on failure.
+ */
+ bool seek(uint32 where) {
+ return seek(Timestamp(where, getRate()));
+ }
+
+ /**
+ * Seeks to a given offset in the stream.
+ *
+ * @param where offset as timestamp
+ * @return true on success, false on failure.
+ */
+ virtual bool seek(const Timestamp &where) = 0;
+
+ /**
+ * Returns the length of the stream.
+ *
+ * @return length as Timestamp.
+ */
+ virtual Timestamp getLength() const = 0;
+
+ virtual bool rewind() { return seek(0); }
+};
+
+/**
+ * Wrapper functionality to efficiently create a stream, which might be looped
+ * in a certain interval.
+ *
+ * This automatically starts the stream at time "start"!
+ *
+ * Note that this function does not return a LoopingAudioStream, because it does
+ * not create one when the loop count is "1". This allows to keep the runtime
+ * overhead down, when the code does not require any functionality only offered
+ * by LoopingAudioStream.
+ *
+ * @param stream Stream to loop (will be automatically destroyed, when the looping is done)
+ * @param start Starttime of the stream interval to be looped
+ * @param end End of the stream interval to be looped (a zero time, means till end)
+ * @param loops How often to loop (0 = infinite)
+ * @return A new AudioStream, which offers the desired functionality.
+ */
+AudioStream *makeLoopingAudioStream(SeekableAudioStream *stream, Timestamp start, Timestamp end, uint loops);
+
+/**
+ * A looping audio stream, which features looping of a nested part of the
+ * stream.
+ *
+ * NOTE:
+ * Currently this implementation stops after the nested loop finished
+ * playback.
+ *
+ * IMPORTANT:
+ * This might be merged with SubSeekableAudioStream for playback purposes.
+ * (After extending it to accept a start time).
+ */
+class SubLoopingAudioStream : public AudioStream {
+public:
+ /**
+ * Constructor for a SubLoopingAudioStream.
+ *
+ * Note that on creation of the SubLoopingAudioStream object
+ * the underlying stream will be rewound.
+ *
+ * @param stream Stream to loop
+ * @param loops How often the stream should be looped (0 means infinite)
+ * @param loopStart Start of the loop (this must be smaller than loopEnd)
+ * @param loopEnd End of the loop (thus must be greater than loopStart)
+ * @param disposeAfterUse Whether the stream should be disposed, when the
+ * SubLoopingAudioStream is destroyed.
+ */
+ SubLoopingAudioStream(SeekableAudioStream *stream, uint loops,
+ const Timestamp loopStart,
+ const Timestamp loopEnd,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+ ~SubLoopingAudioStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool endOfData() const { return _done; }
+
+ bool isStereo() const { return _parent->isStereo(); }
+ int getRate() const { return _parent->getRate(); }
+private:
+ SeekableAudioStream *_parent;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ uint _loops;
+ Timestamp _pos;
+ Timestamp _loopStart, _loopEnd;
+
+ bool _done;
+};
+
+
+/**
+ * A SubSeekableAudioStream provides access to a SeekableAudioStream
+ * just in the range [start, end).
+ * The same caveats apply to SubSeekableAudioStream as do to SeekableAudioStream.
+ *
+ * Manipulating the parent stream directly /will/ mess up a substream.
+ */
+class SubSeekableAudioStream : public SeekableAudioStream {
+public:
+ /**
+ * Creates a new SubSeekableAudioStream.
+ *
+ * @param parent parent stream object.
+ * @param start Start time.
+ * @param end End time.
+ * @param disposeAfterUse Whether the parent stream object should be destroyed on destruction of the SubSeekableAudioStream.
+ */
+ SubSeekableAudioStream(SeekableAudioStream *parent, const Timestamp start, const Timestamp end, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+ ~SubSeekableAudioStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return _parent->isStereo(); }
+
+ int getRate() const { return _parent->getRate(); }
+
+ bool endOfData() const { return (_pos >= _length) || _parent->endOfStream(); }
+
+ bool seek(const Timestamp &where);
+
+ Timestamp getLength() const { return _length; }
+private:
+ SeekableAudioStream *_parent;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ const Timestamp _start;
+ const Timestamp _length;
+ Timestamp _pos;
+};
+
+class QueuingAudioStream : public Audio::AudioStream {
+public:
+
+ /**
+ * Queue an audio stream for playback. This stream plays all queued
+ * streams, in the order they were queued. If disposeAfterUse is set to
+ * DisposeAfterUse::YES, then the queued stream is deleted after all data
+ * contained in it has been played.
+ */
+ virtual void queueAudioStream(Audio::AudioStream *audStream,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES) = 0;
+
+ /**
+ * Queue a block of raw audio data for playback. This stream plays all
+ * queued block, in the order they were queued. If disposeAfterUse is set
+ * to DisposeAfterUse::YES, then the queued block is released using free()
+ * after all data contained in it has been played.
+ *
+ * @note Make sure to allocate the data block with malloc(), not with new[].
+ *
+ * @param data pointer to the audio data block
+ * @param size length of the audio data block
+ * @param disposeAfterUse if equal to DisposeAfterUse::YES, the block is released using free() after use.
+ * @param flags a bit-ORed combination of RawFlags describing the audio data format
+ */
+ void queueBuffer(byte *data, uint32 size, DisposeAfterUse::Flag disposeAfterUse, byte flags);
+
+ /**
+ * 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;
+
+ /**
+ * Return the number of streams still queued for playback (including
+ * the currently playing stream).
+ */
+ virtual uint32 numQueuedStreams() const = 0;
+};
+
+/**
+ * Factory function for an QueuingAudioStream.
+ */
+QueuingAudioStream *makeQueuingAudioStream(int rate, bool stereo);
+
+/**
+ * Converts a point in time to a precise sample offset
+ * with the given parameters.
+ *
+ * @param where Point in time.
+ * @param rate Rate of the stream.
+ * @param isStereo Is the stream a stereo stream?
+ */
+Timestamp convertTimeToStreamPos(const Timestamp &where, int rate, bool isStereo);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
new file mode 100644
index 0000000000..7def89b688
--- /dev/null
+++ b/audio/decoders/adpcm.cpp
@@ -0,0 +1,851 @@
+/* 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/endian.h"
+
+#include "audio/decoders/adpcm.h"
+#include "audio/audiostream.h"
+
+
+namespace Audio {
+
+class ADPCMStream : public RewindableAudioStream {
+protected:
+ Common::SeekableReadStream *_stream;
+ const DisposeAfterUse::Flag _disposeAfterUse;
+ const int32 _startpos;
+ const int32 _endpos;
+ const int _channels;
+ const uint32 _blockAlign;
+ uint32 _blockPos[2];
+ const int _rate;
+
+ struct {
+ // OKI/IMA
+ struct {
+ int32 last;
+ int32 stepIndex;
+ } ima_ch[2];
+ } _status;
+
+ virtual void reset();
+ int16 stepAdjust(byte);
+
+public:
+ ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign);
+ ~ADPCMStream();
+
+ virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); }
+ virtual bool isStereo() const { return _channels == 2; }
+ virtual int getRate() const { return _rate; }
+
+ virtual bool rewind();
+};
+
+// Routines to convert 12 bit linear samples to the
+// Dialogic or Oki ADPCM coding format aka VOX.
+// See also <http://www.comptek.ru/telephony/tnotes/tt1-13.html>
+//
+// IMA ADPCM support is based on
+// <http://wiki.multimedia.cx/index.php?title=IMA_ADPCM>
+//
+// In addition, also MS IMA ADPCM is supported. See
+// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
+
+ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : _stream(stream),
+ _disposeAfterUse(disposeAfterUse),
+ _startpos(stream->pos()),
+ _endpos(_startpos + size),
+ _channels(channels),
+ _blockAlign(blockAlign),
+ _rate(rate) {
+
+ reset();
+}
+
+ADPCMStream::~ADPCMStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _stream;
+}
+
+void ADPCMStream::reset() {
+ memset(&_status, 0, sizeof(_status));
+ _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read
+}
+
+bool ADPCMStream::rewind() {
+ // TODO: Error checking.
+ reset();
+ _stream->seek(_startpos);
+ return true;
+}
+
+
+#pragma mark -
+
+
+class Oki_ADPCMStream : public ADPCMStream {
+public:
+ Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+protected:
+ int16 decodeOKI(byte);
+};
+
+int Oki_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+
+ assert(numSamples % 2 == 0);
+
+ for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ buffer[samples] = decodeOKI((data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeOKI(data & 0x0f);
+ }
+ return samples;
+}
+
+static const int16 okiStepSize[49] = {
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552
+};
+
+// Decode Linear to ADPCM
+int16 Oki_ADPCMStream::decodeOKI(byte code) {
+ int16 diff, E, samp;
+
+ E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8;
+ diff = (code & 0x08) ? -E : E;
+ samp = _status.ima_ch[0].last + diff;
+ // Clip the values to +/- 2^11 (supposed to be 12 bits)
+ samp = CLIP<int16>(samp, -2048, 2047);
+
+ _status.ima_ch[0].last = samp;
+ _status.ima_ch[0].stepIndex += stepAdjust(code);
+ _status.ima_ch[0].stepIndex = CLIP<int32>(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1);
+
+ // * 16 effectively converts 12-bit input to 16-bit output
+ return samp * 16;
+}
+
+
+#pragma mark -
+
+
+class Ima_ADPCMStream : public ADPCMStream {
+protected:
+ int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel
+
+public:
+ Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Ima_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+
+ assert(numSamples % 2 == 0);
+
+ for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ buffer[samples] = decodeIMA((data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0);
+ }
+ return samples;
+}
+
+#pragma mark -
+
+
+class Apple_ADPCMStream : public Ima_ADPCMStream {
+protected:
+ // Apple QuickTime IMA ADPCM
+ int32 _streamPos[2];
+ int16 _buffer[2][2];
+ uint8 _chunkPos[2];
+
+ void reset() {
+ Ima_ADPCMStream::reset();
+ _chunkPos[0] = 0;
+ _chunkPos[1] = 0;
+ _streamPos[0] = 0;
+ _streamPos[1] = _blockAlign;
+ }
+
+public:
+ Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ _chunkPos[0] = 0;
+ _chunkPos[1] = 0;
+ _streamPos[0] = 0;
+ _streamPos[1] = _blockAlign;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+};
+
+int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ // Need to write at least one samples per channel
+ assert((numSamples % _channels) == 0);
+
+ // Current sample positions
+ int samples[2] = { 0, 0};
+
+ // Number of samples per channel
+ int chanSamples = numSamples / _channels;
+
+ for (int i = 0; i < _channels; i++) {
+ _stream->seek(_streamPos[i]);
+
+ while ((samples[i] < chanSamples) &&
+ // Last byte read and a new one needed
+ !((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) {
+
+ if (_blockPos[i] == _blockAlign) {
+ // 2 byte header per block
+ uint16 temp = _stream->readUint16BE();
+
+ // First 9 bits are the upper bits of the predictor
+ _status.ima_ch[i].last = (int16) (temp & 0xFF80);
+ // Lower 7 bits are the step index
+ _status.ima_ch[i].stepIndex = temp & 0x007F;
+
+ // Clip the step index
+ _status.ima_ch[i].stepIndex = CLIP<int32>(_status.ima_ch[i].stepIndex, 0, 88);
+
+ _blockPos[i] = 2;
+ }
+
+ if (_chunkPos[i] == 0) {
+ // Decode data
+ byte data = _stream->readByte();
+ _buffer[i][0] = decodeIMA(data & 0x0F, i);
+ _buffer[i][1] = decodeIMA(data >> 4, i);
+ }
+
+ // The original is interleaved block-wise, we want it sample-wise
+ buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]];
+
+ if (++_chunkPos[i] > 1) {
+ // We're about to decode the next byte, so advance the block position
+ _chunkPos[i] = 0;
+ _blockPos[i]++;
+ }
+
+ samples[i]++;
+
+ if (_channels == 2)
+ if (_blockPos[i] == _blockAlign)
+ // We're at the end of the block.
+ // Since the channels are interleaved, skip the next block
+ _stream->skip(MIN<uint32>(_blockAlign, _endpos - _stream->pos()));
+
+ _streamPos[i] = _stream->pos();
+ }
+ }
+
+ return samples[0] + samples[1];
+}
+
+#pragma mark -
+
+
+class MSIma_ADPCMStream : public Ima_ADPCMStream {
+public:
+ MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign, bool invertSamples = false)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign), _invertSamples(invertSamples) {
+ if (blockAlign == 0)
+ error("ADPCMStream(): blockAlign isn't specified for MS IMA ADPCM");
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples) {
+ if (_channels == 1)
+ return readBufferMSIMA1(buffer, numSamples);
+ else
+ return readBufferMSIMA2(buffer, numSamples);
+ }
+
+ int readBufferMSIMA1(int16 *buffer, const int numSamples);
+ int readBufferMSIMA2(int16 *buffer, const int numSamples);
+
+private:
+ bool _invertSamples; // Some implementations invert the way samples are decoded
+};
+
+int MSIma_ADPCMStream::readBufferMSIMA1(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ byte data;
+
+ assert(numSamples % 2 == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ // read block header
+ _status.ima_ch[0].last = _stream->readSint16LE();
+ _status.ima_ch[0].stepIndex = _stream->readSint16LE();
+ _blockPos[0] = 4;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ _blockPos[0]++;
+ buffer[samples] = decodeIMA(_invertSamples ? (data >> 4) & 0x0f : data & 0x0f);
+ buffer[samples + 1] = decodeIMA(_invertSamples ? data & 0x0f : (data >> 4) & 0x0f);
+ }
+ }
+ return samples;
+}
+
+
+// Microsoft as usual tries to implement it differently. This method
+// is used for stereo data.
+int MSIma_ADPCMStream::readBufferMSIMA2(int16 *buffer, const int numSamples) {
+ int samples;
+ uint32 data;
+ int nibble;
+ byte k;
+
+ // TODO: Currently this implementation only supports
+ // reading a multiple of 16 samples at once. We might
+ // consider changing that so it could read an arbitrary
+ // sample pair count.
+ assert(numSamples % 16 == 0);
+
+ for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos;) {
+ for (int channel = 0; channel < 2; channel++) {
+ data = _stream->readUint32LE();
+
+ for (nibble = 0; nibble < 8; nibble++) {
+ k = ((data & 0xf0000000) >> 28);
+ buffer[samples + channel + nibble * 2] = decodeIMA(k);
+ data <<= 4;
+ }
+ }
+ samples += 16;
+ }
+ return samples;
+}
+
+
+#pragma mark -
+
+
+static const int MSADPCMAdaptCoeff1[] = {
+ 256, 512, 0, 192, 240, 460, 392
+};
+
+static const int MSADPCMAdaptCoeff2[] = {
+ 0, -256, 0, 64, 0, -208, -232
+};
+
+static const int MSADPCMAdaptationTable[] = {
+ 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230
+};
+
+
+class MS_ADPCMStream : public ADPCMStream {
+protected:
+ struct ADPCMChannelStatus {
+ byte predictor;
+ int16 delta;
+ int16 coeff1;
+ int16 coeff2;
+ int16 sample1;
+ int16 sample2;
+ };
+
+ struct {
+ // MS ADPCM
+ ADPCMChannelStatus ch[2];
+ } _status;
+
+ void reset() {
+ ADPCMStream::reset();
+ memset(&_status, 0, sizeof(_status));
+ }
+
+public:
+ MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ if (blockAlign == 0)
+ error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+protected:
+ int16 decodeMS(ADPCMChannelStatus *c, byte);
+};
+
+int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) {
+ int32 predictor;
+
+ predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256;
+ predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta;
+
+ predictor = CLIP<int32>(predictor, -32768, 32767);
+
+ c->sample2 = c->sample1;
+ c->sample1 = predictor;
+ c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8;
+
+ if (c->delta < 16)
+ c->delta = 16;
+
+ return (int16)predictor;
+}
+
+int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+ int i = 0;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ // read block header
+ for (i = 0; i < _channels; i++) {
+ _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
+ _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
+ _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
+ }
+
+ for (i = 0; i < _channels; i++)
+ _status.ch[i].delta = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ _status.ch[i].sample1 = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ buffer[samples++] = _status.ch[i].sample1;
+
+ _blockPos[0] = _channels * 7;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ _blockPos[0]++;
+ buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
+ }
+ }
+
+ return samples;
+}
+
+
+
+#pragma mark -
+
+
+class Tinsel_ADPCMStream : public ADPCMStream {
+protected:
+ struct {
+ // Tinsel
+ double predictor;
+ double K0, K1;
+ double d0, d1;
+ } _status;
+
+ void reset() {
+ ADPCMStream::reset();
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ int16 decodeTinsel(int16, double);
+ void readBufferTinselHeader();
+
+public:
+ Tinsel_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+
+ if (blockAlign == 0)
+ error("Tinsel_ADPCMStream(): blockAlign isn't specified");
+
+ if (channels != 1)
+ error("Tinsel_ADPCMStream(): Tinsel ADPCM only supports mono");
+
+ memset(&_status, 0, sizeof(_status));
+ }
+
+};
+
+static const double TinselFilterTable[4][2] = {
+ {0, 0 },
+ {0.9375, 0},
+ {1.796875, -0.8125},
+ {1.53125, -0.859375}
+};
+
+void Tinsel_ADPCMStream::readBufferTinselHeader() {
+ uint8 start = _stream->readByte();
+ uint8 filterVal = (start & 0xC0) >> 6;
+
+ if ((start & 0x20) != 0) {
+ //Lower 6 bit are negative
+
+ // Negate
+ start = ~(start | 0xC0) + 1;
+
+ _status.predictor = 1 << start;
+ } else {
+ // Lower 6 bit are positive
+
+ // Truncate
+ start &= 0x1F;
+
+ _status.predictor = ((double) 1.0) / (1 << start);
+ }
+
+ _status.K0 = TinselFilterTable[filterVal][0];
+ _status.K1 = TinselFilterTable[filterVal][1];
+}
+
+int16 Tinsel_ADPCMStream::decodeTinsel(int16 code, double eVal) {
+ double sample;
+
+ sample = (double) code;
+ sample *= eVal * _status.predictor;
+ sample += (_status.d0 * _status.K0) + (_status.d1 * _status.K1);
+
+ _status.d1 = _status.d0;
+ _status.d0 = sample;
+
+ return (int16) CLIP<double>(sample, -32768.0, 32767.0);
+}
+
+class Tinsel4_ADPCMStream : public Tinsel_ADPCMStream {
+public:
+ Tinsel4_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel4_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ uint16 data;
+ const double eVal = 1.142822265;
+
+ samples = 0;
+
+ assert(numSamples % 2 == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2, _blockPos[0]++) {
+ // Read 1 byte = 8 bits = two 4 bit blocks
+ data = _stream->readByte();
+ buffer[samples] = decodeTinsel((data << 8) & 0xF000, eVal);
+ buffer[samples+1] = decodeTinsel((data << 12) & 0xF000, eVal);
+ }
+ }
+
+ return samples;
+}
+
+class Tinsel6_ADPCMStream : public Tinsel_ADPCMStream {
+protected:
+ uint8 _chunkPos;
+ uint16 _chunkData;
+
+ void reset() {
+ ADPCMStream::reset();
+ _chunkPos = 0;
+ _chunkData = 0;
+ }
+
+public:
+ Tinsel6_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ _chunkPos = 0;
+ _chunkData = 0;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel6_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ const double eVal = 1.032226562;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ _chunkPos = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _chunkPos = (_chunkPos + 1) % 4) {
+
+ switch (_chunkPos) {
+ case 0:
+ _chunkData = _stream->readByte();
+ buffer[samples] = decodeTinsel((_chunkData << 8) & 0xFC00, eVal);
+ break;
+ case 1:
+ _chunkData = (_chunkData << 8) | (_stream->readByte());
+ buffer[samples] = decodeTinsel((_chunkData << 6) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ case 2:
+ _chunkData = (_chunkData << 8) | (_stream->readByte());
+ buffer[samples] = decodeTinsel((_chunkData << 4) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ case 3:
+ _chunkData = (_chunkData << 8);
+ buffer[samples] = decodeTinsel((_chunkData << 2) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ }
+
+ }
+
+ }
+
+ return samples;
+}
+
+class Tinsel8_ADPCMStream : public Tinsel_ADPCMStream {
+public:
+ Tinsel8_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel8_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+ const double eVal = 1.007843258;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _blockPos[0]++) {
+ // Read 1 byte = 8 bits = one 8 bit block
+ data = _stream->readByte();
+ buffer[samples] = decodeTinsel(data << 8, eVal);
+ }
+ }
+
+ return samples;
+}
+
+
+#pragma mark -
+
+// Duck DK3 IMA ADPCM Decoder
+// Based on FFmpeg's decoder and http://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM
+
+class DK3_ADPCMStream : public Ima_ADPCMStream {
+protected:
+
+ void reset() {
+ Ima_ADPCMStream::reset();
+ _topNibble = false;
+ }
+
+public:
+ DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+
+ // DK3 only works as a stereo stream
+ assert(channels == 2);
+ _topNibble = false;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+private:
+ byte _nibble, _lastByte;
+ bool _topNibble;
+};
+
+#define DK3_READ_NIBBLE() \
+do { \
+ if (_topNibble) { \
+ _nibble = _lastByte >> 4; \
+ _topNibble = false; \
+ } else { \
+ if (_stream->pos() >= _endpos) \
+ break; \
+ if ((_stream->pos() % _blockAlign) == 0) \
+ continue; \
+ _lastByte = _stream->readByte(); \
+ _nibble = _lastByte & 0xf; \
+ _topNibble = true; \
+ } \
+} while (0)
+
+
+int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+
+ assert((numSamples % 4) == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if ((_stream->pos() % _blockAlign) == 0) {
+ _stream->readUint16LE(); // Unknown
+ uint16 rate = _stream->readUint16LE(); // Copy of rate
+ _stream->skip(6); // Unknown
+ // Get predictor for both sum/diff channels
+ _status.ima_ch[0].last = _stream->readSint16LE();
+ _status.ima_ch[1].last = _stream->readSint16LE();
+ // Get index for both sum/diff channels
+ _status.ima_ch[0].stepIndex = _stream->readByte();
+ _status.ima_ch[1].stepIndex = _stream->readByte();
+
+ if (_stream->eos())
+ break;
+
+ // Sanity check
+ assert(rate == getRate());
+ }
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 0);
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 1);
+
+ buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last;
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 0);
+
+ buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last;
+ }
+
+ return samples;
+}
+
+
+#pragma mark -
+
+
+// adjust the step for use on the next sample.
+int16 ADPCMStream::stepAdjust(byte code) {
+ static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8};
+
+ return adjusts[code & 0x07];
+}
+
+static const uint16 imaStepTable[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493,10442,11487,12635,13899,
+ 15289,16818,18500,20350,22385,24623,27086,29794,
+ 32767
+};
+
+int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
+ int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[channel].stepIndex] / 8;
+ int32 diff = (code & 0x08) ? -E : E;
+ int32 samp = CLIP<int32>(_status.ima_ch[channel].last + diff, -32768, 32767);
+
+ _status.ima_ch[channel].last = samp;
+ _status.ima_ch[channel].stepIndex += stepAdjust(code);
+ _status.ima_ch[channel].stepIndex = CLIP<int32>(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1);
+
+ return samp;
+}
+
+RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
+ // If size is 0, report the entire size of the stream
+ if (!size)
+ size = stream->size();
+
+ switch (type) {
+ case kADPCMOki:
+ return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMMSIma:
+ return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMMSImaLastExpress:
+ return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign, true);
+ case kADPCMMS:
+ return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel4:
+ return new Tinsel4_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel6:
+ return new Tinsel6_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel8:
+ return new Tinsel8_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMIma:
+ return new Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMApple:
+ return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMDK3:
+ return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ default:
+ error("Unsupported ADPCM encoding");
+ break;
+ }
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h
new file mode 100644
index 0000000000..38ec870a27
--- /dev/null
+++ b/audio/decoders/adpcm.h
@@ -0,0 +1,90 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - lastexpress
+ * - mohawk
+ * - saga
+ * - scumm
+ * - tinsel
+ */
+
+#ifndef SOUND_ADPCM_H
+#define SOUND_ADPCM_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+
+
+namespace Audio {
+
+class AudioStream;
+class RewindableAudioStream;
+
+// There are several types of ADPCM encoding, only some are supported here
+// For all the different encodings, refer to:
+// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
+// Usually, if the audio stream we're trying to play has the FourCC header
+// string intact, it's easy to discern which encoding is used
+enum typesADPCM {
+ kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
+ kADPCMMSIma, // Microsoft IMA ADPCM
+ kADPCMMSImaLastExpress, // Microsoft IMA ADPCM (with inverted samples)
+ kADPCMMS, // Microsoft ADPCM
+ kADPCMTinsel4, // 4-bit ADPCM used by the Tinsel engine
+ kADPCMTinsel6, // 6-bit ADPCM used by the Tinsel engine
+ kADPCMTinsel8, // 8-bit ADPCM used by the Tinsel engine
+ kADPCMIma, // Standard IMA ADPCM
+ kADPCMApple, // Apple QuickTime IMA ADPCM
+ kADPCMDK3 // Duck DK3 IMA ADPCM
+};
+
+/**
+ * Takes an input stream containing ADPCM compressed sound data and creates
+ * an RewindableAudioStream from that.
+ *
+ * @param stream the SeekableReadStream from which to read the ADPCM data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @param size how many bytes to read from the stream (0 = all)
+ * @param type the compression type used
+ * @param rate the sampling rate
+ * @param channels the number of channels
+ * @param blockAlign block alignment ???
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeADPCMStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse,
+ uint32 size, typesADPCM type,
+ int rate = 22050,
+ int channels = 2,
+ uint32 blockAlign = 0);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp
new file mode 100644
index 0000000000..0f947752f6
--- /dev/null
+++ b/audio/decoders/aiff.cpp
@@ -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 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$
+ *
+ */
+
+/*
+ * The code in this file is based on information found at
+ * http://www.borg.com/~jglatt/tech/aiff.htm
+ *
+ * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX
+ * (http://sox.sourceforge.net) may be a good place to start from.
+ */
+
+#include "common/endian.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/decoders/aiff.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+uint32 readExtended(Common::SeekableReadStream &stream) {
+ // The sample rate is stored as an "80 bit IEEE Standard 754 floating
+ // point number (Standard Apple Numeric Environment [SANE] data type
+ // Extended).
+
+ byte buf[10];
+ uint32 mantissa;
+ uint32 last = 0;
+ byte exp;
+
+ stream.read(buf, 10);
+ mantissa = READ_BE_UINT32(buf + 2);
+ exp = 30 - buf[1];
+
+ while (exp--) {
+ last = mantissa;
+ mantissa >>= 1;
+ }
+
+ if (last & 0x00000001)
+ mantissa++;
+
+ return mantissa;
+}
+
+bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) {
+ byte buf[4];
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "FORM", 4) != 0) {
+ warning("loadAIFFFromStream: No 'FORM' header");
+ return false;
+ }
+
+ stream.readUint32BE();
+
+ // This could be AIFC, but we don't handle that case.
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "AIFF", 4) != 0) {
+ warning("loadAIFFFromStream: No 'AIFF' header");
+ return false;
+ }
+
+ // From here on, we only care about the COMM and SSND chunks, which are
+ // the only required chunks.
+
+ bool foundCOMM = false;
+ bool foundSSND = false;
+
+ uint16 numChannels = 0, bitsPerSample = 0;
+ uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0;
+
+ while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) {
+ uint32 length, pos;
+
+ stream.read(buf, 4);
+ length = stream.readUint32BE();
+ pos = stream.pos();
+
+ if (memcmp(buf, "COMM", 4) == 0) {
+ foundCOMM = true;
+ numChannels = stream.readUint16BE();
+ numSampleFrames = stream.readUint32BE();
+ bitsPerSample = stream.readUint16BE();
+ rate = readExtended(stream);
+ size = numSampleFrames * numChannels * (bitsPerSample / 8);
+ } else if (memcmp(buf, "SSND", 4) == 0) {
+ foundSSND = true;
+ offset = stream.readUint32BE();
+ blockSize = stream.readUint32BE();
+ soundOffset = stream.pos();
+ }
+
+ stream.seek(pos + length);
+ }
+
+ if (!foundCOMM) {
+ warning("loadAIFFFromStream: Cound not find 'COMM' chunk");
+ return false;
+ }
+
+ if (!foundSSND) {
+ warning("loadAIFFFromStream: Cound not find 'SSND' chunk");
+ return false;
+ }
+
+ // We only implement a subset of the AIFF standard.
+
+ if (numChannels < 1 || numChannels > 2) {
+ warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels);
+ return false;
+ }
+
+ if (bitsPerSample != 8 && bitsPerSample != 16) {
+ warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample);
+ return false;
+ }
+
+ if (offset != 0 || blockSize != 0) {
+ warning("loadAIFFFromStream: Block-aligned data is not supported");
+ return false;
+ }
+
+ // Samples are always signed, and big endian.
+
+ flags = 0;
+ if (bitsPerSample == 16)
+ flags |= Audio::FLAG_16BITS;
+ if (numChannels == 2)
+ flags |= Audio::FLAG_STEREO;
+
+ stream.seek(soundOffset);
+
+ // Stream now points at the sample data
+
+ return true;
+}
+
+SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ int size, rate;
+ byte *data, flags;
+
+ if (!loadAIFFFromStream(*stream, size, rate, flags)) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
+ return makeRawStream(data, size, rate, flags);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h
new file mode 100644
index 0000000000..06c56ecd38
--- /dev/null
+++ b/audio/decoders/aiff.h
@@ -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 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - saga
+ * - sci
+ * - sword1
+ */
+
+#ifndef SOUND_AIFF_H
+#define SOUND_AIFF_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class SeekableAudioStream;
+
+/**
+ * Try to load an AIFF from the given seekable stream. Returns true if
+ * successful. In that case, the stream's seek position will be set to the
+ * start of the audio data, and size, rate and flags contain information
+ * necessary for playback. Currently this function only supports uncompressed
+ * raw PCM data as well as IMA ADPCM.
+ */
+extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
+
+/**
+ * Try to load an AIFF from the given seekable stream and create an AudioStream
+ * from that data.
+ *
+ * This function uses loadAIFFFromStream() internally.
+ *
+ * @param stream the SeekableReadStream from which to read the AIFF data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeAIFFStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/flac.cpp b/audio/decoders/flac.cpp
new file mode 100644
index 0000000000..76b6d35419
--- /dev/null
+++ b/audio/decoders/flac.cpp
@@ -0,0 +1,745 @@
+/* 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$
+ *
+ */
+
+// Disable symbol overrides for FILE as that is used in FLAC headers
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+
+#include "audio/decoders/flac.h"
+
+#ifdef USE_FLAC
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like
+#include <FLAC/export.h>
+
+
+// check if we have FLAC >= 1.1.3; LEGACY_FLAC code can be removed once FLAC-1.1.3 propagates everywhere
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8
+#define LEGACY_FLAC
+#else
+#undef LEGACY_FLAC
+#endif
+
+
+#ifdef LEGACY_FLAC
+
+// Before FLAC 1.1.3, we needed to use the stream decoder API.
+#include <FLAC/seekable_stream_decoder.h>
+typedef uint FLAC_size_t;
+
+#else
+
+// With FLAC 1.1.3, the stream decoder API was merged into the regular
+// stream API. In order to stay compatible with older FLAC versions, we
+// simply add some typedefs and #ifdefs to map between the old and new API.
+// We use the typedefs (instead of only #defines) in order to somewhat
+// improve the readability of the code.
+
+#include <FLAC/stream_decoder.h>
+typedef size_t FLAC_size_t;
+// Add aliases for the old names
+typedef FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState;
+typedef FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus;
+typedef FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus;
+typedef FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus;
+typedef FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus;
+typedef FLAC__StreamDecoder FLAC__SeekableStreamDecoder;
+
+#endif
+
+
+namespace Audio {
+
+#pragma mark -
+#pragma mark --- FLAC stream ---
+#pragma mark -
+
+static const uint MAX_OUTPUT_CHANNELS = 2;
+
+
+class FLACStream : public SeekableAudioStream {
+protected:
+ Common::SeekableReadStream *_inStream;
+ bool _disposeAfterUse;
+
+ ::FLAC__SeekableStreamDecoder *_decoder;
+
+ /** Header of the stream */
+ FLAC__StreamMetadata_StreamInfo _streaminfo;
+
+ /** index + 1(!) of the last sample to be played */
+ FLAC__uint64 _lastSample;
+
+ /** total play time */
+ Timestamp _length;
+
+ /** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */
+ bool _lastSampleWritten;
+
+ typedef int16 SampleType;
+ enum { BUFTYPE_BITS = 16 };
+
+ enum {
+ // Maximal buffer size. According to the FLAC format specification, the block size is
+ // a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe).
+ BUFFER_SIZE = 65536
+ };
+
+ struct {
+ SampleType bufData[BUFFER_SIZE];
+ SampleType *bufReadPos;
+ uint bufFill;
+ } _sampleCache;
+
+ SampleType *_outBuffer;
+ uint _requestedSamples;
+
+ typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8);
+ PFCONVERTBUFFERS _methodConvertBuffers;
+
+
+public:
+ FLACStream(Common::SeekableReadStream *inStream, bool dispose);
+ virtual ~FLACStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return _streaminfo.channels >= 2; }
+ int getRate() const { return _streaminfo.sample_rate; }
+ bool endOfData() const {
+ // End of data is reached if there either is no valid stream data available,
+ // or if we reached the last sample and completely emptied the sample cache.
+ return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0);
+ }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+
+ bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; }
+protected:
+ uint getChannels() const { return MIN<uint>(_streaminfo.channels, MAX_OUTPUT_CHANNELS); }
+
+ bool allocateBuffer(uint minSamples);
+
+ inline FLAC__StreamDecoderState getStreamDecoderState() const;
+
+ inline bool processSingleBlock();
+ inline bool processUntilEndOfMetadata();
+ bool seekAbsolute(FLAC__uint64 sample);
+
+ inline ::FLAC__SeekableStreamDecoderReadStatus callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes);
+ inline ::FLAC__SeekableStreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset);
+ inline ::FLAC__SeekableStreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset);
+ inline ::FLAC__SeekableStreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength);
+ inline bool callbackEOF();
+ inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
+ inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata);
+ inline void callbackError(::FLAC__StreamDecoderErrorStatus status);
+
+private:
+ static ::FLAC__SeekableStreamDecoderReadStatus callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData);
+ static ::FLAC__SeekableStreamDecoderSeekStatus callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData);
+ static ::FLAC__SeekableStreamDecoderTellStatus callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData);
+ static ::FLAC__SeekableStreamDecoderLengthStatus callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData);
+ static FLAC__bool callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData);
+ static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData);
+ static void callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData);
+ static void callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData);
+
+ void setBestConvertBufferMethod();
+ static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+};
+
+FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose)
+#ifdef LEGACY_FLAC
+ : _decoder(::FLAC__seekable_stream_decoder_new()),
+#else
+ : _decoder(::FLAC__stream_decoder_new()),
+#endif
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _length(0, 1000), _lastSample(0),
+ _outBuffer(NULL), _requestedSamples(0), _lastSampleWritten(false),
+ _methodConvertBuffers(&FLACStream::convertBuffersGeneric)
+{
+ assert(_inStream);
+ memset(&_streaminfo, 0, sizeof(_streaminfo));
+
+ _sampleCache.bufReadPos = NULL;
+ _sampleCache.bufFill = 0;
+
+ _methodConvertBuffers = &FLACStream::convertBuffersGeneric;
+
+ bool success;
+#ifdef LEGACY_FLAC
+ ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FLACStream::callWrapRead);
+ ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FLACStream::callWrapSeek);
+ ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FLACStream::callWrapTell);
+ ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FLACStream::callWrapLength);
+ ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FLACStream::callWrapEOF);
+ ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FLACStream::callWrapWrite);
+ ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FLACStream::callWrapMetadata);
+ ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FLACStream::callWrapError);
+ ::FLAC__seekable_stream_decoder_set_client_data(_decoder, (void*)this);
+
+ success = (::FLAC__seekable_stream_decoder_init(_decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK);
+#else
+ success = (::FLAC__stream_decoder_init_stream(
+ _decoder,
+ &FLACStream::callWrapRead,
+ &FLACStream::callWrapSeek,
+ &FLACStream::callWrapTell,
+ &FLACStream::callWrapLength,
+ &FLACStream::callWrapEOF,
+ &FLACStream::callWrapWrite,
+ &FLACStream::callWrapMetadata,
+ &FLACStream::callWrapError,
+ (void*)this
+ ) == FLAC__STREAM_DECODER_INIT_STATUS_OK);
+#endif
+ if (success) {
+ if (processUntilEndOfMetadata() && _streaminfo.channels > 0) {
+ _lastSample = _streaminfo.total_samples + 1;
+ _length = Timestamp(0, _lastSample - 1, getRate());
+ return; // no error occurred
+ }
+ }
+
+ warning("FLACStream: could not create audio stream");
+}
+
+FLACStream::~FLACStream() {
+ if (_decoder != NULL) {
+#ifdef LEGACY_FLAC
+ (void) ::FLAC__seekable_stream_decoder_finish(_decoder);
+ ::FLAC__seekable_stream_decoder_delete(_decoder);
+#else
+ (void) ::FLAC__stream_decoder_finish(_decoder);
+ ::FLAC__stream_decoder_delete(_decoder);
+#endif
+ }
+ if (_disposeAfterUse)
+ delete _inStream;
+}
+
+inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return ::FLAC__seekable_stream_decoder_get_stream_decoder_state(_decoder);
+#else
+ return ::FLAC__stream_decoder_get_state(_decoder);
+#endif
+}
+
+inline bool FLACStream::processSingleBlock() {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return 0 != ::FLAC__seekable_stream_decoder_process_single(_decoder);
+#else
+ return 0 != ::FLAC__stream_decoder_process_single(_decoder);
+#endif
+}
+
+inline bool FLACStream::processUntilEndOfMetadata() {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return 0 != ::FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder);
+#else
+ return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder);
+#endif
+}
+
+bool FLACStream::seekAbsolute(FLAC__uint64 sample) {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ const bool result = (0 != ::FLAC__seekable_stream_decoder_seek_absolute(_decoder, sample));
+#else
+ const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample));
+#endif
+ if (result) {
+ _lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE
+ }
+ return result;
+}
+
+bool FLACStream::seek(const Timestamp &where) {
+ _sampleCache.bufFill = 0;
+ _sampleCache.bufReadPos = NULL;
+ // FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter
+ // of the convertTimeToStreamPos helper.
+ return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
+}
+
+int FLACStream::readBuffer(int16 *buffer, const int numSamples) {
+ const uint numChannels = getChannels();
+
+ if (numChannels == 0) {
+ warning("FLACStream: Stream not successfully initialised, cant playback");
+ return -1; // streaminfo wasnt read!
+ }
+
+ assert(numSamples % numChannels == 0); // must be multiple of channels!
+ assert(buffer != NULL);
+ assert(_outBuffer == NULL);
+ assert(_requestedSamples == 0);
+
+ _outBuffer = buffer;
+ _requestedSamples = numSamples;
+
+ // If there is still data in our buffer from the last time around,
+ // copy that first.
+ if (_sampleCache.bufFill > 0) {
+ assert(_sampleCache.bufReadPos >= _sampleCache.bufData);
+ assert(_sampleCache.bufFill % numChannels == 0);
+
+ const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill);
+ memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0]));
+
+ _outBuffer = buffer + copySamples;
+ _requestedSamples = numSamples - copySamples;
+ _sampleCache.bufReadPos += copySamples;
+ _sampleCache.bufFill -= copySamples;
+ }
+
+ bool decoderOk = true;
+
+ FLAC__StreamDecoderState state = getStreamDecoderState();
+
+ // Keep poking FLAC to process more samples until we completely satisfied the request
+ // respectively until we run out of data.
+ while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) {
+ assert(_sampleCache.bufFill == 0);
+ assert(_requestedSamples % numChannels == 0);
+ processSingleBlock();
+ state = getStreamDecoderState();
+
+ if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
+ _lastSampleWritten = true;
+ }
+
+ // Error handling
+ switch (state) {
+ case FLAC__STREAM_DECODER_END_OF_STREAM:
+ _lastSampleWritten = true;
+ break;
+ case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
+ break;
+ default:
+ decoderOk = false;
+ warning("FLACStream: An error occurred while decoding. DecoderState is: %s",
+ FLAC__StreamDecoderStateString[getStreamDecoderState()]);
+ }
+
+ // Compute how many samples we actually produced
+ const int samples = (int)(_outBuffer - buffer);
+ assert(samples % numChannels == 0);
+
+ _outBuffer = NULL; // basically unnecessary, only for the purpose of the asserts
+ _requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts
+
+ return decoderOk ? samples : -1;
+}
+
+inline ::FLAC__SeekableStreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes) {
+ if (*bytes == 0) {
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; /* abort to avoid a deadlock */
+#else
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */
+#endif
+ }
+
+ const uint32 bytesRead = _inStream->read(buffer, *bytes);
+
+ if (bytesRead == 0) {
+#ifdef LEGACY_FLAC
+ return _inStream->eos() ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
+#else
+ return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+#endif
+ }
+
+ *bytes = static_cast<uint>(bytesRead);
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+#endif
+}
+
+void FLACStream::setBestConvertBufferMethod() {
+ PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric;
+
+ const uint numChannels = getChannels();
+ const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
+
+ assert(numChannels >= 1);
+ assert(numBits >= 4 && numBits <=32);
+
+ if (numChannels == 1) {
+ if (numBits == 8)
+ tempMethod = &FLACStream::convertBuffersMono8Bit;
+ if (numBits == BUFTYPE_BITS)
+ tempMethod = &FLACStream::convertBuffersMonoNS;
+ } else if (numChannels == 2) {
+ if (numBits == 8)
+ tempMethod = &FLACStream::convertBuffersStereo8Bit;
+ if (numBits == BUFTYPE_BITS)
+ tempMethod = &FLACStream::convertBuffersStereoNS;
+ } /* else ... */
+
+ _methodConvertBuffers = tempMethod;
+}
+
+// 1 channel, no scaling
+void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 1);
+ assert(numBits == BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0];
+
+ while (numSamples >= 4) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
+ bufDestination[1] = static_cast<SampleType>(inChannel1[1]);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[2]);
+ bufDestination[3] = static_cast<SampleType>(inChannel1[3]);
+ bufDestination += 4;
+ inChannel1 += 4;
+ numSamples -= 4;
+ }
+
+ for (; numSamples > 0; --numSamples) {
+ *bufDestination++ = static_cast<SampleType>(*inChannel1++);
+ }
+
+ inChannels[0] = inChannel1;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 1 channel, scaling from 8Bit
+void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 1);
+ assert(numBits == 8);
+ assert(8 < BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0];
+
+ while (numSamples >= 4) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[2]) << (BUFTYPE_BITS - 8);
+ bufDestination[3] = static_cast<SampleType>(inChannel1[3]) << (BUFTYPE_BITS - 8);
+ bufDestination += 4;
+ inChannel1 += 4;
+ numSamples -= 4;
+ }
+
+ for (; numSamples > 0; --numSamples) {
+ *bufDestination++ = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
+ }
+
+ inChannels[0] = inChannel1;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 2 channels, no scaling
+void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 2);
+ assert(numBits == BUFTYPE_BITS);
+ assert(numSamples % 2 == 0); // must be integral multiply of channels
+
+
+ FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
+ FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
+
+ while (numSamples >= 2*2) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
+ bufDestination[1] = static_cast<SampleType>(inChannel2[0]);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[1]);
+ bufDestination[3] = static_cast<SampleType>(inChannel2[1]);
+ bufDestination += 2 * 2;
+ inChannel1 += 2;
+ inChannel2 += 2;
+ numSamples -= 2 * 2;
+ }
+
+ while (numSamples > 0) {
+ bufDestination[0] = static_cast<SampleType>(*inChannel1++);
+ bufDestination[1] = static_cast<SampleType>(*inChannel2++);
+ bufDestination += 2;
+ numSamples -= 2;
+ }
+
+ inChannels[0] = inChannel1;
+ inChannels[1] = inChannel2;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 2 channels, scaling from 8Bit
+void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 2);
+ assert(numBits == 8);
+ assert(numSamples % 2 == 0); // must be integral multiply of channels
+ assert(8 < BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
+ FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
+
+ while (numSamples >= 2*2) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(inChannel2[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
+ bufDestination[3] = static_cast<SampleType>(inChannel2[1]) << (BUFTYPE_BITS - 8);
+ bufDestination += 2 * 2;
+ inChannel1 += 2;
+ inChannel2 += 2;
+ numSamples -= 2 * 2;
+ }
+
+ while (numSamples > 0) {
+ bufDestination[0] = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(*inChannel2++) << (BUFTYPE_BITS - 8);
+ bufDestination += 2;
+ numSamples -= 2;
+ }
+
+ inChannels[0] = inChannel1;
+ inChannels[1] = inChannel2;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// all Purpose-conversion - slowest of em all
+void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numSamples % numChannels == 0); // must be integral multiply of channels
+
+ if (numBits < BUFTYPE_BITS) {
+ const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits);
+
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++)) << kPower;
+ }
+ } else if (numBits > BUFTYPE_BITS) {
+ const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS);
+
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++) >> kPower);
+ }
+ } else {
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++));
+ }
+ }
+
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {
+ assert(frame->header.channels == _streaminfo.channels);
+ assert(frame->header.sample_rate == _streaminfo.sample_rate);
+ assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample);
+ assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize);
+
+ // We require that either the sample cache is empty, or that no samples were requested
+ assert(_sampleCache.bufFill == 0 || _requestedSamples == 0);
+
+ uint numSamples = frame->header.blocksize;
+ const uint numChannels = getChannels();
+ const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
+
+ assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels
+
+ const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ?
+ frame->header.number.sample_number : (static_cast<FLAC__uint64>(frame->header.number.frame_number)) * _streaminfo.max_blocksize;
+
+ // Check whether we are about to reach beyond the last sample we are supposed to play.
+ if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) {
+ numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber);
+ _lastSampleWritten = true;
+ }
+
+ // The value in _requestedSamples counts raw samples, so if there are more than one
+ // channel, we have to multiply the number of available sample "pairs" by numChannels
+ numSamples *= numChannels;
+
+ const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS];
+ for (uint i = 0; i < numChannels; ++i)
+ inChannels[i] = buffer[i];
+
+ // write the incoming samples directly into the buffer provided to us by the mixer
+ if (_requestedSamples > 0) {
+ assert(_requestedSamples % numChannels == 0);
+ assert(_outBuffer != NULL);
+
+ // Copy & convert the available samples (limited both by how many we have available, and
+ // by how many are actually needed).
+ const uint copySamples = MIN(_requestedSamples, numSamples);
+ (*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits);
+
+ _requestedSamples -= copySamples;
+ numSamples -= copySamples;
+ _outBuffer += copySamples;
+ }
+
+ // Write all remaining samples (i.e. those which didn't fit into the mixer buffer)
+ // into the sample cache.
+ if (_sampleCache.bufFill == 0)
+ _sampleCache.bufReadPos = _sampleCache.bufData;
+ const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill);
+ assert(numSamples <= cacheSpace);
+ (*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits);
+
+ _sampleCache.bufFill += numSamples;
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+inline ::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) {
+ _inStream->seek(absoluteByteOffset, SEEK_SET);
+ const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos());
+
+#ifdef LEGACY_FLAC
+ return result ? FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
+#else
+ return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+#endif
+}
+
+inline ::FLAC__SeekableStreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) {
+ *absoluteByteOffset = static_cast<FLAC__uint64>(_inStream->pos());
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+#endif
+}
+
+inline ::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) {
+ *streamLength = static_cast<FLAC__uint64>(_inStream->size());
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+#endif
+}
+
+inline bool FLACStream::callbackEOF() {
+ return _inStream->eos();
+}
+
+
+inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) {
+ assert(_decoder != NULL);
+ assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting
+
+ _streaminfo = metadata->data.stream_info;
+ setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first
+}
+inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) {
+ // some of these are non-critical-Errors
+ debug(1, "FLACStream: An error occurred while decoding. DecoderState is: %s",
+ FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+/* Static Callback Wrappers */
+::FLAC__SeekableStreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackRead(buffer, bytes);
+}
+
+::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackSeek(absoluteByteOffset);
+}
+
+::FLAC__SeekableStreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackTell(absoluteByteOffset);
+}
+
+::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackLength(streamLength);
+}
+
+FLAC__bool FLACStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackEOF();
+}
+
+::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackWrite(frame, buffer);
+}
+
+void FLACStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ instance->callbackMetadata(metadata);
+}
+
+void FLACStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ instance->callbackError(status);
+}
+
+
+#pragma mark -
+#pragma mark --- FLAC factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeFLACStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse);
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_FLAC
diff --git a/audio/decoders/flac.h b/audio/decoders/flac.h
new file mode 100644
index 0000000000..17f95ec1fb
--- /dev/null
+++ b/audio/decoders/flac.h
@@ -0,0 +1,75 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_FLAC_H
+#define SOUND_FLAC_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_FLAC
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the FLAC data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the FLAC data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeFLACStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_FLAC
+#endif // #ifndef SOUND_FLAC_H
diff --git a/audio/decoders/iff_sound.cpp b/audio/decoders/iff_sound.cpp
new file mode 100644
index 0000000000..2ec189c586
--- /dev/null
+++ b/audio/decoders/iff_sound.cpp
@@ -0,0 +1,130 @@
+/* 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 "audio/decoders/iff_sound.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "common/iff_container.h"
+#include "common/func.h"
+
+namespace Audio {
+
+struct Voice8Header {
+ uint32 oneShotHiSamples;
+ uint32 repeatHiSamples;
+ uint32 samplesPerHiCycle;
+ uint16 samplesPerSec;
+ byte octaves;
+ byte compression;
+ uint32 volume;
+
+ Voice8Header() {
+ memset(this, 0, sizeof(Voice8Header));
+ }
+
+ void load(Common::ReadStream &stream);
+};
+
+void Voice8Header::load(Common::ReadStream &stream) {
+ oneShotHiSamples = stream.readUint32BE();
+ repeatHiSamples = stream.readUint32BE();
+ samplesPerHiCycle = stream.readUint32BE();
+ samplesPerSec = stream.readUint16BE();
+ octaves = stream.readByte();
+ compression = stream.readByte();
+ volume = stream.readUint32BE();
+}
+
+
+
+struct A8SVXLoader {
+ Voice8Header _header;
+ int8 *_data;
+ uint32 _dataSize;
+
+ void load(Common::ReadStream &input) {
+ Common::IFFParser parser(&input);
+ Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback);
+ parser.parse(c);
+ }
+
+ bool callback(Common::IFFChunk &chunk) {
+ switch (chunk._type) {
+ case ID_VHDR:
+ _header.load(*chunk._stream);
+ break;
+
+ case ID_BODY:
+ _dataSize = chunk._size;
+ _data = (int8*)malloc(_dataSize);
+ assert(_data);
+ loadData(chunk._stream);
+ return true;
+ }
+
+ return false;
+ }
+
+ void loadData(Common::ReadStream *stream) {
+ switch (_header.compression) {
+ case 0:
+ stream->read(_data, _dataSize);
+ break;
+
+ case 1:
+ // implement other formats here
+ error("compressed IFF audio is not supported");
+ break;
+ }
+
+ }
+};
+
+
+AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) {
+ A8SVXLoader loader;
+ loader.load(input);
+
+ SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0);
+
+ uint32 loopStart = 0, loopEnd = 0;
+ if (loop) {
+ // the standard way to loop 8SVX audio implies use of the oneShotHiSamples and
+ // repeatHiSamples fields
+ loopStart = 0;
+ loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples;
+
+ if (loopStart != loopEnd) {
+ return new SubLoopingAudioStream(stream, 0,
+ Timestamp(0, loopStart, loader._header.samplesPerSec),
+ Timestamp(0, loopEnd, loader._header.samplesPerSec));
+ }
+ }
+
+ return stream;
+}
+
+}
diff --git a/audio/decoders/iff_sound.h b/audio/decoders/iff_sound.h
new file mode 100644
index 0000000000..4e53059380
--- /dev/null
+++ b/audio/decoders/iff_sound.h
@@ -0,0 +1,47 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - parallaction
+ */
+
+#ifndef SOUND_IFF_H
+#define SOUND_IFF_H
+
+namespace Common {
+ class ReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+
+AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop);
+
+}
+
+#endif
diff --git a/audio/decoders/mac_snd.cpp b/audio/decoders/mac_snd.cpp
new file mode 100644
index 0000000000..7c1a2f75f0
--- /dev/null
+++ b/audio/decoders/mac_snd.cpp
@@ -0,0 +1,116 @@
+/* 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$
+ *
+ */
+
+/*
+ * The code in this file is based on information found at
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Sound/Sound-60.html#HEADING60-15
+ *
+ * We implement both type 1 and type 2 snd resources, but only those that are sampled
+ */
+
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/decoders/mac_snd.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+
+ uint16 sndType = stream->readUint16BE();
+
+ if (sndType == 1) {
+ // "normal" snd resources
+ if (stream->readUint16BE() != 1) {
+ warning("makeMacSndStream(): Unsupported data type count");
+ return 0;
+ }
+
+ if (stream->readUint16BE() != 5) {
+ // 5 == sampled
+ warning("makeMacSndStream(): Unsupported data type");
+ return 0;
+ }
+
+ stream->readUint32BE(); // initialization option
+ } else if (sndType == 2) {
+ // old HyperCard snd resources
+ stream->readUint16BE(); // reference count (unused)
+ } else {
+ warning("makeMacSndStream(): Unknown format type %d", sndType);
+ return 0;
+ }
+
+ // We really should never get this as long as we have sampled data only
+ if (stream->readUint16BE() != 1) {
+ warning("makeMacSndStream(): Unsupported command count");
+ return 0;
+ }
+
+ uint16 command = stream->readUint16BE();
+
+ // 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice
+ // 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound
+ if (command != 0x8050 && command != 0x8051) {
+ warning("makeMacSndStream(): Unsupported command %04x", command);
+ return 0;
+ }
+
+ stream->readUint16BE(); // 0
+ uint32 soundHeaderOffset = stream->readUint32BE();
+
+ stream->seek(soundHeaderOffset);
+
+ uint32 soundDataOffset = stream->readUint32BE();
+ uint32 size = stream->readUint32BE();
+ uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates
+ stream->readUint32BE(); // loop start
+ stream->readUint32BE(); // loop end
+ byte encoding = stream->readByte();
+ stream->readByte(); // base frequency
+
+ if (encoding != 0) {
+ // 0 == PCM
+ warning("makeMacSndStream(): Unsupported compression %d", encoding);
+ return 0;
+ }
+
+ stream->skip(soundDataOffset);
+
+ byte *data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
+ return makeRawStream(data, size, rate, Audio::FLAG_UNSIGNED);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/mac_snd.h b/audio/decoders/mac_snd.h
new file mode 100644
index 0000000000..198a61333e
--- /dev/null
+++ b/audio/decoders/mac_snd.h
@@ -0,0 +1,58 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - sci
+ */
+
+#ifndef SOUND_MAC_SND_H
+#define SOUND_MAC_SND_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class SeekableAudioStream;
+
+/**
+ * Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream
+ * from that data.
+ *
+ * @param stream the SeekableReadStream from which to read the snd data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeMacSndStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp
new file mode 100644
index 0000000000..53d68fa9db
--- /dev/null
+++ b/audio/decoders/mp3.cpp
@@ -0,0 +1,375 @@
+/* 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 "audio/decoders/mp3.h"
+
+#ifdef USE_MAD
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#include <mad.h>
+
+#if defined(__PSP__)
+ #include "backends/platform/psp/mp3.h"
+#endif
+namespace Audio {
+
+
+#pragma mark -
+#pragma mark --- MP3 (MAD) stream ---
+#pragma mark -
+
+
+class MP3Stream : public SeekableAudioStream {
+protected:
+ enum State {
+ MP3_STATE_INIT, // Need to init the decoder
+ MP3_STATE_READY, // ready for processing data
+ MP3_STATE_EOS // end of data reached (may need to loop)
+ };
+
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ uint _posInFrame;
+ State _state;
+
+ Timestamp _length;
+ mad_timer_t _totalTime;
+
+ mad_stream _stream;
+ mad_frame _frame;
+ mad_synth _synth;
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+public:
+ MP3Stream(Common::SeekableReadStream *inStream,
+ DisposeAfterUse::Flag dispose);
+ ~MP3Stream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _state == MP3_STATE_EOS; }
+ bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
+ int getRate() const { return _frame.header.samplerate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+protected:
+ void decodeMP3Data();
+ void readMP3Data();
+
+ void initStream();
+ void readHeader();
+ void deinitStream();
+};
+
+MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _posInFrame(0),
+ _state(MP3_STATE_INIT),
+ _length(0, 1000),
+ _totalTime(mad_timer_zero) {
+
+ // The MAD_BUFFER_GUARD must always contain zeros (the reason
+ // for this is that the Layer III Huffman decoder of libMAD
+ // may read a few bytes beyond the end of the input buffer).
+ memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
+
+ // Calculate the length of the stream
+ initStream();
+
+ while (_state != MP3_STATE_EOS)
+ readHeader();
+
+ // To rule out any invalid sample rate to be encountered here, say in case the
+ // MP3 stream is invalid, we just check the MAD error code here.
+ // We need to assure this, since else we might trigger an assertion in Timestamp
+ // (When getRate() returns 0 or a negative number to be precise).
+ // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according
+ // to mad.h it is also set on EOF.
+ if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0)
+ _length = Timestamp(mad_timer_count(_totalTime, MAD_UNITS_MILLISECONDS), getRate());
+
+ deinitStream();
+
+ // Reinit stream
+ _state = MP3_STATE_INIT;
+
+ // Decode the first chunk of data. This is necessary so that _frame
+ // is setup and isStereo() and getRate() return correct results.
+ decodeMP3Data();
+}
+
+MP3Stream::~MP3Stream() {
+ deinitStream();
+
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+void MP3Stream::decodeMP3Data() {
+ do {
+ if (_state == MP3_STATE_INIT)
+ initStream();
+
+ if (_state == MP3_STATE_EOS)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data();
+
+ while (_state == MP3_STATE_READY) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next frame
+ if (mad_frame_decode(&_frame, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ break; // Read more data
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here.
+ // These are normal and expected (caused by our frame skipping (i.e. "seeking")
+ // code above).
+ debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Synthesize PCM data
+ mad_synth_frame(&_synth, &_frame);
+ _posInFrame = 0;
+ break;
+ }
+ } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MP3Stream::readMP3Data() {
+ uint32 remaining = 0;
+
+ // Give up immediately if we already used up all data in the stream
+ if (_inStream->eos()) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ if (_stream.next_frame) {
+ // If there is still data in the MAD stream, we need to preserve it.
+ // Note that we use memmove, as we are reusing the same buffer,
+ // and hence the data regions we copy from and to may overlap.
+ remaining = _stream.bufend - _stream.next_frame;
+ assert(remaining < BUFFER_SIZE); // Paranoia check
+ memmove(_buf, _stream.next_frame, remaining);
+ }
+
+ // Try to read the next block
+ uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size <= 0) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining);
+}
+
+bool MP3Stream::seek(const Timestamp &where) {
+ if (where == _length) {
+ _state = MP3_STATE_EOS;
+ return true;
+ } else if (where > _length) {
+ return false;
+ }
+
+ const uint32 time = where.msecs();
+
+ mad_timer_t destination;
+ mad_timer_set(&destination, time / 1000, time % 1000, 1000);
+
+ if (_state != MP3_STATE_READY || mad_timer_compare(destination, _totalTime) < 0)
+ initStream();
+
+ while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS)
+ readHeader();
+
+ decodeMP3Data();
+
+ return (_state != MP3_STATE_EOS);
+}
+
+void MP3Stream::initStream() {
+ if (_state != MP3_STATE_INIT)
+ deinitStream();
+
+ // Init MAD
+ mad_stream_init(&_stream);
+ mad_frame_init(&_frame);
+ mad_synth_init(&_synth);
+
+ // Reset the stream data
+ _inStream->seek(0, SEEK_SET);
+ _totalTime = mad_timer_zero;
+ _posInFrame = 0;
+
+ // Update state
+ _state = MP3_STATE_READY;
+
+ // Read the first few sample bytes
+ readMP3Data();
+}
+
+void MP3Stream::readHeader() {
+ if (_state != MP3_STATE_READY)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data();
+
+ while (_state != MP3_STATE_EOS) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next header. Note: mad_frame_decode would do this for us, too.
+ // However, for seeking we don't want to decode the full frame (else it would
+ // be far too slow). Hence we perform this explicitly in a separate step.
+ if (mad_header_decode(&_frame.header, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ readMP3Data(); // Read more data
+ continue;
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Sum up the total playback time so far
+ mad_timer_add(&_totalTime, _frame.header.duration);
+ break;
+ }
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MP3Stream::deinitStream() {
+ if (_state == MP3_STATE_INIT)
+ return;
+
+ // Deinit MAD
+ mad_synth_finish(&_synth);
+ mad_frame_finish(&_frame);
+ mad_stream_finish(&_stream);
+
+ _state = MP3_STATE_EOS;
+}
+
+static inline int scale_sample(mad_fixed_t sample) {
+ // round
+ sample += (1L << (MAD_F_FRACBITS - 16));
+
+ // clip
+ if (sample > MAD_F_ONE - 1)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample = -MAD_F_ONE;
+
+ // quantize and scale to not saturate when mixing a lot of channels
+ return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+int MP3Stream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ // Keep going as long as we have input available
+ while (samples < numSamples && _state != MP3_STATE_EOS) {
+ const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header));
+ while (samples < len) {
+ *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]);
+ samples++;
+ if (MAD_NCHANNELS(&_frame.header) == 2) {
+ *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]);
+ samples++;
+ }
+ _posInFrame++;
+ }
+ if (_posInFrame >= _synth.pcm.length) {
+ // We used up all PCM data in the current frame -- read & decode more
+ decodeMP3Data();
+ }
+ }
+ return samples;
+}
+
+
+#pragma mark -
+#pragma mark --- MP3 factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeMP3Stream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+
+#if defined(__PSP__)
+ SeekableAudioStream *s = 0;
+
+ if (Mp3PspStream::isOkToCreateStream())
+ s = new Mp3PspStream(stream, disposeAfterUse);
+
+ if (!s) // go to regular MAD mp3 stream if ME fails
+ s = new MP3Stream(stream, disposeAfterUse);
+#else
+ SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse);
+#endif
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_MAD
diff --git a/audio/decoders/mp3.h b/audio/decoders/mp3.h
new file mode 100644
index 0000000000..72bc6e1b3e
--- /dev/null
+++ b/audio/decoders/mp3.h
@@ -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 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - mohawk
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_MP3_H
+#define SOUND_MP3_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_MAD
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the MP3 data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the MP3 data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeMP3Stream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_MAD
+#endif // #ifndef SOUND_MP3_H
diff --git a/audio/decoders/raw.cpp b/audio/decoders/raw.cpp
new file mode 100644
index 0000000000..8b833c7838
--- /dev/null
+++ b/audio/decoders/raw.cpp
@@ -0,0 +1,356 @@
+/* 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/endian.h"
+#include "common/memstream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+// This used to be an inline template function, but
+// buggy template function handling in MSVC6 forced
+// us to go with the macro approach. So far this is
+// the only template function that MSVC6 seemed to
+// compile incorrectly. Knock on wood.
+#define READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, ptr, isLE) \
+ ((is16Bit ? (isLE ? READ_LE_UINT16(ptr) : READ_BE_UINT16(ptr)) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0))
+
+
+#pragma mark -
+#pragma mark --- RawStream ---
+#pragma mark -
+
+/**
+ * This is a stream, which allows for playing raw PCM data from a stream.
+ * It also features playback of multiple blocks from a given stream.
+ */
+template<bool is16Bit, bool isUnsigned, bool isLE>
+class RawStream : public SeekableAudioStream {
+public:
+ RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, const RawStreamBlockList &blocks)
+ : _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), _blocks(blocks), _curBlock(_blocks.begin()), _blockLeft(0), _buffer(0) {
+
+ assert(_blocks.size() > 0);
+
+ // Setup our buffer for readBuffer
+ _buffer = new byte[kSampleBufferLength * (is16Bit ? 2 : 1)];
+ assert(_buffer);
+
+ // Set current buffer state, playing first block
+ _stream->seek(_curBlock->pos, SEEK_SET);
+
+ // In case of an error we will stop (or rather
+ // not start) stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len;
+ }
+
+ // Add up length of all blocks in order to caluclate total play time
+ int32 len = 0;
+ for (RawStreamBlockList::const_iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ assert(i->len % (_isStereo ? 2 : 1) == 0);
+ len += i->len;
+ }
+
+ _playtime = Timestamp(0, len / (_isStereo ? 2 : 1), rate);
+ }
+
+ ~RawStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _stream;
+
+ delete[] _buffer;
+ }
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return _isStereo; }
+ bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
+
+ int getRate() const { return _rate; }
+ Timestamp getLength() const { return _playtime; }
+
+ bool seek(const Timestamp &where);
+private:
+ const int _rate; ///< Sample rate of stream
+ const bool _isStereo; ///< Whether this is an stereo stream
+ Timestamp _playtime; ///< Calculated total play time
+ Common::SeekableReadStream *_stream; ///< Stream to read data from
+ const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawStream is destructed
+ const RawStreamBlockList _blocks; ///< Audio block list
+
+ RawStreamBlockList::const_iterator _curBlock; ///< Current audio block number
+ int32 _blockLeft; ///< How many bytes are still left in the current block
+
+ /**
+ * Advance one block in the stream in case
+ * the current one is empty.
+ */
+ void updateBlockIfNeeded();
+
+ byte *_buffer; ///< Buffer used in readBuffer
+ enum {
+ /**
+ * How many samples we can buffer at once.
+ *
+ * TODO: Check whether this size suffices
+ * for systems with slow disk I/O.
+ */
+ kSampleBufferLength = 2048
+ };
+
+ /**
+ * Fill the temporary sample buffer used in readBuffer.
+ *
+ * @param maxSamples Maximum samples to read.
+ * @return actual count of samples read.
+ */
+ int fillBuffer(int maxSamples);
+};
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+int RawStream<is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesLeft = numSamples;
+
+ while (samplesLeft > 0) {
+ // Try to read up to "samplesLeft" samples.
+ int len = fillBuffer(samplesLeft);
+
+ // In case we were not able to read any samples
+ // we will stop reading here.
+ if (!len)
+ break;
+
+ // Adjust the samples left to read.
+ samplesLeft -= len;
+
+ // Copy the data to the caller's buffer.
+ const byte *src = _buffer;
+ while (len-- > 0) {
+ *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, src, isLE);
+ src += (is16Bit ? 2 : 1);
+ }
+ }
+
+ return numSamples - samplesLeft;
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+int RawStream<is16Bit, isUnsigned, isLE>::fillBuffer(int maxSamples) {
+ int bufferedSamples = 0;
+ byte *dst = _buffer;
+
+ // We can only read up to "kSampleBufferLength" samples
+ // so we take this into consideration, when trying to
+ // read up to maxSamples.
+ maxSamples = MIN<int>(kSampleBufferLength, maxSamples);
+
+ // We will only read up to maxSamples
+ while (maxSamples > 0 && !endOfData()) {
+ // Calculate how many samples we can safely read
+ // from the current block.
+ const int len = MIN<int>(maxSamples, _blockLeft);
+
+ // Try to read all the sample data and update the
+ // destination pointer.
+ const int bytesRead = _stream->read(dst, len * (is16Bit ? 2 : 1));
+ dst += bytesRead;
+
+ // Calculate how many samples we actually read.
+ const int samplesRead = bytesRead / (is16Bit ? 2 : 1);
+
+ // Update all status variables
+ bufferedSamples += samplesRead;
+ maxSamples -= samplesRead;
+ _blockLeft -= samplesRead;
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ }
+
+ // Advance to the next block in case the current
+ // one is already finished.
+ updateBlockIfNeeded();
+ }
+
+ return bufferedSamples;
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+void RawStream<is16Bit, isUnsigned, isLE>::updateBlockIfNeeded() {
+ // Have we now finished this block? If so, read the next block
+ if (_blockLeft == 0 && _curBlock != _blocks.end()) {
+ // Next block
+ ++_curBlock;
+
+ // Check whether we reached the end of the stream
+ // yet. In case we did not do this, we will just
+ // setup the next block as new block.
+ if (_curBlock != _blocks.end()) {
+ _stream->seek(_curBlock->pos, SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len;
+ }
+ }
+ }
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+bool RawStream<is16Bit, isUnsigned, isLE>::seek(const Timestamp &where) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+
+ if (where > _playtime)
+ return false;
+
+ const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
+ uint32 curSample = 0;
+
+ // Search for the disk block in which the specific sample is placed
+ for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) {
+ uint32 nextBlockSample = curSample + _curBlock->len;
+
+ if (nextBlockSample > seekSample)
+ break;
+
+ curSample = nextBlockSample;
+ }
+
+ if (_curBlock == _blocks.end()) {
+ return ((seekSample - curSample) == 0);
+ } else {
+ const uint32 offset = seekSample - curSample;
+
+ _stream->seek(_curBlock->pos + offset * (is16Bit ? 2 : 1), SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len - offset;
+ }
+
+ return true;
+ }
+}
+
+#pragma mark -
+#pragma mark --- Raw stream factories ---
+#pragma mark -
+
+/* In the following, we use preprocessor / macro tricks to simplify the code
+ * which instantiates the input streams. We used to use template functions for
+ * this, but MSVC6 / EVC 3-4 (used for WinCE builds) are extremely buggy when it
+ * comes to this feature of C++... so as a compromise we use macros to cut down
+ * on the (source) code duplication a bit.
+ * So while normally macro tricks are said to make maintenance harder, in this
+ * particular case it should actually help it :-)
+ */
+
+#define MAKE_RAW_STREAM(UNSIGNED) \
+ if (is16Bit) { \
+ if (isLE) \
+ return new RawStream<true, UNSIGNED, true>(rate, isStereo, disposeAfterUse, stream, blockList); \
+ else \
+ return new RawStream<true, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList); \
+ } else \
+ return new RawStream<false, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList)
+
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ const RawStreamBlockList &blockList,
+ int rate,
+ byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+ const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0;
+ const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0;
+
+ if (blockList.empty()) {
+ warning("Empty block list passed to makeRawStream");
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ if (isUnsigned) {
+ MAKE_RAW_STREAM(true);
+ } else {
+ MAKE_RAW_STREAM(false);
+ }
+}
+
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ RawStreamBlockList blocks;
+ RawStreamBlock block;
+ block.pos = 0;
+
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+
+ assert(stream->size() % ((is16Bit ? 2 : 1) * (isStereo ? 2 : 1)) == 0);
+
+ block.len = stream->size() / (is16Bit ? 2 : 1);
+ blocks.push_back(block);
+
+ return makeRawStream(stream, blocks, rate, flags, disposeAfterUse);
+}
+
+SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES);
+}
+
+SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, RawStreamBlock *block, int numBlocks,
+ int rate, byte flags, DisposeAfterUse::Flag disposeStream) {
+ assert(numBlocks > 0);
+ RawStreamBlockList blocks;
+ for (int i = 0; i < numBlocks; ++i)
+ blocks.push_back(block[i]);
+
+ return makeRawStream(stream, blocks, rate, flags, disposeStream);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/raw.h b/audio/decoders/raw.h
new file mode 100644
index 0000000000..3e9426012c
--- /dev/null
+++ b/audio/decoders/raw.h
@@ -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 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$
+ *
+ */
+
+#ifndef SOUND_RAW_H
+#define SOUND_RAW_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#include "common/list.h"
+
+
+namespace Common { class SeekableReadStream; }
+
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Various flags which can be bit-ORed and then passed to
+ * makeRawStream and some other AudioStream factories
+ * to control their behavior.
+ *
+ * Engine authors are advised not to rely on a certain value or
+ * order of these flags (in particular, do not store them verbatim
+ * in savestates).
+ */
+enum RawFlags {
+ /** unsigned samples (default: signed) */
+ FLAG_UNSIGNED = 1 << 0,
+
+ /** sound is 16 bits wide (default: 8bit) */
+ FLAG_16BITS = 1 << 1,
+
+ /** samples are little endian (default: big endian) */
+ FLAG_LITTLE_ENDIAN = 1 << 2,
+
+ /** sound is in stereo (default: mono) */
+ FLAG_STEREO = 1 << 3
+};
+
+
+/**
+ * Struct used to define the audio data to be played by a RawStream.
+ */
+struct RawStreamBlock {
+ int32 pos; ///< Position in stream of the block (in bytes of course!)
+ int32 len; ///< Length of the block (in raw samples, not sample pairs!)
+};
+
+/**
+ * List containing all blocks of a raw stream.
+ * @see RawStreamBlock
+ */
+typedef Common::List<RawStreamBlock> RawStreamBlockList;
+
+/**
+ * Creates an audio stream, which plays from the given buffer.
+ *
+ * @param buffer Buffer to play from.
+ * @param size Size of the buffer in bytes.
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to free the buffer after use (with free!).
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * Creates an audio stream, which plays from the given stream.
+ *
+ * @param stream Stream object to play from.
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to delete the stream after use.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * Creates an audio stream, which plays from the given stream.
+ *
+ * @param stream Stream object to play from.
+ * @param blockList List of blocks to play.
+ * @see RawDiskStreamAudioBlock
+ * @see RawStreamBlockList
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to delete the stream after use.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ const RawStreamBlockList &blockList,
+ int rate,
+ byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * NOTE:
+ * This API is considered deprecated.
+ *
+ * Creates a audio stream, which plays from given stream.
+ *
+ * @param stream Stream to play from
+ * @param block Pointer to an RawStreamBlock array
+ * @see RawStreamBlock
+ * @param numBlocks Number of blocks.
+ * @param rate The rate
+ * @param flags Flags combination.
+ * @see RawFlags
+ * @param disposeStream Whether the "stream" object should be destroyed after playback.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream,
+ RawStreamBlock *block, int numBlocks,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeStream);
+
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/vag.cpp b/audio/decoders/vag.cpp
new file mode 100644
index 0000000000..2c3a36202a
--- /dev/null
+++ b/audio/decoders/vag.cpp
@@ -0,0 +1,150 @@
+/* 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 "audio/decoders/vag.h"
+#include "audio/audiostream.h"
+#include "common/stream.h"
+
+namespace Audio {
+
+class VagStream : public Audio::RewindableAudioStream {
+public:
+ VagStream(Common::SeekableReadStream *stream, int rate);
+ ~VagStream();
+
+ bool isStereo() const { return false; }
+ bool endOfData() const { return _stream->pos() == _stream->size(); }
+ int getRate() const { return _rate; }
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool rewind();
+private:
+ Common::SeekableReadStream *_stream;
+
+ byte _predictor;
+ double _samples[28];
+ byte _samplesRemaining;
+ int _rate;
+ double _s1, _s2;
+};
+
+VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) {
+ _samplesRemaining = 0;
+ _predictor = 0;
+ _s1 = _s2 = 0.0;
+ _rate = rate;
+}
+
+
+VagStream::~VagStream() {
+ delete _stream;
+}
+
+static const double s_vagDataTable[5][2] =
+ {
+ { 0.0, 0.0 },
+ { 60.0 / 64.0, 0.0 },
+ { 115.0 / 64.0, -52.0 / 64.0 },
+ { 98.0 / 64.0, -55.0 / 64.0 },
+ { 122.0 / 64.0, -60.0 / 64.0 }
+ };
+
+int VagStream::readBuffer(int16 *buffer, const int numSamples) {
+ int32 samplesDecoded = 0;
+
+ if (_samplesRemaining) {
+ byte i = 0;
+
+ for (i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) {
+ _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1];
+ _s2 = _s1;
+ _s1 = _samples[i];
+ int16 d = (int) (_samples[i] + 0.5);
+ buffer[samplesDecoded] = d;
+ samplesDecoded++;
+ }
+
+#if 0
+ assert(i == 28); // We're screwed if this fails :P
+#endif
+ // This might mean the file is corrupted, or that the stream has
+ // been closed.
+ if (i != 28) return 0;
+
+ _samplesRemaining = 0;
+ }
+
+ while (samplesDecoded < numSamples) {
+ byte i = 0;
+
+ _predictor = _stream->readByte();
+ byte shift = _predictor & 0xf;
+ _predictor >>= 4;
+
+ if (_stream->readByte() == 7)
+ return samplesDecoded;
+
+ for (i = 0; i < 28; i += 2) {
+ byte d = _stream->readByte();
+ int16 s = (d & 0xf) << 12;
+ if (s & 0x8000)
+ s |= 0xffff0000;
+ _samples[i] = (double)(s >> shift);
+ s = (d & 0xf0) << 8;
+ if (s & 0x8000)
+ s |= 0xffff0000;
+ _samples[i + 1] = (double)(s >> shift);
+ }
+
+ for (i = 0; i < 28 && samplesDecoded < numSamples; i++) {
+ _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1];
+ _s2 = _s1;
+ _s1 = _samples[i];
+ int16 d = (int) (_samples[i] + 0.5);
+ buffer[samplesDecoded] = d;
+ samplesDecoded++;
+ }
+
+ if (i != 27)
+ _samplesRemaining = 28 - i;
+ }
+
+ return samplesDecoded;
+}
+
+bool VagStream::rewind() {
+ _stream->seek(0);
+ _samplesRemaining = 0;
+ _predictor = 0;
+ _s1 = _s2 = 0.0;
+
+ return true;
+}
+
+RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) {
+ return new VagStream(stream, rate);
+}
+
+}
diff --git a/audio/decoders/vag.h b/audio/decoders/vag.h
new file mode 100644
index 0000000000..cdf91a8ea1
--- /dev/null
+++ b/audio/decoders/vag.h
@@ -0,0 +1,60 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - sword1 (PSX port of the game)
+ * - sword2 (PSX port of the game)
+ * - tinsel (PSX port of the game)
+ */
+
+#ifndef SOUND_VAG_H
+#define SOUND_VAG_H
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class RewindableAudioStream;
+
+/**
+ * Takes an input stream containing Vag sound data and creates
+ * an RewindableAudioStream from that.
+ *
+ * @param stream the SeekableReadStream from which to read the ADPCM data
+ * @param rate the sampling rate
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeVagStream(
+ Common::SeekableReadStream *stream,
+ int rate = 11025);
+
+} // End of namespace Sword1
+
+#endif
diff --git a/audio/decoders/voc.cpp b/audio/decoders/voc.cpp
new file mode 100644
index 0000000000..b811a640ec
--- /dev/null
+++ b/audio/decoders/voc.cpp
@@ -0,0 +1,403 @@
+/* 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/debug.h"
+#include "common/endian.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/voc.h"
+
+
+namespace Audio {
+
+int getSampleRateFromVOCRate(int vocSR) {
+ if (vocSR == 0xa5 || vocSR == 0xa6) {
+ return 11025;
+ } else if (vocSR == 0xd2 || vocSR == 0xd3) {
+ return 22050;
+ } else {
+ int sr = 1000000L / (256L - vocSR);
+ // inexact sampling rates occur e.g. in the kitchen in Monkey Island,
+ // very easy to reach right from the start of the game.
+ //warning("inexact sample rate used: %i (0x%x)", sr, vocSR);
+ return sr;
+ }
+}
+
+static byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate, int &loops, int &begin_loop, int &end_loop) {
+ VocFileHeader fileHeader;
+
+ debug(2, "loadVOCFromStream");
+
+ if (stream.read(&fileHeader, 8) != 8)
+ goto invalid;
+
+ if (!memcmp(&fileHeader, "VTLK", 4)) {
+ if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
+ goto invalid;
+ } else if (!memcmp(&fileHeader, "Creative", 8)) {
+ if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
+ goto invalid;
+ } else {
+ invalid:;
+ warning("loadVOCFromStream: Invalid header");
+ return NULL;
+ }
+
+ if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
+ error("loadVOCFromStream: Invalid header");
+ if (fileHeader.desc[19] != 0x1A)
+ debug(3, "loadVOCFromStream: Partially invalid header");
+
+ int32 offset = FROM_LE_16(fileHeader.datablock_offset);
+ int16 version = FROM_LE_16(fileHeader.version);
+ int16 code = FROM_LE_16(fileHeader.id);
+ assert(offset == sizeof(VocFileHeader));
+ // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
+ // French version of Simon the Sorcerer 2 (CD)
+ assert(version == 0x010A || version == 0x0114 || version == 0x0100);
+ assert(code == ~version + 0x1234);
+
+ int len;
+ byte *ret_sound = 0;
+ size = 0;
+ begin_loop = 0;
+ end_loop = 0;
+
+ while ((code = stream.readByte())) {
+ len = stream.readByte();
+ len |= stream.readByte() << 8;
+ len |= stream.readByte() << 16;
+
+ debug(2, "Block code %d, len %d", code, len);
+
+ switch (code) {
+ case 1:
+ case 9: {
+ int packing;
+ if (code == 1) {
+ int time_constant = stream.readByte();
+ packing = stream.readByte();
+ len -= 2;
+ rate = getSampleRateFromVOCRate(time_constant);
+ } else {
+ rate = stream.readUint32LE();
+ int bits = stream.readByte();
+ int channels = stream.readByte();
+ if (bits != 8 || channels != 1) {
+ warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels);
+ break;
+ }
+ packing = stream.readUint16LE();
+ stream.readUint32LE();
+ len -= 12;
+ }
+ debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
+ if (packing == 0) {
+ if (size) {
+ ret_sound = (byte *)realloc(ret_sound, size + len);
+ } else {
+ ret_sound = (byte *)malloc(len);
+ }
+ stream.read(ret_sound + size, len);
+ size += len;
+ begin_loop = size;
+ end_loop = size;
+ } else {
+ warning("VOC file packing %d unsupported", packing);
+ }
+ } break;
+ case 3: // silence
+ // occur with a few Igor sounds, voc file starts with a silence block with a
+ // frequency different from the data block. Just ignore fow now (implementing
+ // it wouldn't make a big difference anyway...)
+ assert(len == 3);
+ stream.readUint16LE();
+ stream.readByte();
+ break;
+ case 6: // begin of loop
+ assert(len == 2);
+ loops = stream.readUint16LE();
+ break;
+ case 7: // end of loop
+ assert(len == 0);
+ break;
+ case 8: { // "Extended"
+ // This occures in the LoL Intro demo.
+ // This block overwrites the next parameters of a block 1 "Sound data".
+ // To assure we never get any bad data here, we will assert in case
+ // this tries to define a stereo sound block or tries to use something
+ // different than 8bit unsigned sound data.
+ // TODO: Actually we would need to check the frequency divisor (the
+ // first word) here too. It is used in the following equation:
+ // sampleRate = 256000000/(channels * (65536 - frequencyDivisor))
+ assert(len == 4);
+ stream.readUint16LE();
+ uint8 codec = stream.readByte();
+ uint8 channels = stream.readByte() + 1;
+ assert(codec == 0 && channels == 1);
+ } break;
+ default:
+ warning("Unhandled code %d in VOC file (len %d)", code, len);
+ return ret_sound;
+ }
+ }
+ debug(4, "VOC Data Size : %d", size);
+ return ret_sound;
+}
+
+byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) {
+ int loops, begin_loop, end_loop;
+ return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop);
+}
+
+
+#ifdef STREAM_AUDIO_FROM_DISK
+
+int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) {
+ VocFileHeader fileHeader;
+ int currentBlock = 0;
+ int size = 0;
+
+ debug(2, "parseVOCFormat");
+
+ if (stream.read(&fileHeader, 8) != 8)
+ goto invalid;
+
+ if (!memcmp(&fileHeader, "VTLK", 4)) {
+ if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
+ goto invalid;
+ } else if (!memcmp(&fileHeader, "Creative", 8)) {
+ if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
+ goto invalid;
+ } else {
+ invalid:;
+ warning("loadVOCFromStream: Invalid header");
+ return 0;
+ }
+
+ if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
+ error("loadVOCFromStream: Invalid header");
+ if (fileHeader.desc[19] != 0x1A)
+ debug(3, "loadVOCFromStream: Partially invalid header");
+
+ int32 offset = FROM_LE_16(fileHeader.datablock_offset);
+ int16 version = FROM_LE_16(fileHeader.version);
+ int16 code = FROM_LE_16(fileHeader.id);
+ assert(offset == sizeof(VocFileHeader));
+ // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
+ // French version of Simon the Sorcerer 2 (CD)
+ assert(version == 0x010A || version == 0x0114 || version == 0x0100);
+ assert(code == ~version + 0x1234);
+
+ int len;
+ size = 0;
+ begin_loop = 0;
+ end_loop = 0;
+
+ while ((code = stream.readByte())) {
+ len = stream.readByte();
+ len |= stream.readByte() << 8;
+ len |= stream.readByte() << 16;
+
+ debug(2, "Block code %d, len %d", code, len);
+
+ switch (code) {
+ case 1:
+ case 9: {
+ int packing;
+ if (code == 1) {
+ int time_constant = stream.readByte();
+ packing = stream.readByte();
+ len -= 2;
+ rate = getSampleRateFromVOCRate(time_constant);
+ } else {
+ rate = stream.readUint32LE();
+ int bits = stream.readByte();
+ int channels = stream.readByte();
+ if (bits != 8 || channels != 1) {
+ warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels);
+ break;
+ }
+ packing = stream.readUint16LE();
+ stream.readUint32LE();
+ len -= 12;
+ }
+ debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
+ if (packing == 0) {
+
+ // Found a data block - so add it to the block list
+ block[currentBlock].pos = stream.pos();
+ block[currentBlock].len = len;
+ currentBlock++;
+
+ stream.seek(len, SEEK_CUR);
+
+ size += len;
+ begin_loop = size;
+ end_loop = size;
+ } else {
+ warning("VOC file packing %d unsupported", packing);
+ }
+ } break;
+ case 3: // silence
+ // occur with a few Igor sounds, voc file starts with a silence block with a
+ // frequency different from the data block. Just ignore fow now (implementing
+ // it wouldn't make a big difference anyway...)
+ assert(len == 3);
+ stream.readUint16LE();
+ stream.readByte();
+ break;
+ case 6: // begin of loop
+ assert(len == 2);
+ loops = stream.readUint16LE();
+ break;
+ case 7: // end of loop
+ assert(len == 0);
+ break;
+ case 8: // "Extended"
+ // This occures in the LoL Intro demo. This block can usually be used to create stereo
+ // sound, but the LoL intro has only an empty block, thus this dummy implementation will
+ // work.
+ assert(len == 4);
+ stream.readUint16LE();
+ stream.readByte();
+ stream.readByte();
+ break;
+ default:
+ warning("Unhandled code %d in VOC file (len %d)", code, len);
+ return 0;
+ }
+ }
+ debug(4, "VOC Data Size : %d", size);
+ return currentBlock;
+}
+
+AudioStream *makeVOCDiskStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+ const int MAX_AUDIO_BLOCKS = 256;
+
+ RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS];
+ int rate, loops, begin_loop, end_loop;
+
+ int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop);
+
+ AudioStream *audioStream = 0;
+
+ // Create an audiostream from the data. Note the numBlocks may be 0,
+ // e.g. when invalid data is encountered. See bug #2890038.
+ if (numBlocks)
+ audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse/*, begin_loop, end_loop*/);
+
+ delete[] block;
+
+ return audioStream;
+}
+
+SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+ const int MAX_AUDIO_BLOCKS = 256;
+
+ RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS];
+ int rate, loops, begin_loop, end_loop;
+
+ int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop);
+
+ SeekableAudioStream *audioStream = 0;
+
+ // Create an audiostream from the data. Note the numBlocks may be 0,
+ // e.g. when invalid data is encountered. See bug #2890038.
+ if (numBlocks)
+ audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse);
+
+ delete[] block;
+
+ return audioStream;
+}
+
+#endif
+
+
+AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag disposeAfterUse) {
+#ifdef STREAM_AUDIO_FROM_DISK
+ return makeVOCDiskStream(stream, flags, disposeAfterUse);
+#else
+ int size, rate;
+
+ byte *data = loadVOCFromStream(*stream, size, rate);
+
+ if (!data) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ SeekableAudioStream *s = Audio::makeRawStream(data, size, rate, flags);
+
+ if (loopStart != loopEnd) {
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+
+ if (loopEnd == 0)
+ loopEnd = size;
+ assert(loopStart <= loopEnd);
+ assert(loopEnd <= (uint)size);
+
+ // Verify the buffer sizes are sane
+ if (is16Bit && isStereo)
+ assert((loopStart & 3) == 0 && (loopEnd & 3) == 0);
+ else if (is16Bit || isStereo)
+ assert((loopStart & 1) == 0 && (loopEnd & 1) == 0);
+
+ const uint32 extRate = s->getRate() * (is16Bit ? 2 : 1) * (isStereo ? 2 : 1);
+
+ return new SubLoopingAudioStream(s, 0, Timestamp(0, loopStart, extRate), Timestamp(0, loopEnd, extRate));
+ } else {
+ return s;
+ }
+#endif
+}
+
+SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+#ifdef STREAM_AUDIO_FROM_DISK
+ return makeVOCDiskStreamNoLoop(stream, flags, disposeAfterUse);
+#else
+ int size, rate;
+
+ byte *data = loadVOCFromStream(*stream, size, rate);
+
+ if (!data) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ return makeRawStream(data, size, rate, flags);
+#endif
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/voc.h b/audio/decoders/voc.h
new file mode 100644
index 0000000000..82cc261f2c
--- /dev/null
+++ b/audio/decoders/voc.h
@@ -0,0 +1,107 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - drascula
+ * - kyra
+ * - made
+ * - saga
+ * - scumm
+ * - touche
+ */
+
+#ifndef SOUND_VOC_H
+#define SOUND_VOC_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class ReadStream; }
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct VocFileHeader {
+ uint8 desc[20];
+ uint16 datablock_offset;
+ uint16 version;
+ uint16 id;
+} PACKED_STRUCT;
+
+struct VocBlockHeader {
+ uint8 blocktype;
+ uint8 size[3];
+ uint8 sr;
+ uint8 pack;
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/**
+ * Take a sample rate parameter as it occurs in a VOC sound header, and
+ * return the corresponding sample frequency.
+ *
+ * This method has special cases for the standard rates of 11025 and 22050 kHz,
+ * which due to limitations of the format, cannot be encoded exactly in a VOC
+ * file. As a consequence, many game files have sound data sampled with those
+ * rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code
+ * works around that and "unrounds" the sampling rates.
+ */
+extern int getSampleRateFromVOCRate(int vocSR);
+
+/**
+ * Try to load a VOC from the given stream. Returns a pointer to memory
+ * containing the PCM sample data (allocated with malloc). It is the callers
+ * responsibility to dellocate that data again later on! Currently this
+ * function only supports uncompressed raw PCM data.
+ */
+extern byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate);
+
+/**
+ * Try to load a VOC from the given seekable stream and create an AudioStream
+ * from that data. Currently this function only supports uncompressed raw PCM
+ * data. Optionally supports (infinite) looping of a portion of the data.
+ *
+ * This function uses loadVOCFromStream() internally.
+ */
+AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO);
+
+/**
+ * This does not use any of the looping features of VOC files!
+ */
+SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/vorbis.cpp b/audio/decoders/vorbis.cpp
new file mode 100644
index 0000000000..dc37e852d3
--- /dev/null
+++ b/audio/decoders/vorbis.cpp
@@ -0,0 +1,262 @@
+/* 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$
+ *
+ */
+
+// Disable symbol overrides for FILE and fseek as those are used in the
+// Vorbis headers.
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+#define FORBIDDEN_SYMBOL_EXCEPTION_fseek
+
+#include "audio/decoders/vorbis.h"
+
+#ifdef USE_VORBIS
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#ifdef USE_TREMOR
+#if defined(__GP32__) // custom libtremor locations
+#include <ivorbisfile.h>
+#else
+#include <tremor/ivorbisfile.h>
+#endif
+#else
+#include <vorbis/vorbisfile.h>
+#endif
+
+
+namespace Audio {
+
+// These are wrapper functions to allow using a SeekableReadStream object to
+// provide data to the OggVorbis_File object.
+
+static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+
+ uint32 result = stream->read(ptr, size * nmemb);
+
+ return result / size;
+}
+
+static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ stream->seek((int32)offset, whence);
+ return stream->pos();
+}
+
+static int close_stream_wrap(void *datasource) {
+ // Do nothing -- we leave it up to the VorbisStream to free memory as appropriate.
+ return 0;
+}
+
+static long tell_stream_wrap(void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ return stream->pos();
+}
+
+static ov_callbacks g_stream_wrap = {
+ read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap
+};
+
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis stream ---
+#pragma mark -
+
+
+class VorbisStream : public SeekableAudioStream {
+protected:
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ bool _isStereo;
+ int _rate;
+
+ Timestamp _length;
+
+ OggVorbis_File _ovFile;
+
+ int16 _buffer[4096];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+
+public:
+ // startTime / duration are in milliseconds
+ VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
+ ~VorbisStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _pos >= _bufferEnd; }
+ bool isStereo() const { return _isStereo; }
+ int getRate() const { return _rate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+protected:
+ bool refill();
+};
+
+VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _length(0, 1000),
+ _bufferEnd(_buffer + ARRAYSIZE(_buffer)) {
+
+ int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap);
+ if (res < 0) {
+ warning("Could not create Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return;
+ }
+
+ // Read in initial data
+ if (!refill())
+ return;
+
+ // Setup some header information
+ _isStereo = ov_info(&_ovFile, -1)->channels >= 2;
+ _rate = ov_info(&_ovFile, -1)->rate;
+
+#ifdef USE_TREMOR
+ _length = Timestamp(ov_time_total(&_ovFile, -1), getRate());
+#else
+ _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate());
+#endif
+}
+
+VorbisStream::~VorbisStream() {
+ ov_clear(&_ovFile);
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+int VorbisStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && _pos < _bufferEnd) {
+ const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ if (!refill())
+ break;
+ }
+ }
+ return samples;
+}
+
+bool VorbisStream::seek(const Timestamp &where) {
+ // Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter
+ // of the convertTimeToStreamPos helper.
+ int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
+ if (res) {
+ warning("Error seeking in Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return false;
+ }
+
+ return refill();
+}
+
+bool VorbisStream::refill() {
+ // Read the samples
+ uint len_left = sizeof(_buffer);
+ char *read_pos = (char *)_buffer;
+
+ while (len_left > 0) {
+ long result;
+
+#ifdef USE_TREMOR
+ // Tremor ov_read() always returns data as signed 16 bit interleaved PCM
+ // in host byte order. As such, it does not take arguments to request
+ // specific signedness, byte order or bit depth as in Vorbisfile.
+ result = ov_read(&_ovFile, read_pos, len_left,
+ NULL);
+#else
+#ifdef SCUMM_BIG_ENDIAN
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 1,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#else
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 0,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#endif
+#endif
+ if (result == OV_HOLE) {
+ // Possibly recoverable, just warn about it
+ warning("Corrupted data in Vorbis file");
+ } else if (result == 0) {
+ //warning("End of file while reading from Vorbis file");
+ //_pos = _bufferEnd;
+ //return false;
+ break;
+ } else if (result < 0) {
+ warning("Error reading from Vorbis stream (%d)", int(result));
+ _pos = _bufferEnd;
+ // Don't delete it yet, that causes problems in
+ // the CD player emulation code.
+ return false;
+ } else {
+ len_left -= result;
+ read_pos += result;
+ }
+ }
+
+ _pos = _buffer;
+ _bufferEnd = (int16 *)read_pos;
+
+ return true;
+}
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeVorbisStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse);
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_VORBIS
diff --git a/audio/decoders/vorbis.h b/audio/decoders/vorbis.h
new file mode 100644
index 0000000000..7cc395cccb
--- /dev/null
+++ b/audio/decoders/vorbis.h
@@ -0,0 +1,75 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_VORBIS_H
+#define SOUND_VORBIS_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_VORBIS
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the Ogg Vorbis data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeVorbisStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_VORBIS
+#endif // #ifndef SOUND_VORBIS_H
diff --git a/audio/decoders/wave.cpp b/audio/decoders/wave.cpp
new file mode 100644
index 0000000000..1f0ddd8ceb
--- /dev/null
+++ b/audio/decoders/wave.cpp
@@ -0,0 +1,194 @@
+/* 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/debug.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/wave.h"
+#include "audio/decoders/adpcm.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_) {
+ const int32 initialPos = stream.pos();
+ byte buf[4+1];
+
+ buf[4] = 0;
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "RIFF", 4) != 0) {
+ warning("getWavInfo: No 'RIFF' header");
+ return false;
+ }
+
+ int32 wavLength = stream.readUint32LE();
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "WAVE", 4) != 0) {
+ warning("getWavInfo: No 'WAVE' header");
+ return false;
+ }
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "fmt ", 4) != 0) {
+ warning("getWavInfo: No 'fmt' header");
+ return false;
+ }
+
+ uint32 fmtLength = stream.readUint32LE();
+ if (fmtLength < 16) {
+ // A valid fmt chunk always contains at least 16 bytes
+ warning("getWavInfo: 'fmt' header is too short");
+ return false;
+ }
+
+ // Next comes the "type" field of the fmt header. Some typical
+ // values for it:
+ // 1 -> uncompressed PCM
+ // 17 -> IMA ADPCM compressed WAVE
+ // See <http://www.saettler.com/RIFFNEW/RIFFNEW.htm> for a more complete
+ // list of common WAVE compression formats...
+ uint16 type = stream.readUint16LE(); // == 1 for PCM data
+ uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo
+ uint32 samplesPerSec = stream.readUint32LE(); // in Hz
+ uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8
+
+ uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8
+ uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ...
+ // 8 bit data is unsigned, 16 bit data signed
+
+
+ if (wavType != 0)
+ *wavType = type;
+
+ if (blockAlign_ != 0)
+ *blockAlign_ = blockAlign;
+#if 0
+ debug("WAVE information:");
+ debug(" total size: %d", wavLength);
+ debug(" fmt size: %d", fmtLength);
+ debug(" type: %d", type);
+ debug(" numChannels: %d", numChannels);
+ debug(" samplesPerSec: %d", samplesPerSec);
+ debug(" avgBytesPerSec: %d", avgBytesPerSec);
+ debug(" blockAlign: %d", blockAlign);
+ debug(" bitsPerSample: %d", bitsPerSample);
+#endif
+
+ if (type != 1 && type != 2 && type != 17) {
+ warning("getWavInfo: only PCM, MS ADPCM or IMA ADPCM data is supported (type %d)", type);
+ return false;
+ }
+
+ if (blockAlign != numChannels * bitsPerSample / 8 && type != 2) {
+ debug(0, "getWavInfo: blockAlign is invalid");
+ }
+
+ if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) {
+ debug(0, "getWavInfo: avgBytesPerSec is invalid");
+ }
+
+ // Prepare the return values.
+ rate = samplesPerSec;
+
+ flags = 0;
+ if (bitsPerSample == 8) // 8 bit data is unsigned
+ flags |= Audio::FLAG_UNSIGNED;
+ else if (bitsPerSample == 16) // 16 bit data is signed little endian
+ flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+ else if (bitsPerSample == 4 && (type == 2 || type == 17))
+ flags |= Audio::FLAG_16BITS;
+ else {
+ warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample);
+ return false;
+ }
+
+ if (numChannels == 2)
+ flags |= Audio::FLAG_STEREO;
+ else if (numChannels != 1) {
+ warning("getWavInfo: unsupported number of channels %d", numChannels);
+ return false;
+ }
+
+ // It's almost certainly a WAV file, but we still need to find its
+ // 'data' chunk.
+
+ // Skip over the rest of the fmt chunk.
+ int offset = fmtLength - 16;
+
+ do {
+ stream.seek(offset, SEEK_CUR);
+ if (stream.pos() >= initialPos + wavLength + 8) {
+ warning("getWavInfo: Can't find 'data' chunk");
+ return false;
+ }
+ stream.read(buf, 4);
+ offset = stream.readUint32LE();
+
+#if 0
+ debug(" found a '%s' tag of size %d", buf, offset);
+#endif
+ } while (memcmp(buf, "data", 4) != 0);
+
+ // Stream now points at 'offset' bytes of sample data...
+ size = offset;
+
+ return true;
+}
+
+RewindableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ int size, rate;
+ byte flags;
+ uint16 type;
+ int blockAlign;
+
+ if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ if (type == 17) // MS IMA ADPCM
+ return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMSIma, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign);
+ else if (type == 2) // MS ADPCM
+ return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMS, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign);
+
+ // Raw PCM. Just read everything at once.
+ // TODO: More elegant would be to wrap the stream.
+ byte *data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ return makeRawStream(data, size, rate, flags);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h
new file mode 100644
index 0000000000..2bdbe8f0b6
--- /dev/null
+++ b/audio/decoders/wave.h
@@ -0,0 +1,84 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - gob
+ * - mohawk
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - tucker
+ */
+
+#ifndef SOUND_WAVE_H
+#define SOUND_WAVE_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class RewindableAudioStream;
+
+/**
+ * Try to load a WAVE from the given seekable stream. Returns true if
+ * successful. In that case, the stream's seek position will be set to the
+ * start of the audio data, and size, rate and flags contain information
+ * necessary for playback. Currently this function supports uncompressed
+ * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
+ */
+extern bool loadWAVFromStream(
+ Common::SeekableReadStream &stream,
+ int &size,
+ int &rate,
+ byte &flags,
+ uint16 *wavType = 0,
+ int *blockAlign = 0);
+
+/**
+ * Try to load a WAVE from the given seekable stream and create an AudioStream
+ * from that data. Currently this function supports uncompressed
+ * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
+ *
+ * This function uses loadWAVFromStream() internally.
+ *
+ * @param stream the SeekableReadStream from which to read the WAVE data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeWAVStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp
new file mode 100644
index 0000000000..1f61e16101
--- /dev/null
+++ b/audio/fmopl.cpp
@@ -0,0 +1,197 @@
+/* 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 "audio/fmopl.h"
+
+#include "audio/softsynth/opl/dosbox.h"
+#include "audio/softsynth/opl/mame.h"
+
+#include "common/config-manager.h"
+#include "common/translation.h"
+
+namespace OPL {
+
+// Config implementation
+
+enum OplEmulator {
+ kAuto = 0,
+ kMame = 1,
+ kDOSBox = 2
+};
+
+OPL::OPL() {
+ if (_hasInstance)
+ error("There are multiple OPL output instances running");
+ _hasInstance = true;
+}
+
+const Config::EmulatorDescription Config::_drivers[] = {
+ { "auto", "<default>", kAuto, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
+ { "mame", _s("MAME OPL emulator"), kMame, kFlagOpl2 },
+#ifndef DISABLE_DOSBOX_OPL
+ { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 },
+#endif
+ { 0, 0, 0, 0 }
+};
+
+Config::DriverId Config::parse(const Common::String &name) {
+ for (int i = 0; _drivers[i].name; ++i) {
+ if (name.equalsIgnoreCase(_drivers[i].name))
+ return _drivers[i].id;
+ }
+
+ return -1;
+}
+
+Config::DriverId Config::detect(OplType type) {
+ uint32 flags = 0;
+ switch (type) {
+ case kOpl2:
+ flags = kFlagOpl2;
+ break;
+
+ case kDualOpl2:
+ flags = kFlagDualOpl2;
+ break;
+
+ case kOpl3:
+ flags = kFlagOpl3;
+ break;
+ }
+
+ DriverId drv = parse(ConfMan.get("opl_driver"));
+
+ // When a valid driver is selected, check whether it supports
+ // the requested OPL chip.
+ if (drv != -1 && drv != kAuto) {
+ // If the chip is supported, just use the driver.
+ if ((flags & _drivers[drv].flags)) {
+ return drv;
+ } else {
+ // Else we will output a warning and just
+ // return that no valid driver is found.
+ warning("Your selected OPL driver \"%s\" does not support type %d emulation, which is requested by your game", _drivers[drv].description, type);
+ return -1;
+ }
+ }
+
+ // Detect the first matching emulator
+ drv = -1;
+
+ for (int i = 1; _drivers[i].name; ++i) {
+ if (_drivers[i].flags & flags) {
+ drv = _drivers[i].id;
+ break;
+ }
+ }
+
+ return drv;
+}
+
+OPL *Config::create(OplType type) {
+ return create(kAuto, type);
+}
+
+OPL *Config::create(DriverId driver, OplType type) {
+ // On invalid driver selection, we try to do some fallback detection
+ if (driver == -1) {
+ warning("Invalid OPL driver selected, trying to detect a fallback emulator");
+ driver = kAuto;
+ }
+
+ // If autodetection is selected, we search for a matching
+ // driver.
+ if (driver == kAuto) {
+ driver = detect(type);
+
+ // No emulator for the specified OPL chip could
+ // be found, thus stop here.
+ if (driver == -1) {
+ warning("No OPL emulator available for type %d", type);
+ return 0;
+ }
+ }
+
+ switch (driver) {
+ case kMame:
+ if (type == kOpl2)
+ return new MAME::OPL();
+ else
+ warning("MAME OPL emulator only supports OPL2 emulation");
+ return 0;
+
+#ifndef DISABLE_DOSBOX_OPL
+ case kDOSBox:
+ return new DOSBox::OPL(type);
+#endif
+
+ default:
+ warning("Unsupported OPL emulator %d", driver);
+ // TODO: Maybe we should add some dummy emulator too, which just outputs
+ // silence as sound?
+ return 0;
+ }
+}
+
+bool OPL::_hasInstance = false;
+
+} // End of namespace OPL
+
+void OPLDestroy(FM_OPL *OPL) {
+ delete OPL;
+}
+
+void OPLResetChip(FM_OPL *OPL) {
+ OPL->reset();
+}
+
+void OPLWrite(FM_OPL *OPL, int a, int v) {
+ OPL->write(a, v);
+}
+
+unsigned char OPLRead(FM_OPL *OPL, int a) {
+ return OPL->read(a);
+}
+
+void OPLWriteReg(FM_OPL *OPL, int r, int v) {
+ OPL->writeReg(r, v);
+}
+
+void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) {
+ OPL->readBuffer(buffer, length);
+}
+
+FM_OPL *makeAdLibOPL(int rate) {
+ FM_OPL *opl = OPL::Config::create();
+
+ if (opl) {
+ if (!opl->init(rate)) {
+ delete opl;
+ opl = 0;
+ }
+ }
+
+ return opl;
+}
+
diff --git a/audio/fmopl.h b/audio/fmopl.h
new file mode 100644
index 0000000000..33235f3545
--- /dev/null
+++ b/audio/fmopl.h
@@ -0,0 +1,179 @@
+/* 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$
+ */
+
+#ifndef SOUND_FMOPL_H
+#define SOUND_FMOPL_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace OPL {
+
+class OPL;
+
+class Config {
+public:
+ enum OplFlags {
+ kFlagOpl2 = (1 << 0),
+ kFlagDualOpl2 = (1 << 1),
+ kFlagOpl3 = (1 << 2)
+ };
+
+ /**
+ * OPL type to emulate.
+ */
+ enum OplType {
+ kOpl2,
+ kDualOpl2,
+ kOpl3
+ };
+
+ typedef int8 DriverId;
+ struct EmulatorDescription {
+ const char *name;
+ const char *description;
+
+ DriverId id; // A unique ID for each driver
+ uint32 flags; // Capabilities of this driver
+ };
+
+ /**
+ * Get a list of all available OPL emulators.
+ * @return list of all available OPL emulators, terminated by a zero entry
+ */
+ static const EmulatorDescription *getAvailable() { return _drivers; }
+
+ /**
+ * Returns the driver id of a given name.
+ */
+ static DriverId parse(const Common::String &name);
+
+ /**
+ * Detects a driver for the specific type.
+ *
+ * @return Returns a valid driver id on success, -1 otherwise.
+ */
+ static DriverId detect(OplType type);
+
+ /**
+ * Creates the specific driver with a specific type setup.
+ */
+ static OPL *create(DriverId driver, OplType type);
+
+ /**
+ * Wrapper to easily init an OPL chip, without specifing an emulator.
+ * By default it will try to initialize an OPL2 emulator, thus an AdLib card.
+ */
+ static OPL *create(OplType type = kOpl2);
+
+private:
+ static const EmulatorDescription _drivers[];
+};
+
+class OPL {
+private:
+ static bool _hasInstance;
+public:
+ OPL();
+ virtual ~OPL() { _hasInstance = false; }
+
+ /**
+ * Initializes the OPL emulator.
+ *
+ * @param rate output sample rate
+ * @return true on success, false on failure
+ */
+ virtual bool init(int rate) = 0;
+
+ /**
+ * Reinitializes the OPL emulator
+ */
+ virtual void reset() = 0;
+
+ /**
+ * Writes a byte to the given I/O port.
+ *
+ * @param a port address
+ * @param v value, which will be written
+ */
+ virtual void write(int a, int v) = 0;
+
+ /**
+ * Reads a byte from the given I/O port.
+ *
+ * @param a port address
+ * @return value read
+ */
+ virtual byte read(int a) = 0;
+
+ /**
+ * Function to directly write to a specific OPL register.
+ * This writes to *both* chips for a Dual OPL2.
+ *
+ * @param r hardware register number to write to
+ * @param v value, which will be written
+ */
+ virtual void writeReg(int r, int v) = 0;
+
+ /**
+ * Read up to 'length' samples.
+ *
+ * Data will be in native endianess, 16 bit per sample, signed.
+ * For stereo OPL, buffer will be filled with interleaved
+ * left and right channel samples, starting with a left sample.
+ * Furthermore, the samples in the left and right are summed up.
+ * So if you request 4 samples from a stereo OPL, you will get
+ * a total of two left channel and two right channel samples.
+ */
+ virtual void readBuffer(int16 *buffer, int length) = 0;
+
+ /**
+ * Returns whether the setup OPL mode is stereo or not
+ */
+ virtual bool isStereo() const = 0;
+};
+
+} // End of namespace OPL
+
+// Legacy API
+// !You should not write any new code using the legacy API!
+typedef OPL::OPL FM_OPL;
+
+void OPLDestroy(FM_OPL *OPL);
+
+void OPLResetChip(FM_OPL *OPL);
+void OPLWrite(FM_OPL *OPL, int a, int v);
+unsigned char OPLRead(FM_OPL *OPL, int a);
+void OPLWriteReg(FM_OPL *OPL, int r, int v);
+void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length);
+
+/**
+ * Legacy factory to create an AdLib (OPL2) chip.
+ *
+ * !You should not write any new code using the legacy API!
+ */
+FM_OPL *makeAdLibOPL(int rate);
+
+#endif
+
diff --git a/audio/mididrv.cpp b/audio/mididrv.cpp
new file mode 100644
index 0000000000..a1487ff69d
--- /dev/null
+++ b/audio/mididrv.cpp
@@ -0,0 +1,326 @@
+/* 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 "engines/engine.h"
+#include "common/config-manager.h"
+#include "common/str.h"
+#include "common/system.h"
+#include "common/util.h"
+#include "audio/mididrv.h"
+#include "audio/musicplugin.h"
+#include "common/translation.h"
+
+const byte MidiDriver::_mt32ToGm[128] = {
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 0, 1, 0, 2, 4, 4, 5, 3, 16, 17, 18, 16, 16, 19, 20, 21, // 0x
+ 6, 6, 6, 7, 7, 7, 8, 112, 62, 62, 63, 63, 38, 38, 39, 39, // 1x
+ 88, 95, 52, 98, 97, 99, 14, 54, 102, 96, 53, 102, 81, 100, 14, 80, // 2x
+ 48, 48, 49, 45, 41, 40, 42, 42, 43, 46, 45, 24, 25, 28, 27, 104, // 3x
+ 32, 32, 34, 33, 36, 37, 35, 35, 79, 73, 72, 72, 74, 75, 64, 65, // 4x
+ 66, 67, 71, 71, 68, 69, 70, 22, 56, 59, 57, 57, 60, 60, 58, 61, // 5x
+ 61, 11, 11, 98, 14, 9, 14, 13, 12, 107, 107, 77, 78, 78, 76, 76, // 6x
+ 47, 117, 127, 118, 118, 116, 115, 119, 115, 112, 55, 124, 123, 0, 14, 117 // 7x
+};
+
+const byte MidiDriver::_gmToMt32[128] = {
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ 5, 1, 2, 7, 3, 5, 16, 21, 22, 101, 101, 97, 104, 103, 102, 20, // 0x
+ 8, 9, 11, 12, 14, 15, 87, 15, 59, 60, 61, 62, 67, 44, 79, 23, // 1x
+ 64, 67, 66, 70, 68, 69, 28, 31, 52, 54, 55, 56, 49, 51, 57, 112, // 2x
+ 48, 50, 45, 26, 34, 35, 45, 122, 89, 90, 94, 81, 92, 95, 24, 25, // 3x
+ 80, 78, 79, 78, 84, 85, 86, 82, 74, 72, 76, 77, 110, 107, 108, 76, // 4x
+ 47, 44, 111, 45, 44, 34, 44, 30, 32, 33, 88, 34, 35, 35, 38, 33, // 5x
+ 41, 36, 100, 37, 40, 34, 43, 40, 63, 21, 99, 105, 103, 86, 55, 84, // 6x
+ 101, 103, 100, 120, 117, 113, 99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x
+};
+
+static const uint32 GUIOMapping[] = {
+ MT_PCSPK, Common::GUIO_MIDIPCSPK,
+ MT_CMS, Common::GUIO_MIDICMS,
+ MT_PCJR, Common::GUIO_MIDIPCJR,
+ MT_ADLIB, Common::GUIO_MIDIADLIB,
+ MT_C64, Common::GUIO_MIDIC64,
+ MT_AMIGA, Common::GUIO_MIDIAMIGA,
+ MT_APPLEIIGS, Common::GUIO_MIDIAPPLEIIGS,
+ MT_TOWNS, Common::GUIO_MIDITOWNS,
+ MT_PC98, Common::GUIO_MIDIPC98,
+ MT_GM, Common::GUIO_MIDIGM,
+ MT_MT32, Common::GUIO_MIDIMT32,
+ 0, 0
+};
+
+uint32 MidiDriver::musicType2GUIO(uint32 musicType) {
+ uint32 res = 0;
+
+ for (int i = 0; GUIOMapping[i] || GUIOMapping[i + 1]; i += 2) {
+ if (musicType == GUIOMapping[i] || musicType == (uint32)-1)
+ res |= GUIOMapping[i + 1];
+ }
+
+ return res;
+}
+
+bool MidiDriver::_forceTypeMT32 = false;
+
+MusicType MidiDriver::getMusicType(MidiDriver::DeviceHandle handle) {
+ if (_forceTypeMT32)
+ return MT_MT32;
+
+ if (handle) {
+ const MusicPlugin::List p = MusicMan.getPlugins();
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) {
+ if (handle == d->getHandle())
+ return d->getMusicType();
+ }
+ }
+ }
+
+ return MT_INVALID;
+}
+
+Common::String MidiDriver::getDeviceString(DeviceHandle handle, DeviceStringType type) {
+ if (handle) {
+ const MusicPlugin::List p = MusicMan.getPlugins();
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) {
+ if (handle == d->getHandle()) {
+ if (type == kDriverName)
+ return d->getMusicDriverName();
+ else if (type == kDriverId)
+ return d->getMusicDriverId();
+ else if (type == kDeviceId)
+ return d->getCompleteId();
+ else
+ return Common::String("auto");
+ }
+ }
+ }
+ }
+
+ return Common::String("auto");
+}
+
+MidiDriver::DeviceHandle MidiDriver::detectDevice(int flags) {
+ // Query the selected music device (defaults to MT_AUTO device).
+ DeviceHandle hdl = getDeviceHandle(ConfMan.get("music_driver"));
+
+ _forceTypeMT32 = false;
+
+ // Check whether the selected music driver is compatible with the
+ // given flags.
+ switch (getMusicType(hdl)) {
+ case MT_PCSPK:
+ if (flags & MDT_PCSPK)
+ return hdl;
+ break;
+
+ case MT_PCJR:
+ if (flags & MDT_PCJR)
+ return hdl;
+ break;
+
+ case MT_CMS:
+ if (flags & MDT_CMS)
+ return hdl;
+ break;
+
+ case MT_ADLIB:
+ if (flags & MDT_ADLIB)
+ return hdl;
+ break;
+
+ case MT_C64:
+ if (flags & MDT_C64)
+ return hdl;
+ break;
+
+ case MT_AMIGA:
+ if (flags & MDT_AMIGA)
+ return hdl;
+ break;
+
+ case MT_APPLEIIGS:
+ if (flags & MDT_APPLEIIGS)
+ return hdl;
+ break;
+
+ case MT_TOWNS:
+ if (flags & MDT_TOWNS)
+ return hdl;
+ break;
+
+ case MT_PC98:
+ if (flags & MDT_PC98)
+ return hdl;
+ break;
+
+ case MT_GM:
+ case MT_GS:
+ case MT_MT32:
+ if (flags & MDT_MIDI)
+ return hdl;
+ break;
+
+ case MT_NULL:
+ return hdl;
+
+ default:
+ break;
+ }
+
+ // If the selected driver did not match the flags setting,
+ // we try to determine a suitable and "optimal" music driver.
+ const MusicPlugin::List p = MusicMan.getPlugins();
+ // If only MDT_MIDI but not MDT_PREFER_MT32 or MDT_PREFER_GM is set we prefer the other devices (which will always be
+ // detected since they are hard coded and cannot be disabled.
+ for (int l = (flags & (MDT_PREFER_GM | MDT_PREFER_MT32)) ? 1 : 0; l < 2; ++l) {
+ if ((flags & MDT_MIDI) && (l == 1)) {
+ // If a preferred MT32 or GM device has been selected that device gets returned
+ if (flags & MDT_PREFER_MT32)
+ hdl = getDeviceHandle(ConfMan.get("mt32_device"));
+ else if (flags & MDT_PREFER_GM)
+ hdl = getDeviceHandle(ConfMan.get("gm_device"));
+ else
+ hdl = getDeviceHandle("auto");
+
+ const MusicType type = getMusicType(hdl);
+
+ // If have a "Don't use GM/MT-32" setting we skip this part and jump
+ // to AdLib, PC Speaker etc. detection right away.
+ if (type != MT_NULL) {
+ if (type != MT_AUTO && type != MT_INVALID) {
+ if (flags & MDT_PREFER_MT32)
+ // If we have a preferred MT32 device we disable the gm/mt32 mapping (more about this in mididrv.h)
+ _forceTypeMT32 = true;
+
+ return hdl;
+ }
+
+ // If we have no specific device selected (neither in the scummvm nor in the game domain)
+ // and no preferred MT32 or GM device selected we arrive here.
+ // If MT32 is preferred we try for the first available device with music type 'MT_MT32' (usually the mt32 emulator)
+ if (flags & MDT_PREFER_MT32) {
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) {
+ if (d->getMusicType() == MT_MT32)
+ return d->getHandle();
+ }
+ }
+ }
+
+ // Now we default to the first available device with music type 'MT_GM'
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) {
+ if (d->getMusicType() == MT_GM || d->getMusicType() == MT_GS)
+ return d->getHandle();
+ }
+ }
+ }
+ }
+
+ MusicType tp = MT_AUTO;
+ if (flags & MDT_TOWNS)
+ tp = MT_TOWNS;
+ else if (flags & MDT_PC98)
+ tp = MT_PC98;
+ else if (flags & MDT_ADLIB)
+ tp = MT_ADLIB;
+ else if (flags & MDT_PCJR)
+ tp = MT_PCJR;
+ else if (flags & MDT_PCSPK)
+ tp = MT_PCSPK;
+ else if (flags & MDT_C64)
+ tp = MT_C64;
+ else if (flags & MDT_AMIGA)
+ tp = MT_AMIGA;
+ else if (flags & MDT_APPLEIIGS)
+ tp = MT_APPLEIIGS;
+ else if (l == 0)
+ // if we haven't tried to find a MIDI device yet we do this now.
+ continue;
+ else
+ tp = MT_AUTO;
+
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); ++m) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); ++d) {
+ if (d->getMusicType() == tp)
+ return d->getHandle();
+ }
+ }
+ }
+
+ return 0;
+}
+
+MidiDriver *MidiDriver::createMidi(MidiDriver::DeviceHandle handle) {
+ MidiDriver *driver = 0;
+ const MusicPlugin::List p = MusicMan.getPlugins();
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) {
+ if (getDeviceString(handle, MidiDriver::kDriverId).equals((**m)->getId()))
+ (**m)->createInstance(&driver, handle);
+ }
+
+ return driver;
+}
+
+MidiDriver::DeviceHandle MidiDriver::getDeviceHandle(const Common::String &identifier) {
+ const MusicPlugin::List p = MusicMan.getPlugins();
+
+ if (p.begin() == p.end())
+ error("Music plugins must be loaded prior to calling this method");
+
+ for (MusicPlugin::List::const_iterator m = p.begin(); m != p.end(); m++) {
+ MusicDevices i = (**m)->getDevices();
+ for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) {
+ // The music driver id isn't unique, but it will match
+ // driver's first device. This is useful when selecting
+ // the driver from the command line.
+ if (identifier.equals(d->getMusicDriverId()) || identifier.equals(d->getCompleteId()) || identifier.equals(d->getCompleteName())) {
+ return d->getHandle();
+ }
+ }
+ }
+
+ return 0;
+}
+
+void MidiDriver::sendMT32Reset() {
+ static const byte resetSysEx[] = { 0x41, 0x10, 0x16, 0x12, 0x7F, 0x00, 0x00, 0x01, 0x00 };
+ sysEx(resetSysEx, sizeof(resetSysEx));
+ g_system->delayMillis(100);
+}
+
+void MidiDriver::sendGMReset() {
+ static const byte resetSysEx[] = { 0x7E, 0x7F, 0x09, 0x01 };
+ sysEx(resetSysEx, sizeof(resetSysEx));
+ g_system->delayMillis(100);
+}
+
diff --git a/audio/mididrv.h b/audio/mididrv.h
new file mode 100644
index 0000000000..9e649cba3d
--- /dev/null
+++ b/audio/mididrv.h
@@ -0,0 +1,288 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MIDIDRV_H
+#define SOUND_MIDIDRV_H
+
+#include "common/scummsys.h"
+#include "common/timer.h"
+
+class MidiChannel;
+class MusicDevice;
+
+namespace Audio {
+ class Mixer;
+}
+namespace Common { class String; }
+
+/**
+ * Music Driver Types, used to uniquely identify each music driver.
+ *
+ * The pseudo drivers are listed first, then all native drivers,
+ * then all other MIDI drivers, and finally the non-MIDI drivers.
+ *
+ * @todo Rename MidiDriverType to MusicDriverType
+ */
+
+/**
+ * Music types that music drivers can implement and engines can rely on.
+ */
+enum MusicType {
+ MT_INVALID = -1, // Invalid output
+ MT_AUTO = 0, // Auto
+ MT_NULL, // Null
+ MT_PCSPK, // PC Speaker
+ MT_PCJR, // PCjr
+ MT_CMS, // CMS
+ MT_ADLIB, // AdLib
+ MT_C64, // C64
+ MT_AMIGA, // Amiga
+ MT_APPLEIIGS, // Apple IIGS
+ MT_TOWNS, // FM-TOWNS
+ MT_PC98, // PC98
+ MT_GM, // General MIDI
+ MT_MT32, // MT-32
+ MT_GS // Roland GS
+};
+
+/**
+ * A set of flags to be passed to detectDevice() which can be used to
+ * specify what kind of music driver is preferred / accepted.
+ *
+ * The flags (except for MDT_PREFER_MT32 and MDT_PREFER_GM) indicate whether a given driver
+ * type is acceptable. E.g. the TOWNS music driver could be returned by
+ * detectDevice if and only if MDT_TOWNS is specified.
+ *
+ * @todo Rename MidiDriverFlags to MusicDriverFlags
+ */
+enum MidiDriverFlags {
+ MDT_NONE = 0,
+ MDT_PCSPK = 1 << 0, // PC Speaker: Maps to MD_PCSPK and MD_PCJR
+ MDT_CMS = 1 << 1, // Creative Music System / Gameblaster: Maps to MD_CMS
+ MDT_PCJR = 1 << 2, // Tandy/PC Junior driver
+ MDT_ADLIB = 1 << 3, // AdLib: Maps to MT_ADLIB
+ MDT_C64 = 1 << 4,
+ MDT_AMIGA = 1 << 5,
+ MDT_APPLEIIGS = 1 << 6,
+ MDT_TOWNS = 1 << 7, // FM-TOWNS: Maps to MT_TOWNS
+ MDT_PC98 = 1 << 8, // FM-TOWNS: Maps to MT_PC98
+ MDT_MIDI = 1 << 9, // Real MIDI
+ MDT_PREFER_MT32 = 1 << 10, // MT-32 output is preferred
+ MDT_PREFER_GM = 1 << 11 // GM output is preferred
+};
+
+/**
+ * Abstract description of a MIDI driver. Used by the config file and command
+ * line parsing code, and also to be able to give the user a list of available
+ * drivers.
+ *
+ * @todo Rename MidiDriverType to MusicDriverType
+ */
+
+/**
+ * Abstract MIDI Driver Class
+ *
+ * @todo Rename MidiDriver to MusicDriver
+ */
+class MidiDriver {
+public:
+ /**
+ * The device handle.
+ *
+ * The value 0 is reserved for an invalid device for now.
+ * TODO: Maybe we should use -1 (i.e. 0xFFFFFFFF) as
+ * invalid device?
+ */
+ typedef uint32 DeviceHandle;
+
+ enum DeviceStringType {
+ kDriverName,
+ kDriverId,
+ kDeviceId
+ };
+
+ static uint32 musicType2GUIO(uint32 musicType);
+
+ /** Create music driver matching the given device handle, or NULL if there is no match. */
+ static MidiDriver *createMidi(DeviceHandle handle);
+
+ /** Returns device handle based on the present devices and the flags parameter. */
+ static DeviceHandle detectDevice(int flags);
+
+ /** Find the music driver matching the given driver name/description. */
+ static DeviceHandle getDeviceHandle(const Common::String &identifier);
+
+ /** Get the music type matching the given device handle, or MT_AUTO if there is no match. */
+ static MusicType getMusicType(DeviceHandle handle);
+
+ /** Get the device description string matching the given device handle and the given type. */
+ static Common::String getDeviceString(DeviceHandle handle, DeviceStringType type);
+
+private:
+ // If detectDevice() detects MT32 and we have a preferred MT32 device
+ // we use this to force getMusicType() to return MT_MT32 so that we don't
+ // have to rely on the 'True Roland MT-32' config manager setting (since nobody
+ // would possibly think about activating 'True Roland MT-32' when he has set
+ // 'Music Driver' to '<default>')
+ static bool _forceTypeMT32;
+
+public:
+ virtual ~MidiDriver() { }
+
+ static const byte _mt32ToGm[128];
+ static const byte _gmToMt32[128];
+
+ /**
+ * Error codes returned by open.
+ * Can be converted to a string with getErrorName().
+ */
+ enum {
+ MERR_CANNOT_CONNECT = 1,
+// MERR_STREAMING_NOT_AVAILABLE = 2,
+ MERR_DEVICE_NOT_AVAILABLE = 3,
+ MERR_ALREADY_OPEN = 4
+ };
+
+ enum {
+// PROP_TIMEDIV = 1,
+ PROP_OLD_ADLIB = 2,
+ PROP_CHANNEL_MASK = 3
+ };
+
+ /**
+ * Open the midi driver.
+ * @return 0 if successful, otherwise an error code.
+ */
+ virtual int open() = 0;
+
+ /** Close the midi driver. */
+ virtual void close() = 0;
+
+ /**
+ * Output a packed midi command to the midi stream.
+ * The 'lowest' byte (i.e. b & 0xFF) is the status
+ * code, then come (if used) the first and second
+ * opcode.
+ */
+ virtual void send(uint32 b) = 0;
+
+ /**
+ * Output a midi command to the midi stream. Convenience wrapper
+ * around the usual 'packed' send method.
+ *
+ * Do NOT use this for sysEx transmission; instead, use the sysEx()
+ * method below.
+ */
+ void send(byte status, byte firstOp, byte secondOp) {
+ send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
+ }
+
+ /** Get or set a property. */
+ virtual uint32 property(int prop, uint32 param) { return 0; }
+
+ /** Retrieve a string representation of an error code. */
+ static const char *getErrorName(int error_code);
+
+ // HIGH-LEVEL SEMANTIC METHODS
+ virtual void setPitchBendRange(byte channel, uint range) {
+ send(0xB0 | channel, 101, 0);
+ send(0xB0 | channel, 100, 0);
+ send(0xB0 | channel, 6, range);
+ send(0xB0 | channel, 38, 0);
+ send(0xB0 | channel, 101, 127);
+ send(0xB0 | channel, 100, 127);
+ }
+
+ /**
+ * Send a Roland MT-32 reset sysEx to the midi device.
+ */
+ void sendMT32Reset();
+
+ /**
+ * Send a General MIDI reset sysEx to the midi device.
+ */
+ void sendGMReset();
+
+ /**
+ * Transmit a sysEx to the midi device.
+ *
+ * The given msg MUST NOT contain the usual SysEx frame, i.e.
+ * do NOT include the leading 0xF0 and the trailing 0xF7.
+ *
+ * Furthermore, the maximal supported length of a SysEx
+ * is 264 bytes. Passing longer buffers can lead to
+ * undefined behavior (most likely, a crash).
+ */
+ virtual void sysEx(const byte *msg, uint16 length) { }
+
+ virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { }
+
+ virtual void metaEvent(byte type, byte *data, uint16 length) { }
+
+ // Timing functions - MidiDriver now operates timers
+ virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) = 0;
+
+ /** The time in microseconds between invocations of the timer callback. */
+ virtual uint32 getBaseTempo() = 0;
+
+ // Channel allocation functions
+ virtual MidiChannel *allocateChannel() = 0;
+ virtual MidiChannel *getPercussionChannel() = 0;
+};
+
+class MidiChannel {
+public:
+ virtual ~MidiChannel() {}
+
+ virtual MidiDriver *device() = 0;
+ virtual byte getNumber() = 0;
+ virtual void release() = 0;
+
+ virtual void send(uint32 b) = 0; // 4-bit channel portion is ignored
+
+ // Regular messages
+ virtual void noteOff(byte note) = 0;
+ virtual void noteOn(byte note, byte velocity) = 0;
+ virtual void programChange(byte program) = 0;
+ virtual void pitchBend(int16 bend) = 0; // -0x2000 to +0x1FFF
+
+ // Control Change messages
+ virtual void controlChange(byte control, byte value) = 0;
+ virtual void modulationWheel(byte value) { controlChange(1, value); }
+ virtual void volume(byte value) { controlChange(7, value); }
+ virtual void panPosition(byte value) { controlChange(10, value); }
+ virtual void pitchBendFactor(byte value) = 0;
+ virtual void detune(byte value) { controlChange(17, value); }
+ virtual void priority(byte value) { }
+ virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); }
+ virtual void effectLevel(byte value) { controlChange(91, value); }
+ virtual void chorusLevel(byte value) { controlChange(93, value); }
+ virtual void allNotesOff() { controlChange(123, 0); }
+
+ // SysEx messages
+ virtual void sysEx_customInstrument(uint32 type, const byte *instr) = 0;
+};
+
+#endif
diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp
new file mode 100644
index 0000000000..e01b8a7fc6
--- /dev/null
+++ b/audio/midiparser.cpp
@@ -0,0 +1,467 @@
+/* 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 "audio/midiparser.h"
+#include "audio/mididrv.h"
+#include "common/util.h"
+
+//////////////////////////////////////////////////
+//
+// MidiParser implementation
+//
+//////////////////////////////////////////////////
+
+MidiParser::MidiParser() :
+_hanging_notes_count(0),
+_driver(0),
+_timer_rate(0x4A0000),
+_ppqn(96),
+_tempo(500000),
+_psec_per_tick(5208), // 500000 / 96
+_autoLoop(false),
+_smartJump(false),
+_centerPitchWheelOnUnload(false),
+_sendSustainOffOnNotesOff(false),
+_num_tracks(0),
+_active_track(255),
+_abort_parse(0) {
+ memset(_active_notes, 0, sizeof(_active_notes));
+ _next_event.start = NULL;
+ _next_event.delta = 0;
+ _next_event.event = 0;
+ _next_event.length = 0;
+}
+
+void MidiParser::property(int prop, int value) {
+ switch (prop) {
+ case mpAutoLoop:
+ _autoLoop = (value != 0);
+ break;
+ case mpSmartJump:
+ _smartJump = (value != 0);
+ break;
+ case mpCenterPitchWheelOnUnload:
+ _centerPitchWheelOnUnload = (value != 0);
+ break;
+ case mpSendSustainOffOnNotesOff:
+ _sendSustainOffOnNotesOff = (value != 0);
+ break;
+ }
+}
+
+void MidiParser::sendToDriver(uint32 b) {
+ _driver->send(b);
+}
+
+void MidiParser::setTempo(uint32 tempo) {
+ _tempo = tempo;
+ if (_ppqn)
+ _psec_per_tick = (tempo + (_ppqn >> 2)) / _ppqn;
+}
+
+// This is the conventional (i.e. SMF) variable length quantity
+uint32 MidiParser::readVLQ(byte * &data) {
+ byte str;
+ uint32 value = 0;
+ int i;
+
+ for (i = 0; i < 4; ++i) {
+ str = data[0];
+ ++data;
+ value = (value << 7) | (str & 0x7F);
+ if (!(str & 0x80))
+ break;
+ }
+ return value;
+}
+
+void MidiParser::activeNote(byte channel, byte note, bool active) {
+ if (note >= 128 || channel >= 16)
+ return;
+
+ if (active)
+ _active_notes[note] |= (1 << channel);
+ else
+ _active_notes[note] &= ~(1 << channel);
+
+ // See if there are hanging notes that we can cancel
+ NoteTimer *ptr = _hanging_notes;
+ int i;
+ for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
+ if (ptr->channel == channel && ptr->note == note && ptr->time_left) {
+ ptr->time_left = 0;
+ --_hanging_notes_count;
+ break;
+ }
+ }
+}
+
+void MidiParser::hangingNote(byte channel, byte note, uint32 time_left, bool recycle) {
+ NoteTimer *best = 0;
+ NoteTimer *ptr = _hanging_notes;
+ int i;
+
+ if (_hanging_notes_count >= ARRAYSIZE(_hanging_notes)) {
+ warning("MidiParser::hangingNote(): Exceeded polyphony");
+ return;
+ }
+
+ for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
+ if (ptr->channel == channel && ptr->note == note) {
+ if (ptr->time_left && ptr->time_left < time_left && recycle)
+ return;
+ best = ptr;
+ if (ptr->time_left) {
+ if (recycle)
+ sendToDriver(0x80 | channel, note, 0);
+ --_hanging_notes_count;
+ }
+ break;
+ } else if (!best && ptr->time_left == 0) {
+ best = ptr;
+ }
+ }
+
+ // Occassionally we might get a zero or negative note
+ // length, if the note should be turned on and off in
+ // the same iteration. For now just set it to 1 and
+ // we'll turn it off in the next cycle.
+ if (!time_left || time_left & 0x80000000)
+ time_left = 1;
+
+ if (best) {
+ best->channel = channel;
+ best->note = note;
+ best->time_left = time_left;
+ ++_hanging_notes_count;
+ } else {
+ // We checked this up top. We should never get here!
+ warning("MidiParser::hangingNote(): Internal error");
+ }
+}
+
+void MidiParser::onTimer() {
+ uint32 end_time;
+ uint32 event_time;
+
+ if (!_position._play_pos || !_driver)
+ return;
+
+ _abort_parse = false;
+ end_time = _position._play_time + _timer_rate;
+
+ // Scan our hanging notes for any
+ // that should be turned off.
+ if (_hanging_notes_count) {
+ NoteTimer *ptr = &_hanging_notes[0];
+ int i;
+ for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
+ if (ptr->time_left) {
+ if (ptr->time_left <= _timer_rate) {
+ sendToDriver(0x80 | ptr->channel, ptr->note, 0);
+ ptr->time_left = 0;
+ --_hanging_notes_count;
+ } else {
+ ptr->time_left -= _timer_rate;
+ }
+ }
+ }
+ }
+
+ while (!_abort_parse) {
+ EventInfo &info = _next_event;
+
+ event_time = _position._last_event_time + info.delta * _psec_per_tick;
+ if (event_time > end_time)
+ break;
+
+ // Process the next info.
+ _position._last_event_tick += info.delta;
+ if (info.event < 0x80) {
+ warning("Bad command or running status %02X", info.event);
+ _position._play_pos = 0;
+ return;
+ }
+
+ if (info.event == 0xF0) {
+ // SysEx event
+ // Check for trailing 0xF7 -- if present, remove it.
+ if (info.ext.data[info.length-1] == 0xF7)
+ _driver->sysEx(info.ext.data, (uint16)info.length-1);
+ else
+ _driver->sysEx(info.ext.data, (uint16)info.length);
+ } else if (info.event == 0xFF) {
+ // META event
+ if (info.ext.type == 0x2F) {
+ // End of Track must be processed by us,
+ // as well as sending it to the output device.
+ if (_autoLoop) {
+ jumpToTick(0);
+ parseNextEvent(_next_event);
+ } else {
+ stopPlaying();
+ _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
+ }
+ return;
+ } else if (info.ext.type == 0x51) {
+ if (info.length >= 3) {
+ setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]);
+ }
+ }
+ _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
+ } else {
+ if (info.command() == 0x8) {
+ activeNote(info.channel(), info.basic.param1, false);
+ } else if (info.command() == 0x9) {
+ if (info.length > 0)
+ hangingNote(info.channel(), info.basic.param1, info.length * _psec_per_tick - (end_time - event_time));
+ else
+ activeNote(info.channel(), info.basic.param1, true);
+ }
+ sendToDriver(info.event, info.basic.param1, info.basic.param2);
+ }
+
+
+ if (!_abort_parse) {
+ _position._last_event_time = event_time;
+ parseNextEvent(_next_event);
+ }
+ }
+
+ if (!_abort_parse) {
+ _position._play_time = end_time;
+ _position._play_tick = (_position._play_time - _position._last_event_time) / _psec_per_tick + _position._last_event_tick;
+ }
+}
+
+void MidiParser::allNotesOff() {
+ if (!_driver)
+ return;
+
+ int i, j;
+
+ // Turn off all active notes
+ for (i = 0; i < 128; ++i) {
+ for (j = 0; j < 16; ++j) {
+ if (_active_notes[i] & (1 << j)) {
+ sendToDriver(0x80 | j, i, 0);
+ }
+ }
+ }
+
+ // Turn off all hanging notes
+ for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) {
+ if (_hanging_notes[i].time_left) {
+ sendToDriver(0x80 | _hanging_notes[i].channel, _hanging_notes[i].note, 0);
+ _hanging_notes[i].time_left = 0;
+ }
+ }
+ _hanging_notes_count = 0;
+
+ // To be sure, send an "All Note Off" event (but not all MIDI devices
+ // support this...).
+
+ for (i = 0; i < 16; ++i) {
+ sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
+ if (_sendSustainOffOnNotesOff)
+ sendToDriver(0xB0 | i, 0x40, 0); // Also send a sustain off event (bug #3116608)
+ }
+
+ memset(_active_notes, 0, sizeof(_active_notes));
+}
+
+void MidiParser::resetTracking() {
+ _position.clear();
+}
+
+bool MidiParser::setTrack(int track) {
+ if (track < 0 || track >= _num_tracks)
+ return false;
+ // We allow restarting the track via setTrack when
+ // it isn't playing anymore. This allows us to reuse
+ // a MidiParser when a track has finished and will
+ // be restarted via setTrack by the client again.
+ // This isn't exactly how setTrack behaved before though,
+ // the old MidiParser code did not allow setTrack to be
+ // used to restart a track, which was already finished.
+ //
+ // TODO: Check if any engine has problem with this
+ // handling, if so we need to find a better way to handle
+ // track restarts. (KYRA relies on this working)
+ else if (track == _active_track && isPlaying())
+ return true;
+
+ if (_smartJump)
+ hangAllActiveNotes();
+ else
+ allNotesOff();
+
+ resetTracking();
+ memset(_active_notes, 0, sizeof(_active_notes));
+ _active_track = track;
+ _position._play_pos = _tracks[track];
+ parseNextEvent(_next_event);
+ return true;
+}
+
+void MidiParser::stopPlaying() {
+ allNotesOff();
+ resetTracking();
+}
+
+void MidiParser::hangAllActiveNotes() {
+ // Search for note off events until we have
+ // accounted for every active note.
+ uint16 temp_active[128];
+ memcpy(temp_active, _active_notes, sizeof (temp_active));
+
+ uint32 advance_tick = _position._last_event_tick;
+ while (true) {
+ int i;
+ for (i = 0; i < 128; ++i)
+ if (temp_active[i] != 0)
+ break;
+ if (i == 128)
+ break;
+ parseNextEvent(_next_event);
+ advance_tick += _next_event.delta;
+ if (_next_event.command() == 0x8) {
+ if (temp_active[_next_event.basic.param1] & (1 << _next_event.channel())) {
+ hangingNote(_next_event.channel(), _next_event.basic.param1, (advance_tick - _position._last_event_tick) * _psec_per_tick, false);
+ temp_active[_next_event.basic.param1] &= ~ (1 << _next_event.channel());
+ }
+ } else if (_next_event.event == 0xFF && _next_event.ext.type == 0x2F) {
+ // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left");
+ for (i = 0; i < 128; ++i) {
+ for (int j = 0; j < 16; ++j) {
+ if (temp_active[i] & (1 << j)) {
+ activeNote(j, i, false);
+ sendToDriver(0x80 | j, i, 0);
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool dontSendNoteOn) {
+ if (_active_track >= _num_tracks)
+ return false;
+
+ Tracker currentPos(_position);
+ EventInfo currentEvent(_next_event);
+
+ resetTracking();
+ _position._play_pos = _tracks[_active_track];
+ parseNextEvent(_next_event);
+ if (tick > 0) {
+ while (true) {
+ EventInfo &info = _next_event;
+ if (_position._last_event_tick + info.delta >= tick) {
+ _position._play_time += (tick - _position._last_event_tick) * _psec_per_tick;
+ _position._play_tick = tick;
+ break;
+ }
+
+ _position._last_event_tick += info.delta;
+ _position._last_event_time += info.delta * _psec_per_tick;
+ _position._play_tick = _position._last_event_tick;
+ _position._play_time = _position._last_event_time;
+
+ if (info.event == 0xFF) {
+ if (info.ext.type == 0x2F) { // End of track
+ _position = currentPos;
+ _next_event = currentEvent;
+ return false;
+ } else {
+ if (info.ext.type == 0x51 && info.length >= 3) // Tempo
+ setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]);
+ if (fireEvents)
+ _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length);
+ }
+ } else if (fireEvents) {
+ if (info.event == 0xF0) {
+ if (info.ext.data[info.length-1] == 0xF7)
+ _driver->sysEx(info.ext.data, (uint16)info.length-1);
+ else
+ _driver->sysEx(info.ext.data, (uint16)info.length);
+ } else {
+ // The note on sending code is used by the SCUMM engine. Other engine using this code
+ // (such as SCI) have issues with this, as all the notes sent can be heard when a song
+ // is fast-forwarded. Thus, if the engine requests it, don't send note on events.
+ if (info.command() == 0x9 && dontSendNoteOn) {
+ // Don't send note on; doing so creates a "warble" with some instruments on the MT-32.
+ // Refer to patch #3117577
+ } else {
+ sendToDriver(info.event, info.basic.param1, info.basic.param2);
+ }
+ }
+ }
+
+ parseNextEvent(_next_event);
+ }
+ }
+
+ if (stopNotes) {
+ if (!_smartJump || !currentPos._play_pos) {
+ allNotesOff();
+ } else {
+ EventInfo targetEvent(_next_event);
+ Tracker targetPosition(_position);
+
+ _position = currentPos;
+ _next_event = currentEvent;
+ hangAllActiveNotes();
+
+ _next_event = targetEvent;
+ _position = targetPosition;
+ }
+ }
+
+ _abort_parse = true;
+ return true;
+}
+
+void MidiParser::unloadMusic() {
+ resetTracking();
+ allNotesOff();
+ _num_tracks = 0;
+ _active_track = 255;
+ _abort_parse = true;
+
+ if (_centerPitchWheelOnUnload) {
+ // Center the pitch wheels in preparation for the next piece of
+ // music. It's not safe to do this from within allNotesOff(),
+ // and might not even be safe here, so we only do it if the
+ // client has explicitly asked for it.
+
+ if (_driver) {
+ for (int i = 0; i < 16; ++i) {
+ sendToDriver(0xE0 | i, 0, 0x40);
+ }
+ }
+ }
+}
diff --git a/audio/midiparser.h b/audio/midiparser.h
new file mode 100644
index 0000000000..0b18a19a5b
--- /dev/null
+++ b/audio/midiparser.h
@@ -0,0 +1,404 @@
+/* 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$
+ *
+ */
+
+/// \brief Declarations related to the MidiParser class
+
+#ifndef SOUND_MIDIPARSER_H
+#define SOUND_MIDIPARSER_H
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+
+class MidiParser;
+class MidiDriver;
+
+
+
+//////////////////////////////////////////////////
+//
+// Support entities
+//
+//////////////////////////////////////////////////
+
+/**
+ * Maintains time and position state within a MIDI stream.
+ * A single Tracker struct is used by MidiParser to keep track
+ * of its current position in the MIDI stream. The Tracker
+ * struct, however, allows alternative locations to be cached.
+ * See MidiParser::jumpToTick() for an example of tracking
+ * multiple locations within a MIDI stream. NOTE: It is
+ * important to also maintain pre-parsed EventInfo data for
+ * each Tracker location.
+ */
+struct Tracker {
+ byte * _play_pos; ///< A pointer to the next event to be parsed
+ uint32 _play_time; ///< Current time in microseconds; may be in between event times
+ uint32 _play_tick; ///< Current MIDI tick; may be in between event ticks
+ uint32 _last_event_time; ///< The time, in microseconds, of the last event that was parsed
+ uint32 _last_event_tick; ///< The tick at which the last parsed event occurs
+ byte _running_status; ///< Cached MIDI command, for MIDI streams that rely on implied event codes
+
+ Tracker() { clear(); }
+
+ /// Copy constructor for each duplication of Tracker information.
+ Tracker(const Tracker &copy) :
+ _play_pos(copy._play_pos),
+ _play_time(copy._play_time),
+ _play_tick(copy._play_tick),
+ _last_event_time(copy._last_event_time),
+ _last_event_tick(copy._last_event_tick),
+ _running_status(copy._running_status)
+ { }
+
+ /// Clears all data; used by the constructor for initialization.
+ void clear() {
+ _play_pos = 0;
+ _play_time = 0;
+ _play_tick = 0;
+ _last_event_time = 0;
+ _last_event_tick = 0;
+ _running_status = 0;
+ }
+};
+
+/**
+ * Provides comprehensive information on the next event in the MIDI stream.
+ * An EventInfo struct is instantiated by format-specific implementations
+ * of MidiParser::parseNextEvent() each time another event is needed.
+ */
+struct EventInfo {
+ byte * start; ///< Position in the MIDI stream where the event starts.
+ ///< For delta-based MIDI streams (e.g. SMF and XMIDI), this points to the delta.
+ uint32 delta; ///< The number of ticks after the previous event that this event should occur.
+ byte event; ///< Upper 4 bits are the command code, lower 4 bits are the MIDI channel.
+ ///< For META, event == 0xFF. For SysEx, event == 0xF0.
+ union {
+ struct {
+ byte param1; ///< The first parameter in a simple MIDI message.
+ byte param2; ///< The second parameter in a simple MIDI message.
+ } basic;
+ struct {
+ byte type; ///< For META events, this indicates the META type.
+ byte * data; ///< For META and SysEx events, this points to the start of the data.
+ } ext;
+ };
+ uint32 length; ///< For META and SysEx blocks, this indicates the length of the data.
+ ///< For Note On events, a non-zero value indicates that no Note Off event
+ ///< will occur, and the MidiParser will have to generate one itself.
+ ///< For all other events, this value should always be zero.
+
+ byte channel() { return event & 0x0F; } ///< Separates the MIDI channel from the event.
+ byte command() { return event >> 4; } ///< Separates the command code from the event.
+};
+
+/**
+ * Provides expiration tracking for hanging notes.
+ * Hanging notes are used when a MIDI format does not include explicit Note Off
+ * events, or when "Smart Jump" is enabled so that active notes are intelligently
+ * expired when a jump occurs. The NoteTimer struct keeps track of how much
+ * longer a note should remain active before being turned off.
+ */
+struct NoteTimer {
+ byte channel; ///< The MIDI channel on which the note was played
+ byte note; ///< The note number for the active note
+ uint32 time_left; ///< The time, in microseconds, remaining before the note should be turned off
+ NoteTimer() : channel(0), note(0), time_left(0) {}
+};
+
+
+
+
+//////////////////////////////////////////////////
+//
+// MidiParser declaration
+//
+//////////////////////////////////////////////////
+
+/**
+ * A framework and common functionality for parsing event-based music streams.
+ * The MidiParser provides a framework in which to load,
+ * parse and traverse event-based music data. Note the
+ * avoidance of the phrase "MIDI data." Despite its name,
+ * MidiParser derivatives can be used to manage a wide
+ * variety of event-based music formats. It is, however,
+ * based on the premise that the format in question can
+ * be played in the form of specification MIDI events.
+ *
+ * In order to use MidiParser to parse your music format,
+ * follow these steps:
+ *
+ * <b>STEP 1: Write a MidiParser derivative.</b>
+ * The MidiParser base class provides functionality
+ * considered common to the task of parsing event-based
+ * music. In order to parse a particular format, create
+ * a derived class that implements, at minimum, the
+ * following format-specific methods:
+ * - loadMusic
+ * - parseNextEvent
+ *
+ * In addition to the above functions, the derived class
+ * may also override the default MidiParser behavior for
+ * the following methods:
+ * - resetTracking
+ * - allNotesOff
+ * - unloadMusic
+ * - property
+ * - getTick
+ *
+ * Please see the documentation for these individual
+ * functions for more information on their use.
+ *
+ * The naming convention for classes derived from
+ * MidiParser is MidiParser_XXX, where "XXX" is some
+ * short designator for the format the class will
+ * support. For instance, the MidiParser derivative
+ * for parsing the Standard MIDI File format is
+ * MidiParser_SMF.
+ *
+ * <b>STEP 2: Create an object of your derived class.</b>
+ * Each MidiParser object can parse at most one (1) song
+ * at a time. However, a MidiParser object can be reused
+ * to play another song once it is no longer needed to
+ * play whatever it was playing. In other words, MidiParser
+ * objects do not have to be destroyed and recreated from
+ * one song to the next.
+ *
+ * <b>STEP 3: Specify a MidiDriver to send events to.</b>
+ * MidiParser works by sending MIDI and meta events to a
+ * MidiDriver. In the simplest configuration, you can plug
+ * a single MidiParser directly into the output MidiDriver
+ * being used. However, you can only plug in one at a time;
+ * otherwise channel conflicts will occur. Furthermore,
+ * meta events that may be needed to interactively control
+ * music flow cannot be handled because they are being
+ * sent directly to the output device.
+ *
+ * If you need more control over the MidiParser while it's
+ * playing, you can create your own "pseudo-MidiDriver" and
+ * place it in between your MidiParser and the output
+ * MidiDriver. The MidiParser will send events to your
+ * pseudo-MidiDriver, which in turn must send them to the
+ * output MidiDriver (or do whatever special handling is
+ * required).
+ *
+ * To specify the MidiDriver to send music output to,
+ * use the MidiParser::setMidiDriver method.
+ *
+ * <b>STEP 4: Specify the onTimer call rate.</b>
+ * MidiParser bases the timing of its parsing on an external
+ * clock. Every time MidiParser::onTimer is called, a bit
+ * more music is parsed. You must specify how many
+ * microseconds will occur between each call to onTimer,
+ * in order to ensure an accurate music tempo.
+ *
+ * To set the onTimer call rate, in microseconds,
+ * use the MidiParser::setTimerRate method. The onTimer
+ * call rate will typically match the timer rate for
+ * the output MidiDriver used. This rate can be obtained
+ * by calling MidiDriver::getBaseTempo.
+ *
+ * <b>STEP 5: Load the music.</b>
+ * MidiParser requires that the music data already be loaded
+ * into memory. The client code is responsible for memory
+ * management on this block of memory. That means that the
+ * client code must ensure that the data remain in memory
+ * while the MidiParser is using it, and properly freed
+ * after it is no longer needed. Some MidiParser variants may
+ * require internal buffers as well; memory management for those
+ * buffers is the responsibility of the MidiParser object.
+ *
+ * To load the music into the MidiParser, use the
+ * MidiParser::loadMusic method, specifying a memory pointer
+ * to the music data and the size of the data. (NOTE: Some
+ * MidiParser variants don't require a size, and 0 is fine.
+ * However, when writing client code to use MidiParser, it is
+ * best to assume that a valid size will be required.
+ *
+ * Convention requires that each implementation of
+ * MidiParser::loadMusic automatically set up default tempo
+ * and current track. This effectively means that the
+ * MidiParser will start playing as soon as timer events
+ * start coming in.
+ *
+ * <b>STEP 6: Activate a timer source for the MidiParser.</b>
+ * The easiest timer source to use is the timer of the
+ * output MidiDriver. You can attach the MidiDriver's
+ * timer output directly to a MidiParser by calling
+ * MidiDriver::setTimerCallback. In this case, the timer_proc
+ * will be the static method MidiParser::timerCallback,
+ * and timer_param will be a pointer to your MidiParser object.
+ *
+ * This configuration only allows one MidiParser to be driven
+ * by the MidiDriver at a time. To drive more MidiDrivers, you
+ * will need to create a "pseudo-MidiDriver" as described earlier,
+ * In such a configuration, the pseudo-MidiDriver should be set
+ * as the timer recipient in MidiDriver::setTimerCallback, and
+ * could then call MidiParser::onTimer for each MidiParser object.
+ *
+ * <b>STEP 7: Music shall begin to play!</b>
+ * Congratulations! At this point everything should be hooked up
+ * and the MidiParser should generate music. Note that there is
+ * no way to "stop" the MidiParser. You can "pause" the MidiParser
+ * simply by not sending timer events to it, or you can call
+ * MidiParser::unloadMusic to permanently stop the music. (This
+ * method resets everything and detaches the MidiParser from the
+ * memory block containing the music data.)
+ */
+class MidiParser {
+protected:
+ uint16 _active_notes[128]; ///< Each uint16 is a bit mask for channels that have that note on.
+ NoteTimer _hanging_notes[32]; ///< Maintains expiration info for up to 32 notes.
+ ///< Used for "Smart Jump" and MIDI formats that do not include explicit Note Off events.
+ byte _hanging_notes_count; ///< Count of hanging notes, used to optimize expiration.
+
+ MidiDriver *_driver; ///< The device to which all events will be transmitted.
+ uint32 _timer_rate; ///< The time in microseconds between onTimer() calls. Obtained from the MidiDriver.
+ uint32 _ppqn; ///< Pulses Per Quarter Note. (We refer to "pulses" as "ticks".)
+ uint32 _tempo; ///< Microseconds per quarter note.
+ uint32 _psec_per_tick; ///< Microseconds per tick (_tempo / _ppqn).
+ bool _autoLoop; ///< For lightweight clients that don't provide their own flow control.
+ bool _smartJump; ///< Support smart expiration of hanging notes when jumping
+ bool _centerPitchWheelOnUnload; ///< Center the pitch wheels when unloading a song
+ bool _sendSustainOffOnNotesOff; ///< Send a sustain off on a notes off event, stopping hanging notes
+ byte *_tracks[120]; ///< Multi-track MIDI formats are supported, up to 120 tracks.
+ byte _num_tracks; ///< Count of total tracks for multi-track MIDI formats. 1 for single-track formats.
+ byte _active_track; ///< Keeps track of the currently active track, in multi-track formats.
+
+ Tracker _position; ///< The current time/position in the active track.
+ EventInfo _next_event; ///< The next event to transmit. Events are preparsed
+ ///< so each event is parsed only once; this permits
+ ///< simulated events in certain formats.
+ bool _abort_parse; ///< If a jump or other operation interrupts parsing, flag to abort.
+
+protected:
+ static uint32 readVLQ(byte * &data);
+ virtual void resetTracking();
+ virtual void allNotesOff();
+ virtual void parseNextEvent(EventInfo &info) = 0;
+
+ void activeNote(byte channel, byte note, bool active);
+ void hangingNote(byte channel, byte note, uint32 ticks_left, bool recycle = true);
+ void hangAllActiveNotes();
+
+ virtual void sendToDriver(uint32 b);
+ void sendToDriver(byte status, byte firstOp, byte secondOp) {
+ sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
+ }
+
+ /**
+ * Platform independent BE uint32 read-and-advance.
+ * This helper function reads Big Endian 32-bit numbers
+ * from a memory pointer, at the same time advancing
+ * the pointer.
+ */
+ uint32 read4high(byte * &data) {
+ uint32 val = READ_BE_UINT32(data);
+ data += 4;
+ return val;
+ }
+
+ /**
+ * Platform independent LE uint16 read-and-advance.
+ * This helper function reads Little Endian 16-bit numbers
+ * from a memory pointer, at the same time advancing
+ * the pointer.
+ */
+ uint16 read2low(byte * &data) {
+ uint16 val = READ_LE_UINT16(data);
+ data += 2;
+ return val;
+ }
+
+public:
+ /**
+ * Configuration options for MidiParser
+ * The following options can be set to modify MidiParser's
+ * behavior.
+ */
+ enum {
+ /**
+ * Events containing a pitch bend command should be treated as
+ * single-byte padding before the real event. This allows the
+ * MidiParser to work with some malformed SMF files from Simon 1/2.
+ */
+ mpMalformedPitchBends = 1,
+
+ /**
+ * Sets auto-looping, which can be used by lightweight clients
+ * that don't provide their own flow control.
+ */
+ mpAutoLoop = 2,
+
+ /**
+ * Sets smart jumping, which intelligently expires notes that are
+ * active when a jump is made, rather than just cutting them off.
+ */
+ mpSmartJump = 3,
+
+ /**
+ * Center the pitch wheels when unloading music in preparation
+ * for the next piece of music.
+ */
+ mpCenterPitchWheelOnUnload = 4,
+
+ /**
+ * Sends a sustain off event when a notes off event is triggered.
+ * Stops hanging notes.
+ */
+ mpSendSustainOffOnNotesOff = 5
+ };
+
+public:
+ typedef void (*XMidiCallbackProc)(byte eventData, void *refCon);
+
+ MidiParser();
+ virtual ~MidiParser() { allNotesOff(); }
+
+ virtual bool loadMusic(byte *data, uint32 size) = 0;
+ virtual void unloadMusic();
+ virtual void property(int prop, int value);
+
+ void setMidiDriver(MidiDriver *driver) { _driver = driver; }
+ void setTimerRate(uint32 rate) { _timer_rate = rate; }
+ void setTempo(uint32 tempo);
+ void onTimer();
+
+ bool isPlaying() const { return (_position._play_pos != 0); }
+ void stopPlaying();
+
+ bool setTrack(int track);
+ bool jumpToTick(uint32 tick, bool fireEvents = false, bool stopNotes = true, bool dontSendNoteOn = false);
+
+ uint32 getPPQN() { return _ppqn; }
+ virtual uint32 getTick() { return _position._play_tick; }
+
+ static void defaultXMidiCallback(byte eventData, void *refCon);
+
+ static MidiParser *createParser_SMF();
+ static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0);
+ static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); }
+};
+
+#endif
diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp
new file mode 100644
index 0000000000..9e4e8ed293
--- /dev/null
+++ b/audio/midiparser_smf.cpp
@@ -0,0 +1,384 @@
+/* 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 "audio/midiparser.h"
+#include "audio/mididrv.h"
+#include "common/util.h"
+
+/**
+ * The Standard MIDI File version of MidiParser.
+ */
+class MidiParser_SMF : public MidiParser {
+protected:
+ byte *_buffer;
+ bool _malformedPitchBends;
+
+protected:
+ void compressToType0();
+ void parseNextEvent(EventInfo &info);
+
+public:
+ MidiParser_SMF() : _buffer(0), _malformedPitchBends(false) {}
+ ~MidiParser_SMF();
+
+ bool loadMusic(byte *data, uint32 size);
+ void property(int property, int value);
+};
+
+
+static const byte command_lengths[8] = { 3, 3, 3, 3, 2, 2, 3, 0 };
+static const byte special_lengths[16] = { 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
+
+MidiParser_SMF::~MidiParser_SMF() {
+ free(_buffer);
+}
+
+void MidiParser_SMF::property(int prop, int value) {
+ switch (prop) {
+ case mpMalformedPitchBends:
+ _malformedPitchBends = (value > 0);
+ default:
+ MidiParser::property(prop, value);
+ }
+}
+
+void MidiParser_SMF::parseNextEvent(EventInfo &info) {
+ info.start = _position._play_pos;
+ info.delta = readVLQ(_position._play_pos);
+
+ // Process the next info. If mpMalformedPitchBends
+ // was set, we must skip over any pitch bend events
+ // because they are from Simon games and are not
+ // real pitch bend events, they're just two-byte
+ // prefixes before the real info.
+ do {
+ if ((_position._play_pos[0] & 0xF0) >= 0x80)
+ info.event = *(_position._play_pos++);
+ else
+ info.event = _position._running_status;
+ } while (_malformedPitchBends && (info.event & 0xF0) == 0xE0 && _position._play_pos++);
+ if (info.event < 0x80)
+ return;
+
+ _position._running_status = info.event;
+ switch (info.command()) {
+ case 0x9: // Note On
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ if (info.basic.param2 == 0)
+ info.event = info.channel() | 0x80;
+ info.length = 0;
+ break;
+
+ case 0xC:
+ case 0xD:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ case 0xE:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ info.length = 0;
+ break;
+
+ case 0xF: // System Common, Meta or SysEx event
+ switch (info.event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0x3: // Song Select
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x6:
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ case 0xC:
+ case 0xE:
+ info.basic.param1 = info.basic.param2 = 0;
+ break;
+
+ case 0x0: // SysEx
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ break;
+
+ case 0xF: // META event
+ info.ext.type = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ break;
+
+ default:
+ warning("MidiParser_SMF::parseNextEvent: Unsupported event code %x", info.event);
+ }
+ }
+}
+
+bool MidiParser_SMF::loadMusic(byte *data, uint32 size) {
+ uint32 len;
+ byte midi_type;
+ uint32 total_size;
+ bool isGMF;
+
+ unloadMusic();
+ byte *pos = data;
+ isGMF = false;
+
+ if (!memcmp(pos, "RIFF", 4)) {
+ // Skip the outer RIFF header.
+ pos += 8;
+ }
+
+ if (!memcmp(pos, "MThd", 4)) {
+ // SMF with MTHd information.
+ pos += 4;
+ len = read4high(pos);
+ if (len != 6) {
+ warning("MThd length 6 expected but found %d", (int)len);
+ return false;
+ }
+
+ // Verify that this MIDI either is a Type 2
+ // or has only 1 track. We do not support
+ // multitrack Type 1 files.
+ _num_tracks = pos[2] << 8 | pos[3];
+ midi_type = pos[1];
+ if (midi_type > 2 /*|| (midi_type < 2 && _num_tracks > 1)*/) {
+ warning("No support for a Type %d MIDI with %d tracks", (int)midi_type, (int)_num_tracks);
+ return false;
+ }
+ _ppqn = pos[4] << 8 | pos[5];
+ pos += len;
+ } else if (!memcmp(pos, "GMF\x1", 4)) {
+ // Older GMD/MUS file with no header info.
+ // Assume 1 track, 192 PPQN, and no MTrk headers.
+ isGMF = true;
+ midi_type = 0;
+ _num_tracks = 1;
+ _ppqn = 192;
+ pos += 7; // 'GMD\x1' + 3 bytes of useless (translate: unknown) information
+ } else {
+ warning("Expected MThd or GMD header but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+
+ // Now we identify and store the location for each track.
+ if (_num_tracks > ARRAYSIZE(_tracks)) {
+ warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks);
+ return false;
+ }
+
+ total_size = 0;
+ int tracks_read = 0;
+ while (tracks_read < _num_tracks) {
+ if (memcmp(pos, "MTrk", 4) && !isGMF) {
+ warning("Position: %p ('%c')", pos, *pos);
+ warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+
+ // If needed, skip the MTrk and length bytes
+ _tracks[tracks_read] = pos + (isGMF ? 0 : 8);
+ if (!isGMF) {
+ pos += 4;
+ len = read4high(pos);
+ total_size += len;
+ pos += len;
+ } else {
+ // An SMF End of Track meta event must be placed
+ // at the end of the stream.
+ data[size++] = 0xFF;
+ data[size++] = 0x2F;
+ data[size++] = 0x00;
+ data[size++] = 0x00;
+ }
+ ++tracks_read;
+ }
+
+ // If this is a Type 1 MIDI, we need to now compress
+ // our tracks down into a single Type 0 track.
+ free(_buffer);
+ _buffer = 0;
+
+ if (midi_type == 1) {
+ // FIXME: Doubled the buffer size to prevent crashes with the
+ // Inherit the Earth MIDIs. Jamieson630 said something about a
+ // better fix, but this will have to do in the meantime.
+ _buffer = (byte *)malloc(size * 2);
+ compressToType0();
+ _num_tracks = 1;
+ _tracks[0] = _buffer;
+ }
+
+ // Note that we assume the original data passed in
+ // will persist beyond this call, i.e. we do NOT
+ // copy the data to our own buffer. Take warning....
+ resetTracking();
+ setTempo(500000);
+ setTrack(0);
+ return true;
+}
+
+void MidiParser_SMF::compressToType0() {
+ // We assume that _buffer has been allocated
+ // to sufficient size for this operation.
+
+ // using 0xFF since it could write track_pos[0 to _num_tracks] here
+ // this would cause some illegal writes and could lead to segfaults
+ // (it crashed for some midis for me, they're not used in any game
+ // scummvm supports though). *Maybe* handle this in another way,
+ // it's at the moment only to be sure, that nothing goes wrong.
+ byte *track_pos[0xFF];
+ byte running_status[0xFF];
+ uint32 track_timer[0xFF];
+ uint32 delta;
+ int i;
+
+ for (i = 0; i < _num_tracks; ++i) {
+ running_status[i] = 0;
+ track_pos[i] = _tracks[i];
+ track_timer[i] = readVLQ(track_pos[i]);
+ running_status[i] = 0;
+ }
+
+ int best_i;
+ uint32 length;
+ byte *output = _buffer;
+ byte *pos, *pos2;
+ byte event;
+ uint32 copy_bytes;
+ bool write;
+ byte active_tracks = (byte)_num_tracks;
+
+ while (active_tracks) {
+ write = true;
+ best_i = 255;
+ for (i = 0; i < _num_tracks; ++i) {
+ if (track_pos[i] && (best_i == 255 || track_timer[i] < track_timer[best_i]))
+ best_i = i;
+ }
+ if (best_i == 255) {
+ warning("Premature end of tracks");
+ break;
+ }
+
+ // Initial VLQ delta computation
+ delta = 0;
+ length = track_timer[best_i];
+ for (i = 0; length; ++i) {
+ delta = (delta << 8) | (length & 0x7F) | (i ? 0x80 : 0);
+ length >>= 7;
+ }
+
+ // Process MIDI event.
+ bool implicitEvent = false;
+ copy_bytes = 0;
+ pos = track_pos[best_i];
+ do {
+ event = *(pos++);
+ if (event < 0x80) {
+ event = running_status[best_i];
+ implicitEvent = true;
+ }
+ } while (_malformedPitchBends && (event & 0xF0) == 0xE0 && pos++);
+ running_status[best_i] = event;
+
+ if (command_lengths[(event >> 4) - 8] > 0) {
+ copy_bytes = command_lengths[(event >> 4) - 8];
+ } else if (special_lengths[(event & 0x0F)] > 0) {
+ copy_bytes = special_lengths[(event & 0x0F)];
+ } else if (event == 0xF0) {
+ // SysEx
+ pos2 = pos;
+ length = readVLQ(pos);
+ copy_bytes = 1 + (pos - pos2) + length;
+ } else if (event == 0xFF) {
+ // META
+ event = *(pos++);
+ if (event == 0x2F && active_tracks > 1) {
+ track_pos[best_i] = 0;
+ write = false;
+ } else {
+ pos2 = pos;
+ length = readVLQ(pos);
+ copy_bytes = 2 + (pos - pos2) + length;
+ }
+ if (event == 0x2F)
+ --active_tracks;
+ } else {
+ warning("Bad MIDI command %02X", (int)event);
+ track_pos[best_i] = 0;
+ }
+
+ // Update all tracks' deltas
+ if (write) {
+ for (i = 0; i < _num_tracks; ++i) {
+ if (track_pos[i] && i != best_i)
+ track_timer[i] -= track_timer[best_i];
+ }
+ }
+
+ if (track_pos[best_i]) {
+ if (write) {
+ track_timer[best_i] = 0;
+
+ // Write VLQ delta
+ while (delta & 0x80) {
+ *output++ = (byte)(delta & 0xFF);
+ delta >>= 8;
+ }
+ *output++ = (byte)(delta & 0xFF);
+
+ // Write MIDI data
+ if (!implicitEvent)
+ ++track_pos[best_i];
+ --copy_bytes;
+ *output++ = running_status[best_i];
+ memcpy(output, track_pos[best_i], copy_bytes);
+ output += copy_bytes;
+ }
+
+ // Fetch new VLQ delta for winning track
+ track_pos[best_i] += copy_bytes;
+ if (active_tracks)
+ track_timer[best_i] += readVLQ(track_pos[best_i]);
+ }
+ }
+
+ *output++ = 0x00;
+}
+
+MidiParser *MidiParser::createParser_SMF() { return new MidiParser_SMF; }
diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp
new file mode 100644
index 0000000000..edc7c7a943
--- /dev/null
+++ b/audio/midiparser_xmidi.cpp
@@ -0,0 +1,375 @@
+/* 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 "audio/midiparser.h"
+#include "audio/mididrv.h"
+#include "common/util.h"
+
+/**
+ * The XMIDI version of MidiParser.
+ *
+ * Much of this code is adapted from the XMIDI implementation from the exult
+ * project.
+ */
+class MidiParser_XMIDI : public MidiParser {
+protected:
+ NoteTimer _notes_cache[32];
+ uint32 _inserted_delta; // Track simulated deltas for note-off events
+
+ struct Loop {
+ byte *pos;
+ byte repeat;
+ };
+
+ Loop _loop[4];
+ int _loopCount;
+
+ XMidiCallbackProc _callbackProc;
+ void *_callbackData;
+
+protected:
+ uint32 readVLQ2(byte * &data);
+ void resetTracking();
+ void parseNextEvent(EventInfo &info);
+
+public:
+ MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _inserted_delta(0), _callbackProc(proc), _callbackData(data) {}
+ ~MidiParser_XMIDI() { }
+
+ bool loadMusic(byte *data, uint32 size);
+};
+
+
+// This is a special XMIDI variable length quantity
+uint32 MidiParser_XMIDI::readVLQ2(byte * &pos) {
+ uint32 value = 0;
+ while (!(pos[0] & 0x80)) {
+ value += *pos++;
+ }
+ return value;
+}
+
+void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
+ info.start = _position._play_pos;
+ info.delta = readVLQ2(_position._play_pos) - _inserted_delta;
+
+ // Process the next event.
+ _inserted_delta = 0;
+ info.event = *(_position._play_pos++);
+ switch (info.event >> 4) {
+ case 0x9: // Note On
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ if (info.basic.param2 == 0) {
+ info.event = info.channel() | 0x80;
+ info.length = 0;
+ }
+ break;
+
+ case 0xC:
+ case 0xD:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x8:
+ case 0xA:
+ case 0xE:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0xB:
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+
+ // This isn't a full XMIDI implementation, but it should
+ // hopefully be "good enough" for most things.
+
+ switch (info.basic.param1) {
+ // Simplified XMIDI looping.
+ case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP
+ byte *pos = _position._play_pos;
+ if (_loopCount < ARRAYSIZE(_loop) - 1)
+ _loopCount++;
+ else
+ warning("XMIDI: Exceeding maximum loop count %d", ARRAYSIZE(_loop));
+
+ _loop[_loopCount].pos = pos;
+ _loop[_loopCount].repeat = info.basic.param2;
+ break;
+ }
+
+ case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK
+ if (_loopCount >= 0) {
+ if (info.basic.param2 < 64) {
+ // End the current loop.
+ _loopCount--;
+ } else {
+ // Repeat 0 means "loop forever".
+ if (_loop[_loopCount].repeat) {
+ if (--_loop[_loopCount].repeat == 0)
+ _loopCount--;
+ else
+ _position._play_pos = _loop[_loopCount].pos;
+ } else {
+ _position._play_pos = _loop[_loopCount].pos;
+ }
+ }
+ }
+ break;
+
+ case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG
+ if (_callbackProc)
+ _callbackProc(info.basic.param2, _callbackData);
+ break;
+
+ case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK
+ case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT
+ case 0x70: // XMIDI_CONTROLLER_VOICE_PROT
+ case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT
+ case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE
+ case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX
+ case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT
+ case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX
+ default:
+ if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) {
+ warning("Unsupported XMIDI controller %d (0x%2x)",
+ info.basic.param1, info.basic.param1);
+ }
+ }
+
+ // Should we really keep passing the XMIDI controller events to
+ // the MIDI driver, or should we turn them into some kind of
+ // NOP events? (Dummy meta events, perhaps?) Ah well, it has
+ // worked so far, so it shouldn't cause any damage...
+
+ break;
+
+ case 0xF: // Meta or SysEx event
+ switch (info.event & 0x0F) {
+ case 0x2: // Song Position Pointer
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = *(_position._play_pos++);
+ break;
+
+ case 0x3: // Song Select
+ info.basic.param1 = *(_position._play_pos++);
+ info.basic.param2 = 0;
+ break;
+
+ case 0x6:
+ case 0x8:
+ case 0xA:
+ case 0xB:
+ case 0xC:
+ case 0xE:
+ info.basic.param1 = info.basic.param2 = 0;
+ break;
+
+ case 0x0: // SysEx
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ break;
+
+ case 0xF: // META event
+ info.ext.type = *(_position._play_pos++);
+ info.length = readVLQ(_position._play_pos);
+ info.ext.data = _position._play_pos;
+ _position._play_pos += info.length;
+ if (info.ext.type == 0x51 && info.length == 3) {
+ // Tempo event. We want to make these constant 500,000.
+ info.ext.data[0] = 0x07;
+ info.ext.data[1] = 0xA1;
+ info.ext.data[2] = 0x20;
+ }
+ break;
+
+ default:
+ warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x", info.event);
+ }
+ }
+}
+
+bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
+ uint32 i = 0;
+ byte *start;
+ uint32 len;
+ uint32 chunk_len;
+ char buf[32];
+
+ _loopCount = -1;
+
+ unloadMusic();
+ byte *pos = data;
+
+ if (!memcmp(pos, "FORM", 4)) {
+ pos += 4;
+
+ // Read length of
+ len = read4high(pos);
+ start = pos;
+
+ // XDIRless XMIDI, we can handle them here.
+ if (!memcmp(pos, "XMID", 4)) {
+ warning("XMIDI doesn't have XDIR");
+ pos += 4;
+ _num_tracks = 1;
+ } else if (memcmp(pos, "XDIR", 4)) {
+ // Not an XMIDI that we recognise
+ warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ } else {
+ // Seems Valid
+ pos += 4;
+ _num_tracks = 0;
+
+ for (i = 4; i < len; i++) {
+ // Read 4 bytes of type
+ memcpy(buf, pos, 4);
+ pos += 4;
+
+ // Read length of chunk
+ chunk_len = read4high(pos);
+
+ // Add eight bytes
+ i += 8;
+
+ if (memcmp(buf, "INFO", 4)) {
+ // Must align
+ pos += (chunk_len + 1) & ~1;
+ i += (chunk_len + 1) & ~1;
+ continue;
+ }
+
+ // Must be at least 2 bytes long
+ if (chunk_len < 2) {
+ warning("Invalid chunk length %d for 'INFO' block", (int)chunk_len);
+ return false;
+ }
+
+ _num_tracks = (byte)read2low(pos);
+
+ if (chunk_len > 2) {
+ warning("Chunk length %d is greater than 2", (int)chunk_len);
+ pos += chunk_len - 2;
+ }
+ break;
+ }
+
+ // Didn't get to fill the header
+ if (_num_tracks == 0) {
+ warning("Didn't find a valid track count");
+ return false;
+ }
+
+ // Ok now to start part 2
+ // Goto the right place
+ pos = start + ((len + 1) & ~1);
+
+ if (memcmp(pos, "CAT ", 4)) {
+ // Not an XMID
+ warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ pos += 4;
+
+ // Now read length of this track
+ len = read4high(pos);
+
+ if (memcmp(pos, "XMID", 4)) {
+ // Not an XMID
+ warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ pos += 4;
+
+ }
+
+ // Ok it's an XMIDI.
+ // We're going to identify and store the location for each track.
+ if (_num_tracks > ARRAYSIZE(_tracks)) {
+ warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks);
+ return false;
+ }
+
+ int tracks_read = 0;
+ while (tracks_read < _num_tracks) {
+ if (!memcmp(pos, "FORM", 4)) {
+ // Skip this plus the 4 bytes after it.
+ pos += 8;
+ } else if (!memcmp(pos, "XMID", 4)) {
+ // Skip this.
+ pos += 4;
+ } else if (!memcmp(pos, "TIMB", 4)) {
+ // Custom timbres?
+ // We don't support them.
+ // Read the length, skip it, and hope there was nothing there.
+ pos += 4;
+ len = read4high(pos);
+ pos += (len + 1) & ~1;
+ } else if (!memcmp(pos, "EVNT", 4)) {
+ // Ahh! What we're looking for at last.
+ _tracks[tracks_read] = pos + 8; // Skip the EVNT and length bytes
+ pos += 4;
+ len = read4high(pos);
+ pos += (len + 1) & ~1;
+ ++tracks_read;
+ } else {
+ warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
+ return false;
+ }
+ }
+
+ // If we got this far, we successfully established
+ // the locations for each of our tracks.
+ // Note that we assume the original data passed in
+ // will persist beyond this call, i.e. we do NOT
+ // copy the data to our own buffer. Take warning....
+ _ppqn = 60;
+ resetTracking();
+ setTempo(500000);
+ _inserted_delta = 0;
+ setTrack(0);
+ return true;
+ }
+
+ return false;
+}
+
+void MidiParser_XMIDI::resetTracking() {
+ MidiParser::resetTracking();
+ _inserted_delta = 0;
+}
+
+void MidiParser::defaultXMidiCallback(byte eventData, void *data) {
+ warning("MidiParser: defaultXMidiCallback(%d)", eventData);
+}
+
+MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) {
+ return new MidiParser_XMIDI(proc, data);
+}
diff --git a/audio/mixer.cpp b/audio/mixer.cpp
new file mode 100644
index 0000000000..c2271b1059
--- /dev/null
+++ b/audio/mixer.cpp
@@ -0,0 +1,556 @@
+/* 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/util.h"
+#include "common/system.h"
+
+#include "audio/mixer_intern.h"
+#include "audio/rate.h"
+#include "audio/audiostream.h"
+#include "audio/timestamp.h"
+
+
+namespace Audio {
+
+#pragma mark -
+#pragma mark --- Channel classes ---
+#pragma mark -
+
+
+/**
+ * Channel used by the default Mixer implementation.
+ */
+class Channel {
+public:
+ Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent);
+ ~Channel();
+
+ /**
+ * Mixes the channel's samples into the given buffer.
+ *
+ * @param data buffer where to mix the data
+ * @param len number of sample *pairs*. So a value of
+ * 10 means that the buffer contains twice 10 sample, each
+ * 16 bits, for a total of 40 bytes.
+ */
+ void mix(int16 *data, uint len);
+
+ /**
+ * Queries whether the channel is still playing or not.
+ */
+ bool isFinished() const { return _stream->endOfStream(); }
+
+ /**
+ * Queries whether the channel is a permanent channel.
+ * A permanent channel is not affected by a Mixer::stopAll
+ * call.
+ */
+ bool isPermanent() const { return _permanent; }
+
+ /**
+ * Returns the id of the channel.
+ */
+ int getId() const { return _id; }
+
+ /**
+ * Pauses or unpaused the channel in a recursive fashion.
+ *
+ * @param paused true, when the channel should be paused.
+ * false when it should be unpaused.
+ */
+ void pause(bool paused);
+
+ /**
+ * Queries whether the channel is currently paused.
+ */
+ bool isPaused() const { return (_pauseLevel != 0); }
+
+ /**
+ * Sets the channel's own volume.
+ *
+ * @param volume new volume
+ */
+ void setVolume(const byte volume);
+
+ /**
+ * Sets the channel's balance setting.
+ *
+ * @param balance new balance
+ */
+ void setBalance(const int8 balance);
+
+ /**
+ * Notifies the channel that the global sound type
+ * volume settings changed.
+ */
+ void notifyGlobalVolChange() { updateChannelVolumes(); }
+
+ /**
+ * Queries how long the channel has been playing.
+ */
+ Timestamp getElapsedTime();
+
+ /**
+ * Queries the channel's sound type.
+ */
+ Mixer::SoundType getType() const { return _type; }
+
+ /**
+ * Sets the channel's sound handle.
+ *
+ * @param handle new handle
+ */
+ void setHandle(const SoundHandle handle) { _handle = handle; }
+
+ /**
+ * Queries the channel's sound handle.
+ */
+ SoundHandle getHandle() const { return _handle; }
+
+private:
+ const Mixer::SoundType _type;
+ SoundHandle _handle;
+ bool _permanent;
+ int _pauseLevel;
+ int _id;
+
+ byte _volume;
+ int8 _balance;
+
+ void updateChannelVolumes();
+ st_volume_t _volL, _volR;
+
+ Mixer *_mixer;
+
+ uint32 _samplesConsumed;
+ uint32 _samplesDecoded;
+ uint32 _mixerTimeStamp;
+ uint32 _pauseStartTime;
+ uint32 _pauseTime;
+
+ DisposeAfterUse::Flag _autofreeStream;
+ RateConverter *_converter;
+ AudioStream *_stream;
+};
+
+#pragma mark -
+#pragma mark --- Mixer ---
+#pragma mark -
+
+
+MixerImpl::MixerImpl(OSystem *system, uint sampleRate)
+ : _syst(system), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0) {
+
+ assert(sampleRate > 0);
+
+ int i;
+
+ for (i = 0; i < ARRAYSIZE(_volumeForSoundType); i++)
+ _volumeForSoundType[i] = kMaxMixerVolume;
+
+ for (i = 0; i != NUM_CHANNELS; i++)
+ _channels[i] = 0;
+}
+
+MixerImpl::~MixerImpl() {
+ for (int i = 0; i != NUM_CHANNELS; i++)
+ delete _channels[i];
+}
+
+void MixerImpl::setReady(bool ready) {
+ _mixerReady = ready;
+}
+
+uint MixerImpl::getOutputRate() const {
+ return _sampleRate;
+}
+
+void MixerImpl::insertChannel(SoundHandle *handle, Channel *chan) {
+ int index = -1;
+ for (int i = 0; i != NUM_CHANNELS; i++) {
+ if (_channels[i] == 0) {
+ index = i;
+ break;
+ }
+ }
+ if (index == -1) {
+ warning("MixerImpl::out of mixer slots");
+ delete chan;
+ return;
+ }
+
+ _channels[index] = chan;
+
+ SoundHandle chanHandle;
+ chanHandle._val = index + (_handleSeed * NUM_CHANNELS);
+
+ chan->setHandle(chanHandle);
+ _handleSeed++;
+ if (handle)
+ *handle = chanHandle;
+}
+
+void MixerImpl::playStream(
+ SoundType type,
+ SoundHandle *handle,
+ AudioStream *stream,
+ int id, byte volume, int8 balance,
+ DisposeAfterUse::Flag autofreeStream,
+ bool permanent,
+ bool reverseStereo) {
+ Common::StackLock lock(_mutex);
+
+ if (stream == 0) {
+ warning("stream is 0");
+ return;
+ }
+
+
+ assert(_mixerReady);
+
+ // Prevent duplicate sounds
+ if (id != -1) {
+ for (int i = 0; i != NUM_CHANNELS; i++)
+ if (_channels[i] != 0 && _channels[i]->getId() == id) {
+ // Delete the stream if were asked to auto-dispose it.
+ // Note: This could cause trouble if the client code does not
+ // yet expect the stream to be gone. The primary example to
+ // keep in mind here is QueuingAudioStream.
+ // Thus, as a quick rule of thumb, you should never, ever,
+ // try to play QueuingAudioStreams with a sound id.
+ if (autofreeStream == DisposeAfterUse::YES)
+ delete stream;
+ return;
+ }
+ }
+
+#ifdef AUDIO_REVERSE_STEREO
+ reverseStereo = !reverseStereo;
+#endif
+
+ // Create the channel
+ Channel *chan = new Channel(this, type, stream, autofreeStream, reverseStereo, id, permanent);
+ chan->setVolume(volume);
+ chan->setBalance(balance);
+ insertChannel(handle, chan);
+}
+
+void MixerImpl::mixCallback(byte *samples, uint len) {
+ assert(samples);
+
+ Common::StackLock lock(_mutex);
+
+ int16 *buf = (int16 *)samples;
+ len >>= 2;
+
+ // Since the mixer callback has been called, the mixer must be ready...
+ _mixerReady = true;
+
+ // zero the buf
+ memset(buf, 0, 2 * len * sizeof(int16));
+
+ // mix all channels
+ for (int i = 0; i != NUM_CHANNELS; i++)
+ if (_channels[i]) {
+ if (_channels[i]->isFinished()) {
+ delete _channels[i];
+ _channels[i] = 0;
+ } else if (!_channels[i]->isPaused())
+ _channels[i]->mix(buf, len);
+ }
+}
+
+void MixerImpl::stopAll() {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++) {
+ if (_channels[i] != 0 && !_channels[i]->isPermanent()) {
+ delete _channels[i];
+ _channels[i] = 0;
+ }
+ }
+}
+
+void MixerImpl::stopID(int id) {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++) {
+ if (_channels[i] != 0 && _channels[i]->getId() == id) {
+ delete _channels[i];
+ _channels[i] = 0;
+ }
+ }
+}
+
+void MixerImpl::stopHandle(SoundHandle handle) {
+ Common::StackLock lock(_mutex);
+
+ // Simply ignore stop requests for handles of sounds that already terminated
+ const int index = handle._val % NUM_CHANNELS;
+ if (!_channels[index] || _channels[index]->getHandle()._val != handle._val)
+ return;
+
+ delete _channels[index];
+ _channels[index] = 0;
+}
+
+void MixerImpl::setChannelVolume(SoundHandle handle, byte volume) {
+ Common::StackLock lock(_mutex);
+
+ const int index = handle._val % NUM_CHANNELS;
+ if (!_channels[index] || _channels[index]->getHandle()._val != handle._val)
+ return;
+
+ _channels[index]->setVolume(volume);
+}
+
+void MixerImpl::setChannelBalance(SoundHandle handle, int8 balance) {
+ Common::StackLock lock(_mutex);
+
+ const int index = handle._val % NUM_CHANNELS;
+ if (!_channels[index] || _channels[index]->getHandle()._val != handle._val)
+ return;
+
+ _channels[index]->setBalance(balance);
+}
+
+uint32 MixerImpl::getSoundElapsedTime(SoundHandle handle) {
+ return getElapsedTime(handle).msecs();
+}
+
+Timestamp MixerImpl::getElapsedTime(SoundHandle handle) {
+ Common::StackLock lock(_mutex);
+
+ const int index = handle._val % NUM_CHANNELS;
+ if (!_channels[index] || _channels[index]->getHandle()._val != handle._val)
+ return Timestamp(0, _sampleRate);
+
+ return _channels[index]->getElapsedTime();
+}
+
+void MixerImpl::pauseAll(bool paused) {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++) {
+ if (_channels[i] != 0) {
+ _channels[i]->pause(paused);
+ }
+ }
+}
+
+void MixerImpl::pauseID(int id, bool paused) {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++) {
+ if (_channels[i] != 0 && _channels[i]->getId() == id) {
+ _channels[i]->pause(paused);
+ return;
+ }
+ }
+}
+
+void MixerImpl::pauseHandle(SoundHandle handle, bool paused) {
+ Common::StackLock lock(_mutex);
+
+ // Simply ignore (un)pause requests for sounds that already terminated
+ const int index = handle._val % NUM_CHANNELS;
+ if (!_channels[index] || _channels[index]->getHandle()._val != handle._val)
+ return;
+
+ _channels[index]->pause(paused);
+}
+
+bool MixerImpl::isSoundIDActive(int id) {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++)
+ if (_channels[i] && _channels[i]->getId() == id)
+ return true;
+ return false;
+}
+
+int MixerImpl::getSoundID(SoundHandle handle) {
+ Common::StackLock lock(_mutex);
+ const int index = handle._val % NUM_CHANNELS;
+ if (_channels[index] && _channels[index]->getHandle()._val == handle._val)
+ return _channels[index]->getId();
+ return 0;
+}
+
+bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
+ Common::StackLock lock(_mutex);
+ const int index = handle._val % NUM_CHANNELS;
+ return _channels[index] && _channels[index]->getHandle()._val == handle._val;
+}
+
+bool MixerImpl::hasActiveChannelOfType(SoundType type) {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i != NUM_CHANNELS; i++)
+ if (_channels[i] && _channels[i]->getType() == type)
+ return true;
+ return false;
+}
+
+void MixerImpl::setVolumeForSoundType(SoundType type, int volume) {
+ assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType));
+
+ // Check range
+ if (volume > kMaxMixerVolume)
+ volume = kMaxMixerVolume;
+ else if (volume < 0)
+ volume = 0;
+
+ // TODO: Maybe we should do logarithmic (not linear) volume
+ // scaling? See also Player_V2::setMasterVolume
+
+ Common::StackLock lock(_mutex);
+ _volumeForSoundType[type] = volume;
+
+ for (int i = 0; i != NUM_CHANNELS; ++i) {
+ if (_channels[i] && _channels[i]->getType() == type)
+ _channels[i]->notifyGlobalVolChange();
+ }
+}
+
+int MixerImpl::getVolumeForSoundType(SoundType type) const {
+ assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType));
+
+ return _volumeForSoundType[type];
+}
+
+
+#pragma mark -
+#pragma mark --- Channel implementations ---
+#pragma mark -
+
+Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream,
+ DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent)
+ : _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume),
+ _balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0),
+ _pauseStartTime(0), _pauseTime(0), _autofreeStream(autofreeStream), _converter(0),
+ _stream(stream) {
+ assert(mixer);
+ assert(stream);
+
+ // Get a rate converter instance
+ _converter = makeRateConverter(_stream->getRate(), mixer->getOutputRate(), _stream->isStereo(), reverseStereo);
+}
+
+Channel::~Channel() {
+ delete _converter;
+ if (_autofreeStream == DisposeAfterUse::YES)
+ delete _stream;
+}
+
+void Channel::setVolume(const byte volume) {
+ _volume = volume;
+ updateChannelVolumes();
+}
+
+void Channel::setBalance(const int8 balance) {
+ _balance = balance;
+ updateChannelVolumes();
+}
+
+void Channel::updateChannelVolumes() {
+ // From the channel balance/volume and the global volume, we compute
+ // the effective volume for the left and right channel. Note the
+ // slightly odd divisor: the 255 reflects the fact that the maximal
+ // value for _volume is 255, while the 127 is there because the
+ // balance value ranges from -127 to 127. The mixer (music/sound)
+ // volume is in the range 0 - kMaxMixerVolume.
+ // Hence, the vol_l/vol_r values will be in that range, too
+
+ int vol = _mixer->getVolumeForSoundType(_type) * _volume;
+
+ if (_balance == 0) {
+ _volL = vol / Mixer::kMaxChannelVolume;
+ _volR = vol / Mixer::kMaxChannelVolume;
+ } else if (_balance < 0) {
+ _volL = vol / Mixer::kMaxChannelVolume;
+ _volR = ((127 + _balance) * vol) / (Mixer::kMaxChannelVolume * 127);
+ } else {
+ _volL = ((127 - _balance) * vol) / (Mixer::kMaxChannelVolume * 127);
+ _volR = vol / Mixer::kMaxChannelVolume;
+ }
+}
+
+void Channel::pause(bool paused) {
+ //assert((paused && _pauseLevel >= 0) || (!paused && _pauseLevel));
+
+ if (paused) {
+ _pauseLevel++;
+
+ if (_pauseLevel == 1)
+ _pauseStartTime = g_system->getMillis();
+ } else if (_pauseLevel > 0) {
+ _pauseLevel--;
+
+ if (!_pauseLevel) {
+ _pauseTime = (g_system->getMillis() - _pauseStartTime);
+ _pauseStartTime = 0;
+ }
+ }
+}
+
+Timestamp Channel::getElapsedTime() {
+ const uint32 rate = _mixer->getOutputRate();
+ uint32 delta = 0;
+
+ Audio::Timestamp ts(0, rate);
+
+ if (_mixerTimeStamp == 0)
+ return ts;
+
+ if (isPaused())
+ delta = _pauseStartTime - _mixerTimeStamp;
+ else
+ delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime;
+
+ // Convert the number of samples into a time duration.
+
+ ts = ts.addFrames(_samplesConsumed);
+ ts = ts.addMsecs(delta);
+
+ // In theory it would seem like a good idea to limit the approximation
+ // so that it never exceeds the theoretical upper bound set by
+ // _samplesDecoded. Meanwhile, back in the real world, doing so makes
+ // the Broken Sword cutscenes noticeably jerkier. I guess the mixer
+ // isn't invoked at the regular intervals that I first imagined.
+
+ return ts;
+}
+
+void Channel::mix(int16 *data, uint len) {
+ assert(_stream);
+
+ if (_stream->endOfData()) {
+ // TODO: call drain method
+ } else {
+ assert(_converter);
+
+ _samplesConsumed = _samplesDecoded;
+ _mixerTimeStamp = g_system->getMillis();
+ _pauseTime = 0;
+ _samplesDecoded += _converter->flow(*_stream, data, len, _volL, _volR);
+ }
+}
+
+} // End of namespace Audio
diff --git a/audio/mixer.h b/audio/mixer.h
new file mode 100644
index 0000000000..a048124ca3
--- /dev/null
+++ b/audio/mixer.h
@@ -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 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$
+ *
+ */
+
+#ifndef SOUND_MIXER_H
+#define SOUND_MIXER_H
+
+#include "common/types.h"
+#include "common/mutex.h"
+#include "common/noncopyable.h"
+
+#include "audio/timestamp.h"
+
+class OSystem;
+
+
+namespace Audio {
+
+class AudioStream;
+class Channel;
+class Mixer;
+class MixerImpl;
+
+/**
+ * A SoundHandle instances corresponds to a specific sound
+ * being played via the mixer. It can be used to control that
+ * sound (pause it, stop it, etc.).
+ * @see The Mixer class
+ */
+class SoundHandle {
+ friend class Channel;
+ friend class MixerImpl;
+ uint32 _val;
+public:
+ inline SoundHandle() : _val(0xFFFFFFFF) {}
+};
+
+/**
+ * The main audio mixer handles mixing of an arbitrary number of
+ * audio streams (in the form of AudioStream instances).
+ */
+class Mixer : Common::NonCopyable {
+public:
+ enum SoundType {
+ kPlainSoundType = 0,
+
+ kMusicSoundType = 1,
+ kSFXSoundType = 2,
+ kSpeechSoundType = 3
+ };
+
+ enum {
+ kMaxChannelVolume = 255,
+ kMaxMixerVolume = 256
+ };
+
+public:
+ Mixer() {}
+ virtual ~Mixer() {}
+
+
+
+ /**
+ * Is the mixer ready and setup? This may not be the case on systems which
+ * don't support digital sound output. In that case, the mixer proc may
+ * never be called. That in turn can cause breakage in games which try to
+ * sync with an audio stream. In particular, the AdLib MIDI emulation...
+ *
+ * @return whether the mixer is ready and setup
+ *
+ * @todo get rid of this?
+ */
+ virtual bool isReady() const = 0;
+
+
+ /**
+ * Start playing the given audio stream.
+ *
+ * Note that the sound id assigned below is unique. At most one stream
+ * with a given id can play at any given time. Trying to play a sound
+ * with an id that is already in use causes the new sound to be not played.
+ *
+ * @param type the type (voice/sfx/music) of the stream
+ * @param handle a SoundHandle which can be used to reference and control
+ * the stream via suitable mixer methods
+ * @param stream the actual AudioStream to be played
+ * @param id a unique id assigned to this stream
+ * @param volume the volume with which to play the sound, ranging from 0 to 255
+ * @param balance the balance with which to play the sound, ranging from -128 to 127
+ * @param autofreeStream a flag indicating whether the stream should be
+ * freed after playback finished
+ * @param permanent a flag indicating whether a plain stopAll call should
+ * not stop this particular stream
+ * @param reverseStereo a flag indicating whether left and right channels shall be swapped
+ */
+ virtual void playStream(
+ SoundType type,
+ SoundHandle *handle,
+ AudioStream *stream,
+ int id = -1,
+ byte volume = kMaxChannelVolume,
+ int8 balance = 0,
+ DisposeAfterUse::Flag autofreeStream = DisposeAfterUse::YES,
+ bool permanent = false,
+ bool reverseStereo = false) = 0;
+
+ /**
+ * Stop all currently playing sounds.
+ */
+ virtual void stopAll() = 0;
+
+ /**
+ * Stop playing the sound with given ID.
+ *
+ * @param id the ID of the sound to affect
+ */
+ virtual void stopID(int id) = 0;
+
+ /**
+ * Stop playing the sound corresponding to the given handle.
+ *
+ * @param handle the sound to affect
+ */
+ virtual void stopHandle(SoundHandle handle) = 0;
+
+
+
+ /**
+ * Pause/unpause all sounds, including all regular and permanent
+ * channels
+ *
+ * @param paused true to pause everything, false to unpause
+ */
+ virtual void pauseAll(bool paused) = 0;
+
+ /**
+ * Pause/unpause the sound with the given ID.
+ *
+ * @param id the ID of the sound to affect
+ * @param paused true to pause the sound, false to unpause it
+ */
+ virtual void pauseID(int id, bool paused) = 0;
+
+ /**
+ * Pause/unpause the sound corresponding to the given handle.
+ *
+ * @param handle the sound to affect
+ * @param paused true to pause the sound, false to unpause it
+ */
+ virtual void pauseHandle(SoundHandle handle, bool paused) = 0;
+
+
+
+ /**
+ * Check if a sound with the given ID is active.
+ *
+ * @param id the ID of the sound to query
+ * @return true if the sound is active
+ */
+ virtual bool isSoundIDActive(int id) = 0;
+
+ /**
+ * Get the sound ID of handle sound
+ *
+ * @param handle sound to query
+ * @return sound ID if active
+ */
+ virtual int getSoundID(SoundHandle handle) = 0;
+
+ /**
+ * Check if a sound with the given handle is active.
+ *
+ * @param handle sound to query
+ * @return true if the sound is active
+ */
+ virtual bool isSoundHandleActive(SoundHandle handle) = 0;
+
+
+
+ /**
+ * Set the channel volume for the given handle.
+ *
+ * @param handle the sound to affect
+ * @param volume the new channel volume (0 - kMaxChannelVolume)
+ */
+ virtual void setChannelVolume(SoundHandle handle, byte volume) = 0;
+
+ /**
+ * Set the channel balance for the given handle.
+ *
+ * @param handle the sound to affect
+ * @param balance the new channel balance:
+ * (-127 ... 0 ... 127) corresponds to (left ... center ... right)
+ */
+ virtual void setChannelBalance(SoundHandle handle, int8 balance) = 0;
+
+ /**
+ * Get approximation of for how long the channel has been playing.
+ */
+ virtual uint32 getSoundElapsedTime(SoundHandle handle) = 0;
+
+ /**
+ * Get approximation of for how long the channel has been playing.
+ */
+ virtual Timestamp getElapsedTime(SoundHandle handle) = 0;
+
+ /**
+ * Check whether any channel of the given sound type is active.
+ * For example, this can be used to check whether any SFX sound
+ * is currently playing, by checking for type kSFXSoundType.
+ *
+ * @param type the sound type to look for
+ * @return true if any channels of the specified type are active.
+ */
+ virtual bool hasActiveChannelOfType(SoundType type) = 0;
+
+ /**
+ * Set the volume for the given sound type.
+ *
+ * @param type the sound type
+ * @param volume the new global volume, 0 - kMaxMixerVolume
+ */
+ virtual void setVolumeForSoundType(SoundType type, int volume) = 0;
+
+ /**
+ * Query the global volume.
+ *
+ * @param type the sound type
+ * @return the global music volume, 0 - kMaxMixerVolume
+ */
+ virtual int getVolumeForSoundType(SoundType type) const = 0;
+
+ /**
+ * Query the system's audio output sample rate.
+ *
+ * @return the output sample rate in Hz
+ */
+ virtual uint getOutputRate() const = 0;
+};
+
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mixer_intern.h b/audio/mixer_intern.h
new file mode 100644
index 0000000000..c8df9a594d
--- /dev/null
+++ b/audio/mixer_intern.h
@@ -0,0 +1,135 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MIXER_INTERN_H
+#define SOUND_MIXER_INTERN_H
+
+#include "common/scummsys.h"
+#include "common/mutex.h"
+#include "audio/mixer.h"
+
+namespace Audio {
+
+/**
+ * The (default) implementation of the ScummVM audio mixing subsystem.
+ *
+ * Backends are responsible for allocating (and later releasing) an instance
+ * of this class, which engines can access via OSystem::getMixer().
+ *
+ * Initialisation of instances of this class usually happens as follows:
+ * 1) Creat a new Audio::MixerImpl instance.
+ * 2) Set the hardware output sample rate via the setSampleRate() method.
+ * 3) Hook up the mixCallback() in a suitable audio processing thread/callback.
+ * 4) Change the mixer into ready mode via setReady(true).
+ * 5) Start audio processing (e.g. by resuming the audio thread, if applicable).
+ *
+ * In the future, we might make it possible for backends to provide
+ * (partial) alternative implementations of the mixer, e.g. to make
+ * better use of native sound mixing support on low-end devices.
+ *
+ * @see OSystem::getMixer()
+ */
+class MixerImpl : public Mixer {
+private:
+ enum {
+ NUM_CHANNELS = 16
+ };
+
+ OSystem *_syst;
+ Common::Mutex _mutex;
+
+ const uint _sampleRate;
+ bool _mixerReady;
+ uint32 _handleSeed;
+
+ int _volumeForSoundType[4];
+ Channel *_channels[NUM_CHANNELS];
+
+
+public:
+
+ MixerImpl(OSystem *system, uint sampleRate);
+ ~MixerImpl();
+
+ virtual bool isReady() const { return _mixerReady; }
+
+ virtual void playStream(
+ SoundType type,
+ SoundHandle *handle,
+ AudioStream *input,
+ int id, byte volume, int8 balance,
+ DisposeAfterUse::Flag autofreeStream,
+ bool permanent,
+ bool reverseStereo);
+
+ virtual void stopAll();
+ virtual void stopID(int id);
+ virtual void stopHandle(SoundHandle handle);
+
+ virtual void pauseAll(bool paused);
+ virtual void pauseID(int id, bool paused);
+ virtual void pauseHandle(SoundHandle handle, bool paused);
+
+ virtual bool isSoundIDActive(int id);
+ virtual int getSoundID(SoundHandle handle);
+
+ virtual bool isSoundHandleActive(SoundHandle handle);
+
+ virtual void setChannelVolume(SoundHandle handle, byte volume);
+ virtual void setChannelBalance(SoundHandle handle, int8 balance);
+
+ virtual uint32 getSoundElapsedTime(SoundHandle handle);
+ virtual Timestamp getElapsedTime(SoundHandle handle);
+
+ virtual bool hasActiveChannelOfType(SoundType type);
+
+ virtual void setVolumeForSoundType(SoundType type, int volume);
+ virtual int getVolumeForSoundType(SoundType type) const;
+
+ virtual uint getOutputRate() const;
+
+protected:
+ void insertChannel(SoundHandle *handle, Channel *chan);
+
+public:
+ /**
+ * The mixer callback function, to be called at regular intervals by
+ * the backend (e.g. from an audio mixing thread). All the actual mixing
+ * work is done from here.
+ */
+ void mixCallback(byte *samples, uint len);
+
+ /**
+ * Set the internal 'is ready' flag of the mixer.
+ * Backends should invoke Mixer::setReady(true) once initialisation of
+ * their audio system has been completed.
+ */
+ void setReady(bool ready);
+};
+
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/infogrames.cpp b/audio/mods/infogrames.cpp
new file mode 100644
index 0000000000..27e42c637b
--- /dev/null
+++ b/audio/mods/infogrames.cpp
@@ -0,0 +1,470 @@
+/* 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 "audio/mods/infogrames.h"
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/memstream.h"
+
+namespace Audio {
+
+Infogrames::Instruments::Instruments() {
+ init();
+}
+
+Infogrames::Instruments::~Instruments() {
+ delete[] _sampleData;
+}
+
+void Infogrames::Instruments::init() {
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ _samples[i].data = 0;
+ _samples[i].dataRepeat = 0;
+ _samples[i].length = 0;
+ _samples[i].lengthRepeat = 0;
+ }
+ _count = 0;
+ _sampleData = 0;
+}
+
+bool Infogrames::Instruments::load(const char *ins) {
+ Common::File f;
+
+ if (f.open(ins))
+ return load(f);
+ return false;
+}
+
+bool Infogrames::Instruments::load(Common::SeekableReadStream &ins) {
+ int i;
+ int32 fsize;
+ int32 offset[32];
+ int32 offsetRepeat[32];
+ int32 dataOffset;
+
+ unload();
+
+ fsize = ins.readUint32BE();
+ dataOffset = fsize;
+ for (i = 0; (i < 32) && !ins.eos(); i++) {
+ offset[i] = ins.readUint32BE();
+ offsetRepeat[i] = ins.readUint32BE();
+ if ((offset[i] > fsize) || (offsetRepeat[i] > fsize) ||
+ (offset[i] < (ins.pos() + 4)) ||
+ (offsetRepeat[i] < (ins.pos() + 4))) {
+ // Definitely no real entry anymore
+ ins.seek(-8, SEEK_CUR);
+ break;
+ }
+
+ dataOffset = MIN(dataOffset, MIN(offset[i], offsetRepeat[i]));
+ ins.skip(4); // Unknown
+ _samples[i].length = ins.readUint16BE() * 2;
+ _samples[i].lengthRepeat = ins.readUint16BE() * 2;
+ }
+
+ if (dataOffset >= fsize)
+ return false;
+
+ _count = i;
+ _sampleData = new int8[fsize - dataOffset];
+ ins.seek(dataOffset + 4);
+ ins.read(_sampleData, fsize - dataOffset);
+
+ for (i--; i >= 0; i--) {
+ _samples[i].data = _sampleData + (offset[i] - dataOffset);
+ _samples[i].dataRepeat = _sampleData + (offsetRepeat[i] - dataOffset);
+ }
+
+ return true;
+}
+
+void Infogrames::Instruments::unload() {
+ delete[] _sampleData;
+ init();
+}
+
+const uint8 Infogrames::tickCount[] =
+ {2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96};
+const uint16 Infogrames::periods[] =
+ {0x6ACC, 0x64CC, 0x5F25, 0x59CE, 0x54C3, 0x5003, 0x4B86, 0x4747, 0x4346,
+ 0x3F8B, 0x3BF3, 0x3892, 0x3568, 0x3269, 0x2F93, 0x2CEA, 0x2A66, 0x2801,
+ 0x2566, 0x23A5, 0x21AF, 0x1FC4, 0x1DFE, 0x1C4E, 0x1ABC, 0x1936, 0x17CC,
+ 0x1676, 0x1533, 0x1401, 0x12E4, 0x11D5, 0x10D4, 0x0FE3, 0x0EFE, 0x0E26,
+ 0x0D5B, 0x0C9B, 0x0BE5, 0x0B3B, 0x0A9B, 0x0A02, 0x0972, 0x08E9, 0x0869,
+ 0x07F1, 0x077F, 0x0713, 0x06AD, 0x064D, 0x05F2, 0x059D, 0x054D, 0x0500,
+ 0x04B8, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x038A, 0x0356, 0x0326, 0x02F9,
+ 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C5,
+ 0x01AB, 0x0193, 0x017D, 0x0167, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D,
+ 0x00FE, 0x00F0, 0x00E2, 0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00AA, 0x00A0,
+ 0x0097, 0x008F, 0x0087, 0x007F, 0x0078, 0x0070, 0x0060, 0x0050, 0x0040,
+ 0x0030, 0x0020, 0x0010, 0x0000, 0x0000, 0x0020, 0x2020, 0x2020, 0x2020,
+ 0x2020, 0x3030, 0x3030, 0x3020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020,
+ 0x2020, 0x2020, 0x2020, 0x2090, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040,
+ 0x4040, 0x4040, 0x400C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C40, 0x4040,
+ 0x4040, 0x4040, 0x0909, 0x0909, 0x0909, 0x0101, 0x0101, 0x0101, 0x0101,
+ 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x4040, 0x4040, 0x4040,
+ 0x0A0A, 0x0A0A, 0x0A0A, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202,
+ 0x0202, 0x0202, 0x0202, 0x0202, 0x4040, 0x4040, 0x2000};
+
+Infogrames::Infogrames(Instruments &ins, bool stereo, int rate,
+ int interruptFreq) : Paula(stereo, rate, interruptFreq) {
+ _instruments = &ins;
+ _data = 0;
+ _repCount = -1;
+
+ reset();
+}
+
+Infogrames::~Infogrames() {
+ delete[] _data;
+}
+
+void Infogrames::init() {
+ int i;
+
+ _volume = 0;
+ _period = 0;
+ _sample = 0;
+ _speedCounter = _speed;
+
+ for (i = 0; i < 4; i++) {
+ _chn[i].cmds = 0;
+ _chn[i].cmdBlocks = 0;
+ _chn[i].volSlide.finetuneNeg = 0;
+ _chn[i].volSlide.finetunePos = 0;
+ _chn[i].volSlide.data = 0;
+ _chn[i].volSlide.amount = 0;
+ _chn[i].volSlide.dataOffset = 0;
+ _chn[i].volSlide.flags = 0;
+ _chn[i].volSlide.curDelay1 = 0;
+ _chn[i].volSlide.curDelay2 = 0;
+ _chn[i].periodSlide.finetuneNeg = 0;
+ _chn[i].periodSlide.finetunePos = 0;
+ _chn[i].periodSlide.data = 0;
+ _chn[i].periodSlide.amount = 0;
+ _chn[i].periodSlide.dataOffset = 0;
+ _chn[i].periodSlide.flags = 0;
+ _chn[i].periodSlide.curDelay1 = 0;
+ _chn[i].periodSlide.curDelay2 = 0;
+ _chn[i].period = 0;
+ _chn[i].flags = 0x81;
+ _chn[i].ticks = 0;
+ _chn[i].tickCount = 0;
+ _chn[i].periodMod = 0;
+ }
+
+ _end = (_data == 0);
+}
+
+void Infogrames::reset() {
+ int i;
+
+ stopPlay();
+ init();
+
+ _volSlideBlocks = 0;
+ _periodSlideBlocks = 0;
+ _subSong = 0;
+ _cmdBlocks = 0;
+ _speedCounter = 0;
+ _speed = 0;
+
+ for (i = 0; i < 4; i++)
+ _chn[i].cmdBlockIndices = 0;
+}
+
+bool Infogrames::load(const char *dum) {
+ Common::File f;
+
+ if (f.open(dum))
+ return load(f);
+ return false;
+}
+
+bool Infogrames::load(Common::SeekableReadStream &dum) {
+ int subSong = 0;
+ int i;
+ uint32 size;
+
+ size = dum.size();
+ if (size < 20)
+ return false;
+
+ _data = new uint8[size];
+ dum.seek(0);
+ dum.read(_data, size);
+
+ Common::MemoryReadStream dataStr(_data, size);
+
+ dataStr.seek(subSong * 2);
+ dataStr.seek(dataStr.readUint16BE());
+ _subSong = _data + dataStr.pos();
+ if (_subSong > (_data + size))
+ return false;
+
+ _speedCounter = dataStr.readUint16BE();
+ _speed = _speedCounter;
+ _volSlideBlocks = _subSong + dataStr.readUint16BE();
+ _periodSlideBlocks = _subSong + dataStr.readUint16BE();
+ for (i = 0; i < 4; i++) {
+ _chn[i].cmdBlockIndices = _subSong + dataStr.readUint16BE();
+ _chn[i].flags = 0x81;
+ }
+ _cmdBlocks = _data + dataStr.pos() + 2;
+
+ if ((_volSlideBlocks > (_data + size)) ||
+ (_periodSlideBlocks > (_data + size)) ||
+ (_chn[0].cmdBlockIndices > (_data + size)) ||
+ (_chn[1].cmdBlockIndices > (_data + size)) ||
+ (_chn[2].cmdBlockIndices > (_data + size)) ||
+ (_chn[3].cmdBlockIndices > (_data + size)) ||
+ (_cmdBlocks > (_data + size)))
+ return false;
+
+ startPaula();
+ return true;
+}
+
+void Infogrames::unload() {
+ stopPlay();
+
+ delete[] _data;
+ _data = 0;
+
+ clearVoices();
+ reset();
+}
+
+void Infogrames::getNextSample(Channel &chn) {
+ byte *data;
+ byte cmdBlock = 0;
+ uint16 cmd;
+ bool cont = false;
+
+ if (chn.flags & 64)
+ return;
+
+ if (chn.flags & 1) {
+ chn.flags &= ~1;
+ chn.cmdBlocks = chn.cmdBlockIndices;
+ } else {
+ chn.flags &= ~1;
+ if (_speedCounter == 0)
+ chn.ticks--;
+ if (chn.ticks != 0) {
+ _volume = MAX((int16) 0, tune(chn.volSlide, 0));
+ _period = tune(chn.periodSlide, chn.period);
+ return;
+ } else {
+ chn.ticks = chn.tickCount;
+ cont = true;
+ }
+ }
+
+ while (1) {
+ while (cont || ((cmdBlock = *chn.cmdBlocks) != 0xFF)) {
+ if (!cont) {
+ chn.cmdBlocks++;
+ chn.cmds = _subSong +
+ READ_BE_UINT16(_cmdBlocks + (cmdBlock * 2));
+ } else
+ cont = false;
+ while ((cmd = *chn.cmds) != 0xFF) {
+ chn.cmds++;
+ if (cmd & 128)
+ {
+ switch (cmd & 0xE0) {
+ case 0x80: // 100xxxxx - Set ticks
+ chn.ticks = tickCount[cmd & 0xF];
+ chn.tickCount = tickCount[cmd & 0xF];
+ break;
+ case 0xA0: // 101xxxxx - Set sample
+ _sample = cmd & 0x1F;
+ break;
+ case 0xC0: // 110xxxxx - Set volume slide/finetune
+ data = _volSlideBlocks + (cmd & 0x1F) * 13;
+ chn.volSlide.flags = (*data & 0x80) | 1;
+ chn.volSlide.amount = *data++ & 0x7F;
+ chn.volSlide.data = data;
+ chn.volSlide.dataOffset = 0;
+ chn.volSlide.finetunePos = 0;
+ chn.volSlide.finetuneNeg = 0;
+ chn.volSlide.curDelay1 = 0;
+ chn.volSlide.curDelay2 = 0;
+ break;
+ case 0xE0: // 111xxxxx - Extended
+ switch (cmd & 0x1F) {
+ case 0: // Set period modifier
+ chn.periodMod = (int8) *chn.cmds++;
+ break;
+ case 1: // Set continuous period slide
+ chn.periodSlide.data =
+ _periodSlideBlocks + *chn.cmds++ * 13 + 1;
+ chn.periodSlide.amount = 0;
+ chn.periodSlide.dataOffset = 0;
+ chn.periodSlide.finetunePos = 0;
+ chn.periodSlide.finetuneNeg = 0;
+ chn.periodSlide.curDelay1 = 0;
+ chn.periodSlide.curDelay2 = 0;
+ chn.periodSlide.flags = 0x81;
+ break;
+ case 2: // Set non-continuous period slide
+ chn.periodSlide.data =
+ _periodSlideBlocks + *chn.cmds++ * 13 + 1;
+ chn.periodSlide.amount = 0;
+ chn.periodSlide.dataOffset = 0;
+ chn.periodSlide.finetunePos = 0;
+ chn.periodSlide.finetuneNeg = 0;
+ chn.periodSlide.curDelay1 = 0;
+ chn.periodSlide.curDelay2 = 0;
+ chn.periodSlide.flags = 1;
+ break;
+ case 3: // NOP
+ break;
+ default:
+ warning("Unknown Infogrames command: %X", cmd);
+ }
+ break;
+ }
+ } else { // 0xxxxxxx - Set period
+ if (cmd != 0)
+ cmd += chn.periodMod;
+ chn.period = periods[cmd];
+ chn.volSlide.dataOffset = 0;
+ chn.volSlide.finetunePos = 0;
+ chn.volSlide.finetuneNeg = 0;
+ chn.volSlide.curDelay1 = 0;
+ chn.volSlide.curDelay2 = 0;
+ chn.volSlide.flags |= 1;
+ chn.volSlide.flags &= ~4;
+ chn.periodSlide.dataOffset = 0;
+ chn.periodSlide.finetunePos = 0;
+ chn.periodSlide.finetuneNeg = 0;
+ chn.periodSlide.curDelay1 = 0;
+ chn.periodSlide.curDelay2 = 0;
+ chn.periodSlide.flags |= 1;
+ chn.periodSlide.flags &= ~4;
+ _volume = MAX((int16) 0, tune(chn.volSlide, 0));
+ _period = tune(chn.periodSlide, chn.period);
+ return;
+ }
+ }
+ }
+ if (!(chn.flags & 32)) {
+ chn.flags |= 0x40;
+ _volume = 0;
+ return;
+ } else
+ chn.cmdBlocks = chn.cmdBlockIndices;
+ }
+}
+
+int16 Infogrames::tune(Slide &slide, int16 start) const {
+ byte *data;
+ uint8 off;
+
+ data = slide.data + slide.dataOffset;
+
+ if (slide.flags & 1)
+ slide.finetunePos += (int8) data[1];
+ slide.flags &= ~1;
+
+ start += slide.finetunePos - slide.finetuneNeg;
+ if (start < 0)
+ start = 0;
+
+ if (slide.flags & 4)
+ return start;
+
+ slide.curDelay1++;
+ if (slide.curDelay1 != data[2])
+ return start;
+ slide.curDelay2++;
+ slide.curDelay1 = 0;
+ if (slide.curDelay2 == data[0]) {
+ slide.curDelay2 = 0;
+ off = slide.dataOffset + 3;
+ if (off == 12) {
+ if (slide.flags == 0) {
+ slide.flags |= 4;
+ return start;
+ } else {
+ slide.curDelay2 = 0;
+ slide.finetuneNeg += slide.amount;
+ off = 3;
+ }
+ }
+ slide.dataOffset = off;
+ }
+ slide.flags |= 1;
+ return start;
+}
+
+void Infogrames::interrupt() {
+ int chn;
+
+ if (!_data) {
+ clearVoices();
+ return;
+ }
+
+ _speedCounter--;
+ _sample = 0xFF;
+ for (chn = 0; chn < 4; chn++) {
+ _volume = 0;
+ _period = 0;
+ getNextSample(_chn[chn]);
+ setChannelVolume(chn, _volume);
+ setChannelPeriod(chn, _period);
+ if ((_sample != 0xFF) && (_sample < _instruments->_count)) {
+ setChannelData(chn,
+ _instruments->_samples[_sample].data,
+ _instruments->_samples[_sample].dataRepeat,
+ _instruments->_samples[_sample].length,
+ _instruments->_samples[_sample].lengthRepeat);
+ _sample = 0xFF;
+ }
+ }
+ if (_speedCounter == 0)
+ _speedCounter = _speed;
+
+ // End reached?
+ if ((_chn[0].flags & 64) && (_chn[1].flags & 64) &&
+ (_chn[2].flags & 64) && (_chn[3].flags & 64)) {
+ if (_repCount > 0) {
+ _repCount--;
+ init();
+ } else if (_repCount != -1) {
+ stopPaula();
+ } else {
+ init();
+ }
+ }
+}
+
+} // End of namespace Audio
diff --git a/audio/mods/infogrames.h b/audio/mods/infogrames.h
new file mode 100644
index 0000000000..c7abebf24e
--- /dev/null
+++ b/audio/mods/infogrames.h
@@ -0,0 +1,148 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - gob
+ */
+
+#ifndef SOUND_MODS_INFOGRAMES_H
+#define SOUND_MODS_INFOGRAMES_H
+
+#include "audio/mods/paula.h"
+#include "common/stream.h"
+
+namespace Audio {
+
+/** A player for the Infogrames/RobHubbard2 format */
+class Infogrames : public Paula {
+public:
+ class Instruments {
+ public:
+ Instruments();
+ template<typename T> Instruments(T ins) {
+ init();
+ bool result = load(ins);
+ assert(result);
+ }
+ ~Instruments();
+
+ bool load(Common::SeekableReadStream &ins);
+ bool load(const char *ins);
+ void unload();
+
+ uint8 getCount() const { return _count; }
+
+ protected:
+ struct Sample {
+ int8 *data;
+ int8 *dataRepeat;
+ uint32 length;
+ uint32 lengthRepeat;
+ } _samples[32];
+
+ uint8 _count;
+ int8 *_sampleData;
+
+ void init();
+
+ friend class Infogrames;
+ };
+
+ Infogrames(Instruments &ins, bool stereo = false, int rate = 44100,
+ int interruptFreq = 0);
+ ~Infogrames();
+
+ Instruments *getInstruments() const { return _instruments; }
+ bool getRepeating() const { return _repCount != 0; }
+ void setRepeating (int32 repCount) { _repCount = repCount; }
+
+ bool load(Common::SeekableReadStream &dum);
+ bool load(const char *dum);
+ void unload();
+ void restart() {
+ if (_data) {
+ // Use the mutex here to ensure we do not call init()
+ // while data is being read by the mixer thread.
+ _mutex.lock();
+ init();
+ startPlay();
+ _mutex.unlock();
+ }
+ }
+
+protected:
+ Instruments *_instruments;
+
+ static const uint8 tickCount[];
+ static const uint16 periods[];
+ byte *_data;
+ int32 _repCount;
+
+ byte *_subSong;
+ byte *_cmdBlocks;
+ byte *_volSlideBlocks;
+ byte *_periodSlideBlocks;
+ uint8 _speedCounter;
+ uint8 _speed;
+
+ uint16 _volume;
+ int16 _period;
+ uint8 _sample;
+
+ struct Slide {
+ byte *data;
+ int8 amount;
+ uint8 dataOffset;
+ int16 finetuneNeg;
+ int16 finetunePos;
+ uint8 curDelay1;
+ uint8 curDelay2;
+ uint8 flags; // 0: Apply finetune modifier, 2: Don't slide, 7: Continuous
+ };
+ struct Channel {
+ byte *cmdBlockIndices;
+ byte *cmdBlocks;
+ byte *cmds;
+ uint8 ticks;
+ uint8 tickCount;
+ Slide volSlide;
+ Slide periodSlide;
+ int16 period;
+ int8 periodMod;
+ uint8 flags; // 0: Need init, 5: Loop cmdBlocks, 6: Ignore channel
+ } _chn[4];
+
+ void init();
+ void reset();
+ void getNextSample(Channel &chn);
+ int16 tune(Slide &slide, int16 start) const;
+ virtual void interrupt();
+};
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/maxtrax.cpp b/audio/mods/maxtrax.cpp
new file mode 100644
index 0000000000..a577c72eed
--- /dev/null
+++ b/audio/mods/maxtrax.cpp
@@ -0,0 +1,1040 @@
+/* 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"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/debug.h"
+
+#include "audio/mods/maxtrax.h"
+
+// test for engines using this class.
+#if defined(SOUND_MODS_MAXTRAX_H)
+
+namespace {
+
+enum { K_VALUE = 0x9fd77, PREF_PERIOD = 0x8fd77, PERIOD_LIMIT = 0x6f73d };
+enum { NO_BEND = 64 << 7, MAX_BEND_RANGE = 24 };
+
+int32 precalcNote(byte baseNote, int16 tune, byte octave) {
+ return K_VALUE + 0x3C000 - ((baseNote << 14) + (tune << 11) / 3) / 3 - (octave << 16);
+}
+
+int32 calcVolumeDelta(int32 delta, uint16 time, uint16 vBlankFreq) {
+ const int32 div = time * vBlankFreq;
+ // div <= 1000 means time to small (or even 0)
+ return (div <= 1000) ? delta : (1000 * delta) / div;
+}
+
+int32 calcTempo(const uint16 tempo, uint16 vBlankFreq) {
+ return (int32)(((uint32)(tempo & 0xFFF0) << 8) / (uint16)(5 * vBlankFreq));
+}
+
+void nullFunc(int) {}
+
+// Function to calculate 2^x, where x is a fixedpoint number with 16 fraction bits
+// using exp would be more accurate and needs less space if mathlibrary is already linked
+// but this function should be faster and doesnt use floats
+#if 1
+inline uint32 pow2Fixed(int32 val) {
+ static const uint16 tablePow2[] = {
+ 0, 178, 356, 535, 714, 893, 1073, 1254, 1435, 1617, 1799, 1981, 2164, 2348, 2532, 2716,
+ 2902, 3087, 3273, 3460, 3647, 3834, 4022, 4211, 4400, 4590, 4780, 4971, 5162, 5353, 5546, 5738,
+ 5932, 6125, 6320, 6514, 6710, 6906, 7102, 7299, 7496, 7694, 7893, 8092, 8292, 8492, 8693, 8894,
+ 9096, 9298, 9501, 9704, 9908, 10113, 10318, 10524, 10730, 10937, 11144, 11352, 11560, 11769, 11979, 12189,
+ 12400, 12611, 12823, 13036, 13249, 13462, 13676, 13891, 14106, 14322, 14539, 14756, 14974, 15192, 15411, 15630,
+ 15850, 16071, 16292, 16514, 16737, 16960, 17183, 17408, 17633, 17858, 18084, 18311, 18538, 18766, 18995, 19224,
+ 19454, 19684, 19915, 20147, 20379, 20612, 20846, 21080, 21315, 21550, 21786, 22023, 22260, 22498, 22737, 22977,
+ 23216, 23457, 23698, 23940, 24183, 24426, 24670, 24915, 25160, 25406, 25652, 25900, 26148, 26396, 26645, 26895,
+ 27146, 27397, 27649, 27902, 28155, 28409, 28664, 28919, 29175, 29432, 29690, 29948, 30207, 30466, 30727, 30988,
+ 31249, 31512, 31775, 32039, 32303, 32568, 32834, 33101, 33369, 33637, 33906, 34175, 34446, 34717, 34988, 35261,
+ 35534, 35808, 36083, 36359, 36635, 36912, 37190, 37468, 37747, 38028, 38308, 38590, 38872, 39155, 39439, 39724,
+ 40009, 40295, 40582, 40870, 41158, 41448, 41738, 42029, 42320, 42613, 42906, 43200, 43495, 43790, 44087, 44384,
+ 44682, 44981, 45280, 45581, 45882, 46184, 46487, 46791, 47095, 47401, 47707, 48014, 48322, 48631, 48940, 49251,
+ 49562, 49874, 50187, 50500, 50815, 51131, 51447, 51764, 52082, 52401, 52721, 53041, 53363, 53685, 54008, 54333,
+ 54658, 54983, 55310, 55638, 55966, 56296, 56626, 56957, 57289, 57622, 57956, 58291, 58627, 58964, 59301, 59640,
+ 59979, 60319, 60661, 61003, 61346, 61690, 62035, 62381, 62727, 63075, 63424, 63774, 64124, 64476, 64828, 65182,
+ 0
+ };
+ const uint16 whole = val >> 16;
+ const uint8 index = (uint8)(val >> 8);
+ // calculate fractional part.
+ const uint16 base = tablePow2[index];
+ // linear interpolation and add 1.0
+ uint32 exponent = ((uint32)(uint16)(tablePow2[index + 1] - base) * (uint8)val) + ((uint32)base << 8) + (1 << 24);
+
+ if (whole < 24) {
+ // shift away all but the last fractional bit which is used for rounding,
+ // then round to nearest integer
+ exponent = ((exponent >> (23 - whole)) + 1) >> 1;
+ } else if (whole < 32) {
+ // no need to round here
+ exponent <<= whole - 24;
+ } else if (val > 0) {
+ // overflow
+ exponent = 0xFFFFFFFF;
+ } else {
+ // negative integer, test if >= -0.5
+ exponent = (val >= -0x8000) ? 1 : 0;
+ }
+ return exponent;
+}
+#else
+inline uint32 pow2Fixed(int32 val) {
+ return (uint32)(expf((float)val * (float)(0.69314718055994530942 / (1 << 16))) + 0.5f);
+}
+#endif
+
+} // End of namespace
+
+namespace Audio {
+
+MaxTrax::MaxTrax(int rate, bool stereo, uint16 vBlankFreq, uint16 maxScores)
+ : Paula(stereo, rate, rate / vBlankFreq),
+ _patch(),
+ _scores(),
+ _numScores() {
+ _playerCtx.maxScoreNum = maxScores;
+ _playerCtx.vBlankFreq = vBlankFreq;
+ _playerCtx.frameUnit = (uint16)((1000 << 8) / vBlankFreq);
+ _playerCtx.scoreIndex = -1;
+ _playerCtx.volume = 0x40;
+
+ _playerCtx.tempo = 120;
+ _playerCtx.tempoTime = 0;
+ _playerCtx.filterOn = true;
+ _playerCtx.syncCallBack = &nullFunc;
+
+ resetPlayer();
+ for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i)
+ _channelCtx[i].regParamNumber = 0;
+}
+
+MaxTrax::~MaxTrax() {
+ stopMusic();
+ freePatches();
+ freeScores();
+}
+
+void MaxTrax::interrupt() {
+ // a5 - maxtraxm a4 . globaldata
+
+ // TODO
+ // test for changes in shared struct and make changes
+ // specifically all used channels get marked altered
+
+ _playerCtx.ticks += _playerCtx.tickUnit;
+ const int32 millis = _playerCtx.ticks >> 8; // d4
+
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ VoiceContext &voice = _voiceCtx[i];
+ if (voice.stopEventTime >= 0) {
+ assert(voice.channel);
+ voice.stopEventTime -= (voice.channel < &_channelCtx[kNumChannels]) ? _playerCtx.tickUnit : _playerCtx.frameUnit;
+ if (voice.stopEventTime <= 0 && voice.status > VoiceContext::kStatusRelease) {
+ if ((voice.channel->flags & ChannelContext::kFlagDamper) != 0)
+ voice.hasDamper = true;
+ else
+ voice.status = VoiceContext::kStatusRelease;
+ }
+ }
+ }
+
+ if (_playerCtx.scoreIndex >= 0) {
+ const Event *curEvent = _playerCtx.nextEvent;
+ int32 eventDelta = _playerCtx.nextEventTime - millis;
+ for (; eventDelta <= 0; eventDelta += (++curEvent)->startTime) {
+ const byte cmd = curEvent->command;
+ ChannelContext &channel = _channelCtx[curEvent->parameter & 0x0F];
+
+ // outPutEvent(*curEvent);
+ // debug("CurTime, EventDelta, NextDelta: %d, %d, %d", millis, eventDelta, eventDelta + curEvent[1].startTime );
+
+ if (cmd < 0x80) { // Note
+ const int8 voiceIndex = noteOn(channel, cmd, (curEvent->parameter & 0xF0) >> 1, kPriorityScore);
+ if (voiceIndex >= 0)
+ _voiceCtx[voiceIndex].stopEventTime = MAX<int32>(0, (eventDelta + curEvent->stopTime) << 8);
+
+ } else {
+ switch (cmd) {
+
+ case 0x80: // TEMPO
+ if ((_playerCtx.tickUnit >> 8) > curEvent->stopTime) {
+ _playerCtx.tickUnit = calcTempo(curEvent->parameter << 4, _playerCtx.vBlankFreq);
+ _playerCtx.tempoTime = 0;
+ } else {
+ _playerCtx.tempoStart = _playerCtx.tempo;
+ _playerCtx.tempoDelta = (curEvent->parameter << 4) - _playerCtx.tempoStart;
+ _playerCtx.tempoTime = (curEvent->stopTime << 8);
+ _playerCtx.tempoTicks = 0;
+ }
+ break;
+
+ case 0xC0: // PROGRAM
+ channel.patch = &_patch[curEvent->stopTime & (kNumPatches - 1)];
+ break;
+
+ case 0xE0: // BEND
+ channel.pitchBend = ((curEvent->stopTime & 0x7F00) >> 1) | (curEvent->stopTime & 0x7f);
+ channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8);
+ channel.isAltered = true;
+ break;
+
+ case 0xFF: // END
+ if (_playerCtx.musicLoop) {
+ curEvent = _scores[_playerCtx.scoreIndex].events;
+ eventDelta = curEvent->startTime - millis;
+ _playerCtx.ticks = 0;
+ } else
+ _playerCtx.scoreIndex = -1;
+ // stop processing for this tick
+ goto endOfEventLoop;
+
+ case 0xA0: // SPECIAL
+ switch (curEvent->stopTime >> 8){
+ case 0x01: // SPECIAL_SYNC
+ _playerCtx.syncCallBack(curEvent->stopTime & 0xFF);
+ break;
+ case 0x02: // SPECIAL_BEGINREP
+ // we allow a depth of 4 loops
+ for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) {
+ if (!_playerCtx.repeatPoint[i]) {
+ _playerCtx.repeatPoint[i] = curEvent;
+ _playerCtx.repeatCount[i] = curEvent->stopTime & 0xFF;
+ break;
+ }
+ }
+ break;
+ case 0x03: // SPECIAL_ENDREP
+ for (int i = ARRAYSIZE(_playerCtx.repeatPoint) - 1; i >= 0; --i) {
+ if (_playerCtx.repeatPoint[i]) {
+ if (_playerCtx.repeatCount[i]--)
+ curEvent = _playerCtx.repeatPoint[i]; // gets incremented by 1 at end of loop
+ else
+ _playerCtx.repeatPoint[i] = 0;
+ break;
+ }
+ }
+ break;
+ }
+ break;
+
+ case 0xB0: // CONTROL
+ controlCh(channel, (byte)(curEvent->stopTime >> 8), (byte)curEvent->stopTime);
+ break;
+
+ default:
+ debug("Unhandled Command");
+ outPutEvent(*curEvent);
+ }
+ }
+ }
+endOfEventLoop:
+ _playerCtx.nextEvent = curEvent;
+ _playerCtx.nextEventTime = eventDelta + millis;
+
+ // tempoEffect
+ if (_playerCtx.tempoTime) {
+ _playerCtx.tempoTicks += _playerCtx.tickUnit;
+ uint16 newTempo = _playerCtx.tempoStart;
+ if (_playerCtx.tempoTicks < _playerCtx.tempoTime) {
+ newTempo += (uint16)((_playerCtx.tempoTicks * _playerCtx.tempoDelta) / _playerCtx.tempoTime);
+ } else {
+ _playerCtx.tempoTime = 0;
+ newTempo += _playerCtx.tempoDelta;
+ }
+ _playerCtx.tickUnit = calcTempo(newTempo, _playerCtx.vBlankFreq);
+ }
+ }
+
+ // Handling of Envelopes and Portamento
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ VoiceContext &voice = _voiceCtx[i];
+ if (!voice.channel)
+ continue;
+ const ChannelContext &channel = *voice.channel;
+ const Patch &patch = *voice.patch;
+
+ switch (voice.status) {
+ case VoiceContext::kStatusSustain:
+ // we need to check if some voices have no sustainSample.
+ // in that case they are finished after the attackSample is done
+ if (voice.dmaOff && Paula::getChannelDmaCount((byte)i) >= voice.dmaOff ) {
+ voice.dmaOff = 0;
+ voice.isBlocked = 0;
+ voice.priority = 0;
+ // disable it in next tick
+ voice.stopEventTime = 0;
+ }
+ if (!channel.isAltered && !voice.hasPortamento && !channel.modulation)
+ continue;
+ // Update Volume and Period
+ break;
+
+ case VoiceContext::kStatusHalt:
+ killVoice((byte)i);
+ continue;
+
+ case VoiceContext::kStatusStart:
+ if (patch.attackLen) {
+ voice.envelope = patch.attackPtr;
+ const uint16 duration = voice.envelope->duration;
+ voice.envelopeLeft = patch.attackLen;
+ voice.ticksLeft = duration << 8;
+ voice.status = VoiceContext::kStatusAttack;
+ voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume, duration, _playerCtx.vBlankFreq);
+ // Process Envelope
+ } else {
+ voice.status = VoiceContext::kStatusSustain;
+ voice.baseVolume = patch.volume;
+ // Update Volume and Period
+ }
+ break;
+
+ case VoiceContext::kStatusRelease:
+ if (patch.releaseLen) {
+ voice.envelope = patch.attackPtr + patch.attackLen;
+ const uint16 duration = voice.envelope->duration;
+ voice.envelopeLeft = patch.releaseLen;
+ voice.ticksLeft = duration << 8;
+ voice.status = VoiceContext::kStatusDecay;
+ voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq);
+ // Process Envelope
+ } else {
+ voice.status = VoiceContext::kStatusHalt;
+ voice.lastVolume = 0;
+ // Send Audio Packet
+ }
+ voice.stopEventTime = -1;
+ break;
+ }
+
+ // Process Envelope
+ const uint16 envUnit = _playerCtx.frameUnit;
+ if (voice.envelope) {
+ if (voice.ticksLeft > envUnit) { // envelope still active
+ voice.baseVolume = (uint16) MIN<int32>(MAX<int32>(0, voice.baseVolume + voice.incrVolume), 0x8000);
+ voice.ticksLeft -= envUnit;
+ // Update Volume and Period
+
+ } else { // next or last Envelope
+ voice.baseVolume = voice.envelope->volume;
+ assert(voice.envelopeLeft > 0);
+ if (--voice.envelopeLeft) {
+ ++voice.envelope;
+ const uint16 duration = voice.envelope->duration;
+ voice.ticksLeft = duration << 8;
+ voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq);
+ // Update Volume and Period
+ } else if (voice.status == VoiceContext::kStatusDecay) {
+ voice.status = VoiceContext::kStatusHalt;
+ voice.envelope = 0;
+ voice.lastVolume = 0;
+ // Send Audio Packet
+ } else {
+ assert(voice.status == VoiceContext::kStatusAttack);
+ voice.status = VoiceContext::kStatusSustain;
+ voice.envelope = 0;
+ // Update Volume and Period
+ }
+ }
+ }
+
+ // Update Volume and Period
+ if (voice.status >= VoiceContext::kStatusDecay) {
+ // Calc volume
+ uint16 vol = (voice.noteVolume < (1 << 7)) ? (voice.noteVolume * _playerCtx.volume) >> 7 : _playerCtx.volume;
+ if (voice.baseVolume < (1 << 15))
+ vol = (uint16)(((uint32)vol * voice.baseVolume) >> 15);
+ if (voice.channel->volume < (1 << 7))
+ vol = (vol * voice.channel->volume) >> 7;
+ voice.lastVolume = (byte)MIN(vol, (uint16)0x64);
+
+ // Calc Period
+ if (voice.hasPortamento) {
+ voice.portaTicks += envUnit;
+ if ((uint16)(voice.portaTicks >> 8) >= channel.portamentoTime) {
+ voice.hasPortamento = false;
+ voice.baseNote = voice.endNote;
+ voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave);
+ }
+ voice.lastPeriod = calcNote(voice);
+ } else if (channel.isAltered || channel.modulation)
+ voice.lastPeriod = calcNote(voice);
+ }
+
+ // Send Audio Packet
+ Paula::setChannelPeriod((byte)i, (voice.lastPeriod) ? voice.lastPeriod : 1000);
+ Paula::setChannelVolume((byte)i, (voice.lastPeriod) ? voice.lastVolume : 0);
+ }
+ for (ChannelContext *c = _channelCtx; c != &_channelCtx[ARRAYSIZE(_channelCtx)]; ++c)
+ c->isAltered = false;
+
+#ifdef MAXTRAX_HAS_MODULATION
+ // original player had _playerCtx.sineValue = _playerCtx.frameUnit >> 2
+ // this should fit the comments that modtime=1000 is one second ?
+ _playerCtx.sineValue += _playerCtx.frameUnit;
+#endif
+}
+
+void MaxTrax::controlCh(ChannelContext &channel, const byte command, const byte data) {
+ switch (command) {
+ case 0x01: // modulation level MSB
+ channel.modulation = data << 8;
+ break;
+ case 0x21: // modulation level LSB
+ channel.modulation = (channel.modulation & 0xFF00) || ((data * 2) & 0xFF);
+ break;
+ case 0x05: // portamento time MSB
+ channel.portamentoTime = data << 7;
+ break;
+ case 0x25: // portamento time LSB
+ channel.portamentoTime = (channel.portamentoTime & 0x3f80) || data;
+ break;
+ case 0x06: // data entry MSB
+ if (channel.regParamNumber == 0) {
+ channel.pitchBendRange = (int8)MIN((uint8)MAX_BEND_RANGE, (uint8)data);
+ channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8);
+ channel.isAltered = true;
+ }
+ break;
+ case 0x07: // Main Volume MSB
+ channel.volume = (data == 0) ? 0 : data + 1;
+ channel.isAltered = true;
+ break;
+ case 0x0A: // Pan
+ if (data > 0x40 || (data == 0x40 && ((&channel - _channelCtx) & 1) != 0))
+ channel.flags |= ChannelContext::kFlagRightChannel;
+ else
+ channel.flags &= ~ChannelContext::kFlagRightChannel;
+ break;
+ case 0x10: // GPC as Modulation Time MSB
+ channel.modulationTime = data << 7;
+ break;
+ case 0x30: // GPC as Modulation Time LSB
+ channel.modulationTime = (channel.modulationTime & 0x3f80) || data;
+ break;
+ case 0x11: // GPC as Microtonal Set MSB
+ channel.microtonal = data << 8;
+ break;
+ case 0x31: // GPC as Microtonal Set LSB
+ channel.microtonal = (channel.microtonal & 0xFF00) || ((data * 2) & 0xFF);
+ break;
+ case 0x40: // Damper Pedal
+ if ((data & 0x40) != 0)
+ channel.flags |= ChannelContext::kFlagDamper;
+ else {
+ channel.flags &= ~ChannelContext::kFlagDamper;
+ // release all dampered voices on this channel
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ if (_voiceCtx[i].channel == &channel && _voiceCtx[i].hasDamper) {
+ _voiceCtx[i].hasDamper = false;
+ _voiceCtx[i].status = VoiceContext::kStatusRelease;
+ }
+ }
+ }
+ break;
+ case 0x41: // Portamento off/on
+ if ((data & 0x40) != 0)
+ channel.flags |= ChannelContext::kFlagPortamento;
+ else
+ channel.flags &= ~ChannelContext::kFlagPortamento;
+ break;
+ case 0x50: // Microtonal off/on
+ if ((data & 0x40) != 0)
+ channel.flags |= ChannelContext::kFlagMicrotonal;
+ else
+ channel.flags &= ~ChannelContext::kFlagMicrotonal;
+ break;
+ case 0x51: // Audio Filter off/on
+ Paula::setAudioFilter(data > 0x40 || (data == 0x40 && _playerCtx.filterOn));
+ break;
+ case 0x65: // RPN MSB
+ channel.regParamNumber = (data << 8) || (channel.regParamNumber & 0xFF);
+ break;
+ case 0x64: // RPN LSB
+ channel.regParamNumber = (channel.regParamNumber & 0xFF00) || data;
+ break;
+ case 0x79: // Reset All Controllers
+ resetChannel(channel, ((&channel - _channelCtx) & 1) != 0);
+ break;
+ case 0x7E: // MONO mode
+ channel.flags |= ChannelContext::kFlagMono;
+ goto allNotesOff;
+ case 0x7F: // POLY mode
+ channel.flags &= ~ChannelContext::kFlagMono;
+ // Fallthrough
+ case 0x7B: // All Notes Off
+allNotesOff:
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ if (_voiceCtx[i].channel == &channel) {
+ if ((channel.flags & ChannelContext::kFlagDamper) != 0)
+ _voiceCtx[i].hasDamper = true;
+ else
+ _voiceCtx[i].status = VoiceContext::kStatusRelease;
+ }
+ }
+ break;
+ case 0x78: // All Sounds Off
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ if (_voiceCtx[i].channel == &channel)
+ killVoice((byte)i);
+ }
+ break;
+ }
+}
+
+void MaxTrax::setTempo(const uint16 tempo) {
+ Common::StackLock lock(_mutex);
+ _playerCtx.tickUnit = calcTempo(tempo, _playerCtx.vBlankFreq);
+}
+
+void MaxTrax::resetPlayer() {
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i)
+ killVoice((byte)i);
+
+ for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) {
+ _channelCtx[i].flags = 0;
+ _channelCtx[i].lastNote = (uint8)-1;
+ resetChannel(_channelCtx[i], (i & 1) != 0);
+ _channelCtx[i].patch = (i < kNumChannels) ? &_patch[i] : 0;
+ }
+
+#ifdef MAXTRAX_HAS_MICROTONAL
+ for (int i = 0; i < ARRAYSIZE(_microtonal); ++i)
+ _microtonal[i] = (int16)(i << 8);
+#endif
+}
+
+void MaxTrax::stopMusic() {
+ Common::StackLock lock(_mutex);
+ _playerCtx.scoreIndex = -1;
+ for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) {
+ if (_voiceCtx[i].channel < &_channelCtx[kNumChannels])
+ killVoice((byte)i);
+ }
+}
+
+bool MaxTrax::playSong(int songIndex, bool loop) {
+ if (songIndex < 0 || songIndex >= _numScores)
+ return false;
+ Common::StackLock lock(_mutex);
+ _playerCtx.scoreIndex = -1;
+ resetPlayer();
+ for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i)
+ _playerCtx.repeatPoint[i] = 0;
+
+ setTempo(_playerCtx.tempoInitial << 4);
+ Paula::setAudioFilter(_playerCtx.filterOn);
+ _playerCtx.musicLoop = loop;
+ _playerCtx.tempoTime = 0;
+ _playerCtx.scoreIndex = songIndex;
+ _playerCtx.ticks = 0;
+
+ _playerCtx.nextEvent = _scores[songIndex].events;
+ _playerCtx.nextEventTime = _playerCtx.nextEvent->startTime;
+
+ Paula::startPaula();
+ return true;
+}
+
+void MaxTrax::advanceSong(int advance) {
+ Common::StackLock lock(_mutex);
+ if (_playerCtx.scoreIndex >= 0) {
+ const Event *cev = _playerCtx.nextEvent;
+ if (cev) {
+ for (; advance > 0; --advance) {
+ // TODO - check for boundaries
+ for (; cev->command != 0xFF && (cev->command != 0xA0 || (cev->stopTime >> 8) != 0x00); ++cev)
+ ; // no end_command or special_command + end
+ }
+ _playerCtx.nextEvent = cev;
+ }
+ }
+}
+
+void MaxTrax::killVoice(byte num) {
+ VoiceContext &voice = _voiceCtx[num];
+ voice.channel = 0;
+ voice.envelope = 0;
+ voice.status = VoiceContext::kStatusFree;
+ voice.isBlocked = 0;
+ voice.hasDamper = false;
+ voice.hasPortamento = false;
+ voice.priority = 0;
+ voice.stopEventTime = -1;
+ voice.dmaOff = 0;
+ voice.lastVolume = 0;
+ voice.tieBreak = 0;
+ //voice.uinqueId = 0;
+
+ // "stop" voice, set period to 1, vol to 0
+ Paula::disableChannel(num);
+ Paula::setChannelPeriod(num, 1);
+ Paula::setChannelVolume(num, 0);
+}
+
+int8 MaxTrax::pickvoice(uint pick, int16 pri) {
+ enum { kPrioFlagFixedSide = 1 << 3 };
+ pick &= 3;
+ if ((pri & (kPrioFlagFixedSide)) == 0) {
+ const bool leftSide = (uint)(pick - 1) > 1;
+ const int leftBest = MIN(_voiceCtx[0].status, _voiceCtx[3].status);
+ const int rightBest = MIN(_voiceCtx[1].status, _voiceCtx[2].status);
+ const int sameSide = (leftSide) ? leftBest : rightBest;
+ const int otherSide = leftBest + rightBest - sameSide;
+
+ if (sameSide > VoiceContext::kStatusRelease && otherSide <= VoiceContext::kStatusRelease)
+ pick ^= 1; // switches sides
+ }
+ pri &= ~kPrioFlagFixedSide;
+
+ for (int i = 2; i > 0; --i) {
+ VoiceContext *voice = &_voiceCtx[pick];
+ VoiceContext *alternate = &_voiceCtx[pick ^ 3];
+
+ const uint16 voiceVal = voice->status << 8 | voice->lastVolume;
+ const uint16 altVal = alternate->status << 8 | alternate->lastVolume;
+
+ if (voiceVal + voice->tieBreak > altVal
+ || voice->isBlocked > alternate->isBlocked) {
+
+ // this is somewhat different to the original player,
+ // but has a similar result
+ voice->tieBreak = 0;
+ alternate->tieBreak = 1;
+
+ pick ^= 3; // switch channels
+ VoiceContext *tmp = voice;
+ voice = alternate;
+ alternate = tmp;
+ }
+
+ if (voice->isBlocked || voice->priority > pri) {
+ // if not already done, switch sides and try again
+ pick ^= 1;
+ continue;
+ }
+ // succeded
+ return (int8)pick;
+ }
+ // failed
+ debug(5, "MaxTrax: could not find channel for note");
+ return -1;
+}
+
+uint16 MaxTrax::calcNote(const VoiceContext &voice) {
+ const ChannelContext &channel = *voice.channel;
+ int16 bend = channel.pitchReal;
+
+#ifdef MAXTRAX_HAS_MICROTONAL
+ if (voice.hasPortamento) {
+ if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0)
+ bend += (int16)(((_microtonal[voice.endNote] - _microtonal[voice.baseNote]) * voice.portaTicks) >> 8) / channel.portamentoTime;
+ else
+ bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime;
+ }
+
+ if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0)
+ bend += _microtonal[voice.baseNote];
+#else
+ if (voice.hasPortamento)
+ bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime;
+#endif
+
+#ifdef MAXTRAX_HAS_MODULATION
+ static const uint8 tableSine[] = {
+ 0, 5, 12, 18, 24, 30, 37, 43, 49, 55, 61, 67, 73, 79, 85, 91,
+ 97, 103, 108, 114, 120, 125, 131, 136, 141, 146, 151, 156, 161, 166, 171, 176,
+ 180, 184, 189, 193, 197, 201, 205, 208, 212, 215, 219, 222, 225, 228, 230, 233,
+ 236, 238, 240, 242, 244, 246, 247, 249, 250, 251, 252, 253, 254, 254, 255, 255,
+ 255, 255, 255, 254, 254, 253, 252, 251, 250, 249, 247, 246, 244, 242, 240, 238,
+ 236, 233, 230, 228, 225, 222, 219, 215, 212, 208, 205, 201, 197, 193, 189, 184,
+ 180, 176, 171, 166, 161, 156, 151, 146, 141, 136, 131, 125, 120, 114, 108, 103,
+ 97, 91, 85, 79, 73, 67, 61, 55, 49, 43, 37, 30, 24, 18, 12, 5
+ };
+ if (channel.modulation) {
+ if ((channel.flags & ChannelContext::kFlagModVolume) == 0) {
+ const uint8 sineByte = _playerCtx.sineValue / channel.modulationTime;
+ const uint8 sineIndex = sineByte & 0x7F;
+ const int16 modVal = ((uint32)(uint16)(tableSine[sineIndex] + (sineIndex ? 1 : 0)) * channel.modulation) >> 8;
+ bend = (sineByte < 0x80) ? bend + modVal : bend - modVal;
+ }
+ }
+#endif
+
+ // tone = voice.baseNote << 8 + microtonal
+ // bend = channelPitch + porta + modulation
+
+ const int32 tone = voice.preCalcNote + (bend << 6) / 3;
+
+ return (tone >= PERIOD_LIMIT) ? (uint16)pow2Fixed(tone) : 0;
+}
+
+int8 MaxTrax::noteOn(ChannelContext &channel, const byte note, uint16 volume, uint16 pri) {
+#ifdef MAXTRAX_HAS_MICROTONAL
+ if (channel.microtonal >= 0)
+ _microtonal[note % 127] = channel.microtonal;
+#endif
+
+ if (!volume)
+ return -1;
+
+ const Patch &patch = *channel.patch;
+ if (!patch.samplePtr || patch.sampleTotalLen == 0)
+ return -1;
+ int8 voiceNum = -1;
+ if ((channel.flags & ChannelContext::kFlagMono) == 0) {
+ voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri);
+ } else {
+ VoiceContext *voice = _voiceCtx + ARRAYSIZE(_voiceCtx) - 1;
+ for (voiceNum = ARRAYSIZE(_voiceCtx) - 1; voiceNum >= 0 && voice->channel != &channel; --voiceNum, --voice)
+ ;
+ if (voiceNum < 0)
+ voiceNum = pickvoice((channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri);
+ else if (voice->status >= VoiceContext::kStatusSustain && (channel.flags & ChannelContext::kFlagPortamento) != 0) {
+ // reset previous porta
+ if (voice->hasPortamento)
+ voice->baseNote = voice->endNote;
+ voice->preCalcNote = precalcNote(voice->baseNote, patch.tune, voice->octave);
+ voice->noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128;
+ voice->portaTicks = 0;
+ voice->hasPortamento = true;
+ voice->endNote = channel.lastNote = note;
+ return voiceNum;
+ }
+ }
+
+ if (voiceNum >= 0) {
+ VoiceContext &voice = _voiceCtx[voiceNum];
+ voice.hasDamper = false;
+ voice.isBlocked = 0;
+ voice.hasPortamento = false;
+ if (voice.channel)
+ killVoice(voiceNum);
+ voice.channel = &channel;
+ voice.patch = &patch;
+ voice.baseNote = note;
+
+ // always base octave on the note in the command, regardless of porta
+ const int32 plainNote = precalcNote(note, patch.tune, 0);
+ // calculate which sample to use
+ const int useOctave = (plainNote <= PREF_PERIOD) ? 0 : MIN<int32>((plainNote + 0xFFFF - PREF_PERIOD) >> 16, patch.sampleOctaves - 1);
+ voice.octave = (byte)useOctave;
+ // adjust precalculated value
+ voice.preCalcNote = plainNote - (useOctave << 16);
+
+ // next calculate the actual period which depends on wether porta is enabled
+ if (&channel < &_channelCtx[kNumChannels] && (channel.flags & ChannelContext::kFlagPortamento) != 0) {
+ if ((channel.flags & ChannelContext::kFlagMono) != 0 && channel.lastNote < 0x80 && channel.lastNote != note) {
+ voice.portaTicks = 0;
+ voice.baseNote = channel.lastNote;
+ voice.endNote = note;
+ voice.hasPortamento = true;
+ voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave);
+ }
+ channel.lastNote = note;
+ }
+
+ voice.lastPeriod = calcNote(voice);
+
+ voice.priority = (byte)pri;
+ voice.status = VoiceContext::kStatusStart;
+
+ voice.noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128;
+ voice.baseVolume = 0;
+
+ // TODO: since the original player is using the OS-functions, more than 1 sample could be queued up already
+ // get samplestart for the given octave
+ const int8 *samplePtr = patch.samplePtr + (patch.sampleTotalLen << useOctave) - patch.sampleTotalLen;
+ if (patch.sampleAttackLen) {
+ Paula::setChannelSampleStart(voiceNum, samplePtr);
+ Paula::setChannelSampleLen(voiceNum, (patch.sampleAttackLen << useOctave) / 2);
+
+ Paula::enableChannel(voiceNum);
+ // wait for dma-clear
+ }
+
+ if (patch.sampleTotalLen > patch.sampleAttackLen) {
+ Paula::setChannelSampleStart(voiceNum, samplePtr + (patch.sampleAttackLen << useOctave));
+ Paula::setChannelSampleLen(voiceNum, ((patch.sampleTotalLen - patch.sampleAttackLen) << useOctave) / 2);
+ if (!patch.sampleAttackLen)
+ Paula::enableChannel(voiceNum); // need to enable channel
+ // another pointless wait for DMA-Clear???
+
+ } else { // no sustain sample
+ // this means we must stop playback after the attacksample finished
+ // so we queue up an "empty" sample and note that we need to kill the sample after dma finished
+ Paula::setChannelSampleStart(voiceNum, 0);
+ Paula::setChannelSampleLen(voiceNum, 0);
+ Paula::setChannelDmaCount(voiceNum);
+ voice.dmaOff = 1;
+ }
+
+ Paula::setChannelPeriod(voiceNum, (voice.lastPeriod) ? voice.lastPeriod : 1000);
+ Paula::setChannelVolume(voiceNum, 0);
+ }
+ return voiceNum;
+}
+
+void MaxTrax::resetChannel(ChannelContext &chan, bool rightChannel) {
+ chan.modulation = 0;
+ chan.modulationTime = 1000;
+ chan.microtonal = -1;
+ chan.portamentoTime = 500;
+ chan.pitchBend = NO_BEND;
+ chan.pitchReal = 0;
+ chan.pitchBendRange = MAX_BEND_RANGE;
+ chan.volume = 128;
+ chan.flags &= ~(ChannelContext::kFlagPortamento | ChannelContext::kFlagMicrotonal | ChannelContext::kFlagRightChannel);
+ chan.isAltered = true;
+ if (rightChannel)
+ chan.flags |= ChannelContext::kFlagRightChannel;
+}
+
+void MaxTrax::freeScores() {
+ if (_scores) {
+ for (int i = 0; i < _numScores; ++i)
+ delete[] _scores[i].events;
+ delete[] _scores;
+ _scores = 0;
+ }
+ _numScores = 0;
+ _playerCtx.tempo = 120;
+ _playerCtx.filterOn = true;
+}
+
+void MaxTrax::freePatches() {
+ for (int i = 0; i < ARRAYSIZE(_patch); ++i) {
+ delete[] _patch[i].samplePtr;
+ delete[] _patch[i].attackPtr;
+ }
+ memset(_patch, 0, sizeof(_patch));
+}
+
+void MaxTrax::setSignalCallback(void (*callback) (int)) {
+ Common::StackLock lock(_mutex);
+ _playerCtx.syncCallBack = (callback == 0) ? nullFunc : callback;
+}
+
+int MaxTrax::playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide) {
+ Common::StackLock lock(_mutex);
+ assert(patch < ARRAYSIZE(_patch));
+
+ ChannelContext &channel = _channelCtx[kNumChannels];
+ channel.flags = (rightSide) ? ChannelContext::kFlagRightChannel : 0;
+ channel.isAltered = false;
+ channel.patch = &_patch[patch];
+ const int8 voiceIndex = noteOn(channel, note, (byte)volume, kPriorityNote);
+ if (voiceIndex >= 0) {
+ _voiceCtx[voiceIndex].stopEventTime = duration << 8;
+ Paula::startPaula();
+ }
+ return voiceIndex;
+}
+
+bool MaxTrax::load(Common::SeekableReadStream &musicData, bool loadScores, bool loadSamples) {
+ Common::StackLock lock(_mutex);
+ stopMusic();
+ if (loadSamples)
+ freePatches();
+ if (loadScores)
+ freeScores();
+ const char *errorMsg = 0;
+ // 0x0000: 4 Bytes Header "MXTX"
+ // 0x0004: uint16 tempo
+ // 0x0006: uint16 flags. bit0 = lowpassfilter, bit1 = attackvolume, bit15 = microtonal
+ if (musicData.size() < 10 || musicData.readUint32BE() != 0x4D585458) {
+ warning("Maxtrax: File is not a Maxtrax Module");
+ return false;
+ }
+ const uint16 songTempo = musicData.readUint16BE();
+ const uint16 flags = musicData.readUint16BE();
+ if (loadScores) {
+ _playerCtx.tempoInitial = songTempo;
+ _playerCtx.filterOn = (flags & 1) != 0;
+ _playerCtx.handleVolume = (flags & 2) != 0;
+ }
+
+ if (flags & (1 << 15)) {
+ debug(5, "Maxtrax: Song has microtonal");
+#ifdef MAXTRAX_HAS_MICROTONAL
+ if (loadScores) {
+ for (int i = 0; i < ARRAYSIZE(_microtonal); ++i)
+ _microtonal[i] = musicData.readUint16BE();
+ } else
+ musicData.skip(128 * 2);
+#else
+ musicData.skip(128 * 2);
+#endif
+ }
+
+ int scoresLoaded = 0;
+ // uint16 number of Scores
+ const uint16 scoresInFile = musicData.readUint16BE();
+
+ if (musicData.err() || musicData.eos())
+ goto ioError;
+
+ if (loadScores) {
+ const uint16 tempScores = MIN(scoresInFile, _playerCtx.maxScoreNum);
+ Score *curScore = new Score[tempScores];
+ if (!curScore)
+ goto allocError;
+ _scores = curScore;
+
+ for (scoresLoaded = 0; scoresLoaded < tempScores; ++scoresLoaded, ++curScore) {
+ const uint32 numEvents = musicData.readUint32BE();
+ Event *curEvent = new Event[numEvents];
+ if (!curEvent)
+ goto allocError;
+ curScore->events = curEvent;
+ for (int j = numEvents; j > 0; --j, ++curEvent) {
+ curEvent->command = musicData.readByte();
+ curEvent->parameter = musicData.readByte();
+ curEvent->startTime = musicData.readUint16BE();
+ curEvent->stopTime = musicData.readUint16BE();
+ }
+ curScore->numEvents = numEvents;
+ }
+ _numScores = scoresLoaded;
+ }
+
+ if (loadSamples) {
+ // skip over remaining scores in file
+ for (int i = scoresInFile - scoresLoaded; i > 0; --i)
+ musicData.skip(musicData.readUint32BE() * 6);
+
+ // uint16 number of Samples
+ const uint16 wavesInFile = musicData.readUint16BE();
+ for (int i = wavesInFile; i > 0; --i) {
+ // load disksample structure
+ const uint16 number = musicData.readUint16BE();
+ assert(number < ARRAYSIZE(_patch));
+
+ Patch &curPatch = _patch[number];
+ if (curPatch.attackPtr || curPatch.samplePtr) {
+ delete curPatch.attackPtr;
+ curPatch.attackPtr = 0;
+ delete curPatch.samplePtr;
+ curPatch.samplePtr = 0;
+ }
+ curPatch.tune = musicData.readSint16BE();
+ curPatch.volume = musicData.readUint16BE();
+ curPatch.sampleOctaves = musicData.readUint16BE();
+ curPatch.sampleAttackLen = musicData.readUint32BE();
+ const uint32 sustainLen = musicData.readUint32BE();
+ curPatch.sampleTotalLen = curPatch.sampleAttackLen + sustainLen;
+ // each octave the number of samples doubles.
+ const uint32 totalSamples = curPatch.sampleTotalLen * ((1 << curPatch.sampleOctaves) - 1);
+ curPatch.attackLen = musicData.readUint16BE();
+ curPatch.releaseLen = musicData.readUint16BE();
+ const uint32 totalEnvs = curPatch.attackLen + curPatch.releaseLen;
+
+ // Allocate space for both attack and release Segment.
+ Envelope *envPtr = new Envelope[totalEnvs];
+ if (!envPtr)
+ goto allocError;
+ // Attack Segment
+ curPatch.attackPtr = envPtr;
+ // Release Segment
+ // curPatch.releasePtr = envPtr + curPatch.attackLen;
+
+ // Read Attack and Release Segments
+ for (int j = totalEnvs; j > 0; --j, ++envPtr) {
+ envPtr->duration = musicData.readUint16BE();
+ envPtr->volume = musicData.readUint16BE();
+ }
+
+ // read Samples
+ int8 *allocSamples = new int8[totalSamples];
+ if (!allocSamples)
+ goto allocError;
+ curPatch.samplePtr = allocSamples;
+ musicData.read(allocSamples, totalSamples);
+ }
+ }
+ if (!musicData.err() && !musicData.eos())
+ return true;
+ioError:
+ errorMsg = "Maxtrax: Encountered IO-Error";
+allocError:
+ if (!errorMsg)
+ errorMsg = "Maxtrax: Could not allocate Memory";
+
+ warning("%s", errorMsg);
+ if (loadSamples)
+ freePatches();
+ if (loadScores)
+ freeScores();
+ return false;
+}
+
+#if !defined(NDEBUG) && 0
+void MaxTrax::outPutEvent(const Event &ev, int num) {
+ struct {
+ byte cmd;
+ const char *name;
+ const char *param;
+ } COMMANDS[] = {
+ {0x80, "TEMPO ", "TEMPO, N/A "},
+ {0xa0, "SPECIAL ", "CHAN, SPEC # | VAL"},
+ {0xb0, "CONTROL ", "CHAN, CTRL # | VAL"},
+ {0xc0, "PROGRAM ", "CHANNEL, PROG # "},
+ {0xe0, "BEND ", "CHANNEL, BEND VALUE"},
+ {0xf0, "SYSEX ", "TYPE, SIZE "},
+ {0xf8, "REALTIME", "REALTIME, N/A "},
+ {0xff, "END ", "N/A, N/A "},
+ {0xff, "NOTE ", "VOL | CHAN, STOP"},
+ };
+
+ int i = 0;
+ for (; i < ARRAYSIZE(COMMANDS) - 1 && ev.command != COMMANDS[i].cmd; ++i)
+ ;
+
+ if (num == -1)
+ debug("Event : %02X %s %s %02X %04X %04X", ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime);
+ else
+ debug("Event %3d: %02X %s %s %02X %04X %04X", num, ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime);
+}
+
+void MaxTrax::outPutScore(const Score &sc, int num) {
+ if (num == -1)
+ debug("score : %i Events", sc.numEvents);
+ else
+ debug("score %2d: %i Events", num, sc.numEvents);
+ for (uint i = 0; i < sc.numEvents; ++i)
+ outPutEvent(sc.events[i], i);
+ debug("");
+}
+#else
+void MaxTrax::outPutEvent(const Event &ev, int num) {}
+void MaxTrax::outPutScore(const Score &sc, int num) {}
+#endif // #ifndef NDEBUG
+
+} // End of namespace Audio
+
+#endif // #if defined(SOUND_MODS_MAXTRAX_H)
diff --git a/audio/mods/maxtrax.h b/audio/mods/maxtrax.h
new file mode 100644
index 0000000000..2f890afe2d
--- /dev/null
+++ b/audio/mods/maxtrax.h
@@ -0,0 +1,225 @@
+/* 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$
+ *
+ */
+
+// see if all engines using this class are DISABLED
+#if !defined(ENABLE_KYRA)
+
+// normal Header Guard
+#elif !defined SOUND_MODS_MAXTRAX_H
+#define SOUND_MODS_MAXTRAX_H
+
+// #define MAXTRAX_HAS_MODULATION
+// #define MAXTRAX_HAS_MICROTONAL
+
+#include "audio/mods/paula.h"
+
+namespace Audio {
+
+class MaxTrax : public Paula {
+public:
+ MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128);
+ virtual ~MaxTrax();
+
+ bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true);
+ bool playSong(int songIndex, bool loop = false);
+ void advanceSong(int advance = 1);
+ int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide);
+ void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; }
+ void setTempo(const uint16 tempo);
+ void stopMusic();
+ /**
+ * Set a callback function for sync-events.
+ * @param callback Callback function, will be called synchronously, so DONT modify the player
+ * directly in response
+ */
+ void setSignalCallback(void (*callback) (int));
+
+protected:
+ void interrupt();
+
+private:
+ enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 };
+ enum { kPriorityScore, kPriorityNote, kPrioritySound };
+
+#ifdef MAXTRAX_HAS_MICROTONAL
+ int16 _microtonal[128];
+#endif
+
+ struct Event {
+ uint16 startTime;
+ uint16 stopTime;
+ byte command;
+ byte parameter;
+ };
+
+ const struct Score {
+ const Event *events;
+ uint32 numEvents;
+ } *_scores;
+
+ int _numScores;
+
+ struct {
+ uint32 sineValue;
+ uint16 vBlankFreq;
+ int32 ticks;
+ int32 tickUnit;
+ uint16 frameUnit;
+
+ uint16 maxScoreNum;
+ uint16 tempo;
+ uint16 tempoInitial;
+ uint16 tempoStart;
+ int16 tempoDelta;
+ int32 tempoTime;
+ int32 tempoTicks;
+
+ byte volume;
+
+ bool filterOn;
+ bool handleVolume;
+ bool musicLoop;
+
+ int scoreIndex;
+ const Event *nextEvent;
+ int32 nextEventTime;
+
+ void (*syncCallBack) (int);
+ const Event *repeatPoint[4];
+ byte repeatCount[4];
+ } _playerCtx;
+
+ struct Envelope {
+ uint16 duration;
+ uint16 volume;
+ };
+
+ struct Patch {
+ const Envelope *attackPtr;
+ //Envelope *releasePtr;
+ uint16 attackLen;
+ uint16 releaseLen;
+
+ int16 tune;
+ uint16 volume;
+
+ // this was the SampleData struct in the assembler source
+ const int8 *samplePtr;
+ uint32 sampleTotalLen;
+ uint32 sampleAttackLen;
+ uint16 sampleOctaves;
+ } _patch[kNumPatches];
+
+ struct ChannelContext {
+ const Patch *patch;
+ uint16 regParamNumber;
+
+ uint16 modulation;
+ uint16 modulationTime;
+
+ int16 microtonal;
+
+ uint16 portamentoTime;
+
+ int16 pitchBend;
+ int16 pitchReal;
+ int8 pitchBendRange;
+
+ uint8 volume;
+// uint8 voicesActive;
+
+ enum {
+ kFlagRightChannel = 1 << 0,
+ kFlagPortamento = 1 << 1,
+ kFlagDamper = 1 << 2,
+ kFlagMono = 1 << 3,
+ kFlagMicrotonal = 1 << 4,
+ kFlagModVolume = 1 << 5
+ };
+ byte flags;
+ bool isAltered;
+
+ uint8 lastNote;
+// uint8 program;
+
+ } _channelCtx[kNumChannels + kNumExtraChannels];
+
+ struct VoiceContext {
+ ChannelContext *channel;
+ const Patch *patch;
+ const Envelope *envelope;
+// uint32 uinqueId;
+ int32 preCalcNote;
+ uint32 ticksLeft;
+ int32 portaTicks;
+ int32 incrVolume;
+// int32 periodOffset;
+ uint16 envelopeLeft;
+ uint16 noteVolume;
+ uint16 baseVolume;
+ uint16 lastPeriod;
+ byte baseNote;
+ byte endNote;
+ byte octave;
+// byte number;
+// byte link;
+ enum {
+ kStatusFree,
+ kStatusHalt,
+ kStatusDecay,
+ kStatusRelease,
+ kStatusSustain,
+ kStatusAttack,
+ kStatusStart
+ };
+ uint8 isBlocked;
+ uint8 priority;
+ byte status;
+ byte lastVolume;
+ byte tieBreak;
+ bool hasDamper;
+ bool hasPortamento;
+ byte dmaOff;
+
+ int32 stopEventTime;
+ } _voiceCtx[kNumVoices];
+
+ void controlCh(ChannelContext &channel, byte command, byte data);
+ void freePatches();
+ void freeScores();
+ void resetChannel(ChannelContext &chan, bool rightChannel);
+ void resetPlayer();
+
+ int8 pickvoice(uint pick, int16 pri);
+ uint16 calcNote(const VoiceContext &voice);
+ int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri);
+ void killVoice(byte num);
+
+ static void outPutEvent(const Event &ev, int num = -1);
+ static void outPutScore(const Score &sc, int num = -1);
+};
+} // End of namespace Audio
+
+#endif // !defined SOUND_MODS_MAXTRAX_H
diff --git a/audio/mods/module.cpp b/audio/mods/module.cpp
new file mode 100644
index 0000000000..0da6923b5d
--- /dev/null
+++ b/audio/mods/module.cpp
@@ -0,0 +1,252 @@
+/* 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 "audio/mods/module.h"
+
+#include "common/util.h"
+#include "common/endian.h"
+
+namespace Modules {
+
+const int16 Module::periods[16][60] = {
+ {1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960 , 906,
+ 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480 , 453,
+ 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240 , 226,
+ 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120 , 113,
+ 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 , 56 },
+ {1700, 1604, 1514, 1430, 1348, 1274, 1202, 1134, 1070, 1010, 954 , 900,
+ 850 , 802 , 757 , 715 , 674 , 637 , 601 , 567 , 535 , 505 , 477 , 450,
+ 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 239 , 225,
+ 213 , 201 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119 , 113,
+ 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 , 56 },
+ {1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948 , 894,
+ 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474 , 447,
+ 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237 , 224,
+ 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118 , 112,
+ 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 , 56 },
+ {1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940 , 888,
+ 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470 , 444,
+ 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235 , 222,
+ 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118 , 111,
+ 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 , 55 },
+ {1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 990 , 934 , 882,
+ 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 495 , 467 , 441,
+ 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233 , 220,
+ 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 124 , 117 , 110,
+ 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 62 , 58 , 55 },
+ {1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926 , 874,
+ 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463 , 437,
+ 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232 , 219,
+ 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116 , 109,
+ 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 , 54 },
+ {1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920 , 868,
+ 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460 , 434,
+ 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230 , 217,
+ 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115 , 109,
+ 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 , 54 },
+ {1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914 , 862,
+ 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457 , 431,
+ 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228 , 216,
+ 204 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114 , 108,
+ 102 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 , 54 },
+ {1814, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960,
+ 907 , 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480,
+ 453 , 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240,
+ 226 , 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120,
+ 113 , 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 },
+ {1800, 1700, 1604, 1514, 1430, 1350, 1272, 1202, 1134, 1070, 1010, 954,
+ 900 , 850 , 802 , 757 , 715 , 675 , 636 , 601 , 567 , 535 , 505 , 477,
+ 450 , 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 238,
+ 225 , 212 , 200 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119,
+ 112 , 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 },
+ {1788, 1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948,
+ 894 , 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474,
+ 447 , 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237,
+ 223 , 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118,
+ 111 , 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 },
+ {1774, 1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940,
+ 887 , 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470,
+ 444 , 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235,
+ 222 , 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118,
+ 111 , 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 },
+ {1762, 1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 988 , 934,
+ 881 , 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 494 , 467,
+ 441 , 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233,
+ 220 , 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 123 , 117,
+ 110 , 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 61 , 58 },
+ {1750, 1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926,
+ 875 , 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463,
+ 437 , 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232,
+ 219 , 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116,
+ 109 , 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 },
+ {1736, 1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920,
+ 868 , 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460,
+ 434 , 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230,
+ 217 , 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115,
+ 108 , 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 },
+ {1724, 1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914,
+ 862 , 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457,
+ 431 , 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228,
+ 216 , 203 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114,
+ 108 , 101 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 }};
+
+const uint32 Module::signatures[] = {
+ MKID_BE('M.K.'), MKID_BE('M!K!'), MKID_BE('FLT4')
+};
+
+bool Module::load(Common::SeekableReadStream &st, int offs) {
+ if (offs) {
+ // Load the module with the common sample data
+ load(st, 0);
+ }
+
+ st.seek(offs);
+ st.read(songname, 20);
+ songname[20] = '\0';
+
+ for (int i = 0; i < NUM_SAMPLES; ++i) {
+ st.read(sample[i].name, 22);
+ sample[i].name[22] = '\0';
+ sample[i].len = 2 * st.readUint16BE();
+
+ sample[i].finetune = st.readByte();
+ assert(sample[i].finetune < 0x10);
+
+ sample[i].vol = st.readByte();
+ sample[i].repeat = 2 * st.readUint16BE();
+ sample[i].replen = 2 * st.readUint16BE();
+ }
+
+ songlen = st.readByte();
+ undef = st.readByte();
+
+ st.read(songpos, 128);
+
+ sig = st.readUint32BE();
+
+ bool foundSig = false;
+ for (int i = 0; i < ARRAYSIZE(signatures); i++) {
+ if (sig == signatures[i]) {
+ foundSig = true;
+ break;
+ }
+ }
+
+ if (!foundSig) {
+ warning("No known signature found in protracker module");
+ return false;
+ }
+
+ int maxpattern = 0;
+ for (int i = 0; i < 128; ++i)
+ if (maxpattern < songpos[i])
+ maxpattern = songpos[i];
+
+ pattern = new pattern_t[maxpattern + 1];
+
+ for (int i = 0; i <= maxpattern; ++i) {
+ for (int j = 0; j < 64; ++j) {
+ for (int k = 0; k < 4; ++k) {
+ uint32 note = st.readUint32BE();
+ pattern[i][j][k].sample = (note & 0xf0000000) >> 24 | (note & 0x0000f000) >> 12;
+ pattern[i][j][k].period = (note >> 16) & 0xfff;
+ pattern[i][j][k].effect = note & 0xfff;
+ pattern[i][j][k].note = periodToNote((note >> 16) & 0xfff);
+ }
+ }
+ }
+
+ for (int i = 0; i < NUM_SAMPLES; ++i) {
+ if (offs) {
+ // Restore information for modules that use common sample data
+ for (int j = 0; j < NUM_SAMPLES; ++j) {
+ if (!scumm_stricmp((const char *)commonSamples[j].name, (const char *)sample[i].name)) {
+ sample[i].len = commonSamples[j].len;
+ st.seek(commonSamples[j].offs);
+ break;
+ }
+ }
+ } else {
+ // Store information for modules that use common sample data
+ memcpy(commonSamples[i].name, sample[i].name, 22);
+ commonSamples[i].len = sample[i].len;
+ commonSamples[i].offs = st.pos();
+
+ }
+
+ if (!sample[i].len) {
+ sample[i].data = 0;
+ } else {
+ sample[i].data = new int8[sample[i].len];
+ st.read((byte *)sample[i].data, sample[i].len);
+ }
+ }
+
+ return true;
+}
+
+Module::Module() {
+ pattern = 0;
+ for (int i = 0; i < NUM_SAMPLES; ++i) {
+ sample[i].data = 0;
+ }
+}
+
+Module::~Module() {
+ delete[] pattern;
+ for (int i = 0; i < NUM_SAMPLES; ++i) {
+ delete[] sample[i].data;
+ }
+}
+
+byte Module::periodToNote(int16 period, byte finetune) {
+ int16 diff1;
+ int16 diff2;
+
+ diff1 = ABS(periods[finetune][0] - period);
+ if (diff1 == 0)
+ return 0;
+
+ for (int i = 1; i < 60; i++) {
+ diff2 = ABS(periods[finetune][i] - period);
+ if (diff2 == 0)
+ return i;
+ else if (diff2 > diff1)
+ return i-1;
+ diff1 = diff2;
+ }
+ return 59;
+}
+
+int16 Module::noteToPeriod(byte note, byte finetune) {
+ if (finetune > 15)
+ finetune = 15;
+ if (note > 59)
+ note = 59;
+
+ return periods[finetune][note];
+}
+
+} // End of namespace Modules
diff --git a/audio/mods/module.h b/audio/mods/module.h
new file mode 100644
index 0000000000..550b63617e
--- /dev/null
+++ b/audio/mods/module.h
@@ -0,0 +1,90 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MODS_MODULE_H
+#define SOUND_MODS_MODULE_H
+
+#include "common/stream.h"
+
+namespace Modules {
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct note_t {
+ byte sample;
+ byte note;
+ uint16 period;
+ uint16 effect;
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+typedef note_t pattern_t[64][4];
+
+struct sample_t {
+ byte name[23];
+ uint16 len;
+ byte finetune;
+ byte vol;
+ uint16 repeat;
+ uint16 replen;
+ int8 *data;
+};
+
+struct sample_offs {
+ byte name[23];
+ uint16 len;
+ uint32 offs;
+};
+
+class Module {
+public:
+ byte songname[21];
+
+ static const int NUM_SAMPLES = 31;
+ sample_t sample[NUM_SAMPLES];
+ sample_offs commonSamples[NUM_SAMPLES];
+
+ byte songlen;
+ byte undef;
+ byte songpos[128];
+ uint32 sig;
+ pattern_t *pattern;
+
+ Module();
+ ~Module();
+
+ bool load(Common::SeekableReadStream &stream, int offs);
+ static byte periodToNote(int16 period, byte finetune = 0);
+ static int16 noteToPeriod(byte note, byte finetune = 0);
+
+private:
+ static const int16 periods[16][60];
+ static const uint32 signatures[];
+};
+
+} // End of namespace Modules
+
+#endif
diff --git a/audio/mods/paula.cpp b/audio/mods/paula.cpp
new file mode 100644
index 0000000000..ef841ac9bf
--- /dev/null
+++ b/audio/mods/paula.cpp
@@ -0,0 +1,212 @@
+/* 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 "audio/mods/paula.h"
+#include "audio/null.h"
+
+namespace Audio {
+
+Paula::Paula(bool stereo, int rate, uint interruptFreq) :
+ _stereo(stereo), _rate(rate), _periodScale((double)kPalPaulaClock / rate), _intFreq(interruptFreq) {
+
+ clearVoices();
+ _voice[0].panning = 191;
+ _voice[1].panning = 63;
+ _voice[2].panning = 63;
+ _voice[3].panning = 191;
+
+ if (_intFreq == 0)
+ _intFreq = _rate;
+
+ _curInt = 0;
+ _timerBase = 1;
+ _playing = false;
+ _end = true;
+}
+
+Paula::~Paula() {
+}
+
+void Paula::clearVoice(byte voice) {
+ assert(voice < NUM_VOICES);
+
+ _voice[voice].data = 0;
+ _voice[voice].dataRepeat = 0;
+ _voice[voice].length = 0;
+ _voice[voice].lengthRepeat = 0;
+ _voice[voice].period = 0;
+ _voice[voice].volume = 0;
+ _voice[voice].offset = Offset(0);
+ _voice[voice].dmaCount = 0;
+}
+
+int Paula::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ memset(buffer, 0, numSamples * 2);
+ if (!_playing) {
+ return numSamples;
+ }
+
+ if (_stereo)
+ return readBufferIntern<true>(buffer, numSamples);
+ else
+ return readBufferIntern<false>(buffer, numSamples);
+}
+
+
+template<bool stereo>
+inline int mixBuffer(int16 *&buf, const int8 *data, Paula::Offset &offset, frac_t rate, int neededSamples, uint bufSize, byte volume, byte panning) {
+ int samples;
+ for (samples = 0; samples < neededSamples && offset.int_off < bufSize; ++samples) {
+ const int32 tmp = ((int32) data[offset.int_off]) * volume;
+ if (stereo) {
+ *buf++ += (tmp * (255 - panning)) >> 7;
+ *buf++ += (tmp * (panning)) >> 7;
+ } else
+ *buf++ += tmp;
+
+ // Step to next source sample
+ offset.rem_off += rate;
+ if (offset.rem_off >= (frac_t)FRAC_ONE) {
+ offset.int_off += fracToInt(offset.rem_off);
+ offset.rem_off &= FRAC_LO_MASK;
+ }
+ }
+
+ return samples;
+}
+
+template<bool stereo>
+int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
+ int samples = _stereo ? numSamples / 2 : numSamples;
+ while (samples > 0) {
+
+ // Handle 'interrupts'. This gives subclasses the chance to adjust the channel data
+ // (e.g. insert new samples, do pitch bending, whatever).
+ if (_curInt == 0) {
+ _curInt = _intFreq;
+ interrupt();
+ }
+
+ // Compute how many samples to generate: at most the requested number of samples,
+ // of course, but we may stop earlier when an 'interrupt' is expected.
+ const uint nSamples = MIN((uint)samples, _curInt);
+
+ // Loop over the four channels of the emulated Paula chip
+ for (int voice = 0; voice < NUM_VOICES; voice++) {
+ // No data, or paused -> skip channel
+ if (!_voice[voice].data || (_voice[voice].period <= 0))
+ continue;
+
+ // The Paula chip apparently run at 7.0937892 MHz in the PAL
+ // version and at 7.1590905 MHz in the NTSC version. We divide this
+ // by the requested the requested output sampling rate _rate
+ // (typically 44.1 kHz or 22.05 kHz) obtaining the value _periodScale.
+ // This is then divided by the "period" of the channel we are
+ // processing, to obtain the correct output 'rate'.
+ frac_t rate = doubleToFrac(_periodScale / _voice[voice].period);
+ // Cap the volume
+ _voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume);
+
+
+ Channel &ch = _voice[voice];
+ int16 *p = buffer;
+ int neededSamples = nSamples;
+ assert(ch.offset.int_off < ch.length);
+
+ // Mix the generated samples into the output buffer
+ neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning);
+
+ // Wrap around if necessary
+ if (ch.offset.int_off >= ch.length) {
+ // Important: Wrap around the offset *before* updating the voice length.
+ // Otherwise, if length != lengthRepeat we would wrap incorrectly.
+ // Note: If offset >= 2*len ever occurs, the following would be wrong;
+ // instead of subtracting, we then should compute the modulus using "%=".
+ // Since that requires a division and is slow, and shouldn't be necessary
+ // in practice anyway, we only use subtraction.
+ ch.offset.int_off -= ch.length;
+ ch.dmaCount++;
+
+ ch.data = ch.dataRepeat;
+ ch.length = ch.lengthRepeat;
+ }
+
+ // If we have not yet generated enough samples, and looping is active: loop!
+ if (neededSamples > 0 && ch.length > 2) {
+ // Repeat as long as necessary.
+ while (neededSamples > 0) {
+ // Mix the generated samples into the output buffer
+ neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning);
+
+ if (ch.offset.int_off >= ch.length) {
+ // Wrap around. See also the note above.
+ ch.offset.int_off -= ch.length;
+ ch.dmaCount++;
+ }
+ }
+ }
+
+ }
+ buffer += _stereo ? nSamples * 2 : nSamples;
+ _curInt -= nSamples;
+ samples -= nSamples;
+ }
+ return numSamples;
+}
+
+} // End of namespace Audio
+
+
+// Plugin interface
+// (This can only create a null driver since apple II gs support seeems not to be implemented
+// and also is not part of the midi driver architecture. But we need the plugin for the options
+// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.)
+
+class AmigaMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("Amiga Audio Emulator");
+ }
+
+ const char *getId() const {
+ return "amiga";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices AmigaMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_AMIGA));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(AMIGA)
+ //REGISTER_PLUGIN_DYNAMIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin);
+//#endif
diff --git a/audio/mods/paula.h b/audio/mods/paula.h
new file mode 100644
index 0000000000..f6f159d5a6
--- /dev/null
+++ b/audio/mods/paula.h
@@ -0,0 +1,210 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MODS_PAULA_H
+#define SOUND_MODS_PAULA_H
+
+#include "audio/audiostream.h"
+#include "common/frac.h"
+#include "common/mutex.h"
+
+namespace Audio {
+
+/**
+ * Emulation of the "Paula" Amiga music chip
+ * The interrupt frequency specifies the number of mixed wavesamples between
+ * calls of the interrupt method
+ */
+class Paula : public AudioStream {
+public:
+ static const int NUM_VOICES = 4;
+ enum {
+ kPalSystemClock = 7093790,
+ kNtscSystemClock = 7159090,
+ kPalCiaClock = kPalSystemClock / 10,
+ kNtscCiaClock = kNtscSystemClock / 10,
+ kPalPaulaClock = kPalSystemClock / 2,
+ kNtscPauleClock = kNtscSystemClock / 2
+ };
+
+ /* TODO: Document this */
+ struct Offset {
+ uint int_off; // integral part of the offset
+ frac_t rem_off; // fractional part of the offset, at least 0 and less than 1
+
+ explicit Offset(int off = 0) : int_off(off), rem_off(0) {}
+ };
+
+ Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0);
+ ~Paula();
+
+ bool playing() const { return _playing; }
+ void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; }
+ uint32 getTimerBaseValue() { return _timerBase; }
+ void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; }
+ void setSingleInterruptUnscaled(uint timerDelay) {
+ setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase));
+ }
+ void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; }
+ void setInterruptFreqUnscaled(uint timerDelay) {
+ setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase));
+ }
+ void clearVoice(byte voice);
+ void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); }
+ void startPlay() { _playing = true; }
+ void stopPlay() { _playing = false; }
+ void pausePlay(bool pause) { _playing = !pause; }
+
+// AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return _stereo; }
+ bool endOfData() const { return _end; }
+ int getRate() const { return _rate; }
+
+protected:
+ struct Channel {
+ const int8 *data;
+ const int8 *dataRepeat;
+ uint32 length;
+ uint32 lengthRepeat;
+ int16 period;
+ byte volume;
+ Offset offset;
+ byte panning; // For stereo mixing: 0 = far left, 255 = far right
+ int dmaCount;
+ };
+
+ bool _end;
+ Common::Mutex _mutex;
+
+ virtual void interrupt() = 0;
+
+ void startPaula() {
+ _playing = true;
+ _end = false;
+ }
+
+ void stopPaula() {
+ _playing = false;
+ _end = true;
+ }
+
+ void setChannelPanning(byte channel, byte panning) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].panning = panning;
+ }
+
+ void disableChannel(byte channel) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].data = 0;
+ }
+
+ void enableChannel(byte channel) {
+ assert(channel < NUM_VOICES);
+ Channel &ch = _voice[channel];
+ ch.data = ch.dataRepeat;
+ ch.length = ch.lengthRepeat;
+ // actually first 2 bytes are dropped?
+ ch.offset = Offset(0);
+ // ch.period = ch.periodRepeat;
+ }
+
+ void setChannelPeriod(byte channel, int16 period) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].period = period;
+ }
+
+ void setChannelVolume(byte channel, byte volume) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].volume = volume;
+ }
+
+ void setChannelSampleStart(byte channel, const int8 *data) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].dataRepeat = data;
+ }
+
+ void setChannelSampleLen(byte channel, uint32 length) {
+ assert(channel < NUM_VOICES);
+ assert(length < 32768/2);
+ _voice[channel].lengthRepeat = 2 * length;
+ }
+
+ void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) {
+ assert(channel < NUM_VOICES);
+
+ Channel &ch = _voice[channel];
+
+ ch.dataRepeat = data;
+ ch.lengthRepeat = length;
+ enableChannel(channel);
+ ch.offset = Offset(offset);
+
+ ch.dataRepeat = dataRepeat;
+ ch.lengthRepeat = lengthRepeat;
+ }
+
+ void setChannelOffset(byte channel, Offset offset) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].offset = offset;
+ }
+
+ Offset getChannelOffset(byte channel) {
+ assert(channel < NUM_VOICES);
+ return _voice[channel].offset;
+ }
+
+ int getChannelDmaCount(byte channel) {
+ assert(channel < NUM_VOICES);
+ return _voice[channel].dmaCount;
+ }
+
+ void setChannelDmaCount(byte channel, int dmaVal = 0) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].dmaCount = dmaVal;
+ }
+
+ void setAudioFilter(bool enable) {
+ // TODO: implement
+ }
+
+private:
+ Channel _voice[NUM_VOICES];
+
+ const bool _stereo;
+ const int _rate;
+ const double _periodScale;
+ uint _intFreq;
+ uint _curInt;
+ uint32 _timerBase;
+ bool _playing;
+
+ template<bool stereo>
+ int readBufferIntern(int16 *buffer, const int numSamples);
+};
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp
new file mode 100644
index 0000000000..6051338900
--- /dev/null
+++ b/audio/mods/protracker.cpp
@@ -0,0 +1,466 @@
+/* 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 "audio/mods/protracker.h"
+#include "audio/mods/paula.h"
+#include "audio/mods/module.h"
+
+#include "audio/audiostream.h"
+
+namespace Modules {
+
+class ProtrackerStream : public ::Audio::Paula {
+private:
+ Module _module;
+
+ int _tick;
+ int _row;
+ int _pos;
+
+ int _speed;
+ int _bpm;
+
+ // For effect 0xB - Jump To Pattern;
+ bool _hasJumpToPattern;
+ int _jumpToPattern;
+
+ // For effect 0xD - PatternBreak;
+ bool _hasPatternBreak;
+ int _skipRow;
+
+ // For effect 0xE6 - Pattern Loop
+ bool _hasPatternLoop;
+ int _patternLoopCount;
+ int _patternLoopRow;
+
+ // For effect 0xEE - Pattern Delay
+ byte _patternDelay;
+
+ static const int16 sinetable[];
+
+ struct {
+ byte sample;
+ uint16 period;
+ Offset offset;
+
+ byte vol;
+ byte finetune;
+
+ // For effect 0x0 - Arpeggio
+ bool arpeggio;
+ byte arpeggioNotes[3];
+
+ // For effect 0x3 - Porta to note
+ uint16 portaToNote;
+ byte portaToNoteSpeed;
+
+ // For effect 0x4 - Vibrato
+ int vibrato;
+ byte vibratoPos;
+ byte vibratoSpeed;
+ byte vibratoDepth;
+
+ // For effect 0xED - Delay sample
+ byte delaySample;
+ byte delaySampleTick;
+ } _track[4];
+
+public:
+ ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo);
+
+private:
+ void interrupt();
+
+ void doPorta(int track) {
+ if (_track[track].portaToNote && _track[track].portaToNoteSpeed) {
+ if (_track[track].period < _track[track].portaToNote) {
+ _track[track].period += _track[track].portaToNoteSpeed;
+ if (_track[track].period > _track[track].portaToNote)
+ _track[track].period = _track[track].portaToNote;
+ } else if (_track[track].period > _track[track].portaToNote) {
+ _track[track].period -= _track[track].portaToNoteSpeed;
+ if (_track[track].period < _track[track].portaToNote)
+ _track[track].period = _track[track].portaToNote;
+ }
+ }
+ }
+ void doVibrato(int track) {
+ _track[track].vibrato =
+ (_track[track].vibratoDepth * sinetable[_track[track].vibratoPos]) / 128;
+ _track[track].vibratoPos += _track[track].vibratoSpeed;
+ _track[track].vibratoPos %= 64;
+ }
+ void doVolSlide(int track, byte ex, byte ey) {
+ int vol = _track[track].vol;
+ if (ex == 0)
+ vol -= ey;
+ else if (ey == 0)
+ vol += ex;
+
+ if (vol < 0)
+ vol = 0;
+ else if (vol > 64)
+ vol = 64;
+
+ _track[track].vol = vol;
+ }
+
+ void updateRow();
+ void updateEffects();
+
+};
+
+const int16 ProtrackerStream::sinetable[64] = {
+ 0, 24, 49, 74, 97, 120, 141, 161,
+ 180, 197, 212, 224, 235, 244, 250, 253,
+ 255, 253, 250, 244, 235, 224, 212, 197,
+ 180, 161, 141, 120, 97, 74, 49, 24,
+ 0, -24, -49, -74, -97, -120, -141, -161,
+ -180, -197, -212, -224, -235, -244, -250, -253,
+ -255, -253, -250, -244, -235, -224, -212, -197,
+ -180, -161, -141, -120, -97, -74, -49, -24
+};
+
+ProtrackerStream::ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) :
+ Paula(stereo, rate, rate/50) {
+ bool result = _module.load(*stream, offs);
+ assert(result);
+
+ _tick = _row = _pos = 0;
+
+ _speed = 6;
+ _bpm = 125;
+
+ _hasJumpToPattern = false;
+ _jumpToPattern = 0;
+
+ _hasPatternBreak = false;
+ _skipRow = 0;
+
+ _hasPatternLoop = false;
+ _patternLoopCount = 0;
+ _patternLoopRow = 0;
+
+ _patternDelay = 0;
+
+ memset(_track, 0, sizeof(_track));
+
+ startPaula();
+}
+
+void ProtrackerStream::updateRow() {
+ for (int track = 0; track < 4; track++) {
+ _track[track].arpeggio = false;
+ _track[track].vibrato = 0;
+ _track[track].delaySampleTick = 0;
+ const note_t note =
+ _module.pattern[_module.songpos[_pos]][_row][track];
+
+ const int effect = note.effect >> 8;
+
+ if (note.sample) {
+ if (_track[track].sample != note.sample) {
+ _track[track].vibratoPos = 0;
+ }
+ _track[track].sample = note.sample;
+ _track[track].finetune = _module.sample[note.sample - 1].finetune;
+ _track[track].vol = _module.sample[note.sample - 1].vol;
+ }
+
+ if (note.period) {
+ if (effect != 3 && effect != 5) {
+ if (_track[track].finetune)
+ _track[track].period = _module.noteToPeriod(note.note, _track[track].finetune);
+ else
+ _track[track].period = note.period;
+ _track[track].offset = Offset(0);
+ }
+ }
+
+ const byte exy = note.effect & 0xff;
+ const byte ex = (note.effect >> 4) & 0xf;
+ const byte ey = note.effect & 0xf;
+
+ int vol;
+ switch (effect) {
+ case 0x0:
+ if (exy) {
+ _track[track].arpeggio = true;
+ if (note.period) {
+ _track[track].arpeggioNotes[0] = note.note;
+ _track[track].arpeggioNotes[1] = note.note + ex;
+ _track[track].arpeggioNotes[2] = note.note + ey;
+ }
+ }
+ break;
+ case 0x1:
+ break;
+ case 0x2:
+ break;
+ case 0x3:
+ if (note.period)
+ _track[track].portaToNote = note.period;
+ if (exy)
+ _track[track].portaToNoteSpeed = exy;
+ break;
+ case 0x4:
+ if (exy) {
+ _track[track].vibratoSpeed = ex;
+ _track[track].vibratoDepth = ey;
+ }
+ break;
+ case 0x5:
+ doPorta(track);
+ doVolSlide(track, ex, ey);
+ break;
+ case 0x6:
+ doVibrato(track);
+ doVolSlide(track, ex, ey);
+ break;
+ case 0x9: // Set sample offset
+ if (exy) {
+ _track[track].offset = Offset(exy * 256);
+ setChannelOffset(track, _track[track].offset);
+ }
+ break;
+ case 0xA:
+ break;
+ case 0xB:
+ _hasJumpToPattern = true;
+ _jumpToPattern = exy;
+ break;
+ case 0xC:
+ _track[track].vol = exy;
+ break;
+ case 0xD:
+ _hasPatternBreak = true;
+ _skipRow = ex * 10 + ey;
+ break;
+ case 0xE:
+ switch (ex) {
+ case 0x0: // Switch filters off
+ break;
+ case 0x1: // Fine slide up
+ _track[track].period -= exy;
+ break;
+ case 0x2: // Fine slide down
+ _track[track].period += exy;
+ break;
+ case 0x5: // Set finetune
+ _track[track].finetune = ey;
+ _module.sample[_track[track].sample].finetune = ey;
+ if (note.period) {
+ if (ey)
+ _track[track].period = _module.noteToPeriod(note.note, ey);
+ else
+ _track[track].period = note.period;
+ }
+ break;
+ case 0x6:
+ if (ey == 0) {
+ _patternLoopRow = _row;
+ } else {
+ _patternLoopCount++;
+ if (_patternLoopCount <= ey)
+ _hasPatternLoop = true;
+ else
+ _patternLoopCount = 0;
+ }
+ break;
+ case 0x9:
+ break; // Retrigger note
+ case 0xA: // Fine volume slide up
+ vol = _track[track].vol + ey;
+ if (vol > 64)
+ vol = 64;
+ _track[track].vol = vol;
+ break;
+ case 0xB: // Fine volume slide down
+ vol = _track[track].vol - ey;
+ if (vol < 0)
+ vol = 0;
+ _track[track].vol = vol;
+ break;
+ case 0xD: // Delay sample
+ _track[track].delaySampleTick = ey;
+ _track[track].delaySample = _track[track].sample;
+ _track[track].sample = 0;
+ _track[track].vol = 0;
+ break;
+ case 0xE: // Pattern delay
+ _patternDelay = ey;
+ break;
+ default:
+ warning("Unimplemented effect %X", note.effect);
+ }
+ break;
+
+ case 0xF:
+ if (exy < 0x20) {
+ _speed = exy;
+ } else {
+ _bpm = exy;
+ setInterruptFreq((int) (getRate() / (_bpm * 0.4)));
+ }
+ break;
+ default:
+ warning("Unimplemented effect %X", note.effect);
+ }
+ }
+}
+
+void ProtrackerStream::updateEffects() {
+ for (int track = 0; track < 4; track++) {
+ _track[track].vibrato = 0;
+
+ const note_t note =
+ _module.pattern[_module.songpos[_pos]][_row][track];
+
+ const int effect = note.effect >> 8;
+
+ const int exy = note.effect & 0xff;
+ const int ex = (note.effect >> 4) & 0xf;
+ const int ey = (note.effect) & 0xf;
+
+ switch (effect) {
+ case 0x0:
+ if (exy) {
+ const int idx = (_tick == 1) ? 0 : (_tick % 3);
+ _track[track].period =
+ _module.noteToPeriod(_track[track].arpeggioNotes[idx],
+ _track[track].finetune);
+ }
+ break;
+ case 0x1:
+ _track[track].period -= exy;
+ break;
+ case 0x2:
+ _track[track].period += exy;
+ break;
+ case 0x3:
+ doPorta(track);
+ break;
+ case 0x4:
+ doVibrato(track);
+ break;
+ case 0x5:
+ doPorta(track);
+ doVolSlide(track, ex, ey);
+ break;
+ case 0x6:
+ doVibrato(track);
+ doVolSlide(track, ex, ey);
+ break;
+ case 0xA:
+ doVolSlide(track, ex, ey);
+ break;
+ case 0xE:
+ switch (ex) {
+ case 0x6:
+ break; // Pattern loop
+ case 0x9: // Retrigger note
+ if (ey && (_tick % ey) == 0)
+ _track[track].offset = Offset(0);
+ break;
+ case 0xD: // Delay sample
+ if (_tick == _track[track].delaySampleTick) {
+ _track[track].sample = _track[track].delaySample;
+ _track[track].offset = Offset(0);
+ if (_track[track].sample)
+ _track[track].vol = _module.sample[_track[track].sample - 1].vol;
+ }
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void ProtrackerStream::interrupt() {
+ int track;
+
+ for (track = 0; track < 4; track++) {
+ _track[track].offset = getChannelOffset(track);
+ if (_tick == 0 && _track[track].arpeggio) {
+ _track[track].period = _module.noteToPeriod(_track[track].arpeggioNotes[0],
+ _track[track].finetune);
+ }
+ }
+
+ if (_tick == 0) {
+ if (_hasJumpToPattern) {
+ _hasJumpToPattern = false;
+ _pos = _jumpToPattern;
+ _row = 0;
+ } else if (_hasPatternBreak) {
+ _hasPatternBreak = false;
+ _row = _skipRow;
+ _pos = (_pos + 1) % _module.songlen;
+ _patternLoopRow = 0;
+ } else if (_hasPatternLoop) {
+ _hasPatternLoop = false;
+ _row = _patternLoopRow;
+ }
+ if (_row >= 64) {
+ _row = 0;
+ _pos = (_pos + 1) % _module.songlen;
+ _patternLoopRow = 0;
+ }
+
+ updateRow();
+ } else
+ updateEffects();
+
+ _tick = (_tick + 1) % (_speed + _patternDelay * _speed);
+ if (_tick == 0) {
+ _row++;
+ _patternDelay = 0;
+ }
+
+ for (track = 0; track < 4; track++) {
+ setChannelVolume(track, _track[track].vol);
+ setChannelPeriod(track, _track[track].period + _track[track].vibrato);
+ if (_track[track].sample) {
+ sample_t &sample = _module.sample[_track[track].sample - 1];
+ setChannelData(track,
+ sample.data,
+ sample.replen > 2 ? sample.data + sample.repeat : 0,
+ sample.len,
+ sample.replen);
+ setChannelOffset(track, _track[track].offset);
+ _track[track].sample = 0;
+ }
+ }
+}
+
+} // End of namespace Modules
+
+namespace Audio {
+
+AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) {
+ return new Modules::ProtrackerStream(stream, offs, rate, stereo);
+}
+
+} // End of namespace Audio
diff --git a/audio/mods/protracker.h b/audio/mods/protracker.h
new file mode 100644
index 0000000000..af722637c7
--- /dev/null
+++ b/audio/mods/protracker.h
@@ -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 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - parallaction
+ */
+
+#ifndef SOUND_MODS_PROTRACKER_H
+#define SOUND_MODS_PROTRACKER_H
+
+#include "common/stream.h"
+
+namespace Audio {
+
+class AudioStream;
+
+/*
+ * Factory function for ProTracker streams. Reads all data from the
+ * given ReadStream and creates an AudioStream from this. No reference
+ * to the 'stream' object is kept, so you can safely delete it after
+ * invoking this factory.
+ *
+ * @param stream the ReadStream from which to read the ProTracker data
+ * @param rate TODO
+ * @param stereo TODO
+ * @return a new AudioStream, or NULL, if an error occurred
+ */
+AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/rjp1.cpp b/audio/mods/rjp1.cpp
new file mode 100644
index 0000000000..7423abb668
--- /dev/null
+++ b/audio/mods/rjp1.cpp
@@ -0,0 +1,582 @@
+/* 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/debug.h"
+#include "common/endian.h"
+
+#include "audio/mods/paula.h"
+#include "audio/mods/rjp1.h"
+#include "audio/audiostream.h"
+
+namespace Audio {
+
+struct Rjp1Channel {
+ const int8 *waveData;
+ const int8 *modulatePeriodData;
+ const int8 *modulateVolumeData;
+ const int8 *envelopeData;
+ uint16 volumeScale;
+ int16 volume;
+ uint16 modulatePeriodBase;
+ uint32 modulatePeriodLimit;
+ uint32 modulatePeriodIndex;
+ uint16 modulateVolumeBase;
+ uint32 modulateVolumeLimit;
+ uint32 modulateVolumeIndex;
+ uint8 freqStep;
+ uint32 freqInc;
+ uint32 freqInit;
+ const uint8 *noteData;
+ const uint8 *sequenceOffsets;
+ const uint8 *sequenceData;
+ uint8 loopSeqCount;
+ uint8 loopSeqCur;
+ uint8 loopSeq2Count;
+ uint8 loopSeq2Cur;
+ bool active;
+ int16 modulatePeriodInit;
+ int16 modulatePeriodNext;
+ bool setupNewNote;
+ int8 envelopeMode;
+ int8 envelopeScale;
+ int8 envelopeEnd1;
+ int8 envelopeEnd2;
+ int8 envelopeStart;
+ int8 envelopeVolume;
+ uint8 currentInstrument;
+ const int8 *data;
+ uint16 pos;
+ uint16 len;
+ uint16 repeatPos;
+ uint16 repeatLen;
+ bool isSfx;
+};
+
+class Rjp1 : public Paula {
+public:
+
+ struct Vars {
+ int8 *instData;
+ uint8 *songData[7];
+ uint8 activeChannelsMask;
+ uint8 currentChannel;
+ int subsongsCount;
+ int instrumentsCount;
+ };
+
+ Rjp1(int rate, bool stereo);
+ virtual ~Rjp1();
+
+ bool load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData);
+ void unload();
+
+ void startPattern(int ch, int pat);
+ void startSong(int song);
+
+protected:
+
+ void startSequence(uint8 channelNum, uint8 seqNum);
+ void turnOffChannel(Rjp1Channel *channel);
+ void playChannel(Rjp1Channel *channel);
+ void turnOnChannel(Rjp1Channel *channel);
+ bool executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p);
+ bool executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p);
+ void playSongSequence(Rjp1Channel *channel);
+ void modulateVolume(Rjp1Channel *channel);
+ void modulatePeriod(Rjp1Channel *channel);
+ void setupNote(Rjp1Channel *channel, int16 freq);
+ void setupInstrument(Rjp1Channel *channel, uint8 num);
+ void setRelease(Rjp1Channel *channel);
+ void modulateVolumeEnvelope(Rjp1Channel *channel);
+ void setSustain(Rjp1Channel *channel);
+ void setDecay(Rjp1Channel *channel);
+ void modulateVolumeWaveform(Rjp1Channel *channel);
+ void setVolume(Rjp1Channel *channel);
+
+ void stopPaulaChannel(uint8 channel);
+ void setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen);
+
+ virtual void interrupt();
+
+ Vars _vars;
+ Rjp1Channel _channelsTable[4];
+
+ static const int16 _periodsTable[];
+ static const int _periodsCount;
+};
+
+Rjp1::Rjp1(int rate, bool stereo)
+ : Paula(stereo, rate, rate / 50) {
+ memset(&_vars, 0, sizeof(_vars));
+ memset(_channelsTable, 0, sizeof(_channelsTable));
+}
+
+Rjp1::~Rjp1() {
+ unload();
+}
+
+bool Rjp1::load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData) {
+ if (songData->readUint32BE() == MKID_BE('RJP1') && songData->readUint32BE() == MKID_BE('SMOD')) {
+ for (int i = 0; i < 7; ++i) {
+ uint32 size = songData->readUint32BE();
+ _vars.songData[i] = (uint8 *)malloc(size);
+ if (!_vars.songData[i])
+ return false;
+
+ songData->read(_vars.songData[i], size);
+ switch (i) {
+ case 0:
+ _vars.instrumentsCount = size / 32;
+ break;
+ case 1:
+ break;
+ case 2:
+ // sequence index to offsets, 1 per channel
+ _vars.subsongsCount = size / 4;
+ break;
+ case 3:
+ case 4:
+ // sequence offsets
+ break;
+ case 5:
+ case 6:
+ // sequence data
+ break;
+ }
+ }
+
+ if (instrumentsData->readUint32BE() == MKID_BE('RJP1')) {
+ uint32 size = instrumentsData->size() - 4;
+ _vars.instData = (int8 *)malloc(size);
+ if (!_vars.instData)
+ return false;
+
+ instrumentsData->read(_vars.instData, size);
+
+ }
+ }
+
+ debug(5, "Rjp1::load() _instrumentsCount = %d _subsongsCount = %d", _vars.instrumentsCount, _vars.subsongsCount);
+ return true;
+}
+
+void Rjp1::unload() {
+ for (int i = 0; i < 7; ++i) {
+ free(_vars.songData[i]);
+ }
+ free(_vars.instData);
+ memset(&_vars, 0, sizeof(_vars));
+ memset(_channelsTable, 0, sizeof(_channelsTable));
+}
+
+void Rjp1::startPattern(int ch, int pat) {
+ Rjp1Channel *channel = &_channelsTable[ch];
+ _vars.activeChannelsMask |= 1 << ch;
+ channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + pat * 4) + _vars.songData[6];
+ channel->loopSeqCount = 6;
+ channel->loopSeqCur = channel->loopSeq2Cur = 1;
+ channel->active = true;
+ channel->isSfx = true;
+ // "start" Paula audiostream
+ startPaula();
+}
+
+void Rjp1::startSong(int song) {
+ if (song == 0 || song >= _vars.subsongsCount) {
+ warning("Invalid subsong number %d, defaulting to 1", song);
+ song = 1;
+ }
+ const uint8 *p = _vars.songData[2] + (song & 0x3F) * 4;
+ for (int i = 0; i < 4; ++i) {
+ uint8 seq = *p++;
+ if (seq) {
+ startSequence(i, seq);
+ }
+ }
+ // "start" Paula audiostream
+ startPaula();
+}
+
+void Rjp1::startSequence(uint8 channelNum, uint8 seqNum) {
+ Rjp1Channel *channel = &_channelsTable[channelNum];
+ _vars.activeChannelsMask |= 1 << channelNum;
+ if (seqNum != 0) {
+ const uint8 *p = READ_BE_UINT32(_vars.songData[3] + seqNum * 4) + _vars.songData[5];
+ uint8 seq = *p++;
+ channel->sequenceOffsets = p;
+ channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + seq * 4) + _vars.songData[6];
+ channel->loopSeqCount = 6;
+ channel->loopSeqCur = channel->loopSeq2Cur = 1;
+ channel->active = true;
+ } else {
+ channel->active = false;
+ turnOffChannel(channel);
+ }
+}
+
+void Rjp1::turnOffChannel(Rjp1Channel *channel) {
+ stopPaulaChannel(channel - _channelsTable);
+}
+
+void Rjp1::playChannel(Rjp1Channel *channel) {
+ if (channel->active) {
+ turnOnChannel(channel);
+ if (channel->sequenceData) {
+ playSongSequence(channel);
+ }
+ modulateVolume(channel);
+ modulatePeriod(channel);
+ }
+}
+
+void Rjp1::turnOnChannel(Rjp1Channel *channel) {
+ if (channel->setupNewNote) {
+ channel->setupNewNote = false;
+ setupPaulaChannel(channel - _channelsTable, channel->data, channel->pos, channel->len, channel->repeatPos, channel->repeatLen);
+ }
+}
+
+bool Rjp1::executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) {
+ bool loop = true;
+ switch (code & 7) {
+ case 0:
+ _vars.activeChannelsMask &= ~(1 << _vars.currentChannel);
+ loop = false;
+ stopPaula();
+ break;
+ case 1:
+ setRelease(channel);
+ loop = false;
+ break;
+ case 2:
+ channel->loopSeqCount = *p++;
+ break;
+ case 3:
+ channel->loopSeq2Count = *p++;
+ break;
+ case 4:
+ code = *p++;
+ if (code != 0) {
+ setupInstrument(channel, code);
+ }
+ break;
+ case 7:
+ loop = false;
+ break;
+ }
+ return loop;
+}
+
+bool Rjp1::executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) {
+ bool loop = true;
+ const uint8 *offs;
+ switch (code & 7) {
+ case 0:
+ offs = channel->sequenceOffsets;
+ channel->loopSeq2Count = 1;
+ while (1) {
+ code = *offs++;
+ if (code != 0) {
+ channel->sequenceOffsets = offs;
+ p = READ_BE_UINT32(_vars.songData[4] + code * 4) + _vars.songData[6];
+ break;
+ } else {
+ code = offs[0];
+ if (code == 0) {
+ p = 0;
+ channel->active = false;
+ _vars.activeChannelsMask &= ~(1 << _vars.currentChannel);
+ loop = false;
+ break;
+ } else if (code & 0x80) {
+ code = offs[1];
+ offs = READ_BE_UINT32(_vars.songData[3] + code * 4) + _vars.songData[5];
+ } else {
+ offs -= code;
+ }
+ }
+ }
+ break;
+ case 1:
+ setRelease(channel);
+ loop = false;
+ break;
+ case 2:
+ channel->loopSeqCount = *p++;
+ break;
+ case 3:
+ channel->loopSeq2Count = *p++;
+ break;
+ case 4:
+ code = *p++;
+ if (code != 0) {
+ setupInstrument(channel, code);
+ }
+ break;
+ case 5:
+ channel->volumeScale = *p++;
+ break;
+ case 6:
+ channel->freqStep = *p++;
+ channel->freqInc = READ_BE_UINT32(p); p += 4;
+ channel->freqInit = 0;
+ break;
+ case 7:
+ loop = false;
+ break;
+ }
+ return loop;
+}
+
+void Rjp1::playSongSequence(Rjp1Channel *channel) {
+ const uint8 *p = channel->sequenceData;
+ --channel->loopSeqCur;
+ if (channel->loopSeqCur == 0) {
+ --channel->loopSeq2Cur;
+ if (channel->loopSeq2Cur == 0) {
+ bool loop = true;
+ do {
+ uint8 code = *p++;
+ if (code & 0x80) {
+ if (channel->isSfx) {
+ loop = executeSfxSequenceOp(channel, code, p);
+ } else {
+ loop = executeSongSequenceOp(channel, code, p);
+ }
+ } else {
+ code >>= 1;
+ if (code < _periodsCount) {
+ setupNote(channel, _periodsTable[code]);
+ }
+ loop = false;
+ }
+ } while (loop);
+ channel->sequenceData = p;
+ channel->loopSeq2Cur = channel->loopSeq2Count;
+ }
+ channel->loopSeqCur = channel->loopSeqCount;
+ }
+}
+
+void Rjp1::modulateVolume(Rjp1Channel *channel) {
+ modulateVolumeEnvelope(channel);
+ modulateVolumeWaveform(channel);
+ setVolume(channel);
+}
+
+void Rjp1::modulatePeriod(Rjp1Channel *channel) {
+ if (channel->modulatePeriodData) {
+ uint32 per = channel->modulatePeriodIndex;
+ int period = (channel->modulatePeriodData[per] * channel->modulatePeriodInit) / 128;
+ period = -period;
+ if (period < 0) {
+ period /= 2;
+ }
+ channel->modulatePeriodNext = period + channel->modulatePeriodInit;
+ ++per;
+ if (per == channel->modulatePeriodLimit) {
+ per = channel->modulatePeriodBase * 2;
+ }
+ channel->modulatePeriodIndex = per;
+ }
+ if (channel->freqStep != 0) {
+ channel->freqInit += channel->freqInc;
+ --channel->freqStep;
+ }
+ setChannelPeriod(channel - _channelsTable, channel->freqInit + channel->modulatePeriodNext);
+}
+
+void Rjp1::setupNote(Rjp1Channel *channel, int16 period) {
+ const uint8 *note = channel->noteData;
+ if (note) {
+ channel->modulatePeriodInit = channel->modulatePeriodNext = period;
+ channel->freqInit = 0;
+ const int8 *e = (const int8 *)_vars.songData[1] + READ_BE_UINT16(note + 12);
+ channel->envelopeData = e;
+ channel->envelopeStart = e[1];
+ channel->envelopeScale = e[1] - e[0];
+ channel->envelopeEnd2 = e[2];
+ channel->envelopeEnd1 = e[2];
+ channel->envelopeMode = 4;
+ channel->data = channel->waveData;
+ channel->pos = READ_BE_UINT16(note + 16);
+ channel->len = channel->pos + READ_BE_UINT16(note + 18);
+ channel->setupNewNote = true;
+ }
+}
+
+void Rjp1::setupInstrument(Rjp1Channel *channel, uint8 num) {
+ if (channel->currentInstrument != num) {
+ channel->currentInstrument = num;
+ const uint8 *p = _vars.songData[0] + num * 32;
+ channel->noteData = p;
+ channel->repeatPos = READ_BE_UINT16(p + 20);
+ channel->repeatLen = READ_BE_UINT16(p + 22);
+ channel->volumeScale = READ_BE_UINT16(p + 14);
+ channel->modulatePeriodBase = READ_BE_UINT16(p + 24);
+ channel->modulatePeriodIndex = 0;
+ channel->modulatePeriodLimit = READ_BE_UINT16(p + 26) * 2;
+ channel->modulateVolumeBase = READ_BE_UINT16(p + 28);
+ channel->modulateVolumeIndex = 0;
+ channel->modulateVolumeLimit = READ_BE_UINT16(p + 30) * 2;
+ channel->waveData = _vars.instData + READ_BE_UINT32(p);
+ uint32 off = READ_BE_UINT32(p + 4);
+ if (off) {
+ channel->modulatePeriodData = _vars.instData + off;
+ }
+ off = READ_BE_UINT32(p + 8);
+ if (off) {
+ channel->modulateVolumeData = _vars.instData + off;
+ }
+ }
+}
+
+void Rjp1::setRelease(Rjp1Channel *channel) {
+ const int8 *e = channel->envelopeData;
+ if (e) {
+ channel->envelopeStart = 0;
+ channel->envelopeScale = -channel->envelopeVolume;
+ channel->envelopeEnd2 = e[5];
+ channel->envelopeEnd1 = e[5];
+ channel->envelopeMode = -1;
+ }
+}
+
+void Rjp1::modulateVolumeEnvelope(Rjp1Channel *channel) {
+ if (channel->envelopeMode) {
+ int16 es = channel->envelopeScale;
+ if (es) {
+ int8 m = channel->envelopeEnd1;
+ if (m == 0) {
+ es = 0;
+ } else {
+ es *= m;
+ m = channel->envelopeEnd2;
+ if (m == 0) {
+ es = 0;
+ } else {
+ es /= m;
+ }
+ }
+ }
+ channel->envelopeVolume = channel->envelopeStart - es;
+ --channel->envelopeEnd1;
+ if (channel->envelopeEnd1 == -1) {
+ switch (channel->envelopeMode) {
+ case 0:
+ break;
+ case 2:
+ setSustain(channel);
+ break;
+ case 4:
+ setDecay(channel);
+ break;
+ case -1:
+ setSustain(channel);
+ break;
+ default:
+ error("Unhandled envelope mode %d", channel->envelopeMode);
+ break;
+ }
+ return;
+ }
+ }
+ channel->volume = channel->envelopeVolume;
+}
+
+void Rjp1::setSustain(Rjp1Channel *channel) {
+ channel->envelopeMode = 0;
+}
+
+void Rjp1::setDecay(Rjp1Channel *channel) {
+ const int8 *e = channel->envelopeData;
+ if (e) {
+ channel->envelopeStart = e[3];
+ channel->envelopeScale = e[3] - e[1];
+ channel->envelopeEnd2 = e[4];
+ channel->envelopeEnd1 = e[4];
+ channel->envelopeMode = 2;
+ }
+}
+
+void Rjp1::modulateVolumeWaveform(Rjp1Channel *channel) {
+ if (channel->modulateVolumeData) {
+ uint32 i = channel->modulateVolumeIndex;
+ channel->volume += channel->modulateVolumeData[i] * channel->volume / 128;
+ ++i;
+ if (i == channel->modulateVolumeLimit) {
+ i = channel->modulateVolumeBase * 2;
+ }
+ channel->modulateVolumeIndex = i;
+ }
+}
+
+void Rjp1::setVolume(Rjp1Channel *channel) {
+ channel->volume = (channel->volume * channel->volumeScale) / 64;
+ channel->volume = CLIP<int16>(channel->volume, 0, 64);
+ setChannelVolume(channel - _channelsTable, channel->volume);
+}
+
+void Rjp1::stopPaulaChannel(uint8 channel) {
+ clearVoice(channel);
+}
+
+void Rjp1::setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen) {
+ if (waveData) {
+ setChannelData(channel, waveData, waveData + repeatPos * 2, len * 2, repeatLen * 2, offset * 2);
+ }
+}
+
+void Rjp1::interrupt() {
+ for (int i = 0; i < 4; ++i) {
+ _vars.currentChannel = i;
+ playChannel(&_channelsTable[i]);
+ }
+}
+
+const int16 Rjp1::_periodsTable[] = {
+ 0x01C5, 0x01E0, 0x01FC, 0x021A, 0x023A, 0x025C, 0x0280, 0x02A6, 0x02D0,
+ 0x02FA, 0x0328, 0x0358, 0x00E2, 0x00F0, 0x00FE, 0x010D, 0x011D, 0x012E,
+ 0x0140, 0x0153, 0x0168, 0x017D, 0x0194, 0x01AC, 0x0071, 0x0078, 0x007F,
+ 0x0087, 0x008F, 0x0097, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00CA, 0x00D6
+};
+
+const int Rjp1::_periodsCount = ARRAYSIZE(_periodsTable);
+
+AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate, bool stereo) {
+ Rjp1 *stream = new Rjp1(rate, stereo);
+ if (stream->load(songData, instrumentsData)) {
+ if (num < 0) {
+ stream->startPattern(3, -num);
+ } else {
+ stream->startSong(num);
+ }
+ return stream;
+ }
+ delete stream;
+ return 0;
+}
+
+} // End of namespace Audio
diff --git a/audio/mods/rjp1.h b/audio/mods/rjp1.h
new file mode 100644
index 0000000000..e1960921b2
--- /dev/null
+++ b/audio/mods/rjp1.h
@@ -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 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - queen
+ */
+
+#ifndef SOUND_MODS_RJP1_H
+#define SOUND_MODS_RJP1_H
+
+#include "common/stream.h"
+
+namespace Audio {
+
+class AudioStream;
+
+/*
+ * Factory function for RichardJoseph1 modules. Reads all data from the
+ * given songData and instrumentsData streams and creates an AudioStream
+ * from this. No references to these stream objects are kept.
+ */
+AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate = 44100, bool stereo = true);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/soundfx.cpp b/audio/mods/soundfx.cpp
new file mode 100644
index 0000000000..06a1e29514
--- /dev/null
+++ b/audio/mods/soundfx.cpp
@@ -0,0 +1,275 @@
+/* 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/endian.h"
+
+#include "audio/mods/paula.h"
+#include "audio/mods/soundfx.h"
+#include "audio/audiostream.h"
+
+namespace Audio {
+
+struct SoundFxInstrument {
+ char name[23];
+ uint16 len;
+ uint8 finetune;
+ uint8 volume;
+ uint16 repeatPos;
+ uint16 repeatLen;
+ int8 *data;
+};
+
+class SoundFx : public Paula {
+public:
+
+ enum {
+ NUM_CHANNELS = 4,
+ NUM_INSTRUMENTS = 15
+ };
+
+ SoundFx(int rate, bool stereo);
+ virtual ~SoundFx();
+
+ bool load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb);
+ void play();
+
+protected:
+
+ void handlePattern(int ch, uint32 pat);
+ void updateEffects(int ch);
+ void handleTick();
+
+ void disablePaulaChannel(uint8 channel);
+ void setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen);
+
+ virtual void interrupt();
+
+ uint8 _ticks;
+ uint16 _delay;
+ SoundFxInstrument _instruments[NUM_INSTRUMENTS];
+ uint8 _numOrders;
+ uint8 _curOrder;
+ uint16 _curPos;
+ uint8 _ordersTable[128];
+ uint8 *_patternData;
+ uint16 _effects[NUM_CHANNELS];
+};
+
+SoundFx::SoundFx(int rate, bool stereo)
+ : Paula(stereo, rate) {
+ setTimerBaseValue(kPalCiaClock);
+ _ticks = 0;
+ _delay = 0;
+ memset(_instruments, 0, sizeof(_instruments));
+ _numOrders = 0;
+ _curOrder = 0;
+ _curPos = 0;
+ memset(_ordersTable, 0, sizeof(_ordersTable));
+ _patternData = 0;
+ memset(_effects, 0, sizeof(_effects));
+}
+
+SoundFx::~SoundFx() {
+ free(_patternData);
+ for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
+ free(_instruments[i].data);
+ }
+}
+
+bool SoundFx::load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb) {
+ int instrumentsSize[15];
+ if (!loadCb) {
+ for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
+ instrumentsSize[i] = data->readUint32BE();
+ }
+ }
+ uint8 tag[4];
+ data->read(tag, 4);
+ if (memcmp(tag, "SONG", 4) != 0) {
+ return false;
+ }
+ _delay = data->readUint16BE();
+ data->skip(7 * 2);
+ for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
+ SoundFxInstrument *ins = &_instruments[i];
+ data->read(ins->name, 22); ins->name[22] = 0;
+ ins->len = data->readUint16BE();
+ ins->finetune = data->readByte();
+ ins->volume = data->readByte();
+ ins->repeatPos = data->readUint16BE();
+ ins->repeatLen = data->readUint16BE();
+ }
+ _numOrders = data->readByte();
+ data->skip(1);
+ data->read(_ordersTable, 128);
+ int maxOrder = 0;
+ for (int i = 0; i < _numOrders; ++i) {
+ if (_ordersTable[i] > maxOrder) {
+ maxOrder = _ordersTable[i];
+ }
+ }
+ int patternSize = (maxOrder + 1) * 4 * 4 * 64;
+ _patternData = (uint8 *)malloc(patternSize);
+ if (!_patternData) {
+ return false;
+ }
+ data->read(_patternData, patternSize);
+ for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
+ SoundFxInstrument *ins = &_instruments[i];
+ if (!loadCb) {
+ if (instrumentsSize[i] != 0) {
+ assert(ins->len <= 1 || ins->len * 2 <= instrumentsSize[i]);
+ assert(ins->repeatLen <= 1 || (ins->repeatPos + ins->repeatLen) * 2 <= instrumentsSize[i]);
+ ins->data = (int8 *)malloc(instrumentsSize[i]);
+ if (!ins->data) {
+ return false;
+ }
+ data->read(ins->data, instrumentsSize[i]);
+ }
+ } else {
+ if (ins->name[0]) {
+ ins->name[8] = '\0';
+ ins->data = (int8 *)(*loadCb)(ins->name, 0);
+ if (!ins->data) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void SoundFx::play() {
+ _curPos = 0;
+ _curOrder = 0;
+ _ticks = 0;
+ setInterruptFreqUnscaled(_delay);
+ startPaula();
+}
+
+void SoundFx::handlePattern(int ch, uint32 pat) {
+ uint16 note1 = pat >> 16;
+ uint16 note2 = pat & 0xFFFF;
+ if (note1 == 0xFFFD) { // PIC
+ _effects[ch] = 0;
+ return;
+ }
+ _effects[ch] = note2;
+ if (note1 == 0xFFFE) { // STP
+ disablePaulaChannel(ch);
+ return;
+ }
+ int ins = (note2 & 0xF000) >> 12;
+ if (ins != 0) {
+ SoundFxInstrument *i = &_instruments[ins - 1];
+ setupPaulaChannel(ch, i->data, i->len, i->repeatPos, i->repeatLen);
+ int effect = (note2 & 0xF00) >> 8;
+ int volume = i->volume;
+ switch (effect) {
+ case 5: // volume up
+ volume += (note2 & 0xFF);
+ if (volume > 63) {
+ volume = 63;
+ }
+ break;
+ case 6: // volume down
+ volume -= (note2 & 0xFF);
+ if (volume < 0) {
+ volume = 0;
+ }
+ break;
+ }
+ setChannelVolume(ch, volume);
+ }
+ if (note1 != 0) {
+ setChannelPeriod(ch, note1);
+ }
+}
+
+void SoundFx::updateEffects(int ch) {
+ // updateEffects() is a no-op in all Delphine Software games using SoundFx : FW,OS,Cruise,AW
+ if (_effects[ch] != 0) {
+ switch (_effects[ch]) {
+ case 1: // appreggiato
+ case 2: // pitchbend
+ case 3: // ledon, enable low-pass filter
+ case 4: // ledoff, disable low-pass filter
+ case 7: // set step up
+ case 8: // set step down
+ warning("Unhandled effect %d", _effects[ch]);
+ break;
+ }
+ }
+}
+
+void SoundFx::handleTick() {
+ ++_ticks;
+ if (_ticks != 6) {
+ for (int ch = 0; ch < 4; ++ch) {
+ updateEffects(ch);
+ }
+ } else {
+ _ticks = 0;
+ const uint8 *patternData = _patternData + _ordersTable[_curOrder] * 1024 + _curPos;
+ for (int ch = 0; ch < 4; ++ch) {
+ handlePattern(ch, READ_BE_UINT32(patternData));
+ patternData += 4;
+ }
+ _curPos += 4 * 4;
+ if (_curPos >= 1024) {
+ _curPos = 0;
+ ++_curOrder;
+ if (_curOrder == _numOrders) {
+ stopPaula();
+ }
+ }
+ }
+}
+
+void SoundFx::disablePaulaChannel(uint8 channel) {
+ disableChannel(channel);
+}
+
+void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) {
+ if (data && len > 1) {
+ setChannelData(channel, data, data + repeatPos * 2, len * 2, repeatLen * 2);
+ }
+}
+
+void SoundFx::interrupt() {
+ handleTick();
+}
+
+AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate, bool stereo) {
+ SoundFx *stream = new SoundFx(rate, stereo);
+ if (stream->load(data, loadCb)) {
+ stream->play();
+ return stream;
+ }
+ delete stream;
+ return 0;
+}
+
+} // End of namespace Audio
diff --git a/audio/mods/soundfx.h b/audio/mods/soundfx.h
new file mode 100644
index 0000000000..089c19d292
--- /dev/null
+++ b/audio/mods/soundfx.h
@@ -0,0 +1,53 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - cine
+ */
+
+#ifndef SOUND_MODS_SOUNDFX_H
+#define SOUND_MODS_SOUNDFX_H
+
+#include "common/stream.h"
+
+namespace Audio {
+
+class AudioStream;
+
+typedef byte *(*LoadSoundFxInstrumentCallback)(const char *name, uint32 *size);
+
+/*
+ * Factory function for SoundFX modules. Reads all data from the
+ * given data stream and creates an AudioStream from this (no references to the
+ * stream object is kept). If loadCb is non 0, then instruments are loaded using
+ * it, buffers returned are free'd at the end of playback.
+ */
+AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate = 44100, bool stereo = true);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/mods/tfmx.cpp b/audio/mods/tfmx.cpp
new file mode 100644
index 0000000000..8c69a75ebd
--- /dev/null
+++ b/audio/mods/tfmx.cpp
@@ -0,0 +1,1193 @@
+/* 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"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/debug.h"
+
+#include "audio/mods/tfmx.h"
+
+// test for engines using this class.
+#if defined(SOUND_MODS_TFMX_H)
+
+// couple debug-functions
+namespace {
+
+#if 0
+void displayPatternstep(const void * const vptr);
+void displayMacroStep(const void * const vptr);
+#endif
+
+static const uint16 noteIntervalls[64] = {
+ 1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017, 960, 908,
+ 856, 810, 764, 720, 680, 642, 606, 571, 539, 509, 480, 454,
+ 428, 404, 381, 360, 340, 320, 303, 286, 270, 254, 240, 227,
+ 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113,
+ 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113,
+ 214, 202, 191, 180
+};
+
+} // End of anonymous namespace
+
+namespace Audio {
+
+Tfmx::Tfmx(int rate, bool stereo)
+ : Paula(stereo, rate),
+ _resource(),
+ _resourceSample(),
+ _playerCtx(),
+ _deleteResource(false) {
+
+ _playerCtx.stopWithLastPattern = false;
+
+ for (int i = 0; i < kNumVoices; ++i)
+ _channelCtx[i].paulaChannel = (byte)i;
+
+ _playerCtx.volume = 0x40;
+ _playerCtx.patternSkip = 6;
+ stopSongImpl();
+
+ setTimerBaseValue(kPalCiaClock);
+ setInterruptFreqUnscaled(kPalDefaultCiaVal);
+}
+
+Tfmx::~Tfmx() {
+ freeResourceDataImpl();
+}
+
+void Tfmx::interrupt() {
+ assert(!_end);
+ ++_playerCtx.tickCount;
+
+ for (int i = 0; i < kNumVoices; ++i) {
+ if (_channelCtx[i].dmaIntCount) {
+ // wait for DMA Interupts to happen
+ int doneDma = getChannelDmaCount(i);
+ if (doneDma >= _channelCtx[i].dmaIntCount) {
+ _channelCtx[i].dmaIntCount = 0;
+ _channelCtx[i].macroRun = true;
+ }
+ }
+ }
+
+ for (int i = 0; i < kNumVoices; ++i) {
+ ChannelContext &channel = _channelCtx[i];
+
+ if (channel.sfxLockTime >= 0)
+ --channel.sfxLockTime;
+ else {
+ channel.sfxLocked = false;
+ channel.customMacroPrio = 0;
+ }
+
+ // externally queued macros
+ if (channel.customMacro) {
+ const byte * const noteCmd = (const byte *)&channel.customMacro;
+ channel.sfxLocked = false;
+ noteCommand(noteCmd[0], noteCmd[1], (noteCmd[2] & 0xF0) | (uint8)i, noteCmd[3]);
+ channel.customMacro = 0;
+ channel.sfxLocked = (channel.customMacroPrio != 0);
+ }
+
+ // apply timebased effects on Parameters
+ if (channel.macroSfxRun > 0)
+ effects(channel);
+
+ // see if we have to run the macro-program
+ if (channel.macroRun) {
+ if (!channel.macroWait)
+ macroRun(channel);
+ else
+ --channel.macroWait;
+ }
+
+ Paula::setChannelPeriod(i, channel.period);
+ if (channel.macroSfxRun >= 0)
+ channel.macroSfxRun = 1;
+
+ // TODO: handling pending DMAOff?
+ }
+
+ // Patterns are only processed each _playerCtx.timerCount + 1 tick
+ if (_playerCtx.song >= 0 && !_playerCtx.patternCount--) {
+ _playerCtx.patternCount = _playerCtx.patternSkip;
+ advancePatterns();
+ }
+}
+
+void Tfmx::effects(ChannelContext &channel) {
+ // addBegin
+ if (channel.addBeginLength) {
+ channel.sampleStart += channel.addBeginDelta;
+ Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart));
+ if (!(--channel.addBeginCount)) {
+ channel.addBeginCount = channel.addBeginLength;
+ channel.addBeginDelta = -channel.addBeginDelta;
+ }
+ }
+
+ // vibrato
+ if (channel.vibLength) {
+ channel.vibValue += channel.vibDelta;
+ if (--channel.vibCount == 0) {
+ channel.vibCount = channel.vibLength;
+ channel.vibDelta = -channel.vibDelta;
+ }
+ if (!channel.portaDelta) {
+ // 16x16 bit multiplication, casts needed for the right results
+ channel.period = (uint16)(((uint32)channel.refPeriod * (uint16)((1 << 11) + channel.vibValue)) >> 11);
+ }
+ }
+
+ // portamento
+ if (channel.portaDelta && !(--channel.portaCount)) {
+ channel.portaCount = channel.portaSkip;
+
+ bool resetPorta = true;
+ const uint16 period = channel.refPeriod;
+ uint16 portaVal = channel.portaValue;
+
+ if (period > portaVal) {
+ portaVal = ((uint32)portaVal * (uint16)((1 << 8) + channel.portaDelta)) >> 8;
+ resetPorta = (period <= portaVal);
+
+ } else if (period < portaVal) {
+ portaVal = ((uint32)portaVal * (uint16)((1 << 8) - channel.portaDelta)) >> 8;
+ resetPorta = (period >= portaVal);
+ }
+
+ if (resetPorta) {
+ channel.portaDelta = 0;
+ channel.portaValue = period & 0x7FF;
+ } else
+ channel.period = channel.portaValue = portaVal & 0x7FF;
+ }
+
+ // envelope
+ if (channel.envSkip && !channel.envCount--) {
+ channel.envCount = channel.envSkip;
+
+ const int8 endVol = channel.envEndVolume;
+ int8 volume = channel.volume;
+ bool resetEnv = true;
+
+ if (endVol > volume) {
+ volume += channel.envDelta;
+ resetEnv = endVol <= volume;
+ } else {
+ volume -= channel.envDelta;
+ resetEnv = volume <= 0 || endVol >= volume;
+ }
+
+ if (resetEnv) {
+ channel.envSkip = 0;
+ volume = endVol;
+ }
+ channel.volume = volume;
+ }
+
+ // Fade
+ if (_playerCtx.fadeDelta && !(--_playerCtx.fadeCount)) {
+ _playerCtx.fadeCount = _playerCtx.fadeSkip;
+
+ _playerCtx.volume += _playerCtx.fadeDelta;
+ if (_playerCtx.volume == _playerCtx.fadeEndVolume)
+ _playerCtx.fadeDelta = 0;
+ }
+
+ // Volume
+ const uint8 finVol = _playerCtx.volume * channel.volume >> 6;
+ Paula::setChannelVolume(channel.paulaChannel, finVol);
+}
+
+void Tfmx::macroRun(ChannelContext &channel) {
+ bool deferWait = channel.deferWait;
+ for (;;) {
+ const byte *const macroPtr = (const byte *)(getMacroPtr(channel.macroOffset) + channel.macroStep);
+ ++channel.macroStep;
+
+ switch (macroPtr[0]) {
+ case 0x00: // Reset + DMA Off. Parameters: deferWait, addset, vol
+ clearEffects(channel);
+ // FT
+ case 0x13: // DMA Off. Parameters: deferWait, addset, vol
+ // TODO: implement PArameters
+ Paula::disableChannel(channel.paulaChannel);
+ channel.deferWait = deferWait = (macroPtr[1] != 0);
+ if (deferWait) {
+ // if set, then we expect a DMA On in the same tick.
+ channel.period = 4;
+ //Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ Paula::setChannelSampleLen(channel.paulaChannel, 1);
+ // in this state we then need to allow some commands that normally
+ // would halt the macroprogamm to continue instead.
+ // those commands are: Wait, WaitDMA, AddPrevNote, AddNote, SetNote, <unknown Cmd>
+ // DMA On is affected aswell
+ // TODO remember time disabled, remember pending dmaoff?.
+ }
+
+ if (macroPtr[2] || macroPtr[3]) {
+ channel.volume = (macroPtr[2] ? 0 : channel.relVol * 3) + macroPtr[3];
+ Paula::setChannelVolume(channel.paulaChannel, channel.volume);
+ }
+ continue;
+
+ case 0x01: // DMA On
+ // TODO: Parameter macroPtr[1] - en-/disable effects
+ channel.dmaIntCount = 0;
+ if (deferWait) {
+ // TODO
+ // there is actually a small delay in the player, but I think that
+ // only allows to clear DMA-State on real Hardware
+ }
+ Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ Paula::enableChannel(channel.paulaChannel);
+ channel.deferWait = deferWait = false;
+ continue;
+
+ case 0x02: // Set Beginn. Parameters: SampleOffset(L)
+ channel.addBeginLength = 0;
+ channel.sampleStart = READ_BE_UINT32(macroPtr) & 0xFFFFFF;
+ Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart));
+ continue;
+
+ case 0x03: // SetLength. Parameters: SampleLength(W)
+ channel.sampleLen = READ_BE_UINT16(&macroPtr[2]);
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+
+ case 0x04: // Wait. Parameters: Ticks to wait(W).
+ // TODO: some unknown Parameter? (macroPtr[1] & 1)
+ channel.macroWait = READ_BE_UINT16(&macroPtr[2]);
+ break;
+
+ case 0x10: // Loop Key Up. Parameters: Loopcount, MacroStep(W)
+ if (channel.keyUp)
+ continue;
+ // FT
+ case 0x05: // Loop. Parameters: Loopcount, MacroStep(W)
+ if (channel.macroLoopCount != 0) {
+ if (channel.macroLoopCount == 0xFF)
+ channel.macroLoopCount = macroPtr[1];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ }
+ --channel.macroLoopCount;
+ continue;
+
+ case 0x06: // Jump. Parameters: MacroIndex, MacroStep(W)
+ // channel.macroIndex = macroPtr[1] & (kMaxMacroOffsets - 1);
+ channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ channel.macroLoopCount = 0xFF;
+ continue;
+
+ case 0x07: // Stop Macro
+ channel.macroRun = false;
+ --channel.macroStep;
+ return;
+
+ case 0x08: // AddNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, channel.note + macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x09: // SetNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x0A: // Clear Effects
+ clearEffects(channel);
+ continue;
+
+ case 0x0B: // Portamento. Parameters: count, speed
+ channel.portaSkip = macroPtr[1];
+ channel.portaCount = 1;
+ // if porta is already running, then keep using old value
+ if (!channel.portaDelta)
+ channel.portaValue = channel.refPeriod;
+ channel.portaDelta = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x0C: // Vibrato. Parameters: Speed, intensity
+ channel.vibLength = macroPtr[1];
+ channel.vibCount = macroPtr[1] / 2;
+ channel.vibDelta = macroPtr[3];
+ // TODO: Perhaps a bug, vibValue could be left uninitialised
+ if (!channel.portaDelta) {
+ channel.period = channel.refPeriod;
+ channel.vibValue = 0;
+ }
+ continue;
+
+ case 0x0D: // Add Volume. Parameters: note, addNoteFlag, volume
+ if (macroPtr[2] == 0xFE)
+ setNoteMacro(channel, channel.note + macroPtr[1], 0);
+ channel.volume = channel.relVol * 3 + macroPtr[3];
+ continue;
+
+ case 0x0E: // Set Volume. Parameters: note, addNoteFlag, volume
+ if (macroPtr[2] == 0xFE)
+ setNoteMacro(channel, channel.note + macroPtr[1], 0);
+ channel.volume = macroPtr[3];
+ continue;
+
+ case 0x0F: // Envelope. Parameters: speed, count, endvol
+ channel.envDelta = macroPtr[1];
+ channel.envCount = channel.envSkip = macroPtr[2];
+ channel.envEndVolume = macroPtr[3];
+ continue;
+
+ case 0x11: // Add Beginn. Parameters: times, Offset(W)
+ channel.addBeginLength = channel.addBeginCount = macroPtr[1];
+ channel.addBeginDelta = (int16)READ_BE_UINT16(&macroPtr[2]);
+ channel.sampleStart += channel.addBeginDelta;
+ Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart));
+ continue;
+
+ case 0x12: // Add Length. Parameters: added Length(W)
+ channel.sampleLen += (int16)READ_BE_UINT16(&macroPtr[2]);
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+
+ case 0x14: // Wait key up. Parameters: wait cycles
+ if (channel.keyUp || channel.macroLoopCount == 0) {
+ channel.macroLoopCount = 0xFF;
+ continue;
+ } else if (channel.macroLoopCount == 0xFF)
+ channel.macroLoopCount = macroPtr[3];
+ --channel.macroLoopCount;
+ --channel.macroStep;
+ return;
+
+ case 0x15: // Subroutine. Parameters: MacroIndex, Macrostep(W)
+ channel.macroReturnOffset = channel.macroOffset;
+ channel.macroReturnStep = channel.macroStep;
+
+ channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ // TODO: MI does some weird stuff there. Figure out which varioables need to be set
+ continue;
+
+ case 0x16: // Return from Sub.
+ channel.macroOffset = channel.macroReturnOffset;
+ channel.macroStep = channel.macroReturnStep;
+ continue;
+
+ case 0x17: // Set Period. Parameters: Period(W)
+ channel.refPeriod = READ_BE_UINT16(&macroPtr[2]);
+ if (!channel.portaDelta) {
+ channel.period = channel.refPeriod;
+ //Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ }
+ continue;
+
+ case 0x18: { // Sampleloop. Parameters: Offset from Samplestart(W)
+ // TODO: MI loads 24 bit, but thats useless?
+ const uint16 temp = /* ((int8)macroPtr[1] << 16) | */ READ_BE_UINT16(&macroPtr[2]);
+ if (macroPtr[1] || (temp & 1))
+ warning("Tfmx: Problematic value for sampleloop: %06X", (macroPtr[1] << 16) | temp);
+ channel.sampleStart += temp & 0xFFFE;
+ channel.sampleLen -= (temp / 2) /* & 0x7FFF */;
+ Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart));
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+ }
+ case 0x19: // Set One-Shot Sample
+ channel.addBeginLength = 0;
+ channel.sampleStart = 0;
+ channel.sampleLen = 1;
+ Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(0));
+ Paula::setChannelSampleLen(channel.paulaChannel, 1);
+ continue;
+
+ case 0x1A: // Wait on DMA. Parameters: Cycles-1(W) to wait
+ channel.dmaIntCount = READ_BE_UINT16(&macroPtr[2]) + 1;
+ channel.macroRun = false;
+ Paula::setChannelDmaCount(channel.paulaChannel);
+ break;
+
+/* case 0x1B: // Random play. Parameters: macro/speed/mode
+ warnMacroUnimplemented(macroPtr, 0);
+ continue;*/
+
+ case 0x1C: // Branch on Note. Parameters: note/macrostep(W)
+ if (channel.note > macroPtr[1])
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x1D: // Branch on Volume. Parameters: volume/macrostep(W)
+ if (channel.volume > macroPtr[1])
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+/* case 0x1E: // Addvol+note. Parameters: note/CONST./volume
+ warnMacroUnimplemented(macroPtr, 0);
+ continue;*/
+
+ case 0x1F: // AddPrevNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, channel.prevNote + macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x20: // Signal. Parameters: signalnumber, value(W)
+ if (_playerCtx.numSignals > macroPtr[1])
+ _playerCtx.signal[macroPtr[1]] = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x21: // Play macro. Parameters: macro, chan, detune
+ noteCommand(channel.note, macroPtr[1], (channel.relVol << 4) | macroPtr[2], macroPtr[3]);
+ continue;
+
+ // 0x22 - 0x29 are used by Gem`X
+ // 0x30 - 0x34 are used by Carribean Disaster
+
+ default:
+ debug(3, "Tfmx: Macro %02X not supported", macroPtr[0]);
+ }
+ if (!deferWait)
+ return;
+ }
+}
+
+void Tfmx::advancePatterns() {
+startPatterns:
+ int runningPatterns = 0;
+
+ for (int i = 0; i < kNumChannels; ++i) {
+ PatternContext &pattern = _patternCtx[i];
+ const uint8 pattCmd = pattern.command;
+ if (pattCmd < 0x90) { // execute Patternstep
+ ++runningPatterns;
+ if (!pattern.wait) {
+ // issue all Steps for this tick
+ if (patternRun(pattern)) {
+ // we load the next Trackstep Command and then process all Channels again
+ if (trackRun(true))
+ goto startPatterns;
+ else
+ break;
+ }
+
+ } else
+ --pattern.wait;
+
+ } else if (pattCmd == 0xFE) { // Stop voice in pattern.expose
+ pattern.command = 0xFF;
+ ChannelContext &channel = _channelCtx[pattern.expose & (kNumVoices - 1)];
+ if (!channel.sfxLocked) {
+ haltMacroProgramm(channel);
+ Paula::disableChannel(channel.paulaChannel);
+ }
+ } // else this pattern-Channel is stopped
+ }
+ if (_playerCtx.stopWithLastPattern && !runningPatterns) {
+ stopPaula();
+ }
+}
+
+bool Tfmx::patternRun(PatternContext &pattern) {
+ for (;;) {
+ const byte *const patternPtr = (const byte *)(getPatternPtr(pattern.offset) + pattern.step);
+ ++pattern.step;
+ const byte pattCmd = patternPtr[0];
+
+ if (pattCmd < 0xF0) { // Playnote
+ bool doWait = false;
+ byte noteCmd = pattCmd + pattern.expose;
+ byte param3 = patternPtr[3];
+ if (pattCmd < 0xC0) { // Note
+ if (pattCmd >= 0x80) { // Wait
+ pattern.wait = param3;
+ param3 = 0;
+ doWait = true;
+ }
+ noteCmd &= 0x3F;
+ } // else Portamento
+ noteCommand(noteCmd, patternPtr[1], patternPtr[2], param3);
+ if (doWait)
+ return false;
+
+ } else { // Patterncommand
+ switch (pattCmd & 0xF) {
+ case 0: // End Pattern + Next Trackstep
+ pattern.command = 0xFF;
+ --pattern.step;
+ return true;
+
+ case 1: // Loop Pattern. Parameters: Loopcount, PatternStep(W)
+ if (pattern.loopCount != 0) {
+ if (pattern.loopCount == 0xFF)
+ pattern.loopCount = patternPtr[1];
+ pattern.step = READ_BE_UINT16(&patternPtr[2]);
+ }
+ --pattern.loopCount;
+ continue;
+
+ case 2: // Jump. Parameters: PatternIndex, PatternStep(W)
+ pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)];
+ pattern.step = READ_BE_UINT16(&patternPtr[2]);
+ continue;
+
+ case 3: // Wait. Paramters: ticks to wait
+ pattern.wait = patternPtr[1];
+ return false;
+
+ case 14: // Stop custompattern
+ // TODO apparently toggles on/off pattern channel 7
+ debug(3, "Tfmx: Encountered 'Stop custompattern' command");
+ // FT
+ case 4: // Stop this pattern
+ pattern.command = 0xFF;
+ --pattern.step;
+ // TODO: try figuring out if this was the last Channel?
+ return false;
+
+ case 5: // Key Up Signal. Paramters: channel
+ if (!_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked)
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].keyUp = true;
+ continue;
+
+ case 6: // Vibrato. Parameters: length, channel, rate
+ case 7: // Envelope. Parameters: rate, tempo | channel, endVol
+ noteCommand(pattCmd, patternPtr[1], patternPtr[2], patternPtr[3]);
+ continue;
+
+ case 8: // Subroutine. Parameters: pattern, patternstep(W)
+ pattern.savedOffset = pattern.offset;
+ pattern.savedStep = pattern.step;
+
+ pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)];
+ pattern.step = READ_BE_UINT16(&patternPtr[2]);
+ continue;
+
+ case 9: // Return from Subroutine
+ pattern.offset = pattern.savedOffset;
+ pattern.step = pattern.savedStep;
+ continue;
+
+ case 10: // fade. Parameters: tempo, endVol
+ initFadeCommand((uint8)patternPtr[1], (int8)patternPtr[3]);
+ continue;
+
+ case 11: // play pattern. Parameters: patternCmd, channel, expose
+ initPattern(_patternCtx[patternPtr[2] & (kNumChannels - 1)], patternPtr[1], patternPtr[3], _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]);
+ continue;
+
+ case 12: // Lock. Parameters: lockFlag, channel, lockTime
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked = (patternPtr[1] != 0);
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLockTime = patternPtr[3];
+ continue;
+
+ case 13: // Cue. Parameters: signalnumber, value(W)
+ if (_playerCtx.numSignals > patternPtr[1])
+ _playerCtx.signal[patternPtr[1]] = READ_BE_UINT16(&patternPtr[2]);
+ continue;
+
+ case 15: // NOP
+ continue;
+ }
+ }
+ }
+}
+
+bool Tfmx::trackRun(const bool incStep) {
+ assert(_playerCtx.song >= 0);
+ if (incStep) {
+ // TODO Optionally disable looping
+ if (_trackCtx.posInd == _trackCtx.stopInd)
+ _trackCtx.posInd = _trackCtx.startInd;
+ else
+ ++_trackCtx.posInd;
+ }
+ for (;;) {
+ const uint16 *const trackData = getTrackPtr(_trackCtx.posInd);
+
+ if (trackData[0] != FROM_BE_16(0xEFFE)) {
+ // 8 commands for Patterns
+ for (int i = 0; i < 8; ++i) {
+ const uint8 *patCmd = (const uint8 *)&trackData[i];
+ // First byte is pattern number
+ const uint8 patNum = patCmd[0];
+ // if highest bit is set then keep previous pattern
+ if (patNum < 0x80) {
+ initPattern(_patternCtx[i], patNum, patCmd[1], _resource->patternOffset[patNum]);
+ } else {
+ _patternCtx[i].command = patNum;
+ _patternCtx[i].expose = (int8)patCmd[1];
+ }
+ }
+ return true;
+
+ } else {
+ // 16 byte Trackstep Command
+ switch (READ_BE_UINT16(&trackData[1])) {
+ case 0: // Stop Player. No Parameters
+ stopPaula();
+ return false;
+
+ case 1: // Branch/Loop section of tracksteps. Parameters: branch target, loopcount
+ if (_trackCtx.loopCount != 0) {
+ if (_trackCtx.loopCount < 0)
+ _trackCtx.loopCount = READ_BE_UINT16(&trackData[3]);
+ _trackCtx.posInd = READ_BE_UINT16(&trackData[2]);
+ continue;
+ }
+ --_trackCtx.loopCount;
+ break;
+
+ case 2: { // Set Tempo. Parameters: tempo, divisor
+ _playerCtx.patternCount = _playerCtx.patternSkip = READ_BE_UINT16(&trackData[2]); // tempo
+ const uint16 temp = READ_BE_UINT16(&trackData[3]); // divisor
+
+ if (!(temp & 0x8000) && (temp & 0x1FF))
+ setInterruptFreqUnscaled(temp & 0x1FF);
+ break;
+ }
+ case 4: // Fade. Parameters: tempo, endVol
+ // load the LSB of the 16bit words
+ initFadeCommand(((const uint8 *)&trackData[2])[1], ((const int8 *)&trackData[3])[1]);
+ break;
+
+ case 3: // Unknown, stops player aswell
+ default:
+ debug(3, "Tfmx: Unknown Trackstep Command: %02X", READ_BE_UINT16(&trackData[1]));
+ // MI-Player handles this by stopping the player, we just continue
+ }
+ }
+
+ if (_trackCtx.posInd == _trackCtx.stopInd) {
+ warning("Tfmx: Reached invalid Song-Position");
+ return false;
+ }
+ ++_trackCtx.posInd;
+ }
+}
+
+void Tfmx::noteCommand(const uint8 note, const uint8 param1, const uint8 param2, const uint8 param3) {
+ ChannelContext &channel = _channelCtx[param2 & (kNumVoices - 1)];
+
+ if (note == 0xFC) { // Lock command
+ channel.sfxLocked = (param1 != 0);
+ channel.sfxLockTime = param3; // only 1 byte read!
+
+ } else if (channel.sfxLocked) { // Channel still locked, do nothing
+
+ } else if (note < 0xC0) { // Play Note - Parameters: note, macro, relVol | channel, finetune
+
+ channel.prevNote = channel.note;
+ channel.note = note;
+ // channel.macroIndex = param1 & (kMaxMacroOffsets - 1);
+ channel.macroOffset = _resource->macroOffset[param1 & (kMaxMacroOffsets - 1)];
+ channel.relVol = param2 >> 4;
+ channel.fineTune = (int8)param3;
+
+ // TODO: the point where the channel gets initialised varies with the games, needs more research.
+ initMacroProgramm(channel);
+ channel.keyUp = false; // key down = playing a Note
+
+ } else if (note < 0xF0) { // Portamento - Parameters: note, tempo, channel, rate
+ channel.portaSkip = param1;
+ channel.portaCount = 1;
+ if (!channel.portaDelta)
+ channel.portaValue = channel.refPeriod;
+ channel.portaDelta = param3;
+
+ channel.note = note & 0x3F;
+ channel.refPeriod = noteIntervalls[channel.note];
+
+ } else switch (note) { // Command
+
+ case 0xF5: // Key Up Signal
+ channel.keyUp = true;
+ break;
+
+ case 0xF6: // Vibratio - Parameters: length, channel, rate
+ channel.vibLength = param1 & 0xFE;
+ channel.vibCount = param1 / 2;
+ channel.vibDelta = param3;
+ channel.vibValue = 0;
+ break;
+
+ case 0xF7: // Envelope - Parameters: rate, tempo | channel, endVol
+ channel.envDelta = param1;
+ channel.envCount = channel.envSkip = (param2 >> 4) + 1;
+ channel.envEndVolume = param3;
+ break;
+ }
+}
+
+void Tfmx::initMacroProgramm(ChannelContext &channel) {
+ channel.macroStep = 0;
+ channel.macroWait = 0;
+ channel.macroRun = true;
+ channel.macroSfxRun = 0;
+ channel.macroLoopCount = 0xFF;
+ channel.dmaIntCount = 0;
+ channel.deferWait = false;
+
+ channel.macroReturnOffset = 0;
+ channel.macroReturnStep = 0;
+}
+
+void Tfmx::clearEffects(ChannelContext &channel) {
+ channel.addBeginLength = 0;
+ channel.envSkip = 0;
+ channel.vibLength = 0;
+ channel.portaDelta = 0;
+}
+
+void Tfmx::haltMacroProgramm(ChannelContext &channel) {
+ channel.macroRun = false;
+ channel.dmaIntCount = 0;
+}
+
+void Tfmx::unlockMacroChannel(ChannelContext &channel) {
+ channel.customMacro = 0;
+ channel.customMacroIndex = 0;
+ channel.customMacroPrio = 0;
+ channel.sfxLocked = false;
+ channel.sfxLockTime = -1;
+}
+
+void Tfmx::initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset) {
+ pattern.command = cmd;
+ pattern.offset = offset;
+ pattern.expose = expose;
+ pattern.step = 0;
+ pattern.wait = 0;
+ pattern.loopCount = 0xFF;
+
+ pattern.savedOffset = 0;
+ pattern.savedStep = 0;
+}
+
+void Tfmx::stopSongImpl(bool stopAudio) {
+ _playerCtx.song = -1;
+ for (int i = 0; i < kNumChannels; ++i) {
+ _patternCtx[i].command = 0xFF;
+ _patternCtx[i].expose = 0;
+ }
+ if (stopAudio) {
+ stopPaula();
+ for (int i = 0; i < kNumVoices; ++i) {
+ clearEffects(_channelCtx[i]);
+ unlockMacroChannel(_channelCtx[i]);
+ haltMacroProgramm(_channelCtx[i]);
+ _channelCtx[i].note = 0;
+ _channelCtx[i].volume = 0;
+ _channelCtx[i].macroSfxRun = -1;
+ _channelCtx[i].vibValue = 0;
+
+ _channelCtx[i].sampleStart = 0;
+ _channelCtx[i].sampleLen = 2;
+ _channelCtx[i].refPeriod = 4;
+ _channelCtx[i].period = 4;
+ Paula::disableChannel(i);
+ }
+ }
+}
+
+void Tfmx::setNoteMacro(ChannelContext &channel, uint note, int fineTune) {
+ const uint16 noteInt = noteIntervalls[note & 0x3F];
+ const uint16 finetune = (uint16)(fineTune + channel.fineTune + (1 << 8));
+ channel.refPeriod = ((uint32)noteInt * finetune >> 8);
+ if (!channel.portaDelta)
+ channel.period = channel.refPeriod;
+}
+
+void Tfmx::initFadeCommand(const uint8 fadeTempo, const int8 endVol) {
+ _playerCtx.fadeCount = _playerCtx.fadeSkip = fadeTempo;
+ _playerCtx.fadeEndVolume = endVol;
+
+ if (fadeTempo) {
+ const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume;
+ _playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0;
+ } else {
+ _playerCtx.volume = endVol;
+ _playerCtx.fadeDelta = 0;
+ }
+}
+
+void Tfmx::setModuleData(Tfmx &otherPlayer) {
+ setModuleData(otherPlayer._resource, otherPlayer._resourceSample.sampleData, otherPlayer._resourceSample.sampleLen, false);
+}
+
+bool Tfmx::load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete) {
+ const MdatResource *mdat = loadMdatFile(musicData);
+ if (mdat) {
+ uint32 sampleLen = 0;
+ const int8 *sampleDat = loadSampleFile(sampleLen, sampleData);
+ if (sampleDat) {
+ setModuleData(mdat, sampleDat, sampleLen, autoDelete);
+ return true;
+ }
+ delete[] mdat->mdatAlloc;
+ delete mdat;
+ }
+ return false;
+}
+
+void Tfmx::freeResourceDataImpl() {
+ if (_deleteResource) {
+ if (_resource) {
+ delete[] _resource->mdatAlloc;
+ delete _resource;
+ }
+ delete[] _resourceSample.sampleData;
+ }
+ _resource = 0;
+ _resourceSample.sampleData = 0;
+ _resourceSample.sampleLen = 0;
+ _deleteResource = false;
+}
+
+void Tfmx::setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete) {
+ Common::StackLock lock(_mutex);
+ stopSongImpl(true);
+ freeResourceDataImpl();
+ _resource = resource;
+ _resourceSample.sampleData = sampleData;
+ _resourceSample.sampleLen = sampleData ? sampleLen : 0;
+ _deleteResource = autoDelete;
+}
+
+const int8 *Tfmx::loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream) {
+ sampleLen = 0;
+
+ const int32 sampleSize = sampleStream.size();
+ if (sampleSize < 4) {
+ warning("Tfmx: Cant load Samplefile");
+ return false;
+ }
+
+ int8 *sampleAlloc = new int8[sampleSize];
+ if (!sampleAlloc) {
+ warning("Tfmx: Could not allocate Memory: %dKB", sampleSize / 1024);
+ return 0;
+ }
+
+ if (sampleStream.read(sampleAlloc, sampleSize) == (uint32)sampleSize) {
+ sampleAlloc[0] = sampleAlloc[1] = sampleAlloc[2] = sampleAlloc[3] = 0;
+ sampleLen = sampleSize;
+ } else {
+ delete[] sampleAlloc;
+ warning("Tfmx: Encountered IO-Error");
+ return 0;
+ }
+ return sampleAlloc;
+}
+
+const Tfmx::MdatResource *Tfmx::loadMdatFile(Common::SeekableReadStream &musicData) {
+ bool hasHeader = false;
+ const int32 mdatSize = musicData.size();
+ if (mdatSize >= 0x200) {
+ byte buf[16] = { 0 };
+ // 0x0000: 10 Bytes Header "TFMX-SONG "
+ musicData.read(buf, 10);
+ hasHeader = memcmp(buf, "TFMX-SONG ", 10) == 0;
+ }
+
+ if (!hasHeader) {
+ warning("Tfmx: File is not a Tfmx Module");
+ return 0;
+ }
+
+ MdatResource *resource = new MdatResource;
+
+ resource->mdatAlloc = 0;
+ resource->mdatData = 0;
+ resource->mdatLen = 0;
+
+ // 0x000A: int16 flags
+ resource->headerFlags = musicData.readUint16BE();
+ // 0x000C: int32 ?
+ // 0x0010: 6*40 Textfield
+ musicData.skip(4 + 6 * 40);
+
+ /* 0x0100: Songstart x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ resource->subsong[i].songstart = musicData.readUint16BE();
+ /* 0x0140: Songend x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ resource->subsong[i].songend = musicData.readUint16BE();
+ /* 0x0180: Tempo x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ resource->subsong[i].tempo = musicData.readUint16BE();
+
+ /* 0x01c0: unused ? */
+ musicData.skip(16);
+
+ /* 0x01d0: trackstep, pattern data p, macro data p */
+ const uint32 offTrackstep = musicData.readUint32BE();
+ uint32 offPatternP, offMacroP;
+
+ // This is how MI`s TFMX-Player tests for unpacked Modules.
+ if (offTrackstep == 0) { // unpacked File
+ resource->trackstepOffset = 0x600 + 0x200;
+ offPatternP = 0x200 + 0x200;
+ offMacroP = 0x400 + 0x200;
+ } else { // packed File
+ resource->trackstepOffset = offTrackstep;
+ offPatternP = musicData.readUint32BE();
+ offMacroP = musicData.readUint32BE();
+ }
+
+ // End of basic header, check if everything worked ok
+ if (musicData.err()) {
+ warning("Tfmx: Encountered IO-Error");
+ delete resource;
+ return 0;
+ }
+
+ // TODO: if a File is packed it could have for Ex only 2 Patterns/Macros
+ // the following loops could then read beyond EOF.
+ // To correctly handle this it would be necessary to sort the pointers and
+ // figure out the number of Macros/Patterns
+ // We could also analyze pointers if they are correct offsets,
+ // so that accesses can be unchecked later
+
+ // Read in pattern starting offsets
+ musicData.seek(offPatternP);
+ for (int i = 0; i < kMaxPatternOffsets; ++i)
+ resource->patternOffset[i] = musicData.readUint32BE();
+
+ // use last PatternOffset (stored at 0x5FC in mdat) if unpacked File
+ // or fixed offset 0x200 if packed
+ resource->sfxTableOffset = offTrackstep ? 0x200 : resource->patternOffset[127];
+
+ // Read in macro starting offsets
+ musicData.seek(offMacroP);
+ for (int i = 0; i < kMaxMacroOffsets; ++i)
+ resource->macroOffset[i] = musicData.readUint32BE();
+
+ // Read in mdat-file
+ // TODO: we can skip everything thats already stored in the resource-structure.
+ const int32 mdatOffset = offTrackstep ? 0x200 : 0x600; // 0x200 is very conservative
+ const uint32 allocSize = (uint32)mdatSize - mdatOffset;
+
+ byte *mdatAlloc = new byte[allocSize];
+ if (!mdatAlloc) {
+ warning("Tfmx: Could not allocate Memory: %dKB", allocSize / 1024);
+ delete resource;
+ return 0;
+ }
+ musicData.seek(mdatOffset);
+ if (musicData.read(mdatAlloc, allocSize) == allocSize) {
+ resource->mdatAlloc = mdatAlloc;
+ resource->mdatData = mdatAlloc - mdatOffset;
+ resource->mdatLen = mdatSize;
+ } else {
+ delete[] mdatAlloc;
+ warning("Tfmx: Encountered IO-Error");
+ delete resource;
+ return 0;
+ }
+
+ return resource;
+}
+
+void Tfmx::doMacro(int note, int macro, int relVol, int finetune, int channelNo) {
+ assert(0 <= macro && macro < kMaxMacroOffsets);
+ assert(0 <= note && note < 0xC0);
+ Common::StackLock lock(_mutex);
+
+ if (!hasResources())
+ return;
+ channelNo &= (kNumVoices - 1);
+ ChannelContext &channel = _channelCtx[channelNo];
+ unlockMacroChannel(channel);
+
+ noteCommand((uint8)note, (uint8)macro, (uint8)((relVol << 4) | channelNo), (uint8)finetune);
+ startPaula();
+}
+
+void Tfmx::stopMacroEffect(int channel) {
+ assert(0 <= channel && channel < kNumVoices);
+ Common::StackLock lock(_mutex);
+ unlockMacroChannel(_channelCtx[channel]);
+ haltMacroProgramm(_channelCtx[channel]);
+ Paula::disableChannel(_channelCtx[channel].paulaChannel);
+}
+
+void Tfmx::doSong(int songPos, bool stopAudio) {
+ assert(0 <= songPos && songPos < kNumSubsongs);
+ Common::StackLock lock(_mutex);
+
+ stopSongImpl(stopAudio);
+
+ if (!hasResources())
+ return;
+
+ _trackCtx.loopCount = -1;
+ _trackCtx.startInd = _trackCtx.posInd = _resource->subsong[songPos].songstart;
+ _trackCtx.stopInd = _resource->subsong[songPos].songend;
+ _playerCtx.song = (int8)songPos;
+
+ const bool palFlag = (_resource->headerFlags & 2) != 0;
+ const uint16 tempo = _resource->subsong[songPos].tempo;
+ uint16 ciaIntervall;
+ if (tempo >= 0x10) {
+ ciaIntervall = (uint16)(kCiaBaseInterval / tempo);
+ _playerCtx.patternSkip = 0;
+ } else {
+ ciaIntervall = palFlag ? (uint16)kPalDefaultCiaVal : (uint16)kNtscDefaultCiaVal;
+ _playerCtx.patternSkip = tempo;
+ }
+ setInterruptFreqUnscaled(ciaIntervall);
+ Paula::setAudioFilter(true);
+
+ _playerCtx.patternCount = 0;
+ if (trackRun())
+ startPaula();
+}
+
+int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) {
+ assert(sfxIndex < 128);
+ Common::StackLock lock(_mutex);
+
+ if (!hasResources())
+ return -1;
+ const byte *sfxEntry = getSfxPtr(sfxIndex);
+ if (sfxEntry[0] == 0xFB) {
+ warning("Tfmx: custom patterns are not supported");
+ // custompattern
+ /* const uint8 patCmd = sfxEntry[2];
+ const int8 patExp = (int8)sfxEntry[3]; */
+ } else {
+ // custommacro
+ const byte channelNo = ((_playerCtx.song >= 0) ? sfxEntry[2] : sfxEntry[4]) & (kNumVoices - 1);
+ const byte priority = sfxEntry[5] & 0x7F;
+
+ ChannelContext &channel = _channelCtx[channelNo];
+ if (unlockChannel)
+ unlockMacroChannel(channel);
+
+ const int16 sfxLocktime = channel.sfxLockTime;
+ if (priority >= channel.customMacroPrio || sfxLocktime < 0) {
+ if (sfxIndex != channel.customMacroIndex || sfxLocktime < 0 || (sfxEntry[5] < 0x80)) {
+ channel.customMacro = READ_UINT32(sfxEntry); // intentionally not "endian-correct"
+ channel.customMacroPrio = priority;
+ channel.customMacroIndex = (uint8)sfxIndex;
+ debug(3, "Tfmx: running Macro %08X on channel %i - priority: %02X", TO_BE_32(channel.customMacro), channelNo, priority);
+ return channelNo;
+ }
+ }
+ }
+ return -1;
+}
+
+} // End of namespace Audio
+
+// some debugging functions
+#if 0
+namespace {
+
+void displayMacroStep(const void * const vptr) {
+ static const char *tableMacros[] = {
+ "DMAoff+Resetxx/xx/xx flag/addset/vol ",
+ "DMAon (start sample at selected begin) ",
+ "SetBegin xxxxxx sample-startadress",
+ "SetLen ..xxxx sample-length ",
+ "Wait ..xxxx count (VBI''s) ",
+ "Loop xx/xxxx count/step ",
+ "Cont xx/xxxx macro-number/step ",
+ "-------------STOP----------------------",
+ "AddNote xx/xxxx note/detune ",
+ "SetNote xx/xxxx note/detune ",
+ "Reset Vibrato-Portamento-Envelope ",
+ "Portamento xx/../xx count/speed ",
+ "Vibrato xx/../xx speed/intensity ",
+ "AddVolume ....xx volume 00-3F ",
+ "SetVolume ....xx volume 00-3F ",
+ "Envelope xx/xx/xx speed/count/endvol",
+ "Loop key up xx/xxxx count/step ",
+ "AddBegin xx/xxxx count/add to start",
+ "AddLen ..xxxx add to sample-len ",
+ "DMAoff stop sample but no clear ",
+ "Wait key up ....xx count (VBI''s) ",
+ "Go submacro xx/xxxx macro-number/step ",
+ "--------Return to old macro------------",
+ "Setperiod ..xxxx DMA period ",
+ "Sampleloop ..xxxx relative adress ",
+ "-------Set one shot sample-------------",
+ "Wait on DMA ..xxxx count (Wavecycles)",
+ "Random play xx/xx/xx macro/speed/mode ",
+ "Splitkey xx/xxxx key/macrostep ",
+ "Splitvolume xx/xxxx volume/macrostep ",
+ "Addvol+note xx/fe/xx note/CONST./volume",
+ "SetPrevNote xx/xxxx note/detune ",
+ "Signal xx/xxxx signalnumber/value",
+ "Play macro xx/.x/xx macro/chan/detune ",
+ "SID setbeg xxxxxx sample-startadress",
+ "SID setlen xx/xxxx buflen/sourcelen ",
+ "SID op3 ofs xxxxxx offset ",
+ "SID op3 frq xx/xxxx speed/amplitude ",
+ "SID op2 ofs xxxxxx offset ",
+ "SID op2 frq xx/xxxx speed/amplitude ",
+ "SID op1 xx/xx/xx speed/amplitude/TC",
+ "SID stop xx.... flag (1=clear all)"
+ };
+
+ const byte *const macroData = (const byte * const)vptr;
+ if (macroData[0] < ARRAYSIZE(tableMacros))
+ debug("%s %02X%02X%02X", tableMacros[macroData[0]], macroData[1], macroData[2], macroData[3]);
+ else
+ debug("Unknown Macro #%02X %02X%02X%02X", macroData[0], macroData[1], macroData[2], macroData[3]);
+}
+
+void displayPatternstep(const void * const vptr) {
+ static const char *tablePatterns[] = {
+ "End --Next track step--",
+ "Loop[count / step.w]",
+ "Cont[patternno./ step.w]",
+ "Wait[count 00-FF--------",
+ "Stop--Stop this pattern-",
+ "Kup^-Set key up/channel]",
+ "Vibr[speed / rate.b]",
+ "Enve[speed /endvolume.b]",
+ "GsPt[patternno./ step.w]",
+ "RoPt-Return old pattern-",
+ "Fade[speed /endvolume.b]",
+ "PPat[patt./track+transp]",
+ "Lock---------ch./time.b]",
+ "Cue [number.b/ value.w]",
+ "Stop-Stop custompattern-",
+ "NOP!-no operation-------"
+ };
+
+ const byte * const patData = (const byte * const)vptr;
+ const byte command = patData[0];
+ if (command < 0xF0) { // Playnote
+ const byte flags = command >> 6; // 0-1 means note+detune, 2 means wait, 3 means portamento?
+ const char *flagsSt[] = { "Note ", "Note ", "Wait ", "Porta" };
+ debug("%s %02X%02X%02X%02X", flagsSt[flags], patData[0], patData[1], patData[2], patData[3]);
+ } else
+ debug("%s %02X%02X%02X",tablePatterns[command & 0xF], patData[1], patData[2], patData[3]);
+}
+
+} // End of anonymous namespace
+#endif
+
+#endif // #if defined(SOUND_MODS_TFMX_H)
diff --git a/audio/mods/tfmx.h b/audio/mods/tfmx.h
new file mode 100644
index 0000000000..1930487eb8
--- /dev/null
+++ b/audio/mods/tfmx.h
@@ -0,0 +1,284 @@
+/* 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$
+ *
+ */
+
+// see if all engines using this class are DISABLED
+#if !defined(ENABLE_SCUMM)
+
+// normal Header Guard
+#elif !defined(SOUND_MODS_TFMX_H)
+#define SOUND_MODS_TFMX_H
+
+#include "audio/mods/paula.h"
+
+namespace Audio {
+
+class Tfmx : public Paula {
+public:
+ Tfmx(int rate, bool stereo);
+ virtual ~Tfmx();
+
+ /**
+ * Stops a playing Song (but leaves macros running) and optionally also stops the player
+ *
+ * @param stopAudio stops player and audio output
+ * @param dataSize number of bytes to be written
+ * @return the number of bytes which were actually written.
+ */
+ void stopSong(bool stopAudio = true) { Common::StackLock lock(_mutex); stopSongImpl(stopAudio); }
+ /**
+ * Stops currently playing Song (if any) and cues up a new one.
+ * if stopAudio is specified, the player gets reset before starting the new song
+ *
+ * @param songPos index of Song to play
+ * @param stopAudio stops player and audio output
+ * @param dataSize number of bytes to be written
+ * @return the number of bytes which were actually written.
+ */
+ void doSong(int songPos, bool stopAudio = false);
+ /**
+ * plays an effect from the sfx-table, does not start audio-playback.
+ *
+ * @param sfxIndex index of effect to play
+ * @param unlockChannel overwrite higher priority effects
+ * @return index of the channel which now queued up the effect.
+ * -1 in case the effect couldnt be queued up
+ */
+ int doSfx(uint16 sfxIndex, bool unlockChannel = false);
+ /**
+ * stop a running macro channel
+ *
+ * @param channel index of effect to stop
+ */
+ void stopMacroEffect(int channel);
+
+ void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0);
+ int getTicks() const { return _playerCtx.tickCount; }
+ int getSongIndex() const { return _playerCtx.song; }
+ void setSignalPtr(uint16 *ptr, uint16 numSignals) { _playerCtx.signal = ptr; _playerCtx.numSignals = numSignals; }
+ void freeResources() { _deleteResource = true; freeResourceDataImpl(); }
+ bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete = true);
+ void setModuleData(Tfmx &otherPlayer);
+
+protected:
+ void interrupt();
+
+private:
+ enum { kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8 };
+ enum { kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128 };
+
+ struct MdatResource {
+ const byte *mdatAlloc; ///< allocated Block of Memory
+ const byte *mdatData; ///< Start of mdat-File, might point before mdatAlloc to correct Offset
+ uint32 mdatLen;
+
+ uint16 headerFlags;
+// uint32 headerUnknown;
+// char textField[6 * 40];
+
+ struct Subsong {
+ uint16 songstart; ///< Index in Trackstep-Table
+ uint16 songend; ///< Last index in Trackstep-Table
+ uint16 tempo;
+ } subsong[kNumSubsongs];
+
+ uint32 trackstepOffset; ///< Offset in mdat
+ uint32 sfxTableOffset;
+
+ uint32 patternOffset[kMaxPatternOffsets]; ///< Offset in mdat
+ uint32 macroOffset[kMaxMacroOffsets]; ///< Offset in mdat
+
+ void boundaryCheck(const void *address, size_t accessLen = 1) const {
+ assert(mdatAlloc <= address && (const byte *)address + accessLen <= (const byte *)mdatData + mdatLen);
+ }
+ } const *_resource;
+
+ struct SampleResource {
+ const int8 *sampleData; ///< The whole sample-File
+ uint32 sampleLen;
+
+ void boundaryCheck(const void *address, size_t accessLen = 2) const {
+ assert(sampleData <= address && (const byte *)address + accessLen <= (const byte *)sampleData + sampleLen);
+ }
+ } _resourceSample;
+
+ bool _deleteResource;
+
+ bool hasResources() {
+ return _resource && _resource->mdatLen && _resourceSample.sampleLen;
+ }
+
+ struct ChannelContext {
+ byte paulaChannel;
+
+// byte macroIndex;
+ uint16 macroWait;
+ uint32 macroOffset;
+ uint32 macroReturnOffset;
+ uint16 macroStep;
+ uint16 macroReturnStep;
+ uint8 macroLoopCount;
+ bool macroRun;
+ int8 macroSfxRun; ///< values are the folowing: -1 macro disabled, 0 macro init, 1 macro running
+
+ uint32 customMacro;
+ uint8 customMacroIndex;
+ uint8 customMacroPrio;
+
+ bool sfxLocked;
+ int16 sfxLockTime;
+ bool keyUp;
+
+ bool deferWait;
+ uint16 dmaIntCount;
+
+ uint32 sampleStart;
+ uint16 sampleLen;
+ uint16 refPeriod;
+ uint16 period;
+
+ int8 volume;
+ uint8 relVol;
+ uint8 note;
+ uint8 prevNote;
+ int16 fineTune; // always a signextended byte
+
+ uint8 portaSkip;
+ uint8 portaCount;
+ uint16 portaDelta;
+ uint16 portaValue;
+
+ uint8 envSkip;
+ uint8 envCount;
+ uint8 envDelta;
+ int8 envEndVolume;
+
+ uint8 vibLength;
+ uint8 vibCount;
+ int16 vibValue;
+ int8 vibDelta;
+
+ uint8 addBeginLength;
+ uint8 addBeginCount;
+ int32 addBeginDelta;
+ } _channelCtx[kNumVoices];
+
+ struct PatternContext {
+ uint32 offset; // patternStart, Offset from mdat
+ uint32 savedOffset; // for subroutine calls
+ uint16 step; // distance from patternStart
+ uint16 savedStep;
+
+ uint8 command;
+ int8 expose;
+ uint8 loopCount;
+ uint8 wait; ///< how many ticks to wait before next Command
+ } _patternCtx[kNumChannels];
+
+ struct TrackStepContext {
+ uint16 startInd;
+ uint16 stopInd;
+ uint16 posInd;
+ int16 loopCount;
+ } _trackCtx;
+
+ struct PlayerContext {
+ int8 song; ///< >= 0 if Song is running (means process Patterns)
+
+ uint16 patternCount;
+ uint16 patternSkip; ///< skip that amount of CIA-Interrupts
+
+ int8 volume; ///< Master Volume
+
+ uint8 fadeSkip;
+ uint8 fadeCount;
+ int8 fadeEndVolume;
+ int8 fadeDelta;
+
+ int tickCount;
+
+ uint16 *signal;
+ uint16 numSignals;
+
+ bool stopWithLastPattern; ///< hack to automatically stop the whole player if no Pattern is running
+ } _playerCtx;
+
+ const byte *getSfxPtr(uint16 index = 0) const {
+ const byte *sfxPtr = (const byte *)(_resource->mdatData + _resource->sfxTableOffset + index * 8);
+
+ _resource->boundaryCheck(sfxPtr, 8);
+ return sfxPtr;
+ }
+
+ const uint16 *getTrackPtr(uint16 trackstep = 0) const {
+ const uint16 *trackData = (const uint16 *)(_resource->mdatData + _resource->trackstepOffset + 16 * trackstep);
+
+ _resource->boundaryCheck(trackData, 16);
+ return trackData;
+ }
+
+ const uint32 *getPatternPtr(uint32 offset) const {
+ const uint32 *pattData = (const uint32 *)(_resource->mdatData + offset);
+
+ _resource->boundaryCheck(pattData, 4);
+ return pattData;
+ }
+
+ const uint32 *getMacroPtr(uint32 offset) const {
+ const uint32 *macroData = (const uint32 *)(_resource->mdatData + offset);
+
+ _resource->boundaryCheck(macroData, 4);
+ return macroData;
+ }
+
+ const int8 *getSamplePtr(const uint32 offset) const {
+ const int8 *sample = _resourceSample.sampleData + offset;
+
+ _resourceSample.boundaryCheck(sample, 2);
+ return sample;
+ }
+
+ static inline void initMacroProgramm(ChannelContext &channel);
+ static inline void clearEffects(ChannelContext &channel);
+ static inline void haltMacroProgramm(ChannelContext &channel);
+ static inline void unlockMacroChannel(ChannelContext &channel);
+ static inline void initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset);
+ void stopSongImpl(bool stopAudio = true);
+ static inline void setNoteMacro(ChannelContext &channel, uint note, int fineTune);
+ void initFadeCommand(const uint8 fadeTempo, const int8 endVol);
+ void setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete = true);
+ static const MdatResource *loadMdatFile(Common::SeekableReadStream &musicData);
+ static const int8 *loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream);
+ void freeResourceDataImpl();
+ void effects(ChannelContext &channel);
+ void macroRun(ChannelContext &channel);
+ void advancePatterns();
+ bool patternRun(PatternContext &pattern);
+ bool trackRun(bool incStep = false);
+ void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3);
+};
+
+} // End of namespace Audio
+
+#endif // !defined(SOUND_MODS_TFMX_H)
diff --git a/audio/module.mk b/audio/module.mk
new file mode 100644
index 0000000000..5b93c80d57
--- /dev/null
+++ b/audio/module.mk
@@ -0,0 +1,61 @@
+MODULE := audio
+
+MODULE_OBJS := \
+ audiostream.o \
+ fmopl.o \
+ mididrv.o \
+ midiparser_smf.o \
+ midiparser_xmidi.o \
+ midiparser.o \
+ mixer.o \
+ mpu401.o \
+ musicplugin.o \
+ null.o \
+ timestamp.o \
+ decoders/adpcm.o \
+ decoders/aiff.o \
+ decoders/flac.o \
+ decoders/iff_sound.o \
+ decoders/mac_snd.o \
+ decoders/mp3.o \
+ decoders/raw.o \
+ decoders/vag.o \
+ decoders/voc.o \
+ decoders/vorbis.o \
+ decoders/wave.o \
+ mods/infogrames.o \
+ mods/maxtrax.o \
+ mods/module.o \
+ mods/protracker.o \
+ mods/paula.o \
+ mods/rjp1.o \
+ mods/soundfx.o \
+ mods/tfmx.o \
+ softsynth/adlib.o \
+ softsynth/cms.o \
+ softsynth/opl/dbopl.o \
+ softsynth/opl/dosbox.o \
+ softsynth/opl/mame.o \
+ softsynth/fmtowns_pc98/towns_audio.o \
+ softsynth/fmtowns_pc98/towns_euphony.o \
+ softsynth/fmtowns_pc98/towns_pc98_driver.o \
+ softsynth/fmtowns_pc98/towns_pc98_fmsynth.o \
+ softsynth/appleiigs.o \
+ softsynth/ym2612.o \
+ softsynth/fluidsynth.o \
+ softsynth/mt32.o \
+ softsynth/pcspk.o \
+ softsynth/sid.o \
+ softsynth/wave6581.o
+
+ifndef USE_ARM_SOUND_ASM
+MODULE_OBJS += \
+ rate.o
+else
+MODULE_OBJS += \
+ rate_arm.o \
+ rate_arm_asm.o
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/audio/mpu401.cpp b/audio/mpu401.cpp
new file mode 100644
index 0000000000..4f62de930c
--- /dev/null
+++ b/audio/mpu401.cpp
@@ -0,0 +1,145 @@
+/* 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 "audio/mpu401.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include "common/util.h" // for ARRAYSIZE
+
+void MidiChannel_MPU401::init(MidiDriver *owner, byte channel) {
+ _owner = owner;
+ _channel = channel;
+ _allocated = false;
+}
+
+bool MidiChannel_MPU401::allocate() {
+ if (_allocated)
+ return false;
+ return (_allocated = true);
+}
+
+MidiDriver *MidiChannel_MPU401::device() {
+ return _owner;
+}
+
+void MidiChannel_MPU401::send(uint32 b) {
+ _owner->send((b & 0xFFFFFFF0) | (_channel & 0xF));
+}
+
+void MidiChannel_MPU401::noteOff(byte note) {
+ _owner->send(note << 8 | 0x80 | _channel);
+}
+
+void MidiChannel_MPU401::noteOn(byte note, byte velocity) {
+ _owner->send(velocity << 16 | note << 8 | 0x90 | _channel);
+}
+
+void MidiChannel_MPU401::programChange(byte program) {
+ _owner->send(program << 8 | 0xC0 | _channel);
+}
+
+void MidiChannel_MPU401::pitchBend(int16 bend) {
+ _owner->send((((bend + 0x2000) >> 7) & 0x7F) << 16 | ((bend + 0x2000) & 0x7F) << 8 | 0xE0 | _channel);
+}
+
+void MidiChannel_MPU401::controlChange(byte control, byte value) {
+ _owner->send(value << 16 | control << 8 | 0xB0 | _channel);
+}
+
+void MidiChannel_MPU401::pitchBendFactor(byte value) {
+ _owner->setPitchBendRange(_channel, value);
+}
+
+void MidiChannel_MPU401::sysEx_customInstrument(uint32 type, const byte *instr) {
+ _owner->sysEx_customInstrument(_channel, type, instr);
+}
+
+const char *MidiDriver::getErrorName(int error_code) {
+ static const char *const midi_errors[] = {
+ "No error",
+ "Cannot connect",
+ "Streaming not available",
+ "Device not available",
+ "Driver already open"
+ };
+
+ if ((uint)error_code >= ARRAYSIZE(midi_errors))
+ return "Unknown Error";
+ return midi_errors[error_code];
+}
+
+MidiDriver_MPU401::MidiDriver_MPU401() :
+ MidiDriver(),
+ _timer_proc(0),
+ _channel_mask(0xFFFF) // Permit all 16 channels by default
+{
+
+ uint i;
+ for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) {
+ _midi_channels[i].init(this, i);
+ }
+}
+
+void MidiDriver_MPU401::close() {
+ if (_timer_proc)
+ g_system->getTimerManager()->removeTimerProc(_timer_proc);
+ _timer_proc = 0;
+ for (int i = 0; i < 16; ++i)
+ send(0x7B << 8 | 0xB0 | i);
+}
+
+uint32 MidiDriver_MPU401::property(int prop, uint32 param) {
+ switch (prop) {
+ case PROP_CHANNEL_MASK:
+ _channel_mask = param & 0xFFFF;
+ return 1;
+ }
+
+ return 0;
+}
+
+MidiChannel *MidiDriver_MPU401::allocateChannel() {
+ MidiChannel_MPU401 *chan;
+ uint i;
+
+ for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) {
+ if (i == 9 || !(_channel_mask & (1 << i)))
+ continue;
+ chan = &_midi_channels[i];
+ if (chan->allocate()) {
+ return chan;
+ }
+ }
+ return NULL;
+}
+
+void MidiDriver_MPU401::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ if (!_timer_proc || !timer_proc) {
+ if (_timer_proc)
+ g_system->getTimerManager()->removeTimerProc(_timer_proc);
+ _timer_proc = timer_proc;
+ if (timer_proc)
+ g_system->getTimerManager()->installTimerProc(timer_proc, 10000, timer_param);
+ }
+}
diff --git a/audio/mpu401.h b/audio/mpu401.h
new file mode 100644
index 0000000000..070eaf636a
--- /dev/null
+++ b/audio/mpu401.h
@@ -0,0 +1,92 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_MPU401_H
+#define SOUND_MPU401_H
+
+#include "audio/mididrv.h"
+
+////////////////////////////////////////
+//
+// Common MPU401 implementation methods
+//
+////////////////////////////////////////
+
+class MidiDriver_MPU401;
+
+class MidiChannel_MPU401 : public MidiChannel {
+
+private:
+ MidiDriver *_owner;
+ bool _allocated;
+ byte _channel;
+
+public:
+ MidiDriver *device();
+ byte getNumber() { return _channel; }
+ void release() { _allocated = false; }
+
+ void send(uint32 b);
+
+ // Regular messages
+ void noteOff(byte note);
+ void noteOn(byte note, byte velocity);
+ void programChange(byte program);
+ void pitchBend(int16 bend);
+
+ // Control Change messages
+ void controlChange(byte control, byte value);
+ void pitchBendFactor(byte value);
+
+ // SysEx messages
+ void sysEx_customInstrument(uint32 type, const byte *instr);
+
+ // Only to be called by the owner
+ void init(MidiDriver *owner, byte channel);
+ bool allocate();
+};
+
+
+
+class MidiDriver_MPU401 : public MidiDriver {
+private:
+ MidiChannel_MPU401 _midi_channels[16];
+ Common::TimerManager::TimerProc _timer_proc;
+ uint16 _channel_mask;
+
+public:
+ MidiDriver_MPU401();
+
+ virtual void close();
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
+ uint32 getBaseTempo(void) { return 10000; }
+ uint32 property(int prop, uint32 param);
+
+ MidiChannel *allocateChannel();
+ MidiChannel *getPercussionChannel() { return &_midi_channels[9]; }
+};
+
+
+#endif
diff --git a/audio/musicplugin.cpp b/audio/musicplugin.cpp
new file mode 100644
index 0000000000..eb28d2f4c9
--- /dev/null
+++ b/audio/musicplugin.cpp
@@ -0,0 +1,64 @@
+/* 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 "audio/musicplugin.h"
+#include "common/hash-str.h"
+#include "common/translation.h"
+
+MusicDevice::MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt) :
+ _musicDriverName(_(musicPlugin->getName())), _musicDriverId(musicPlugin->getId()),
+ _name(_(name)), _type(mt) {
+}
+
+Common::String MusicDevice::getCompleteName() {
+ Common::String name;
+
+ if (_name.empty()) {
+ // Default device, just show the driver name
+ name = _musicDriverName;
+ } else {
+ // Show both device and driver names
+ name = _name;
+ name += " [";
+ name += _musicDriverName;
+ name += "]";
+ }
+
+ return name;
+}
+
+Common::String MusicDevice::getCompleteId() {
+ Common::String id = _musicDriverId;
+ if (!_name.empty()) {
+ id += "_";
+ id += _name;
+ }
+
+ return id;
+}
+
+MidiDriver::DeviceHandle MusicDevice::getHandle() {
+ return (MidiDriver::DeviceHandle)Common::hashit(getCompleteId().c_str());
+}
diff --git a/audio/musicplugin.h b/audio/musicplugin.h
new file mode 100644
index 0000000000..da0e0ff816
--- /dev/null
+++ b/audio/musicplugin.h
@@ -0,0 +1,125 @@
+/* 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$
+ */
+
+#ifndef SOUND_MUSICPLUGIN_H
+#define SOUND_MUSICPLUGIN_H
+
+#include "base/plugins.h"
+#include "audio/mididrv.h"
+#include "common/list.h"
+
+class MusicPluginObject;
+
+/**
+ * Description of a Music device. Used to list the devices a Music driver
+ * can manage and their capabilities.
+ * A device with an empty name means the default device.
+ */
+class MusicDevice {
+public:
+ MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt);
+
+ Common::String &getName() { return _name; }
+ Common::String &getMusicDriverName() { return _musicDriverName; }
+ Common::String &getMusicDriverId() { return _musicDriverId; }
+ MusicType getMusicType() { return _type; }
+
+ /**
+ * Returns a user readable string that contains the name of the current
+ * device name (if it isn't the default one) and the name of the driver.
+ */
+ Common::String getCompleteName();
+
+ /**
+ * Returns a user readable string that contains the name of the current
+ * device name (if it isn't the default one) and the id of the driver.
+ */
+ Common::String getCompleteId();
+
+ MidiDriver::DeviceHandle getHandle();
+
+private:
+ Common::String _name;
+ Common::String _musicDriverName;
+ Common::String _musicDriverId;
+ MusicType _type;
+};
+
+/** List of music devices. */
+typedef Common::List<MusicDevice> MusicDevices;
+
+/**
+ * A MusicPluginObject is essentially a factory for MidiDriver instances with
+ * the added ability of listing the available devices and their capabilities.
+ */
+class MusicPluginObject : public PluginObject {
+public:
+ virtual ~MusicPluginObject() {}
+
+ /**
+ * Returns a unique string identifier which will be used to save the
+ * selected MIDI driver to the config file.
+ */
+ virtual const char *getId() const = 0;
+
+ /**
+ * Returns a list of the available devices.
+ */
+ virtual MusicDevices getDevices() const = 0;
+
+ /**
+ * Tries to instantiate a MIDI Driver instance based on the device
+ * previously detected via MidiDriver::detectDevice()
+ *
+ * @param mididriver Pointer to a pointer which the MusicPluginObject sets
+ * to the newly create MidiDriver, or 0 in case of an error
+ *
+ * @param dev Pointer to a device to be used then creating the driver instance.
+ * Default value of zero for driver types without devices.
+ *
+ * @return a Common::Error describing the error which occurred, or kNoError
+ */
+ virtual Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const = 0;
+};
+
+
+// Music plugins
+
+typedef PluginSubclass<MusicPluginObject> MusicPlugin;
+
+/**
+ * Singleton class which manages all Music plugins.
+ */
+class MusicManager : public Common::Singleton<MusicManager> {
+private:
+ friend class Common::Singleton<SingletonBaseType>;
+
+public:
+ const MusicPlugin::List &getPlugins() const;
+};
+
+/** Convenience shortcut for accessing the Music manager. */
+#define MusicMan MusicManager::instance()
+
+#endif
diff --git a/audio/null.cpp b/audio/null.cpp
new file mode 100644
index 0000000000..152f5da03e
--- /dev/null
+++ b/audio/null.cpp
@@ -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 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 "audio/null.h"
+
+Common::Error NullMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
+ *mididriver = new MidiDriver_NULL();
+
+ return Common::kNoError;
+}
+
+MusicDevices NullMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_NULL));
+ return devices;
+}
+
+class AutoMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("<default>");
+ }
+
+ const char *getId() const {
+ return "auto";
+ }
+ MusicDevices getDevices() const;
+};
+
+MusicDevices AutoMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_AUTO));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(NULL)
+ //REGISTER_PLUGIN_DYNAMIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(AUTO, PLUGIN_TYPE_MUSIC, AutoMusicPlugin);
+ REGISTER_PLUGIN_STATIC(NULL, PLUGIN_TYPE_MUSIC, NullMusicPlugin);
+//#endif
diff --git a/audio/null.h b/audio/null.h
new file mode 100644
index 0000000000..5df7493a06
--- /dev/null
+++ b/audio/null.h
@@ -0,0 +1,56 @@
+/* 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$
+ */
+
+#ifndef SOUND_NULL_H
+#define SOUND_NULL_H
+
+#include "audio/musicplugin.h"
+#include "audio/mpu401.h"
+#include "common/translation.h"
+
+/* NULL driver */
+class MidiDriver_NULL : public MidiDriver_MPU401 {
+public:
+ int open() { return 0; }
+ void send(uint32 b) { }
+};
+
+
+// Plugin interface
+
+class NullMusicPlugin : public MusicPluginObject {
+public:
+ virtual const char *getName() const {
+ return _s("No music");
+ }
+
+ virtual const char *getId() const {
+ return "null";
+ }
+
+ virtual MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+#endif
diff --git a/audio/rate.cpp b/audio/rate.cpp
new file mode 100644
index 0000000000..1fcf0ade2e
--- /dev/null
+++ b/audio/rate.cpp
@@ -0,0 +1,358 @@
+/* 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$
+ *
+ */
+
+/*
+ * The code in this file is based on code with Copyright 1998 Fabrice Bellard
+ * Fabrice original code is part of SoX (http://sox.sourceforge.net).
+ * Max Horn adapted that code to the needs of ScummVM and rewrote it partial,
+ * in the process removing any use of floating point arithmetic. Various other
+ * improvements over the original code were made.
+ */
+
+#include "audio/audiostream.h"
+#include "audio/rate.h"
+#include "audio/mixer.h"
+#include "common/frac.h"
+#include "common/util.h"
+
+namespace Audio {
+
+
+/**
+ * The size of the intermediate input cache. Bigger values may increase
+ * performance, but only until some point (depends largely on cache size,
+ * target processor and various other factors), at which it will decrease
+ * again.
+ */
+#define INTERMEDIATE_BUFFER_SIZE 512
+
+
+/**
+ * Audio rate converter based on simple resampling. Used when no
+ * interpolation is required.
+ *
+ * Limited to sampling frequency <= 65535 Hz.
+ */
+template<bool stereo, bool reverseStereo>
+class SimpleRateConverter : public RateConverter {
+protected:
+ st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
+ const st_sample_t *inPtr;
+ int inLen;
+
+ /** position of how far output is ahead of input */
+ /** Holds what would have been opos-ipos */
+ long opos;
+
+ /** fractional position increment in the output stream */
+ long opos_inc;
+
+public:
+ SimpleRateConverter(st_rate_t inrate, st_rate_t outrate);
+ int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
+ int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return ST_SUCCESS;
+ }
+};
+
+
+/*
+ * Prepare processing.
+ */
+template<bool stereo, bool reverseStereo>
+SimpleRateConverter<stereo, reverseStereo>::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) {
+ if ((inrate % outrate) != 0) {
+ error("Input rate must be a multiple of output rate to use rate effect");
+ }
+
+ if (inrate >= 65536 || outrate >= 65536) {
+ error("rate effect can only handle rates < 65536");
+ }
+
+ opos = 1;
+
+ /* increment */
+ opos_inc = inrate / outrate;
+
+ inLen = 0;
+}
+
+/*
+ * Processed signed long samples from ibuf to obuf.
+ * Return number of sample pairs processed.
+ */
+template<bool stereo, bool reverseStereo>
+int SimpleRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+ st_sample_t *ostart, *oend;
+
+ ostart = obuf;
+ oend = obuf + osamp * 2;
+
+ while (obuf < oend) {
+
+ // read enough input samples so that opos >= 0
+ do {
+ // Check if we have to refill the buffer
+ if (inLen == 0) {
+ inPtr = inBuf;
+ inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf));
+ if (inLen <= 0)
+ return (obuf - ostart) / 2;
+ }
+ inLen -= (stereo ? 2 : 1);
+ opos--;
+ if (opos >= 0) {
+ inPtr += (stereo ? 2 : 1);
+ }
+ } while (opos >= 0);
+
+ st_sample_t out0, out1;
+ out0 = *inPtr++;
+ out1 = (stereo ? *inPtr++ : out0);
+
+ // Increment output position
+ opos += opos_inc;
+
+ // output left channel
+ clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
+
+ // output right channel
+ clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
+
+ obuf += 2;
+ }
+ return (obuf - ostart) / 2;
+}
+
+/**
+ * Audio rate converter based on simple linear Interpolation.
+ *
+ * The use of fractional increment allows us to use no buffer. It
+ * avoid the problems at the end of the buffer we had with the old
+ * method which stored a possibly big buffer of size
+ * lcm(in_rate,out_rate).
+ *
+ * Limited to sampling frequency <= 65535 Hz.
+ */
+
+template<bool stereo, bool reverseStereo>
+class LinearRateConverter : public RateConverter {
+protected:
+ st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
+ const st_sample_t *inPtr;
+ int inLen;
+
+ /** fractional position of the output stream in input stream unit */
+ frac_t opos;
+
+ /** fractional position increment in the output stream */
+ frac_t opos_inc;
+
+ /** last sample(s) in the input stream (left/right channel) */
+ st_sample_t ilast0, ilast1;
+ /** current sample(s) in the input stream (left/right channel) */
+ st_sample_t icur0, icur1;
+
+public:
+ LinearRateConverter(st_rate_t inrate, st_rate_t outrate);
+ int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
+ int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return ST_SUCCESS;
+ }
+};
+
+
+/*
+ * Prepare processing.
+ */
+template<bool stereo, bool reverseStereo>
+LinearRateConverter<stereo, reverseStereo>::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) {
+ if (inrate >= 65536 || outrate >= 65536) {
+ error("rate effect can only handle rates < 65536");
+ }
+
+ opos = FRAC_ONE;
+
+ // Compute the linear interpolation increment.
+ // This will overflow if inrate >= 2^16, and underflow if outrate >= 2^16.
+ // Also, if the quotient of the two rate becomes too small / too big, that
+ // would cause problems, but since we rarely scale from 1 to 65536 Hz or vice
+ // versa, I think we can live with that limitation ;-).
+ opos_inc = (inrate << FRAC_BITS) / outrate;
+
+ ilast0 = ilast1 = 0;
+ icur0 = icur1 = 0;
+
+ inLen = 0;
+}
+
+/*
+ * Processed signed long samples from ibuf to obuf.
+ * Return number of sample pairs processed.
+ */
+template<bool stereo, bool reverseStereo>
+int LinearRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+ st_sample_t *ostart, *oend;
+
+ ostart = obuf;
+ oend = obuf + osamp * 2;
+
+ while (obuf < oend) {
+
+ // read enough input samples so that opos < 0
+ while ((frac_t)FRAC_ONE <= opos) {
+ // Check if we have to refill the buffer
+ if (inLen == 0) {
+ inPtr = inBuf;
+ inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf));
+ if (inLen <= 0)
+ return (obuf - ostart) / 2;
+ }
+ inLen -= (stereo ? 2 : 1);
+ ilast0 = icur0;
+ icur0 = *inPtr++;
+ if (stereo) {
+ ilast1 = icur1;
+ icur1 = *inPtr++;
+ }
+ opos -= FRAC_ONE;
+ }
+
+ // Loop as long as the outpos trails behind, and as long as there is
+ // still space in the output buffer.
+ while (opos < (frac_t)FRAC_ONE && obuf < oend) {
+ // interpolate
+ st_sample_t out0, out1;
+ out0 = (st_sample_t)(ilast0 + (((icur0 - ilast0) * opos + FRAC_HALF) >> FRAC_BITS));
+ out1 = (stereo ?
+ (st_sample_t)(ilast1 + (((icur1 - ilast1) * opos + FRAC_HALF) >> FRAC_BITS)) :
+ out0);
+
+ // output left channel
+ clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
+
+ // output right channel
+ clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
+
+ obuf += 2;
+
+ // Increment output position
+ opos += opos_inc;
+ }
+ }
+ return (obuf - ostart) / 2;
+}
+
+
+#pragma mark -
+
+
+/**
+ * Simple audio rate converter for the case that the inrate equals the outrate.
+ */
+template<bool stereo, bool reverseStereo>
+class CopyRateConverter : public RateConverter {
+ st_sample_t *_buffer;
+ st_size_t _bufferSize;
+public:
+ CopyRateConverter() : _buffer(0), _bufferSize(0) {}
+ ~CopyRateConverter() {
+ free(_buffer);
+ }
+
+ virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+ assert(input.isStereo() == stereo);
+
+ st_sample_t *ptr;
+ st_size_t len;
+
+ st_sample_t *ostart = obuf;
+
+ if (stereo)
+ osamp *= 2;
+
+ // Reallocate temp buffer, if necessary
+ if (osamp > _bufferSize) {
+ free(_buffer);
+ _buffer = (st_sample_t *)malloc(osamp * 2);
+ _bufferSize = osamp;
+ }
+
+ // Read up to 'osamp' samples into our temporary buffer
+ len = input.readBuffer(_buffer, osamp);
+
+ // Mix the data into the output buffer
+ ptr = _buffer;
+ for (; len > 0; len -= (stereo ? 2 : 1)) {
+ st_sample_t out0, out1;
+ out0 = *ptr++;
+ out1 = (stereo ? *ptr++ : out0);
+
+ // output left channel
+ clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
+
+ // output right channel
+ clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
+
+ obuf += 2;
+ }
+ return (obuf - ostart) / 2;
+ }
+
+ virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return ST_SUCCESS;
+ }
+};
+
+
+#pragma mark -
+
+template<bool stereo, bool reverseStereo>
+RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate) {
+ if (inrate != outrate) {
+ if ((inrate % outrate) == 0) {
+ return new SimpleRateConverter<stereo, reverseStereo>(inrate, outrate);
+ } else {
+ return new LinearRateConverter<stereo, reverseStereo>(inrate, outrate);
+ }
+ } else {
+ return new CopyRateConverter<stereo, reverseStereo>();
+ }
+}
+
+/**
+ * Create and return a RateConverter object for the specified input and output rates.
+ */
+RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) {
+ if (stereo) {
+ if (reverseStereo)
+ return makeRateConverter<true, true>(inrate, outrate);
+ else
+ return makeRateConverter<true, false>(inrate, outrate);
+ } else
+ return makeRateConverter<false, false>(inrate, outrate);
+}
+
+} // End of namespace Audio
diff --git a/audio/rate.h b/audio/rate.h
new file mode 100644
index 0000000000..fb231e4c4a
--- /dev/null
+++ b/audio/rate.h
@@ -0,0 +1,90 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_RATE_H
+#define SOUND_RATE_H
+
+#include "common/scummsys.h"
+#include "engines/engine.h"
+
+class AudioStream;
+
+
+namespace Audio {
+
+typedef int16 st_sample_t;
+typedef uint16 st_volume_t;
+typedef uint32 st_size_t;
+typedef uint32 st_rate_t;
+
+/* Minimum and maximum values a sample can hold. */
+enum {
+ ST_SAMPLE_MAX = 0x7fffL,
+ ST_SAMPLE_MIN = (-ST_SAMPLE_MAX - 1L)
+};
+
+enum {
+ ST_EOF = -1,
+ ST_SUCCESS = 0
+};
+
+static inline void clampedAdd(int16& a, int b) {
+ register int val;
+#ifdef OUTPUT_UNSIGNED_AUDIO
+ val = (a ^ 0x8000) + b;
+#else
+ val = a + b;
+#endif
+
+ if (val > ST_SAMPLE_MAX)
+ val = ST_SAMPLE_MAX;
+ else if (val < ST_SAMPLE_MIN)
+ val = ST_SAMPLE_MIN;
+
+#ifdef OUTPUT_UNSIGNED_AUDIO
+ a = ((int16)val) ^ 0x8000;
+#else
+ a = val;
+#endif
+}
+
+class RateConverter {
+public:
+ RateConverter() {}
+ virtual ~RateConverter() {}
+
+ /**
+ * @return Number of sample pairs written into the buffer.
+ */
+ virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) = 0;
+
+ virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) = 0;
+};
+
+RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo = false);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/rate_arm.cpp b/audio/rate_arm.cpp
new file mode 100644
index 0000000000..41944ef698
--- /dev/null
+++ b/audio/rate_arm.cpp
@@ -0,0 +1,465 @@
+/* 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$
+ *
+ */
+
+/*
+ * The code in this file, together with the rate_arm_asm.s file offers
+ * an ARM optimised version of the code in rate.cpp. The operation of this
+ * code should be identical to that of rate.cpp, but faster. The heavy
+ * lifting is done in the assembler file.
+ *
+ * To be as portable as possible we implement the core routines with C
+ * linkage in assembly, and implement the C++ routines that call into
+ * the C here. The C++ symbol mangling varies wildly between compilers,
+ * so this is the simplest way to ensure that the C/C++ combination should
+ * work on as many ARM based platforms as possible.
+ *
+ * Essentially the algorithm herein is the same as that in rate.cpp, so
+ * anyone seeking to understand this should attempt to understand that
+ * first. That code was based in turn on code with Copyright 1998 Fabrice
+ * Bellard - part of SoX (http://sox.sourceforge.net).
+ * Max Horn adapted that code to the needs of ScummVM and partially rewrote
+ * it, in the process removing any use of floating point arithmetic. Various
+ * other improvments over the original code were made.
+ */
+
+#include "audio/audiostream.h"
+#include "audio/rate.h"
+#include "audio/mixer.h"
+#include "common/util.h"
+
+//#define DEBUG_RATECONV
+
+namespace Audio {
+
+/**
+ * The precision of the fractional computations used by the rate converter.
+ * Normally you should never have to modify this value.
+ * This stuff is defined in common/frac.h, but we redefine it here as the
+ * ARM routine we call doesn't respect those definitions.
+ */
+#define FRAC_BITS 16
+#define FRAC_ONE (1<<FRAC_BITS)
+
+/**
+ * The size of the intermediate input cache. Bigger values may increase
+ * performance, but only until some point (depends largely on cache size,
+ * target processor and various other factors), at which it will decrease
+ * again.
+ */
+#define INTERMEDIATE_BUFFER_SIZE 512
+
+
+/**
+ * Audio rate converter based on simple resampling. Used when no
+ * interpolation is required.
+ *
+ * Limited to sampling frequency <= 65535 Hz.
+ */
+typedef struct {
+ const st_sample_t *inPtr;
+ int inLen;
+
+ /** position of how far output is ahead of input */
+ /** Holds what would have been opos-ipos */
+ long opos;
+
+ /** fractional position increment in the output stream */
+ long opos_inc;
+
+ st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
+} SimpleRateDetails;
+
+template<bool stereo, bool reverseStereo>
+class SimpleRateConverter : public RateConverter {
+protected:
+ SimpleRateDetails sr;
+public:
+ SimpleRateConverter(st_rate_t inrate, st_rate_t outrate);
+ int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
+ int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return (ST_SUCCESS);
+ }
+};
+
+
+/*
+ * Prepare processing.
+ */
+template<bool stereo, bool reverseStereo>
+SimpleRateConverter<stereo, reverseStereo>::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) {
+ if (inrate == outrate) {
+ error("Input and Output rates must be different to use rate effect");
+ }
+
+ if ((inrate % outrate) != 0) {
+ error("Input rate must be a multiple of Output rate to use rate effect");
+ }
+
+ if (inrate >= 65536 || outrate >= 65536) {
+ error("rate effect can only handle rates < 65536");
+ }
+
+ sr.opos = 1;
+
+ /* increment */
+ sr.opos_inc = inrate / outrate;
+
+ sr.inLen = 0;
+}
+
+extern "C" {
+#ifndef IPHONE
+#define ARM_SimpleRate_M _ARM_SimpleRate_M
+#define ARM_SimpleRate_S _ARM_SimpleRate_S
+#define ARM_SimpleRate_R _ARM_SimpleRate_R
+#endif
+}
+
+extern "C" st_sample_t *ARM_SimpleRate_M(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ SimpleRateDetails *sr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+extern "C" st_sample_t *ARM_SimpleRate_S(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ SimpleRateDetails *sr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+extern "C" st_sample_t *ARM_SimpleRate_R(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ SimpleRateDetails *sr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+extern "C" int SimpleRate_readFudge(Audio::AudioStream &input,
+ int16 *a, int b)
+{
+#ifdef DEBUG_RATECONV
+ debug("Reading ptr=%x n%d", a, b);
+#endif
+ return input.readBuffer(a, b);
+}
+
+template<bool stereo, bool reverseStereo>
+int SimpleRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+
+#ifdef DEBUG_RATECONV
+ debug("Simple st=%d rev=%d", stereo, reverseStereo);
+#endif
+ st_sample_t *ostart = obuf;
+
+ if (!stereo) {
+ obuf = ARM_SimpleRate_M(input,
+ &SimpleRate_readFudge,
+ &sr,
+ obuf, osamp, vol_l, vol_r);
+ } else if (reverseStereo) {
+ obuf = ARM_SimpleRate_R(input,
+ &SimpleRate_readFudge,
+ &sr,
+ obuf, osamp, vol_l, vol_r);
+ } else {
+ obuf = ARM_SimpleRate_S(input,
+ &SimpleRate_readFudge,
+ &sr,
+ obuf, osamp, vol_l, vol_r);
+ }
+ return (obuf-ostart)/2;
+}
+
+/**
+ * Audio rate converter based on simple linear Interpolation.
+ *
+ * The use of fractional increment allows us to use no buffer. It
+ * avoid the problems at the end of the buffer we had with the old
+ * method which stored a possibly big buffer of size
+ * lcm(in_rate,out_rate).
+ *
+ * Limited to sampling frequency <= 65535 Hz.
+ */
+
+typedef struct {
+ const st_sample_t *inPtr;
+ int inLen;
+
+ /** position of how far output is ahead of input */
+ /** Holds what would have been opos-ipos<<16 + opos_frac */
+ long opos;
+
+ /** integer position increment in the output stream */
+ long opos_inc;
+
+ /** current sample(s) in the input stream (left/right channel) */
+ st_sample_t icur[2];
+ /** last sample(s) in the input stream (left/right channel) */
+ /** Note, these are deliberately ints, not st_sample_t's */
+ int32 ilast[2];
+
+ st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
+} LinearRateDetails;
+
+extern "C" {
+#ifndef IPHONE
+#define ARM_LinearRate_M _ARM_LinearRate_M
+#define ARM_LinearRate_S _ARM_LinearRate_S
+#define ARM_LinearRate_R _ARM_LinearRate_R
+#endif
+}
+
+extern "C" st_sample_t *ARM_LinearRate_M(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ LinearRateDetails *lr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+extern "C" st_sample_t *ARM_LinearRate_S(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ LinearRateDetails *lr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+extern "C" st_sample_t *ARM_LinearRate_R(
+ AudioStream &input,
+ int (*fn)(Audio::AudioStream&,int16*,int),
+ LinearRateDetails *lr,
+ st_sample_t *obuf,
+ st_size_t osamp,
+ st_volume_t vol_l,
+ st_volume_t vol_r);
+
+template<bool stereo, bool reverseStereo>
+class LinearRateConverter : public RateConverter {
+protected:
+ LinearRateDetails lr;
+
+public:
+ LinearRateConverter(st_rate_t inrate, st_rate_t outrate);
+ int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
+ int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return (ST_SUCCESS);
+ }
+};
+
+
+/*
+ * Prepare processing.
+ */
+template<bool stereo, bool reverseStereo>
+LinearRateConverter<stereo, reverseStereo>::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) {
+ unsigned long incr;
+
+ if (inrate == outrate) {
+ error("Input and Output rates must be different to use rate effect");
+ }
+
+ if (inrate >= 65536 || outrate >= 65536) {
+ error("rate effect can only handle rates < 65536");
+ }
+
+ lr.opos = FRAC_ONE;
+
+ /* increment */
+ incr = (inrate << FRAC_BITS) / outrate;
+
+ lr.opos_inc = incr;
+
+ lr.ilast[0] = lr.ilast[1] = 32768;
+ lr.icur[0] = lr.icur[1] = 0;
+
+ lr.inLen = 0;
+}
+
+/*
+ * Processed signed long samples from ibuf to obuf.
+ * Return number of sample pairs processed.
+ */
+template<bool stereo, bool reverseStereo>
+int LinearRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+
+#ifdef DEBUG_RATECONV
+ debug("Linear st=%d rev=%d", stereo, reverseStereo);
+#endif
+ st_sample_t *ostart = obuf;
+
+ if (!stereo) {
+ obuf = ARM_LinearRate_M(input,
+ &SimpleRate_readFudge,
+ &lr,
+ obuf, osamp, vol_l, vol_r);
+ } else if (reverseStereo) {
+ obuf = ARM_LinearRate_R(input,
+ &SimpleRate_readFudge,
+ &lr,
+ obuf, osamp, vol_l, vol_r);
+ } else {
+ obuf = ARM_LinearRate_S(input,
+ &SimpleRate_readFudge,
+ &lr,
+ obuf, osamp, vol_l, vol_r);
+ }
+ return (obuf-ostart)/2;
+}
+
+
+#pragma mark -
+
+
+/**
+ * Simple audio rate converter for the case that the inrate equals the outrate.
+ */
+extern "C" {
+#ifndef IPHONE
+#define ARM_CopyRate_M _ARM_CopyRate_M
+#define ARM_CopyRate_S _ARM_CopyRate_S
+#define ARM_CopyRate_R _ARM_CopyRate_R
+#endif
+}
+
+extern "C" st_sample_t *ARM_CopyRate_M(
+ st_size_t len,
+ st_sample_t *obuf,
+ st_volume_t vol_l,
+ st_volume_t vol_r,
+ st_sample_t *_buffer);
+
+extern "C" st_sample_t *ARM_CopyRate_S(
+ st_size_t len,
+ st_sample_t *obuf,
+ st_volume_t vol_l,
+ st_volume_t vol_r,
+ st_sample_t *_buffer);
+
+extern "C" st_sample_t *ARM_CopyRate_R(
+ st_size_t len,
+ st_sample_t *obuf,
+ st_volume_t vol_l,
+ st_volume_t vol_r,
+ st_sample_t *_buffer);
+
+
+template<bool stereo, bool reverseStereo>
+class CopyRateConverter : public RateConverter {
+ st_sample_t *_buffer;
+ st_size_t _bufferSize;
+public:
+ CopyRateConverter() : _buffer(0), _bufferSize(0) {}
+ ~CopyRateConverter() {
+ free(_buffer);
+ }
+
+ virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
+ assert(input.isStereo() == stereo);
+
+#ifdef DEBUG_RATECONV
+ debug("Copy st=%d rev=%d", stereo, reverseStereo);
+#endif
+ st_size_t len;
+ st_sample_t *ostart = obuf;
+
+ if (stereo)
+ osamp *= 2;
+
+ // Reallocate temp buffer, if necessary
+ if (osamp > _bufferSize) {
+ free(_buffer);
+ _buffer = (st_sample_t *)malloc(osamp * 2);
+ _bufferSize = osamp;
+ }
+
+ // Read up to 'osamp' samples into our temporary buffer
+ len = input.readBuffer(_buffer, osamp);
+ if (len <= 0)
+ return 0;
+
+ // Mix the data into the output buffer
+ if (stereo && reverseStereo)
+ obuf = ARM_CopyRate_R(len, obuf, vol_l, vol_r, _buffer);
+ else if (stereo)
+ obuf = ARM_CopyRate_S(len, obuf, vol_l, vol_r, _buffer);
+ else
+ obuf = ARM_CopyRate_M(len, obuf, vol_l, vol_r, _buffer);
+
+ return (obuf-ostart)/2;
+ }
+ virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
+ return (ST_SUCCESS);
+ }
+};
+
+
+#pragma mark -
+
+
+/**
+ * Create and return a RateConverter object for the specified input and output rates.
+ */
+RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) {
+ if (inrate != outrate) {
+ if ((inrate % outrate) == 0) {
+ if (stereo) {
+ if (reverseStereo)
+ return new SimpleRateConverter<true, true>(inrate, outrate);
+ else
+ return new SimpleRateConverter<true, false>(inrate, outrate);
+ } else
+ return new SimpleRateConverter<false, false>(inrate, outrate);
+ } else {
+ if (stereo) {
+ if (reverseStereo)
+ return new LinearRateConverter<true, true>(inrate, outrate);
+ else
+ return new LinearRateConverter<true, false>(inrate, outrate);
+ } else
+ return new LinearRateConverter<false, false>(inrate, outrate);
+ }
+ } else {
+ if (stereo) {
+ if (reverseStereo)
+ return new CopyRateConverter<true, true>();
+ else
+ return new CopyRateConverter<true, false>();
+ } else
+ return new CopyRateConverter<false, false>();
+ }
+}
+
+} // End of namespace Audio
diff --git a/audio/rate_arm_asm.s b/audio/rate_arm_asm.s
new file mode 100644
index 0000000000..9431ae0649
--- /dev/null
+++ b/audio/rate_arm_asm.s
@@ -0,0 +1,687 @@
+@ 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$
+@
+@ @author Robin Watts (robin@wss.co.uk)
+@
+@ This file, together with rate_arm.cpp, provides an ARM optimised version
+@ of rate.cpp. The algorithm is essentially the same as that within rate.cpp
+@ so to understand this file you should understand rate.cpp first.
+
+ .text
+
+ .global _ARM_CopyRate_M
+ .global _ARM_CopyRate_S
+ .global _ARM_CopyRate_R
+ .global _ARM_SimpleRate_M
+ .global _ARM_SimpleRate_S
+ .global _ARM_SimpleRate_R
+ .global _ARM_LinearRate_M
+ .global _ARM_LinearRate_S
+ .global _ARM_LinearRate_R
+
+_ARM_CopyRate_M:
+ @ r0 = len
+ @ r1 = obuf
+ @ r2 = vol_l
+ @ r3 = vol_r
+ @ <> = ptr
+ LDR r12,[r13]
+ STMFD r13!,{r4-r7,r14}
+
+ MOV r14,#0 @ r14= 0
+ ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits
+ ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits
+CopyRate_M_loop:
+ LDRSH r5, [r12], #2 @ r5 = tmp0 = tmp1 = *ptr++
+ LDRSH r6, [r1] @ r6 = obuf[0]
+ LDRSH r7, [r1, #2] @ r7 = obuf[1]
+ MUL r4, r2, r5 @ r4 = tmp0*vol_l
+ MUL r5, r3, r5 @ r5 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r14,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r14,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r1], #2 @ Store output value
+ STRH r7, [r1], #2 @ Store output value
+
+ SUBS r0,r0,#1 @ len--
+ BGT CopyRate_M_loop @ and loop
+
+ MOV r0, r1 @ return obuf
+
+ LDMFD r13!,{r4-r7,PC}
+
+_ARM_CopyRate_S:
+ @ r0 = len
+ @ r1 = obuf
+ @ r2 = vol_l
+ @ r3 = vol_r
+ @ <> = ptr
+ LDR r12,[r13]
+ STMFD r13!,{r4-r7,r14}
+
+ MOV r14,#0 @ r14= 0
+ ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits
+ ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits
+CopyRate_S_loop:
+ LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++
+ LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++
+ LDRSH r6, [r1] @ r6 = obuf[0]
+ LDRSH r7, [r1,#2] @ r7 = obuf[1]
+ MUL r4, r2, r4 @ r5 = tmp0*vol_l
+ MUL r5, r3, r5 @ r6 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r14,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r14,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r1],#2 @ Store output value
+ STRH r7, [r1],#2 @ Store output value
+
+ SUBS r0,r0,#2 @ len -= 2
+ BGT CopyRate_S_loop @ and loop
+
+ MOV r0, r1 @ return obuf
+
+ LDMFD r13!,{r4-r7,PC}
+
+_ARM_CopyRate_R:
+ @ r0 = len
+ @ r1 = obuf
+ @ r2 = vol_l
+ @ r3 = vol_r
+ @ <> = ptr
+ LDR r12,[r13]
+ STMFD r13!,{r4-r7,r14}
+
+ MOV r14,#0 @ r14= 0
+ ORR r2, r2, r2, LSL #8 @ r2 = vol_l as 16 bits
+ ORR r3, r3, r3, LSL #8 @ r3 = vol_r as 16 bits
+CopyRate_R_loop:
+ LDRSH r5, [r12],#2 @ r5 = tmp1 = *ptr++
+ LDRSH r4, [r12],#2 @ r4 = tmp0 = *ptr++
+ LDRSH r6, [r1] @ r6 = obuf[0]
+ LDRSH r7, [r1,#2] @ r7 = obuf[1]
+ MUL r4, r2, r4 @ r4 = tmp0*vol_l
+ MUL r5, r3, r5 @ r5 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r14,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r14,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r1],#2 @ Store output value
+ STRH r7, [r1],#2 @ Store output value
+
+ SUBS r0,r0,#2 @ len -= 2
+ BGT CopyRate_R_loop @ and loop
+
+ MOV r0, r1 @ return obuf
+
+ LDMFD r13!,{r4-r7,PC}
+
+_ARM_SimpleRate_M:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r2,r4-r8,r10-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r2 = opos
+ @ r8 = opos_inc
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE SimpleRate_M_end @ bale
+ MOV r10,#0
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+SimpleRate_M_loop:
+ SUBS r1, r1, #1 @ r1 = inLen -= 1
+ BLT SimpleRate_M_read
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++
+ BGE SimpleRate_M_loop @ and loop }
+SimpleRate_M_read_return:
+ LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++
+ LDRSH r6, [r3] @ r6 = obuf[0]
+ LDRSH r7, [r3,#2] @ r7 = obuf[1]
+ ADD r2, r2, r8 @ r2 = opos += opos_inc
+ MUL r4, r12,r5 @ r4 = tmp0*vol_l
+ MUL r5, r14,r5 @ r5 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r10,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r10,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r3],#2 @ Store output value
+ STRH r7, [r3],#2 @ Store output value
+
+ SUBS r11,r11,#1 @ len--
+ BGT SimpleRate_M_loop @ and loop
+SimpleRate_M_end:
+ LDR r14,[r13,#8] @ r14 = sr
+ ADD r13,r13,#12 @ Skip over r0-r2 on stack
+ STMIA r14,{r0,r1,r2} @ Store back updated values
+
+ MOV r0, r3 @ return obuf
+
+ LDMFD r13!,{r4-r8,r10-r11,PC}
+SimpleRate_M_read:
+ LDR r0, [r13,#8] @ r0 = sr (8 = 4*2)
+ ADD r0, r0, #16 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 3+8+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #1 @ r1 = inLen-1
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT SimpleRate_M_end
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #2 @ if (r2 >= 0) { sr.inPtr++
+ BGE SimpleRate_M_loop @ and loop }
+ B SimpleRate_M_read_return
+
+
+_ARM_SimpleRate_S:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r2,r4-r8,r10-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r2 = opos
+ @ r8 = opos_inc
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE SimpleRate_S_end @ bale
+ MOV r10,#0
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+SimpleRate_S_loop:
+ SUBS r1, r1, #2 @ r1 = inLen -= 2
+ BLT SimpleRate_S_read
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2
+ BGE SimpleRate_S_loop @ and loop }
+SimpleRate_S_read_return:
+ LDRSH r4, [r0],#2 @ r4 = tmp0 = *inPtr++
+ LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++
+ LDRSH r6, [r3] @ r6 = obuf[0]
+ LDRSH r7, [r3,#2] @ r7 = obuf[1]
+ ADD r2, r2, r8 @ r2 = opos += opos_inc
+ MUL r4, r12,r4 @ r5 = tmp0*vol_l
+ MUL r5, r14,r5 @ r6 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r10,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r10,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r3],#2 @ Store output value
+ STRH r7, [r3],#2 @ Store output value
+
+ SUBS r11,r11,#1 @ osamp--
+ BGT SimpleRate_S_loop @ and loop
+SimpleRate_S_end:
+ LDR r14,[r13,#8] @ r14 = sr
+ ADD r13,r13,#12 @ skip over r0-r2 on stack
+ STMIA r14,{r0,r1,r2} @ store back updated values
+ MOV r0, r3 @ return obuf
+ LDMFD r13!,{r4-r8,r10-r11,PC}
+SimpleRate_S_read:
+ LDR r0, [r13,#8] @ r0 = sr (8 = 4*2)
+ ADD r0, r0, #16 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 3+8+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #2 @ r1 = inLen-2
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT SimpleRate_S_end
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2
+ BGE SimpleRate_S_loop @ and loop }
+ B SimpleRate_S_read_return
+
+
+
+_ARM_SimpleRate_R:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r2,r4-r8,r10-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r2,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r2 = opos
+ @ r8 = opos_inc
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE SimpleRate_R_end @ bale
+ MOV r10,#0
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+SimpleRate_R_loop:
+ SUBS r1, r1, #2 @ r1 = inLen -= 2
+ BLT SimpleRate_R_read
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2
+ BGE SimpleRate_R_loop @ and loop }
+SimpleRate_R_read_return:
+ LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++
+ LDRSH r4, [r0],#2 @ r4 = tmp1 = *inPtr++
+ LDRSH r6, [r3] @ r6 = obuf[0]
+ LDRSH r7, [r3,#2] @ r7 = obuf[1]
+ ADD r2, r2, r8 @ r2 = opos += opos_inc
+ MUL r4, r12,r4 @ r5 = tmp0*vol_l
+ MUL r5, r14,r5 @ r6 = tmp1*vol_r
+
+ ADDS r6, r4, r6, LSL #16 @ r6 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r6, r10,#0x80000000 @ Clamp r6
+ ADDS r7, r5, r7, LSL #16 @ r7 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r7, r10,#0x80000000 @ Clamp r7
+
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+
+ STRH r6, [r3],#2 @ Store output value
+ STRH r7, [r3],#2 @ Store output value
+
+ SUBS r11,r11,#1 @ osamp--
+ BGT SimpleRate_R_loop @ and loop
+SimpleRate_R_end:
+ LDR r14,[r13,#8] @ r14 = sr
+ ADD r13,r13,#12 @ Skip over r0-r2 on stack
+ STMIA r14,{r0,r1,r2} @ Store back updated values
+ MOV r0, r3 @ return obuf
+ LDMFD r13!,{r4-r8,r10-r11,PC}
+SimpleRate_R_read:
+ LDR r0, [r13,#8] @ r0 = sr (8 = 4*2)
+ ADD r0, r0, #16 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 3+8+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #2 @ r1 = inLen-2
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT SimpleRate_R_end
+ SUBS r2, r2, #1 @ r2 = opos--
+ ADDGE r0, r0, #4 @ if (r2 >= 0) { sr.inPtr += 2
+ BGE SimpleRate_R_loop @ and loop }
+ B SimpleRate_R_read_return
+
+
+_ARM_LinearRate_M:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r1,r4-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r8 = opos
+ MOV r10,#0
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE LinearRate_M_end @ bale
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+ CMP r1,#0
+ BGT LinearRate_M_part2
+
+ @ part1 - read input samples
+LinearRate_M_loop:
+ SUBS r1, r1, #1 @ r1 = inLen -= 1
+ BLT LinearRate_M_read
+LinearRate_M_read_return:
+ LDRH r4, [r2, #16] @ r4 = icur[0]
+ LDRSH r5, [r0],#2 @ r5 = tmp1 = *inPtr++
+ SUBS r8, r8, #65536 @ r8 = opos--
+ STRH r4, [r2,#22] @ ilast[0] = icur[0]
+ STRH r5, [r2,#16] @ icur[0] = tmp1
+ BGE LinearRate_M_loop
+
+ @ part2 - form output samples
+LinearRate_M_part2:
+ @ We are guaranteed that opos < 0 here
+ LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768
+ LDRSH r5, [r2,#16] @ r5 = icur[0]
+ MOV r4, r8, LSL #16
+ MOV r4, r4, LSR #16
+ SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0]
+ MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0]
+
+ LDRSH r4, [r3] @ r4 = obuf[0]
+ LDRSH r5, [r3,#2] @ r5 = obuf[1]
+ MOV r6, r6, ASR #16 @ r6 = tmp0 = tmp1 >>= 16
+ MUL r7, r12,r6 @ r7 = tmp0*vol_l
+ MUL r6, r14,r6 @ r6 = tmp1*vol_r
+
+ ADDS r7, r7, r4, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l
+ RSCVS r7, r10, #0x80000000 @ Clamp r7
+ ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r6, r10, #0x80000000 @ Clamp r6
+
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+
+ LDR r5, [r2,#12] @ r5 = opos_inc
+ STRH r7, [r3],#2 @ Store output value
+ STRH r6, [r3],#2 @ Store output value
+ SUBS r11, r11,#1 @ osamp--
+ BLE LinearRate_M_end @ end if needed
+
+ ADDS r8, r8, r5 @ r8 = opos += opos_inc
+ BLT LinearRate_M_part2
+ B LinearRate_M_loop
+LinearRate_M_end:
+ ADD r13,r13,#8
+ STMIA r2,{r0,r1,r8}
+ MOV r0, r3 @ return obuf
+ LDMFD r13!,{r4-r11,PC}
+LinearRate_M_read:
+ ADD r0, r2, #28 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 2+9+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #1 @ r1 = inLen-1
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT LinearRate_M_end
+ B LinearRate_M_read_return
+
+_ARM_LinearRate_S:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r1,r4-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r8 = opos
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE LinearRate_S_end @ bale
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+ CMP r1,#0
+ BGT LinearRate_S_part2
+
+ @ part1 - read input samples
+LinearRate_S_loop:
+ SUBS r1, r1, #2 @ r1 = inLen -= 2
+ BLT LinearRate_S_read
+LinearRate_S_read_return:
+ LDR r10,[r2, #16] @ r10= icur[0,1]
+ LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++
+ LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++
+ SUBS r8, r8, #65536 @ r8 = opos--
+ STRH r10,[r2,#22] @ ilast[0] = icur[0]
+ MOV r10,r10,LSR #16
+ STRH r10,[r2,#26] @ ilast[1] = icur[1]
+ STRH r5, [r2,#16] @ icur[0] = tmp0
+ STRH r6, [r2,#18] @ icur[1] = tmp1
+ BGE LinearRate_S_loop
+
+ @ part2 - form output samples
+LinearRate_S_part2:
+ @ We are guaranteed that opos < 0 here
+ LDR r6, [r2,#20] @ r6 = ilast[0]<<16 + 32768
+ LDRSH r5, [r2,#16] @ r5 = icur[0]
+ MOV r4, r8, LSL #16
+ MOV r4, r4, LSR #16
+ SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0]
+ MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0]
+
+ LDR r7, [r2,#24] @ r7 = ilast[1]<<16 + 32768
+ LDRSH r5, [r2,#18] @ r5 = icur[1]
+ LDRSH r10,[r3] @ r10= obuf[0]
+ MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16
+ SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1]
+ MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1]
+
+ LDRSH r5, [r3,#2] @ r5 = obuf[1]
+ MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16
+ MUL r7, r12,r7 @ r7 = tmp0*vol_l
+ MUL r6, r14,r6 @ r6 = tmp1*vol_r
+
+ ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l
+ MOV r4, #0
+ RSCVS r7, r4, #0x80000000 @ Clamp r7
+ ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r6, r4, #0x80000000 @ Clamp r6
+
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+
+ LDR r5, [r2,#12] @ r5 = opos_inc
+ STRH r7, [r3],#2 @ Store output value
+ STRH r6, [r3],#2 @ Store output value
+ SUBS r11, r11,#1 @ osamp--
+ BLE LinearRate_S_end @ and loop
+
+ ADDS r8, r8, r5 @ r8 = opos += opos_inc
+ BLT LinearRate_S_part2
+ B LinearRate_S_loop
+LinearRate_S_end:
+ ADD r13,r13,#8
+ STMIA r2,{r0,r1,r8}
+ MOV r0, r3 @ return obuf
+ LDMFD r13!,{r4-r11,PC}
+LinearRate_S_read:
+ ADD r0, r2, #28 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 2+9+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #2 @ r1 = inLen-2
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT LinearRate_S_end
+ B LinearRate_S_read_return
+
+_ARM_LinearRate_R:
+ @ r0 = AudioStream &input
+ @ r1 = input.readBuffer
+ @ r2 = input->sr
+ @ r3 = obuf
+ @ <> = osamp
+ @ <> = vol_l
+ @ <> = vol_r
+ MOV r12,r13
+ STMFD r13!,{r0-r1,r4-r11,r14}
+ LDMFD r12,{r11,r12,r14} @ r11= osamp
+ @ r12= vol_l
+ @ r14= vol_r
+ LDMIA r2,{r0,r1,r8} @ r0 = inPtr
+ @ r1 = inLen
+ @ r8 = opos
+ CMP r11,#0 @ if (osamp <= 0)
+ BLE LinearRate_R_end @ bale
+ ORR r12,r12,r12,LSL #8 @ r12= vol_l as 16 bits
+ ORR r14,r14,r14,LSL #8 @ r14= vol_r as 16 bits
+ CMP r1,#0
+ BGT LinearRate_R_part2
+
+ @ part1 - read input samples
+LinearRate_R_loop:
+ SUBS r1, r1, #2 @ r1 = inLen -= 2
+ BLT LinearRate_R_read
+LinearRate_R_read_return:
+ LDR r10,[r2, #16] @ r10= icur[0,1]
+ LDRSH r5, [r0],#2 @ r5 = tmp0 = *inPtr++
+ LDRSH r6, [r0],#2 @ r5 = tmp1 = *inPtr++
+ SUBS r8, r8, #65536 @ r8 = opos--
+ STRH r10,[r2,#22] @ ilast[0] = icur[0]
+ MOV r10,r10,LSR #16
+ STRH r10,[r2,#22] @ ilast[1] = icur[1]
+ STRH r5, [r2,#16] @ icur[0] = tmp0
+ STRH r6, [r2,#18] @ icur[1] = tmp1
+ BGE LinearRate_R_loop
+
+ @ part2 - form output samples
+LinearRate_R_part2:
+ @ We are guaranteed that opos < 0 here
+ LDR r6, [r2,#20] @ r6 = ilast[0]
+ LDRSH r5, [r2,#16] @ r5 = icur[0]
+ MOV r4, r8, LSL #16
+ MOV r4, r4, LSR #16
+ SUB r5, r5, r6, ASR #16 @ r5 = icur[0] - ilast[0]
+ MLA r6, r4, r5, r6 @ r6 = (icur[0]-ilast[0])*opos_frac+ilast[0]
+
+ LDR r7, [r2,#24] @ r7 = ilast[1]
+ LDRSH r5, [r2,#18] @ r5 = icur[1]
+ LDR r10,[r3] @ r10= obuf[0]
+ MOV r6, r6, ASR #16 @ r6 = tmp1 >>= 16
+ SUB r5, r5, r7, ASR #16 @ r5 = icur[1] - ilast[1]
+ MLA r7, r4, r5, r7 @ r7 = (icur[1]-ilast[1])*opos_frac+ilast[1]
+
+ LDRSH r5, [r3,#2] @ r5 = obuf[1]
+ MOV r7, r7, ASR #16 @ r7 = tmp0 >>= 16
+ MUL r7, r12,r7 @ r7 = tmp0*vol_l
+ MUL r6, r14,r6 @ r6 = tmp1*vol_r
+
+ ADDS r7, r7, r10, LSL #16 @ r7 = obuf[0]<<16 + tmp0*vol_l
+ MOV r4, #0
+ RSCVS r7, r4, #0x80000000 @ Clamp r7
+ ADDS r6, r6, r5, LSL #16 @ r6 = obuf[1]<<16 + tmp1*vol_r
+ RSCVS r6, r4, #0x80000000 @ Clamp r6
+
+ MOV r7, r7, LSR #16 @ Shift back to halfword
+ MOV r6, r6, LSR #16 @ Shift back to halfword
+
+ LDR r5, [r2,#12] @ r5 = opos_inc
+ STRH r6, [r3],#2 @ Store output value
+ STRH r7, [r3],#2 @ Store output value
+ SUBS r11, r11,#1 @ osamp--
+ BLE LinearRate_R_end @ and loop
+
+ ADDS r8, r8, r5 @ r8 = opos += opos_inc
+ BLT LinearRate_R_part2
+ B LinearRate_R_loop
+LinearRate_R_end:
+ ADD r13,r13,#8
+ STMIA r2,{r0,r1,r8}
+ MOV r0, r3 @ return obuf
+ LDMFD r13!,{r4-r11,PC}
+LinearRate_R_read:
+ ADD r0, r2, #28 @ r0 = inPtr = inBuf
+ STMFD r13!,{r0,r2-r3,r12,r14}
+
+ MOV r1, r0 @ r1 = inBuf
+ LDR r0, [r13,#20] @ r0 = AudioStream & input (20 = 4*5)
+ MOV r2, #512 @ r2 = ARRAYSIZE(inBuf)
+
+ @ Calling back into C++ here. WinCE is fairly easy about such things
+ @ but other OS are more awkward. r9 is preserved for Symbian, and
+ @ we have 2+9+5 = 16 things on the stack (an even number).
+ MOV r14,PC
+ LDR PC,[r13,#24] @ inLen = input.readBuffer(inBuf,512) (24 = 4*6)
+ SUBS r1, r0, #2 @ r1 = inLen-2
+ LDMFD r13!,{r0,r2-r3,r12,r14}
+ BLT LinearRate_R_end
+ B LinearRate_R_read_return
diff --git a/audio/softsynth/adlib.cpp b/audio/softsynth/adlib.cpp
new file mode 100644
index 0000000000..4a9ce54c75
--- /dev/null
+++ b/audio/softsynth/adlib.cpp
@@ -0,0 +1,1617 @@
+/* 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 "audio/softsynth/emumidi.h"
+#include "common/debug.h"
+#include "common/util.h"
+#include "audio/fmopl.h"
+#include "audio/musicplugin.h"
+#include "common/translation.h"
+
+#ifdef DEBUG_ADLIB
+static int tick;
+#endif
+
+class MidiDriver_ADLIB;
+struct AdLibVoice;
+
+// We use packing for the following two structs, because the code
+// does simply copy them over from byte streams, without any
+// serialization. Check AdLibPart::sysEx_customInstrument for an
+// example of this.
+//
+// It might be very well possible, that none of the compilers we support
+// add any padding bytes at all, since the structs contain only variables
+// of the type 'byte'. But better safe than sorry.
+#include "common/pack-start.h"
+struct InstrumentExtra {
+ byte a, b, c, d, e, f, g, h;
+} PACKED_STRUCT;
+
+struct AdLibInstrument {
+ byte mod_characteristic;
+ byte mod_scalingOutputLevel;
+ byte mod_attackDecay;
+ byte mod_sustainRelease;
+ byte mod_waveformSelect;
+ byte car_characteristic;
+ byte car_scalingOutputLevel;
+ byte car_attackDecay;
+ byte car_sustainRelease;
+ byte car_waveformSelect;
+ byte feedback;
+ byte flags_a;
+ InstrumentExtra extra_a;
+ byte flags_b;
+ InstrumentExtra extra_b;
+ byte duration;
+
+ AdLibInstrument() { memset(this, 0, sizeof(AdLibInstrument)); }
+} PACKED_STRUCT;
+#include "common/pack-end.h"
+
+class AdLibPart : public MidiChannel {
+ friend class MidiDriver_ADLIB;
+
+protected:
+// AdLibPart *_prev, *_next;
+ AdLibVoice *_voice;
+ int16 _pitchbend;
+ byte _pitchbend_factor;
+ int8 _transpose_eff;
+ byte _vol_eff;
+ int8 _detune_eff;
+ byte _modwheel;
+ bool _pedal;
+ byte _program;
+ byte _pri_eff;
+ AdLibInstrument _part_instr;
+
+protected:
+ MidiDriver_ADLIB *_owner;
+ bool _allocated;
+ byte _channel;
+
+ void init(MidiDriver_ADLIB *owner, byte channel);
+ void allocate() { _allocated = true; }
+
+public:
+ AdLibPart() {
+ _voice = 0;
+ _pitchbend = 0;
+ _pitchbend_factor = 2;
+ _transpose_eff = 0;
+ _vol_eff = 0;
+ _detune_eff = 0;
+ _modwheel = 0;
+ _pedal = 0;
+ _program = 0;
+ _pri_eff = 0;
+
+ _owner = 0;
+ _allocated = false;
+ _channel = 0;
+ }
+
+ MidiDriver *device();
+ byte getNumber() { return _channel; }
+ void release() { _allocated = false; }
+
+ void send (uint32 b);
+
+ // Regular messages
+ void noteOff(byte note);
+ void noteOn(byte note, byte velocity);
+ void programChange(byte program);
+ void pitchBend(int16 bend);
+
+ // Control Change messages
+ void controlChange(byte control, byte value);
+ void modulationWheel(byte value);
+ void volume(byte value);
+ void panPosition(byte value) { return; } // Not supported
+ void pitchBendFactor(byte value);
+ void detune(byte value);
+ void priority(byte value);
+ void sustain(bool value);
+ void effectLevel(byte value) { return; } // Not supported
+ void chorusLevel(byte value) { return; } // Not supported
+ void allNotesOff();
+
+ // SysEx messages
+ void sysEx_customInstrument(uint32 type, const byte *instr);
+};
+
+// FYI (Jamieson630)
+// It is assumed that any invocation to AdLibPercussionChannel
+// will be done through the MidiChannel base class as opposed to the
+// AdLibPart base class. If this were NOT the case, all the functions
+// listed below would need to be virtual in AdLibPart as well as MidiChannel.
+class AdLibPercussionChannel : public AdLibPart {
+ friend class MidiDriver_ADLIB;
+
+protected:
+ void init(MidiDriver_ADLIB *owner, byte channel);
+
+public:
+ ~AdLibPercussionChannel();
+
+ void noteOff(byte note);
+ void noteOn(byte note, byte velocity);
+ void programChange(byte program) { }
+ void pitchBend(int16 bend) { }
+
+ // Control Change messages
+ void modulationWheel(byte value) { }
+ void pitchBendFactor(byte value) { }
+ void detune(byte value) { }
+ void priority(byte value) { }
+ void sustain(bool value) { }
+
+ // SysEx messages
+ void sysEx_customInstrument(uint32 type, const byte *instr);
+
+private:
+ byte _notes[256];
+ AdLibInstrument *_customInstruments[256];
+};
+
+struct Struct10 {
+ byte active;
+ int16 cur_val;
+ int16 count;
+ uint16 max_value;
+ int16 start_value;
+ byte loop;
+ byte table_a[4];
+ byte table_b[4];
+ int8 unk3;
+ int8 modwheel;
+ int8 modwheel_last;
+ uint16 speed_lo_max;
+ uint16 num_steps;
+ int16 speed_hi;
+ int8 direction;
+ uint16 speed_lo;
+ uint16 speed_lo_counter;
+};
+
+struct Struct11 {
+ int16 modify_val;
+ byte param, flag0x40, flag0x10;
+ Struct10 *s10;
+};
+
+struct AdLibVoice {
+ AdLibPart *_part;
+ AdLibVoice *_next, *_prev;
+ byte _waitforpedal;
+ byte _note;
+ byte _channel;
+ byte _twochan;
+ byte _vol_1, _vol_2;
+ int16 _duration;
+
+ Struct10 _s10a;
+ Struct11 _s11a;
+ Struct10 _s10b;
+ Struct11 _s11b;
+
+ AdLibVoice() { memset(this, 0, sizeof(AdLibVoice)); }
+};
+
+struct AdLibSetParams {
+ byte a, b, c, d;
+};
+
+static const byte channel_mappings[9] = {
+ 0, 1, 2, 8,
+ 9, 10, 16, 17,
+ 18
+};
+
+static const byte channel_mappings_2[9] = {
+ 3, 4, 5, 11,
+ 12, 13, 19, 20,
+ 21
+};
+
+static const AdLibSetParams adlib_setparam_table[] = {
+ {0x40, 0, 63, 63}, // level
+ {0xE0, 2, 0, 0}, // unused
+ {0x40, 6, 192, 0}, // level key scaling
+ {0x20, 0, 15, 0}, // modulator frequency multiple
+ {0x60, 4, 240, 15}, // attack rate
+ {0x60, 0, 15, 15}, // decay rate
+ {0x80, 4, 240, 15}, // sustain level
+ {0x80, 0, 15, 15}, // release rate
+ {0xE0, 0, 3, 0}, // waveformSelect select
+ {0x20, 7, 128, 0}, // amp mod
+ {0x20, 6, 64, 0}, // vib
+ {0x20, 5, 32, 0}, // eg typ
+ {0x20, 4, 16, 0}, // ksr
+ {0xC0, 0, 1, 0}, // decay alg
+ {0xC0, 1, 14, 0} // feedback
+};
+
+static const byte param_table_1[16] = {
+ 29, 28, 27, 0,
+ 3, 4, 7, 8,
+ 13, 16, 17, 20,
+ 21, 30, 31, 0
+};
+
+static const uint16 maxval_table[16] = {
+ 0x2FF, 0x1F, 0x7, 0x3F,
+ 0x0F, 0x0F, 0x0F, 0x3,
+ 0x3F, 0x0F, 0x0F, 0x0F,
+ 0x3, 0x3E, 0x1F, 0
+};
+
+static const uint16 num_steps_table[] = {
+ 1, 2, 4, 5,
+ 6, 7, 8, 9,
+ 10, 12, 14, 16,
+ 18, 21, 24, 30,
+ 36, 50, 64, 82,
+ 100, 136, 160, 192,
+ 240, 276, 340, 460,
+ 600, 860, 1200, 1600
+};
+
+static const byte note_to_f_num[] = {
+ 90, 91, 92, 92, 93, 94, 94, 95,
+ 96, 96, 97, 98, 98, 99, 100, 101,
+ 101, 102, 103, 104, 104, 105, 106, 107,
+ 107, 108, 109, 110, 111, 111, 112, 113,
+ 114, 115, 115, 116, 117, 118, 119, 120,
+ 121, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142,
+ 143, 145, 146, 147, 148, 149, 150, 151,
+ 152, 153, 154, 155, 157, 158, 159, 160,
+ 161, 162, 163, 165, 166, 167, 168, 169,
+ 171, 172, 173, 174, 176, 177, 178, 180,
+ 181, 182, 184, 185, 186, 188, 189, 190,
+ 192, 193, 194, 196, 197, 199, 200, 202,
+ 203, 205, 206, 208, 209, 211, 212, 214,
+ 215, 217, 218, 220, 222, 223, 225, 226,
+ 228, 230, 231, 233, 235, 236, 238, 240,
+ 242, 243, 245, 247, 249, 251, 252, 254
+};
+
+static const byte map_gm_to_fm[128][30] = {
+ // 0x00
+{ 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 },
+{ 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 },
+{ 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1A },
+{ 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+ // 0x10
+{ 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 },
+{ 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 },
+{ 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x19 },
+{ 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0A },
+{ 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0B },
+ // 0x20
+{ 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x20 },
+{ 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 },
+{ 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 },
+{ 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+ // 0x30
+{ 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 },
+{ 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+ // 0x40
+{ 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+ // 0x50
+{ 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+ // 0x60
+{ 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1C },
+{ 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x12 },
+{ 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 },
+{ 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 },
+{ 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D },
+{ 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 },
+{ 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+ // 0x70
+{ 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0F },
+{ 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 },
+{ 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x38 },
+{ 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }
+};
+
+static byte gm_percussion_to_fm[39][30] = {
+{ 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 },
+{ 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x01 },
+{ 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C },
+{ 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C },
+{ 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 },
+{ 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 },
+{ 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 },
+{ 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 },
+{ 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 },
+{ 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 },
+{ 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 },
+{ 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 },
+{ 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 },
+{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D },
+{ 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 },
+{ 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 },
+{ 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 },
+{ 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }
+};
+
+static const byte gm_percussion_lookup[128] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0xFF, 0xFF, 0x17, 0x18, 0x19, 0x1A,
+ 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x21, 0x22, 0x23, 0xFF, 0xFF,
+ 0x24, 0x25, 0xFF, 0xFF, 0xFF, 0x26, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+static byte lookup_table[64][32];
+
+static const byte volume_table[] = {
+ 0, 4, 7, 11,
+ 13, 16, 18, 20,
+ 22, 24, 26, 27,
+ 29, 30, 31, 33,
+ 34, 35, 36, 37,
+ 38, 39, 40, 41,
+ 42, 43, 44, 44,
+ 45, 46, 47, 47,
+ 48, 49, 49, 50,
+ 51, 51, 52, 53,
+ 53, 54, 54, 55,
+ 55, 56, 56, 57,
+ 57, 58, 58, 59,
+ 59, 60, 60, 60,
+ 61, 61, 62, 62,
+ 62, 63, 63, 63
+};
+
+static int lookup_volume(int a, int b) {
+ if (b == 0)
+ return 0;
+
+ if (b == 31)
+ return a;
+
+ if (a < -63 || a > 63) {
+ return b * (a + 1) >> 5;
+ }
+
+ if (b < 0) {
+ if (a < 0) {
+ return lookup_table[-a][-b];
+ } else {
+ return -lookup_table[a][-b];
+ }
+ } else {
+ if (a < 0) {
+ return -lookup_table[-a][b];
+ } else {
+ return lookup_table[a][b];
+ }
+ }
+}
+
+static void create_lookup_table() {
+ int i, j;
+ int sum;
+
+ for (i = 0; i < 64; i++) {
+ sum = i;
+ for (j = 0; j < 32; j++) {
+ lookup_table[i][j] = sum >> 5;
+ sum += i;
+ }
+ }
+ for (i = 0; i < 64; i++)
+ lookup_table[i][0] = 0;
+}
+
+////////////////////////////////////////
+//
+// AdLib MIDI driver
+//
+////////////////////////////////////////
+
+class MidiDriver_ADLIB : public MidiDriver_Emulated {
+ friend class AdLibPart;
+ friend class AdLibPercussionChannel;
+
+public:
+ MidiDriver_ADLIB(Audio::Mixer *mixer);
+
+ int open();
+ void close();
+ void send(uint32 b);
+ void send(byte channel, uint32 b); // Supports higher than channel 15
+ uint32 property(int prop, uint32 param);
+
+ void setPitchBendRange(byte channel, uint range);
+ void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
+
+ MidiChannel *allocateChannel();
+ MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported
+
+
+ // AudioStream API
+ bool isStereo() const { return false; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+private:
+ bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games
+
+ FM_OPL *_opl;
+ byte *_adlib_reg_cache;
+
+ int _adlib_timer_counter;
+
+ uint16 channel_table_2[9];
+ int _voice_index;
+ int _timer_p;
+ int _timer_q;
+ uint16 curnote_table[9];
+ AdLibVoice _voices[9];
+ AdLibPart _parts[32];
+ AdLibPercussionChannel _percussion;
+
+ void generateSamples(int16 *buf, int len);
+ void onTimer();
+ void part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity);
+ void part_key_off(AdLibPart *part, byte note);
+
+ void adlib_key_off(int chan);
+ void adlib_note_on(int chan, byte note, int mod);
+ void adlib_note_on_ex(int chan, byte note, int mod);
+ int adlib_get_reg_value_param(int chan, byte data);
+ void adlib_setup_channel(int chan, AdLibInstrument * instr, byte vol_1, byte vol_2);
+ byte adlib_get_reg_value(byte reg) {
+ return _adlib_reg_cache[reg];
+ }
+ void adlib_set_param(int channel, byte param, int value);
+ void adlib_key_onoff(int channel);
+ void adlib_write(byte reg, byte value);
+ void adlib_playnote(int channel, int note);
+
+ AdLibVoice *allocate_voice(byte pri);
+
+ void mc_off(AdLibVoice * voice);
+
+ static void link_mc(AdLibPart *part, AdLibVoice *voice);
+ void mc_inc_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11);
+ void mc_init_stuff(AdLibVoice *voice, Struct10 * s10, Struct11 * s11, byte flags,
+ InstrumentExtra * ie);
+
+ void struct10_init(Struct10 * s10, InstrumentExtra * ie);
+ static byte struct10_ontimer(Struct10 * s10, Struct11 * s11);
+ static void struct10_setup(Struct10 * s10);
+ static int random_nr(int a);
+ void mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity);
+};
+
+// MidiChannel method implementations
+
+void AdLibPart::init(MidiDriver_ADLIB *owner, byte channel) {
+ _owner = owner;
+ _channel = channel;
+ _pri_eff = 127;
+ programChange(0);
+}
+
+MidiDriver *AdLibPart::device() {
+ return _owner;
+}
+
+void AdLibPart::send(uint32 b) {
+ _owner->send(_channel, b);
+}
+
+void AdLibPart::noteOff(byte note) {
+#ifdef DEBUG_ADLIB
+ debug(6, "%10d: noteOff(%d)", tick, note);
+#endif
+ _owner->part_key_off(this, note);
+}
+
+void AdLibPart::noteOn(byte note, byte velocity) {
+#ifdef DEBUG_ADLIB
+ debug(6, "%10d: noteOn(%d,%d)", tick, note, velocity);
+#endif
+ _owner->part_key_on(this, &_part_instr, note, velocity);
+}
+
+void AdLibPart::programChange(byte program) {
+ if (program > 127)
+ return;
+
+ uint i;
+ uint count = 0;
+ for (i = 0; i < ARRAYSIZE(map_gm_to_fm[0]); ++i)
+ count += map_gm_to_fm[program][i];
+ if (!count)
+ warning("No AdLib instrument defined for GM program %d", (int) program);
+ _program = program;
+ memcpy(&_part_instr, &map_gm_to_fm[program], sizeof(AdLibInstrument));
+}
+
+void AdLibPart::pitchBend(int16 bend) {
+ AdLibVoice *voice;
+
+ _pitchbend = bend;
+ for (voice = _voice; voice; voice = voice->_next) {
+ _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff,
+ (_pitchbend * _pitchbend_factor >> 6) + _detune_eff);
+ }
+}
+
+void AdLibPart::controlChange(byte control, byte value) {
+ switch (control) {
+ case 0:
+ case 32:
+ break; // Bank select. Not supported
+ case 1: modulationWheel(value); break;
+ case 7: volume(value); break;
+ case 10: break; // Pan position. Not supported.
+ case 16: pitchBendFactor(value); break;
+ case 17: detune(value); break;
+ case 18: priority(value); break;
+ case 64: sustain(value > 0); break;
+ case 91: break; // Effects level. Not supported.
+ case 93: break; // Chorus level. Not supported.
+ case 119: break; // Unknown, used in Simon the Sorcerer 2
+ case 121: // reset all controllers
+ modulationWheel(0);
+ pitchBendFactor(0);
+ detune(0);
+ sustain(0);
+ break;
+ case 123: allNotesOff(); break;
+ default:
+ warning("AdLib: Unknown control change message %d (%d)", (int) control, (int)value);
+ }
+}
+
+void AdLibPart::modulationWheel(byte value) {
+ AdLibVoice *voice;
+
+ _modwheel = value;
+ for (voice = _voice; voice; voice = voice->_next) {
+ if (voice->_s10a.active && voice->_s11a.flag0x40)
+ voice->_s10a.modwheel = _modwheel >> 2;
+ if (voice->_s10b.active && voice->_s11b.flag0x40)
+ voice->_s10b.modwheel = _modwheel >> 2;
+ }
+}
+
+void AdLibPart::volume(byte value) {
+ AdLibVoice *voice;
+
+ _vol_eff = value;
+ for (voice = _voice; voice; voice = voice->_next) {
+ _owner->adlib_set_param(voice->_channel, 0, volume_table[lookup_table[voice->_vol_2][_vol_eff >> 2]]);
+ if (voice->_twochan) {
+ _owner->adlib_set_param(voice->_channel, 13, volume_table[lookup_table[voice->_vol_1][_vol_eff >> 2]]);
+ }
+ }
+}
+
+void AdLibPart::pitchBendFactor(byte value) {
+ AdLibVoice *voice;
+
+ _pitchbend_factor = value;
+ for (voice = _voice; voice; voice = voice->_next) {
+ _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff,
+ (_pitchbend * _pitchbend_factor >> 6) + _detune_eff);
+ }
+}
+
+void AdLibPart::detune(byte value) {
+ AdLibVoice *voice;
+
+ _detune_eff = value;
+ for (voice = _voice; voice; voice = voice->_next) {
+ _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff,
+ (_pitchbend * _pitchbend_factor >> 6) + _detune_eff);
+ }
+}
+
+void AdLibPart::priority(byte value) {
+ _pri_eff = value;
+}
+
+void AdLibPart::sustain(bool value) {
+ AdLibVoice *voice;
+
+ _pedal = value;
+ if (!value) {
+ for (voice = _voice; voice; voice = voice->_next) {
+ if (voice->_waitforpedal)
+ _owner->mc_off(voice);
+ }
+ }
+}
+
+void AdLibPart::allNotesOff() {
+ while (_voice)
+ _owner->mc_off(_voice);
+}
+
+void AdLibPart::sysEx_customInstrument(uint32 type, const byte *instr) {
+ if (type == 'ADL ') {
+ AdLibInstrument *i = &_part_instr;
+ memcpy(i, instr, sizeof(AdLibInstrument));
+ }
+}
+
+// MidiChannel method implementations for percussion
+
+AdLibPercussionChannel::~AdLibPercussionChannel() {
+ for (int i = 0; i < ARRAYSIZE(_customInstruments); ++i) {
+ delete _customInstruments[i];
+ }
+}
+
+void AdLibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) {
+ AdLibPart::init(owner, channel);
+ _pri_eff = 0;
+ _vol_eff = 127;
+
+ // Initialize the custom instruments data
+ memset(_notes, 0, sizeof(_notes));
+ memset(_customInstruments, 0, sizeof(_customInstruments));
+}
+
+void AdLibPercussionChannel::noteOff(byte note) {
+ // Jamieson630: Unless I run into a specific instrument that
+ // may require a key off, I'm going to ignore this message.
+ // The rationale is that a percussion instrument should
+ // fade out of its own accord, and the AdLib instrument
+ // definitions used should follow this rule. Since
+ // percussion voices are allocated at the lowest priority
+ // anyway, we know that "hanging" percussion sounds will
+ // not prevent later musical instruments (or even other
+ // percussion sounds) from playing.
+/*
+ _owner->part_key_off(this, note);
+*/
+}
+
+void AdLibPercussionChannel::noteOn(byte note, byte velocity) {
+ AdLibInstrument *inst = NULL;
+
+ // The custom instruments have priority over the default mapping
+ inst = _customInstruments[note];
+ if (inst)
+ note = _notes[note];
+
+ if (!inst) {
+ // Use the default GM to FM mapping as a fallback as a fallback
+ byte key = gm_percussion_lookup[note];
+ if (key != 0xFF)
+ inst = (AdLibInstrument *)&gm_percussion_to_fm[key];
+ }
+
+ if (!inst) {
+ debug(2, "No instrument FM definition for GM percussion key %d", (int)note);
+ return;
+ }
+
+ _owner->part_key_on(this, inst, note, velocity);
+}
+
+void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *instr) {
+ if (type == 'ADLP') {
+ byte note = instr[0];
+ _notes[note] = instr[1];
+
+ // Allocate memory for the new instruments
+ if (!_customInstruments[note]) {
+ _customInstruments[note] = new AdLibInstrument;
+ }
+
+ // Save the new instrument data
+ _customInstruments[note]->mod_characteristic = instr[2];
+ _customInstruments[note]->mod_scalingOutputLevel = instr[3];
+ _customInstruments[note]->mod_attackDecay = instr[4];
+ _customInstruments[note]->mod_sustainRelease = instr[5];
+ _customInstruments[note]->mod_waveformSelect = instr[6];
+ _customInstruments[note]->car_characteristic = instr[7];
+ _customInstruments[note]->car_scalingOutputLevel = instr[8];
+ _customInstruments[note]->car_attackDecay = instr[9];
+ _customInstruments[note]->car_sustainRelease = instr[10];
+ _customInstruments[note]->car_waveformSelect = instr[11];
+ _customInstruments[note]->feedback = instr[12];
+ }
+}
+
+// MidiDriver method implementations
+
+MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer)
+ : MidiDriver_Emulated(mixer) {
+ uint i;
+
+ _scummSmallHeader = false;
+
+ _adlib_reg_cache = 0;
+
+ _adlib_timer_counter = 0;
+ _voice_index = 0;
+ for (i = 0; i < ARRAYSIZE(curnote_table); ++i) {
+ curnote_table[i] = 0;
+ }
+
+ for (i = 0; i < ARRAYSIZE(_parts); ++i) {
+ _parts[i].init(this, i + ((i >= 9) ? 1 : 0));
+ }
+ _percussion.init(this, 9);
+ _timer_p = 0xD69;
+ _timer_q = 0x411B;
+}
+
+int MidiDriver_ADLIB::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ MidiDriver_Emulated::open();
+
+ int i;
+ AdLibVoice *voice;
+
+ for (i = 0, voice = _voices; i != ARRAYSIZE(_voices); i++, voice++) {
+ voice->_channel = i;
+ voice->_s11a.s10 = &voice->_s10b;
+ voice->_s11b.s10 = &voice->_s10a;
+ }
+
+ _adlib_reg_cache = (byte *)calloc(256, 1);
+
+ _opl = makeAdLibOPL(getRate());
+
+ adlib_write(1, 0x20);
+ adlib_write(8, 0x40);
+ adlib_write(0xBD, 0x00);
+ create_lookup_table();
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ return 0;
+}
+
+void MidiDriver_ADLIB::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_mixerSoundHandle);
+
+ uint i;
+ for (i = 0; i < ARRAYSIZE(_voices); ++i) {
+ if (_voices[i]._part)
+ mc_off(&_voices[i]);
+ }
+
+ // Turn off the OPL emulation
+ OPLDestroy(_opl);
+// YM3812Shutdown();
+
+ free(_adlib_reg_cache);
+}
+
+void MidiDriver_ADLIB::send(uint32 b) {
+ send(b & 0xF, b & 0xFFFFFFF0);
+}
+
+void MidiDriver_ADLIB::send(byte chan, uint32 b) {
+ //byte param3 = (byte) ((b >> 24) & 0xFF);
+ byte param2 = (byte) ((b >> 16) & 0xFF);
+ byte param1 = (byte) ((b >> 8) & 0xFF);
+ byte cmd = (byte) (b & 0xF0);
+
+ AdLibPart *part;
+ if (chan == 9)
+ part = &_percussion;
+ else
+ part = &_parts[chan];
+
+ switch (cmd) {
+ case 0x80:// Note Off
+ part->noteOff(param1);
+ break;
+ case 0x90: // Note On
+ part->noteOn(param1, param2);
+ break;
+ case 0xA0: // Aftertouch
+ break; // Not supported.
+ case 0xB0: // Control Change
+ part->controlChange(param1, param2);
+ break;
+ case 0xC0: // Program Change
+ part->programChange(param1);
+ break;
+ case 0xD0: // Channel Pressure
+ break; // Not supported.
+ case 0xE0: // Pitch Bend
+ part->pitchBend((param1 | (param2 << 7)) - 0x2000);
+ break;
+ case 0xF0: // SysEx
+ // We should never get here! SysEx information has to be
+ // sent via high-level semantic methods.
+ warning("MidiDriver_ADLIB: Receiving SysEx command on a send() call");
+ break;
+
+ default:
+ warning("MidiDriver_ADLIB: Unknown send() command 0x%02X", cmd);
+ }
+}
+
+uint32 MidiDriver_ADLIB::property(int prop, uint32 param) {
+ switch (prop) {
+ case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm
+ _scummSmallHeader = (param > 0);
+ if (_scummSmallHeader) {
+ _timer_p = 473;
+ _timer_q = 1000;
+ } else {
+ _timer_p = 0xD69;
+ _timer_q = 0x411B;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) {
+ AdLibVoice *voice;
+ AdLibPart *part = &_parts[channel];
+
+ part->_pitchbend_factor = range;
+ for (voice = part->_voice; voice; voice = voice->_next) {
+ adlib_note_on(voice->_channel, voice->_note + part->_transpose_eff,
+ (part->_pitchbend * part->_pitchbend_factor >> 6) + part->_detune_eff);
+ }
+}
+
+void MidiDriver_ADLIB::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
+ _parts[channel].sysEx_customInstrument(type, instr);
+}
+
+MidiChannel *MidiDriver_ADLIB::allocateChannel() {
+ AdLibPart *part;
+ uint i;
+
+ for (i = 0; i < ARRAYSIZE(_parts); ++i) {
+ part = &_parts[i];
+ if (!part->_allocated) {
+ part->allocate();
+ return part;
+ }
+ }
+ return NULL;
+}
+
+// All the code brought over from IMuseAdLib
+
+void MidiDriver_ADLIB::adlib_write(byte reg, byte value) {
+ if (_adlib_reg_cache[reg] == value)
+ return;
+#ifdef DEBUG_ADLIB
+ debug(6, "%10d: adlib_write[%x] = %x", tick, reg, value);
+#endif
+ _adlib_reg_cache[reg] = value;
+
+ OPLWriteReg(_opl, reg, value);
+}
+
+void MidiDriver_ADLIB::generateSamples(int16 *data, int len) {
+ memset(data, 0, sizeof(int16) * len);
+ YM3812UpdateOne(_opl, data, len);
+}
+
+void MidiDriver_ADLIB::onTimer() {
+ AdLibVoice *voice;
+ int i;
+
+ _adlib_timer_counter += _timer_p;
+ while (_adlib_timer_counter >= _timer_q) {
+ _adlib_timer_counter -= _timer_q;
+#ifdef DEBUG_ADLIB
+ tick++;
+#endif
+ voice = _voices;
+ for (i = 0; i != ARRAYSIZE(_voices); i++, voice++) {
+ if (!voice->_part)
+ continue;
+ if (voice->_duration && (voice->_duration -= 0x11) <= 0) {
+ mc_off(voice);
+ return;
+ }
+ if (voice->_s10a.active) {
+ mc_inc_stuff(voice, &voice->_s10a, &voice->_s11a);
+ }
+ if (voice->_s10b.active) {
+ mc_inc_stuff(voice, &voice->_s10b, &voice->_s11b);
+ }
+ }
+ }
+}
+
+void MidiDriver_ADLIB::mc_off(AdLibVoice *voice) {
+ AdLibVoice *tmp;
+
+ adlib_key_off(voice->_channel);
+
+ tmp = voice->_prev;
+
+ if (voice->_next)
+ voice->_next->_prev = tmp;
+ if (tmp)
+ tmp->_next = voice->_next;
+ else
+ voice->_part->_voice = voice->_next;
+ voice->_part = NULL;
+}
+
+void MidiDriver_ADLIB::mc_inc_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11) {
+ byte code;
+ AdLibPart *part = voice->_part;
+
+ code = struct10_ontimer(s10, s11);
+
+ if (code & 1) {
+ switch (s11->param) {
+ case 0:
+ voice->_vol_2 = s10->start_value + s11->modify_val;
+ if (!_scummSmallHeader) {
+ adlib_set_param(voice->_channel, 0,
+ volume_table[lookup_table[voice->_vol_2]
+ [part->_vol_eff >> 2]]);
+ } else {
+ adlib_set_param(voice->_channel, 0, voice->_vol_2);
+ }
+ break;
+ case 13:
+ voice->_vol_1 = s10->start_value + s11->modify_val;
+ if (voice->_twochan && !_scummSmallHeader) {
+ adlib_set_param(voice->_channel, 13,
+ volume_table[lookup_table[voice->_vol_1]
+ [part->_vol_eff >> 2]]);
+ } else {
+ adlib_set_param(voice->_channel, 13, voice->_vol_1);
+ }
+ break;
+ case 30:
+ s11->s10->modwheel = (char)s11->modify_val;
+ break;
+ case 31:
+ s11->s10->unk3 = (char)s11->modify_val;
+ break;
+ default:
+ adlib_set_param(voice->_channel, s11->param,
+ s10->start_value + s11->modify_val);
+ break;
+ }
+ }
+
+ if (code & 2 && s11->flag0x10)
+ adlib_key_onoff(voice->_channel);
+}
+
+void MidiDriver_ADLIB::adlib_key_off(int chan){
+ byte reg = chan + 0xB0;
+ adlib_write(reg, adlib_get_reg_value(reg) & ~0x20);
+}
+
+byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) {
+ byte result = 0;
+ int i;
+
+ if (s10->count && (s10->count -= 17) <= 0) {
+ s10->active = 0;
+ return 0;
+ }
+
+ i = s10->cur_val + s10->speed_hi;
+ s10->speed_lo_counter += s10->speed_lo;
+ if (s10->speed_lo_counter >= s10->speed_lo_max) {
+ s10->speed_lo_counter -= s10->speed_lo_max;
+ i += s10->direction;
+ }
+ if (s10->cur_val != i || s10->modwheel != s10->modwheel_last) {
+ s10->cur_val = i;
+ s10->modwheel_last = s10->modwheel;
+ i = lookup_volume(i, s10->modwheel_last);
+ if (i != s11->modify_val) {
+ s11->modify_val = i;
+ result = 1;
+ }
+ }
+
+ if (!--s10->num_steps) {
+ s10->active++;
+ if (s10->active > 4) {
+ if (s10->loop) {
+ s10->active = 1;
+ result |= 2;
+ struct10_setup(s10);
+ } else {
+ s10->active = 0;
+ }
+ } else {
+ struct10_setup(s10);
+ }
+ }
+
+ return result;
+}
+
+void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) {
+ const AdLibSetParams *as;
+ byte reg;
+
+ assert(channel >= 0 && channel < 9);
+
+ if (param <= 12) {
+ reg = channel_mappings_2[channel];
+ } else if (param <= 25) {
+ param -= 13;
+ reg = channel_mappings[channel];
+ } else if (param <= 27) {
+ param -= 13;
+ reg = channel;
+ } else if (param == 28 || param == 29) {
+ if (param == 28)
+ value -= 15;
+ else
+ value -= 383;
+ value <<= 4;
+ channel_table_2[channel] = value;
+ adlib_playnote(channel, curnote_table[channel] + value);
+ return;
+ } else {
+ return;
+ }
+
+ as = &adlib_setparam_table[param];
+ if (as->d)
+ value = as->d - value;
+ reg += as->a;
+ adlib_write(reg, (adlib_get_reg_value(reg) & ~as->c) | (((byte)value) << as->b));
+}
+
+void MidiDriver_ADLIB::adlib_key_onoff(int channel) {
+ byte val;
+ byte reg = channel + 0xB0;
+ assert(channel >= 0 && channel < 9);
+
+ val = adlib_get_reg_value(reg);
+ adlib_write(reg, val & ~0x20);
+ adlib_write(reg, val | 0x20);
+}
+
+void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) {
+ int b, c, d, e, f, g, h;
+ byte t;
+
+ b = s10->unk3;
+ f = s10->active - 1;
+
+ t = s10->table_a[f];
+ e = num_steps_table[lookup_table[t & 0x7F][b]];
+ if (t & 0x80) {
+ e = random_nr(e);
+ }
+ if (e == 0)
+ e++;
+
+ s10->num_steps = s10->speed_lo_max = e;
+
+ if (f != 2) {
+ c = s10->max_value;
+ g = s10->start_value;
+ t = s10->table_b[f];
+ d = lookup_volume(c, (t & 0x7F) - 31);
+ if (t & 0x80) {
+ d = random_nr(d);
+ }
+ if (d + g > c) {
+ h = c - g;
+ } else {
+ h = d;
+ if (d + g < 0)
+ h = -g;
+ }
+ h -= s10->cur_val;
+ } else {
+ h = 0;
+ }
+
+ s10->speed_hi = h / e;
+ if (h < 0) {
+ h = -h;
+ s10->direction = -1;
+ } else {
+ s10->direction = 1;
+ }
+
+ s10->speed_lo = h % e;
+ s10->speed_lo_counter = 0;
+}
+
+void MidiDriver_ADLIB::adlib_playnote(int channel, int note) {
+ byte old, oct, notex;
+ int note2;
+ int i;
+
+ note2 = (note >> 7) - 4;
+ note2 = (note2 < 128) ? note2 : 0;
+
+ oct = (note2 / 12);
+ if (oct > 7)
+ oct = 7 << 2;
+ else
+ oct <<= 2;
+ notex = note2 % 12 + 3;
+
+ old = adlib_get_reg_value(channel + 0xB0);
+ if (old & 0x20) {
+ old &= ~0x20;
+ if (oct > old) {
+ if (notex < 6) {
+ notex += 12;
+ oct -= 4;
+ }
+ } else if (oct < old) {
+ if (notex > 11) {
+ notex -= 12;
+ oct += 4;
+ }
+ }
+ }
+
+ i = (notex << 3) + ((note >> 4) & 0x7);
+ adlib_write(channel + 0xA0, note_to_f_num[i]);
+ adlib_write(channel + 0xB0, oct | 0x20);
+}
+
+int MidiDriver_ADLIB::random_nr(int a) {
+ static byte _rand_seed = 1;
+ if (_rand_seed & 1) {
+ _rand_seed >>= 1;
+ _rand_seed ^= 0xB8;
+ } else {
+ _rand_seed >>= 1;
+ }
+ return _rand_seed * a >> 8;
+}
+
+void MidiDriver_ADLIB::part_key_off(AdLibPart *part, byte note) {
+ AdLibVoice *voice;
+
+ for (voice = part->_voice; voice; voice = voice->_next) {
+ if (voice->_note == note) {
+ if (part->_pedal)
+ voice->_waitforpedal = true;
+ else
+ mc_off(voice);
+ }
+ }
+}
+
+void MidiDriver_ADLIB::part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity) {
+ AdLibVoice *voice;
+
+ voice = allocate_voice(part->_pri_eff);
+ if (!voice)
+ return;
+
+ link_mc(part, voice);
+ mc_key_on(voice, instr, note, velocity);
+}
+
+AdLibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) {
+ AdLibVoice *ac, *best = NULL;
+ int i;
+
+ for (i = 0; i < 9; i++) {
+ if (++_voice_index >= 9)
+ _voice_index = 0;
+ ac = &_voices[_voice_index];
+ if (!ac->_part)
+ return ac;
+ if (!ac->_next) {
+ if (ac->_part->_pri_eff <= pri) {
+ pri = ac->_part->_pri_eff;
+ best = ac;
+ }
+ }
+ }
+
+ /* SCUMM V3 games don't have note priorities, first comes wins. */
+ if (_scummSmallHeader)
+ return NULL;
+
+ if (best)
+ mc_off(best);
+ return best;
+}
+
+void MidiDriver_ADLIB::link_mc(AdLibPart *part, AdLibVoice *voice) {
+ voice->_part = part;
+ voice->_next = (AdLibVoice *)part->_voice;
+ part->_voice = voice;
+ voice->_prev = NULL;
+
+ if (voice->_next)
+ voice->_next->_prev = voice;
+}
+
+void MidiDriver_ADLIB::mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity) {
+ AdLibPart *part = voice->_part;
+ int c;
+ byte vol_1, vol_2;
+
+ voice->_twochan = instr->feedback & 1;
+ voice->_note = note;
+ voice->_waitforpedal = false;
+ voice->_duration = instr->duration;
+ if (voice->_duration != 0)
+ voice->_duration *= 63;
+
+ if (!_scummSmallHeader)
+ vol_1 = (instr->mod_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->mod_waveformSelect >> 2];
+ else
+ vol_1 = 0x3f - (instr->mod_scalingOutputLevel & 0x3F);
+ if (vol_1 > 0x3F)
+ vol_1 = 0x3F;
+ voice->_vol_1 = vol_1;
+
+ if (!_scummSmallHeader)
+ vol_2 = (instr->car_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->car_waveformSelect >> 2];
+ else
+ vol_2 = 0x3f - (instr->car_scalingOutputLevel & 0x3F);
+ if (vol_2 > 0x3F)
+ vol_2 = 0x3F;
+ voice->_vol_2 = vol_2;
+
+ c = part->_vol_eff >> 2;
+
+ if (!_scummSmallHeader) {
+ vol_2 = volume_table[lookup_table[vol_2][c]];
+ if (voice->_twochan)
+ vol_1 = volume_table[lookup_table[vol_1][c]];
+ }
+
+ adlib_setup_channel(voice->_channel, instr, vol_1, vol_2);
+ adlib_note_on_ex(voice->_channel, part->_transpose_eff + note, part->_detune_eff + (part->_pitchbend * part->_pitchbend_factor >> 6));
+
+ if (instr->flags_a & 0x80) {
+ mc_init_stuff(voice, &voice->_s10a, &voice->_s11a, instr->flags_a, &instr->extra_a);
+ } else {
+ voice->_s10a.active = 0;
+ }
+
+ if (instr->flags_b & 0x80) {
+ mc_init_stuff(voice, &voice->_s10b, &voice->_s11b, instr->flags_b, &instr->extra_b);
+ } else {
+ voice->_s10b.active = 0;
+ }
+}
+
+void MidiDriver_ADLIB::adlib_setup_channel(int chan, AdLibInstrument *instr, byte vol_1, byte vol_2) {
+ byte channel;
+
+ assert(chan >= 0 && chan < 9);
+
+ channel = channel_mappings[chan];
+ adlib_write(channel + 0x20, instr->mod_characteristic);
+ adlib_write(channel + 0x40, (instr->mod_scalingOutputLevel | 0x3F) - vol_1 );
+ adlib_write(channel + 0x60, 0xff & (~instr->mod_attackDecay));
+ adlib_write(channel + 0x80, 0xff & (~instr->mod_sustainRelease));
+ adlib_write(channel + 0xE0, instr->mod_waveformSelect);
+
+ channel = channel_mappings_2[chan];
+ adlib_write(channel + 0x20, instr->car_characteristic);
+ adlib_write(channel + 0x40, (instr->car_scalingOutputLevel | 0x3F) - vol_2 );
+ adlib_write(channel + 0x60, 0xff & (~instr->car_attackDecay));
+ adlib_write(channel + 0x80, 0xff & (~instr->car_sustainRelease));
+ adlib_write(channel + 0xE0, instr->car_waveformSelect);
+
+ adlib_write((byte)chan + 0xC0, instr->feedback);
+}
+
+void MidiDriver_ADLIB::adlib_note_on_ex(int chan, byte note, int mod)
+{
+ int code;
+ assert(chan >= 0 && chan < 9);
+ code = (note << 7) + mod;
+ curnote_table[chan] = code;
+ channel_table_2[chan] = 0;
+ adlib_playnote(chan, code);
+}
+
+void MidiDriver_ADLIB::mc_init_stuff(AdLibVoice *voice, Struct10 * s10,
+ Struct11 * s11, byte flags, InstrumentExtra * ie) {
+ AdLibPart *part = voice->_part;
+ s11->modify_val = 0;
+ s11->flag0x40 = flags & 0x40;
+ s10->loop = flags & 0x20;
+ s11->flag0x10 = flags & 0x10;
+ s11->param = param_table_1[flags & 0xF];
+ s10->max_value = maxval_table[flags & 0xF];
+ s10->unk3 = 31;
+ if (s11->flag0x40) {
+ s10->modwheel = part->_modwheel >> 2;
+ } else {
+ s10->modwheel = 31;
+ }
+
+ switch (s11->param) {
+ case 0:
+ s10->start_value = voice->_vol_2;
+ break;
+ case 13:
+ s10->start_value = voice->_vol_1;
+ break;
+ case 30:
+ s10->start_value = 31;
+ s11->s10->modwheel = 0;
+ break;
+ case 31:
+ s10->start_value = 0;
+ s11->s10->unk3 = 0;
+ break;
+ default:
+ s10->start_value = adlib_get_reg_value_param(voice->_channel, s11->param);
+ }
+
+ struct10_init(s10, ie);
+}
+
+void MidiDriver_ADLIB::struct10_init(Struct10 *s10, InstrumentExtra *ie) {
+ s10->active = 1;
+ if (!_scummSmallHeader) {
+ s10->cur_val = 0;
+ } else {
+ s10->cur_val = s10->start_value;
+ s10->start_value = 0;
+ }
+ s10->modwheel_last = 31;
+ s10->count = ie->a;
+ if (s10->count)
+ s10->count *= 63;
+ s10->table_a[0] = ie->b;
+ s10->table_a[1] = ie->d;
+ s10->table_a[2] = ie->f;
+ s10->table_a[3] = ie->g;
+
+ s10->table_b[0] = ie->c;
+ s10->table_b[1] = ie->e;
+ s10->table_b[2] = 0;
+ s10->table_b[3] = ie->h;
+
+ struct10_setup(s10);
+}
+
+int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) {
+ const AdLibSetParams *as;
+ byte val;
+ byte channel;
+
+ assert(chan >= 0 && chan < 9);
+
+ if (param <= 12) {
+ channel = channel_mappings_2[chan];
+ } else if (param <= 25) {
+ param -= 13;
+ channel = channel_mappings[chan];
+ } else if (param <= 27) {
+ param -= 13;
+ channel = chan;
+ } else if (param == 28) {
+ return 0xF;
+ } else if (param == 29) {
+ return 0x17F;
+ } else {
+ return 0;
+ }
+
+ as = &adlib_setparam_table[param];
+ val = adlib_get_reg_value(channel + as->a);
+ val &= as->c;
+ val >>= as->b;
+ if (as->d)
+ val = as->d - val;
+
+ return val;
+}
+
+void MidiDriver_ADLIB::adlib_note_on(int chan, byte note, int mod) {
+ int code;
+ assert(chan >= 0 && chan < 9);
+ code = (note << 7) + mod;
+ curnote_table[chan] = code;
+ adlib_playnote(chan, (int16) channel_table_2[chan] + code);
+}
+
+
+// Plugin interface
+
+class AdLibEmuMusicPlugin : public MusicPluginObject {
+public:
+ const char *getName() const {
+ return _s("AdLib Emulator");
+ }
+
+ const char *getId() const {
+ return "adlib";
+ }
+
+ MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+MusicDevices AdLibEmuMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_ADLIB));
+ return devices;
+}
+
+Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
+ *mididriver = new MidiDriver_ADLIB(g_system->getMixer());
+
+ return Common::kNoError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(ADLIB)
+ //REGISTER_PLUGIN_DYNAMIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(ADLIB, PLUGIN_TYPE_MUSIC, AdLibEmuMusicPlugin);
+//#endif
diff --git a/audio/softsynth/appleiigs.cpp b/audio/softsynth/appleiigs.cpp
new file mode 100644
index 0000000000..80159c79ce
--- /dev/null
+++ b/audio/softsynth/appleiigs.cpp
@@ -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 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 "audio/null.h"
+
+// Plugin interface
+// (This can only create a null driver since apple II gs support seeems not to be implemented
+// and also is not part of the midi driver architecture. But we need the plugin for the options
+// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.)
+
+class AppleIIGSMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("Apple II GS Emulator (NOT IMPLEMENTED)");
+ }
+
+ const char *getId() const {
+ return "appleIIgs";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices AppleIIGSMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_APPLEIIGS));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(APPLEIIGS)
+ //REGISTER_PLUGIN_DYNAMIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(APPLEIIGS, PLUGIN_TYPE_MUSIC, AppleIIGSMusicPlugin);
+//#endif
+
diff --git a/audio/softsynth/cms.cpp b/audio/softsynth/cms.cpp
new file mode 100644
index 0000000000..fcc15f127e
--- /dev/null
+++ b/audio/softsynth/cms.cpp
@@ -0,0 +1,376 @@
+/* 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 "audio/softsynth/cms.h"
+#include "audio/null.h"
+
+#include "common/textconsole.h"
+#include "common/translation.h"
+#include "common/debug.h"
+
+// CMS/Gameblaster Emulation taken from DosBox
+
+#define LEFT 0x00
+#define RIGHT 0x01
+
+static const byte envelope[8][64] = {
+ /* zero amplitude */
+ { 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, 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 },
+ /* maximum amplitude */
+ {15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
+ 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
+ 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
+ 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, },
+ /* single decay */
+ {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ /* repetitive decay */
+ {15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
+ /* single triangular */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 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 },
+ /* repetitive triangular */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
+ /* single attack */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ /* repetitive attack */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 }
+};
+
+static const int amplitude_lookup[16] = {
+ 0*32767/16, 1*32767/16, 2*32767/16, 3*32767/16,
+ 4*32767/16, 5*32767/16, 6*32767/16, 7*32767/16,
+ 8*32767/16, 9*32767/16, 10*32767/16, 11*32767/16,
+ 12*32767/16, 13*32767/16, 14*32767/16, 15*32767/16
+};
+
+void CMSEmulator::portWrite(int port, int val) {
+ switch (port) {
+ case 0x220:
+ portWriteIntern(0, 1, val);
+ break;
+
+ case 0x221:
+ _saa1099[0].selected_reg = val & 0x1f;
+ if (_saa1099[0].selected_reg == 0x18 || _saa1099[0].selected_reg == 0x19) {
+ /* clock the envelope channels */
+ if (_saa1099[0].env_clock[0])
+ envelope(0, 0);
+ if (_saa1099[0].env_clock[1])
+ envelope(0, 1);
+ }
+ break;
+
+ case 0x222:
+ portWriteIntern(1, 1, val);
+ break;
+
+ case 0x223:
+ _saa1099[1].selected_reg = val & 0x1f;
+ if (_saa1099[1].selected_reg == 0x18 || _saa1099[1].selected_reg == 0x19) {
+ /* clock the envelope channels */
+ if (_saa1099[1].env_clock[0])
+ envelope(1, 0);
+ if (_saa1099[1].env_clock[1])
+ envelope(1, 1);
+ }
+ break;
+
+ default:
+ warning("CMSEmulator got port: 0x%X", port);
+ break;
+ }
+}
+
+void CMSEmulator::readBuffer(int16 *buffer, const int numSamples) {
+ update(0, &buffer[0], numSamples);
+ update(1, &buffer[0], numSamples);
+}
+
+void CMSEmulator::envelope(int chip, int ch) {
+ SAA1099 *saa = &_saa1099[chip];
+ if (saa->env_enable[ch]) {
+ int step, mode, mask;
+ mode = saa->env_mode[ch];
+ /* step from 0..63 and then loop in steps 32..63 */
+ step = saa->env_step[ch] = ((saa->env_step[ch] + 1) & 0x3f) | (saa->env_step[ch] & 0x20);
+
+ mask = 15;
+ if (saa->env_bits[ch])
+ mask &= ~1; /* 3 bit resolution, mask LSB */
+
+ saa->channels[ch*3+0].envelope[ LEFT] =
+ saa->channels[ch*3+1].envelope[ LEFT] =
+ saa->channels[ch*3+2].envelope[ LEFT] = ::envelope[mode][step] & mask;
+ if (saa->env_reverse_right[ch] & 0x01) {
+ saa->channels[ch*3+0].envelope[RIGHT] =
+ saa->channels[ch*3+1].envelope[RIGHT] =
+ saa->channels[ch*3+2].envelope[RIGHT] = (15 - ::envelope[mode][step]) & mask;
+ } else {
+ saa->channels[ch*3+0].envelope[RIGHT] =
+ saa->channels[ch*3+1].envelope[RIGHT] =
+ saa->channels[ch*3+2].envelope[RIGHT] = ::envelope[mode][step] & mask;
+ }
+ } else {
+ /* envelope mode off, set all envelope factors to 16 */
+ saa->channels[ch*3+0].envelope[ LEFT] =
+ saa->channels[ch*3+1].envelope[ LEFT] =
+ saa->channels[ch*3+2].envelope[ LEFT] =
+ saa->channels[ch*3+0].envelope[RIGHT] =
+ saa->channels[ch*3+1].envelope[RIGHT] =
+ saa->channels[ch*3+2].envelope[RIGHT] = 16;
+ }
+}
+
+void CMSEmulator::update(int chip, int16 *buffer, int length) {
+ struct SAA1099 *saa = &_saa1099[chip];
+ int j, ch;
+
+ /* if the channels are disabled we're done */
+ if (!saa->all_ch_enable) {
+ /* init output data */
+ if (chip == 0) {
+ memset(buffer, 0, sizeof(int16)*length*2);
+ }
+ return;
+ }
+
+ if (chip == 0) {
+ memset(buffer, 0, sizeof(int16)*length*2);
+ }
+
+ for (ch = 0; ch < 2; ch++) {
+ switch (saa->noise_params[ch]) {
+ case 0: saa->noise[ch].freq = 31250.0 * 2; break;
+ case 1: saa->noise[ch].freq = 15625.0 * 2; break;
+ case 2: saa->noise[ch].freq = 7812.5 * 2; break;
+ case 3: saa->noise[ch].freq = saa->channels[ch * 3].freq; break;
+ }
+ }
+
+ /* fill all data needed */
+ for (j = 0; j < length; ++j) {
+ int output_l = 0, output_r = 0;
+
+ /* for each channel */
+ for (ch = 0; ch < 6; ch++) {
+ if (saa->channels[ch].freq == 0.0)
+ saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) /
+ (511.0 - (double)saa->channels[ch].frequency);
+
+ /* check the actual position in the square wave */
+ saa->channels[ch].counter -= saa->channels[ch].freq;
+ while (saa->channels[ch].counter < 0) {
+ /* calculate new frequency now after the half wave is updated */
+ saa->channels[ch].freq = (double)((2 * 15625) << saa->channels[ch].octave) /
+ (511.0 - (double)saa->channels[ch].frequency);
+
+ saa->channels[ch].counter += _sampleRate;
+ saa->channels[ch].level ^= 1;
+
+ /* eventually clock the envelope counters */
+ if (ch == 1 && saa->env_clock[0] == 0)
+ envelope(chip, 0);
+ if (ch == 4 && saa->env_clock[1] == 0)
+ envelope(chip, 1);
+ }
+
+ /* if the noise is enabled */
+ if (saa->channels[ch].noise_enable) {
+ /* if the noise level is high (noise 0: chan 0-2, noise 1: chan 3-5) */
+ if (saa->noise[ch/3].level & 1) {
+ /* subtract to avoid overflows, also use only half amplitude */
+ output_l -= saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16 / 2;
+ output_r -= saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16 / 2;
+ }
+ }
+
+ /* if the square wave is enabled */
+ if (saa->channels[ch].freq_enable) {
+ /* if the channel level is high */
+ if (saa->channels[ch].level & 1) {
+ output_l += saa->channels[ch].amplitude[ LEFT] * saa->channels[ch].envelope[ LEFT] / 16;
+ output_r += saa->channels[ch].amplitude[RIGHT] * saa->channels[ch].envelope[RIGHT] / 16;
+ }
+ }
+ }
+
+ for (ch = 0; ch < 2; ch++) {
+ /* check the actual position in noise generator */
+ saa->noise[ch].counter -= saa->noise[ch].freq;
+ while (saa->noise[ch].counter < 0) {
+ saa->noise[ch].counter += _sampleRate;
+ if (((saa->noise[ch].level & 0x4000) == 0) == ((saa->noise[ch].level & 0x0040) == 0) )
+ saa->noise[ch].level = (saa->noise[ch].level << 1) | 1;
+ else
+ saa->noise[ch].level <<= 1;
+ }
+ }
+ /* write sound data to the buffer */
+ buffer[j*2] += output_l / 6;
+ buffer[j*2+1] += output_r / 6;
+ }
+}
+
+void CMSEmulator::portWriteIntern(int chip, int offset, int data) {
+ SAA1099 *saa = &_saa1099[chip];
+ int reg = saa->selected_reg;
+ int ch;
+
+ switch (reg) {
+ /* channel i amplitude */
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ ch = reg & 7;
+ saa->channels[ch].amplitude[LEFT] = amplitude_lookup[data & 0x0f];
+ saa->channels[ch].amplitude[RIGHT] = amplitude_lookup[(data >> 4) & 0x0f];
+ break;
+
+ /* channel i frequency */
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ ch = reg & 7;
+ saa->channels[ch].frequency = data & 0xff;
+ break;
+
+ /* channel i octave */
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ ch = (reg - 0x10) << 1;
+ saa->channels[ch + 0].octave = data & 0x07;
+ saa->channels[ch + 1].octave = (data >> 4) & 0x07;
+ break;
+
+ /* channel i frequency enable */
+ case 0x14:
+ saa->channels[0].freq_enable = data & 0x01;
+ saa->channels[1].freq_enable = data & 0x02;
+ saa->channels[2].freq_enable = data & 0x04;
+ saa->channels[3].freq_enable = data & 0x08;
+ saa->channels[4].freq_enable = data & 0x10;
+ saa->channels[5].freq_enable = data & 0x20;
+ break;
+
+ /* channel i noise enable */
+ case 0x15:
+ saa->channels[0].noise_enable = data & 0x01;
+ saa->channels[1].noise_enable = data & 0x02;
+ saa->channels[2].noise_enable = data & 0x04;
+ saa->channels[3].noise_enable = data & 0x08;
+ saa->channels[4].noise_enable = data & 0x10;
+ saa->channels[5].noise_enable = data & 0x20;
+ break;
+
+ /* noise generators parameters */
+ case 0x16:
+ saa->noise_params[0] = data & 0x03;
+ saa->noise_params[1] = (data >> 4) & 0x03;
+ break;
+
+ /* envelope generators parameters */
+ case 0x18:
+ case 0x19:
+ ch = reg - 0x18;
+ saa->env_reverse_right[ch] = data & 0x01;
+ saa->env_mode[ch] = (data >> 1) & 0x07;
+ saa->env_bits[ch] = data & 0x10;
+ saa->env_clock[ch] = data & 0x20;
+ saa->env_enable[ch] = data & 0x80;
+ /* reset the envelope */
+ saa->env_step[ch] = 0;
+ break;
+
+ /* channels enable & reset generators */
+ case 0x1c:
+ saa->all_ch_enable = data & 0x01;
+ saa->sync_state = data & 0x02;
+ if (data & 0x02) {
+ int i;
+ /* Synch & Reset generators */
+ for (i = 0; i < 6; i++) {
+ saa->channels[i].level = 0;
+ saa->channels[i].counter = 0.0;
+ }
+ }
+ break;
+
+ default:
+ // The CMS allows all registers to be written, so we just output some debug
+ // message here
+ debug(5, "CMS Unknown write to reg %x with %x",reg, data);
+ }
+}
+
+class CMSMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("Creative Music System Emulator");
+ }
+
+ const char *getId() const {
+ return "cms";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices CMSMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_CMS));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(CMS)
+ //REGISTER_PLUGIN_DYNAMIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(CMS, PLUGIN_TYPE_MUSIC, CMSMusicPlugin);
+//#endif
diff --git a/audio/softsynth/cms.h b/audio/softsynth/cms.h
new file mode 100644
index 0000000000..d5bb7f0a42
--- /dev/null
+++ b/audio/softsynth/cms.h
@@ -0,0 +1,92 @@
+/* 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$
+ */
+
+#ifndef SOUND_SOFTSYNTH_CMS_H
+#define SOUND_SOFTSYNTH_CMS_H
+
+#include "common/scummsys.h"
+
+/* this structure defines a channel */
+struct saa1099_channel {
+ int frequency; /* frequency (0x00..0xff) */
+ int freq_enable; /* frequency enable */
+ int noise_enable; /* noise enable */
+ int octave; /* octave (0x00..0x07) */
+ int amplitude[2]; /* amplitude (0x00..0x0f) */
+ int envelope[2]; /* envelope (0x00..0x0f or 0x10 == off) */
+
+ /* vars to simulate the square wave */
+ double counter;
+ double freq;
+ int level;
+};
+
+/* this structure defines a noise channel */
+struct saa1099_noise {
+ /* vars to simulate the noise generator output */
+ double counter;
+ double freq;
+ int level; /* noise polynomal shifter */
+};
+
+/* this structure defines a SAA1099 chip */
+struct SAA1099 {
+ int stream; /* our stream */
+ int noise_params[2]; /* noise generators parameters */
+ int env_enable[2]; /* envelope generators enable */
+ int env_reverse_right[2]; /* envelope reversed for right channel */
+ int env_mode[2]; /* envelope generators mode */
+ int env_bits[2]; /* non zero = 3 bits resolution */
+ int env_clock[2]; /* envelope clock mode (non-zero external) */
+ int env_step[2]; /* current envelope step */
+ int all_ch_enable; /* all channels enable */
+ int sync_state; /* sync all channels */
+ int selected_reg; /* selected register */
+ struct saa1099_channel channels[6]; /* channels */
+ struct saa1099_noise noise[2]; /* noise generators */
+};
+
+class CMSEmulator {
+public:
+ CMSEmulator(uint32 sampleRate) {
+ _sampleRate = sampleRate;
+ memset(_saa1099, 0, sizeof(SAA1099)*2);
+ }
+
+ ~CMSEmulator() { }
+
+ void portWrite(int port, int val);
+ void readBuffer(int16 *buffer, const int numSamples);
+private:
+ uint32 _sampleRate;
+
+ SAA1099 _saa1099[2];
+
+ void envelope(int chip, int ch);
+ void update(int chip, int16 *buffer, int length);
+ void portWriteIntern(int chip, int offset, int data);
+};
+
+
+#endif
diff --git a/audio/softsynth/emumidi.h b/audio/softsynth/emumidi.h
new file mode 100644
index 0000000000..35c81490e4
--- /dev/null
+++ b/audio/softsynth/emumidi.h
@@ -0,0 +1,116 @@
+/* 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$
+ */
+
+#ifndef SOUND_SOFTSYNTH_EMUMIDI_H
+#define SOUND_SOFTSYNTH_EMUMIDI_H
+
+#include "audio/audiostream.h"
+#include "audio/mididrv.h"
+#include "audio/mixer.h"
+
+#define FIXP_SHIFT 16
+
+class MidiDriver_Emulated : public Audio::AudioStream, public MidiDriver {
+protected:
+ bool _isOpen;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _mixerSoundHandle;
+
+private:
+ Common::TimerManager::TimerProc _timerProc;
+ void *_timerParam;
+
+ int _nextTick;
+ int _samplesPerTick;
+
+protected:
+ virtual void generateSamples(int16 *buf, int len) = 0;
+ virtual void onTimer() {}
+
+ int _baseFreq;
+
+public:
+ MidiDriver_Emulated(Audio::Mixer *mixer) : _mixer(mixer) {
+ _isOpen = false;
+
+ _timerProc = 0;
+ _timerParam = 0;
+
+ _nextTick = 0;
+ _samplesPerTick = 0;
+
+ _baseFreq = 250;
+ }
+
+ int open() {
+ _isOpen = true;
+
+ int d = getRate() / _baseFreq;
+ int r = getRate() % _baseFreq;
+
+ // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ
+ // but less prone to arithmetic overflow.
+
+ _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq;
+ return 0;
+ }
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ _timerProc = timer_proc;
+ _timerParam = timer_param;
+ }
+
+ uint32 getBaseTempo() { return 1000000 / _baseFreq; }
+
+
+ // AudioStream API
+ int readBuffer(int16 *data, const int numSamples) {
+ const int stereoFactor = isStereo() ? 2 : 1;
+ int len = numSamples / stereoFactor;
+ int step;
+
+ do {
+ step = len;
+ if (step > (_nextTick >> FIXP_SHIFT))
+ step = (_nextTick >> FIXP_SHIFT);
+
+ generateSamples(data, step);
+
+ _nextTick -= step << FIXP_SHIFT;
+ if (!(_nextTick >> FIXP_SHIFT)) {
+ if (_timerProc)
+ (*_timerProc)(_timerParam);
+ onTimer();
+ _nextTick += _samplesPerTick;
+ }
+ data += step * stereoFactor;
+ len -= step;
+ } while (len);
+
+ return numSamples;
+ }
+ bool endOfData() const { return false; }
+};
+
+#endif
diff --git a/audio/softsynth/fluidsynth.cpp b/audio/softsynth/fluidsynth.cpp
new file mode 100644
index 0000000000..bd016548ec
--- /dev/null
+++ b/audio/softsynth/fluidsynth.cpp
@@ -0,0 +1,254 @@
+/* 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_FLUIDSYNTH
+
+#include "common/config-manager.h"
+#include "audio/musicplugin.h"
+#include "audio/mpu401.h"
+#include "audio/softsynth/emumidi.h"
+
+#include <fluidsynth.h>
+
+class MidiDriver_FluidSynth : public MidiDriver_Emulated {
+private:
+ MidiChannel_MPU401 _midiChannels[16];
+ fluid_settings_t *_settings;
+ fluid_synth_t *_synth;
+ int _soundFont;
+ int _outputRate;
+ Audio::SoundHandle _handle;
+
+protected:
+ // Because GCC complains about casting from const to non-const...
+ void setInt(const char *name, int val);
+ void setNum(const char *name, double num);
+ void setStr(const char *name, const char *str);
+
+ void generateSamples(int16 *buf, int len);
+
+public:
+ MidiDriver_FluidSynth(Audio::Mixer *mixer);
+
+ int open();
+ void close();
+ void send(uint32 b);
+
+ MidiChannel *allocateChannel();
+ MidiChannel *getPercussionChannel();
+
+ // AudioStream API
+ bool isStereo() const { return true; }
+ int getRate() const { return _outputRate; }
+};
+
+// MidiDriver method implementations
+
+MidiDriver_FluidSynth::MidiDriver_FluidSynth(Audio::Mixer *mixer)
+ : MidiDriver_Emulated(mixer) {
+
+ for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) {
+ _midiChannels[i].init(this, i);
+ }
+
+ // It ought to be possible to get FluidSynth to generate samples at
+ // lower
+
+ _outputRate = _mixer->getOutputRate();
+ if (_outputRate < 22050)
+ _outputRate = 22050;
+ else if (_outputRate > 96000)
+ _outputRate = 96000;
+}
+
+void MidiDriver_FluidSynth::setInt(const char *name, int val) {
+ char *name2 = strdup(name);
+
+ fluid_settings_setint(_settings, name2, val);
+ free(name2);
+}
+
+void MidiDriver_FluidSynth::setNum(const char *name, double val) {
+ char *name2 = strdup(name);
+
+ fluid_settings_setnum(_settings, name2, val);
+ free(name2);
+}
+
+void MidiDriver_FluidSynth::setStr(const char *name, const char *val) {
+ char *name2 = strdup(name);
+ char *val2 = strdup(val);
+
+ fluid_settings_setstr(_settings, name2, val2);
+ free(name2);
+ free(val2);
+}
+
+int MidiDriver_FluidSynth::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ if (!ConfMan.hasKey("soundfont"))
+ error("FluidSynth requires a 'soundfont' setting");
+
+ _settings = new_fluid_settings();
+
+ // The default gain setting is ridiculously low - at least for me. This
+ // cannot be fixed by ScummVM's volume settings because they can only
+ // soften the sound, not amplify it, so instead we add an option to
+ // adjust the gain of FluidSynth itself.
+
+ double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
+
+ setNum("synth.gain", gain);
+ setNum("synth.sample-rate", _outputRate);
+
+ _synth = new_fluid_synth(_settings);
+
+ // In theory, this ought to reduce CPU load... but it doesn't make any
+ // noticeable difference for me, so disable it for now.
+
+ // fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR);
+ // fluid_synth_set_reverb_on(_synth, 0);
+ // fluid_synth_set_chorus_on(_synth, 0);
+
+ const char *soundfont = ConfMan.get("soundfont").c_str();
+
+ _soundFont = fluid_synth_sfload(_synth, soundfont, 1);
+ if (_soundFont == -1)
+ error("Failed loading custom sound font '%s'", soundfont);
+
+ MidiDriver_Emulated::open();
+
+ // The MT-32 emulator uses kSFXSoundType here. I don't know why.
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ return 0;
+}
+
+void MidiDriver_FluidSynth::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_handle);
+
+ if (_soundFont != -1)
+ fluid_synth_sfunload(_synth, _soundFont, 1);
+
+ delete_fluid_synth(_synth);
+ delete_fluid_settings(_settings);
+}
+
+void MidiDriver_FluidSynth::send(uint32 b) {
+ //byte param3 = (byte) ((b >> 24) & 0xFF);
+ uint param2 = (byte) ((b >> 16) & 0xFF);
+ uint param1 = (byte) ((b >> 8) & 0xFF);
+ byte cmd = (byte) (b & 0xF0);
+ byte chan = (byte) (b & 0x0F);
+
+ switch (cmd) {
+ case 0x80: // Note Off
+ fluid_synth_noteoff(_synth, chan, param1);
+ break;
+ case 0x90: // Note On
+ fluid_synth_noteon(_synth, chan, param1, param2);
+ break;
+ case 0xA0: // Aftertouch
+ break;
+ case 0xB0: // Control Change
+ fluid_synth_cc(_synth, chan, param1, param2);
+ break;
+ case 0xC0: // Program Change
+ fluid_synth_program_change(_synth, chan, param1);
+ break;
+ case 0xD0: // Channel Pressure
+ break;
+ case 0xE0: // Pitch Bend
+ fluid_synth_pitch_bend(_synth, chan, (param2 << 7) | param1);
+ break;
+ case 0xF0: // SysEx
+ // We should never get here! SysEx information has to be
+ // sent via high-level semantic methods.
+ warning("MidiDriver_FluidSynth: Receiving SysEx command on a send() call");
+ break;
+ default:
+ warning("MidiDriver_FluidSynth: Unknown send() command 0x%02X", cmd);
+ break;
+ }
+}
+
+MidiChannel *MidiDriver_FluidSynth::allocateChannel() {
+ for (int i = 0; i < ARRAYSIZE(_midiChannels); i++) {
+ if (i != 9 && _midiChannels[i].allocate())
+ return &_midiChannels[i];
+ }
+ return NULL;
+}
+
+MidiChannel *MidiDriver_FluidSynth::getPercussionChannel() {
+ return &_midiChannels[9];
+}
+
+void MidiDriver_FluidSynth::generateSamples(int16 *data, int len) {
+ fluid_synth_write_s16(_synth, len, data, 0, 2, data, 1, 2);
+}
+
+
+// Plugin interface
+
+class FluidSynthMusicPlugin : public MusicPluginObject {
+public:
+ const char *getName() const {
+ return "FluidSynth";
+ }
+
+ const char *getId() const {
+ return "fluidsynth";
+ }
+
+ MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+MusicDevices FluidSynthMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_GM));
+ return devices;
+}
+
+Common::Error FluidSynthMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
+ *mididriver = new MidiDriver_FluidSynth(g_system->getMixer());
+
+ return Common::kNoError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(FLUIDSYNTH)
+ //REGISTER_PLUGIN_DYNAMIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(FLUIDSYNTH, PLUGIN_TYPE_MUSIC, FluidSynthMusicPlugin);
+//#endif
+
+#endif
diff --git a/audio/softsynth/fmtowns_pc98/towns_audio.cpp b/audio/softsynth/fmtowns_pc98/towns_audio.cpp
new file mode 100644
index 0000000000..e019aa2481
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_audio.cpp
@@ -0,0 +1,1583 @@
+/* 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 "audio/softsynth/fmtowns_pc98/towns_audio.h"
+#include "common/endian.h"
+#include "backends/audiocd/audiocd.h"
+
+
+class TownsAudio_PcmChannel {
+friend class TownsAudioInterface;
+public:
+ TownsAudio_PcmChannel();
+ ~TownsAudio_PcmChannel();
+
+private:
+ void loadExtData(uint8 *buffer, uint32 size);
+ void setupLoop(uint32 start, uint32 len);
+ void clear();
+
+ void envAttack();
+ void envDecay();
+ void envSustain();
+ void envRelease();
+
+ uint8 *curInstrument;
+ uint8 note;
+ uint8 velo;
+
+ int8 *data;
+ int8 *dataEnd;
+
+ int8 *loopEnd;
+ uint32 loopLen;
+
+ uint16 stepNote;
+ uint16 stepPitch;
+ uint16 step;
+
+ uint8 panLeft;
+ uint8 panRight;
+
+ uint32 pos;
+
+ uint8 envTotalLevel;
+ uint8 envAttackRate;
+ uint8 envDecayRate;
+ uint8 envSustainLevel;
+ uint8 envSustainRate;
+ uint8 envReleaseRate;
+
+ int16 envStep;
+ int16 envCurrentLevel;
+
+ EnvelopeState envState;
+
+ int8 *extData;
+};
+
+class TownsAudio_WaveTable {
+friend class TownsAudioInterface;
+public:
+ TownsAudio_WaveTable();
+ ~TownsAudio_WaveTable();
+
+private:
+ void readHeader(const uint8 *buffer);
+ void readData(const uint8 *buffer);
+ void clear();
+
+ char name[9];
+ int32 id;
+ uint32 size;
+ uint32 loopStart;
+ uint32 loopLen;
+ uint16 rate;
+ uint16 rateOffs;
+ uint16 baseNote;
+ int8 *data;
+};
+
+TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver) : TownsPC98_FmSynth(mixer, kTypeTowns),
+ _fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0),
+ _baserate(55125.0f / (float)mixer->getOutputRate()), _tickLength(0), _timer(0), _drv(driver),
+ _pcmSfxChanMask(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume),
+ _outputVolumeFlags(0), _outputMuteFlags(0), _pcmChanOut(0), _pcmChanReserved(0), _pcmChanKeyPressed(0),
+ _pcmChanEffectPlaying(0), _pcmChanKeyPlaying(0), _ready(false) {
+
+#define INTCB(x) &TownsAudioInterface::intf_##x
+ static const TownsAudioIntfCallback intfCb[] = {
+ // 0
+ INTCB(reset),
+ INTCB(keyOn),
+ INTCB(keyOff),
+ INTCB(setPanPos),
+ // 4
+ INTCB(setInstrument),
+ INTCB(loadInstrument),
+ INTCB(notImpl),
+ INTCB(setPitch),
+ // 8
+ INTCB(setLevel),
+ INTCB(chanOff),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 12
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 16
+ INTCB(notImpl),
+ INTCB(writeReg),
+ INTCB(notImpl),
+ INTCB(writeRegBuffer),
+ // 20
+ INTCB(readRegBuffer),
+ INTCB(setTimerA),
+ INTCB(setTimerB),
+ INTCB(enableTimerA),
+ // 24
+ INTCB(enableTimerB),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 28
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 32
+ INTCB(loadSamples),
+ INTCB(reserveEffectChannels),
+ INTCB(loadWaveTable),
+ INTCB(unloadWaveTable),
+ // 36
+ INTCB(notImpl),
+ INTCB(pcmPlayEffect),
+ INTCB(notImpl),
+ INTCB(pcmChanOff),
+ // 40
+ INTCB(pcmEffectPlaying),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 44
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 48
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(fmKeyOn),
+ INTCB(fmKeyOff),
+ // 52
+ INTCB(fmSetPanPos),
+ INTCB(fmSetInstrument),
+ INTCB(fmLoadInstrument),
+ INTCB(notImpl),
+ // 56
+ INTCB(fmSetPitch),
+ INTCB(fmSetLevel),
+ INTCB(fmReset),
+ INTCB(notImpl),
+ // 60
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 64
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(setOutputVolume),
+ // 68
+ INTCB(resetOutputVolume),
+ INTCB(notImpl),
+ INTCB(updateOutputVolume),
+ INTCB(notImpl),
+ // 72
+ INTCB(notImpl),
+ INTCB(cdaToggle),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 76
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ INTCB(notImpl),
+ // 80
+ INTCB(pcmUpdateEnvelopeGenerator),
+ INTCB(notImpl)
+ };
+#undef INTCB
+
+ _intfOpcodes = intfCb;
+
+ memset(_fmSaveReg, 0, sizeof(_fmSaveReg));
+ memset(_outputLevel, 0, sizeof(_outputLevel));
+
+ _timerBase = (uint32)(_baserate * 1000000.0f);
+ _tickLength = 2 * _timerBase;
+}
+
+TownsAudioInterface::~TownsAudioInterface() {
+ _ready = false;
+ deinit();
+
+ delete[] _fmSaveReg[0];
+ delete[] _fmSaveReg[1];
+ delete[] _fmInstruments;
+ delete[] _pcmInstruments;
+ delete[] _waveTables;
+ delete[] _pcmChan;
+}
+
+bool TownsAudioInterface::init() {
+ if (_ready)
+ return true;
+
+ if (!TownsPC98_FmSynth::init())
+ return false;
+
+ _fmSaveReg[0] = new uint8[256];
+ _fmSaveReg[1] = new uint8[256];
+ _fmInstruments = new uint8[128 * 48];
+ _pcmInstruments = new uint8[32 * 128];
+ _waveTables = new TownsAudio_WaveTable[128];
+ _pcmChan = new TownsAudio_PcmChannel[8];
+
+ _timer = 0;
+
+ setVolumeChannelMasks(-1, 0);
+
+ _ready = true;
+ callback(0);
+
+ return true;
+}
+
+int TownsAudioInterface::callback(int command, ...) {
+ if (!_ready)
+ return 1;
+
+ va_list args;
+ va_start(args, command);
+
+ if (command > 81) {
+ va_end(args);
+ return 4;
+ }
+
+ int res = (this->*_intfOpcodes[command])(args);
+
+ va_end(args);
+ return res;
+}
+
+void TownsAudioInterface::setMusicVolume(int volume) {
+ _musicVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
+ setVolumeIntern(_musicVolume, _sfxVolume);
+}
+
+void TownsAudioInterface::setSoundEffectVolume(int volume) {
+ _sfxVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
+ setVolumeIntern(_musicVolume, _sfxVolume);
+}
+
+void TownsAudioInterface::setSoundEffectChanMask(int mask) {
+ _pcmSfxChanMask = mask >> 6;
+ mask &= 0x3f;
+ setVolumeChannelMasks(~mask, mask);
+}
+
+void TownsAudioInterface::nextTickEx(int32 *buffer, uint32 bufferSize) {
+ if (!_ready)
+ return;
+
+ for (uint32 i = 0; i < bufferSize; i++) {
+ _timer += _tickLength;
+ while (_timer > 0x514767) {
+ _timer -= 0x514767;
+
+ for (int ii = 0; ii < 8; ii++) {
+ if ((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii])) {
+ TownsAudio_PcmChannel *s = &_pcmChan[ii];
+ s->pos += s->step;
+
+ if (&s->data[s->pos >> 11] >= s->loopEnd) {
+ if (s->loopLen) {
+ s->pos -= s->loopLen;
+ } else {
+ s->pos = 0;
+ _pcmChanEffectPlaying &= ~_chanFlags[ii];
+ _pcmChanKeyPlaying &= ~_chanFlags[ii];
+ }
+ }
+ }
+ }
+ }
+
+ int32 finOutL = 0;
+ int32 finOutR = 0;
+
+ for (int ii = 0; ii < 8; ii++) {
+ if (_pcmChanOut & _chanFlags[ii]) {
+ int32 o = _pcmChan[ii].data[_pcmChan[ii].pos >> 11] * _pcmChan[ii].velo;
+ if ((1 << ii) & (~_pcmSfxChanMask))
+ o = (o * _musicVolume) / Audio::Mixer::kMaxMixerVolume;
+ if ((1 << ii) & _pcmSfxChanMask)
+ o = (o * _sfxVolume) / Audio::Mixer::kMaxMixerVolume;
+ if (_pcmChan[ii].panLeft)
+ finOutL += ((o * _pcmChan[ii].panLeft) >> 3);
+ if (_pcmChan[ii].panRight)
+ finOutR += ((o * _pcmChan[ii].panRight) >> 3);
+ if (!((_pcmChanKeyPlaying & _chanFlags[ii]) || (_pcmChanEffectPlaying & _chanFlags[ii])))
+ _pcmChanOut &= ~_chanFlags[ii];
+ }
+ }
+
+ buffer[i << 1] += finOutL;
+ buffer[(i << 1) + 1] += finOutR;
+ }
+}
+
+void TownsAudioInterface::timerCallbackA() {
+ Common::StackLock lock(_mutex);
+ if (_drv && _ready)
+ _drv->timerCallback(0);
+}
+
+void TownsAudioInterface::timerCallbackB() {
+ Common::StackLock lock(_mutex);
+ if (_ready) {
+ if (_drv)
+ _drv->timerCallback(1);
+ callback(80);
+ }
+}
+
+int TownsAudioInterface::intf_reset(va_list &args) {
+ fmReset();
+ pcmReset();
+ callback(68);
+ return 0;
+}
+
+int TownsAudioInterface::intf_keyOn(va_list &args) {
+ int chan = va_arg(args, int);
+ int note = va_arg(args, int);
+ int velo = va_arg(args, int);
+ return (chan & 0x40) ? pcmKeyOn(chan, note, velo) : fmKeyOn(chan, note, velo);
+}
+
+int TownsAudioInterface::intf_keyOff(va_list &args) {
+ int chan = va_arg(args, int);
+ return (chan & 0x40) ? pcmKeyOff(chan) : fmKeyOff(chan);
+}
+
+int TownsAudioInterface::intf_setPanPos(va_list &args) {
+ int chan = va_arg(args, int);
+ int mode = va_arg(args, int);
+ return (chan & 0x40) ? pcmSetPanPos(chan, mode) : fmSetPanPos(chan, mode);
+}
+
+int TownsAudioInterface::intf_setInstrument(va_list &args) {
+ int chan = va_arg(args, int);
+ int instrId = va_arg(args, int);
+ return (chan & 0x40) ? pcmSetInstrument(chan, instrId) : fmSetInstrument(chan, instrId);
+}
+
+int TownsAudioInterface::intf_loadInstrument(va_list &args) {
+ int chanType = va_arg(args, int);
+ int instrId = va_arg(args, int);
+ uint8 *instrData = va_arg(args, uint8 *);
+ return (chanType & 0x40) ? pcmLoadInstrument(instrId, instrData) : fmLoadInstrument(instrId, instrData);
+}
+
+int TownsAudioInterface::intf_setPitch(va_list &args) {
+ int chan = va_arg(args, int);
+ int16 pitch = (int16)(va_arg(args, int) & 0xffff);
+ return (chan & 0x40) ? pcmSetPitch(chan, pitch) : fmSetPitch(chan, pitch);
+}
+
+int TownsAudioInterface::intf_setLevel(va_list &args) {
+ int chan = va_arg(args, int);
+ int lvl = va_arg(args, int);
+ return (chan & 0x40) ? pcmSetLevel(chan, lvl) : fmSetLevel(chan, lvl);
+}
+
+int TownsAudioInterface::intf_chanOff(va_list &args) {
+ int chan = va_arg(args, int);
+ return (chan & 0x40) ? pcmChanOff(chan) : fmChanOff(chan);
+}
+
+int TownsAudioInterface::intf_writeReg(va_list &args) {
+ int part = va_arg(args, int) ? 1 : 0;
+ int reg = va_arg(args, int);
+ int val = va_arg(args, int);
+ if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xb6))
+ return 3;
+
+ bufferedWriteReg(part, reg, val);
+ return 0;
+}
+
+int TownsAudioInterface::intf_writeRegBuffer(va_list &args) {
+ int part = va_arg(args, int) ? 1 : 0;
+ int reg = va_arg(args, int);
+ int val = va_arg(args, int);
+
+ if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef))
+ return 3;
+
+ _fmSaveReg[part][reg] = val;
+ return 0;
+}
+
+int TownsAudioInterface::intf_readRegBuffer(va_list &args) {
+ int part = va_arg(args, int) ? 1 : 0;
+ int reg = va_arg(args, int);
+ uint8 *dst = va_arg(args, uint8 *);
+ *dst = 0;
+
+ if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef))
+ return 3;
+
+ *dst = _fmSaveReg[part][reg];
+ return 0;
+}
+
+int TownsAudioInterface::intf_setTimerA(va_list &args) {
+ int enable = va_arg(args, int);
+ int tempo = va_arg(args, int);
+
+ if (enable) {
+ bufferedWriteReg(0, 0x25, tempo & 3);
+ bufferedWriteReg(0, 0x24, (tempo >> 2) & 0xff);
+ bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x05);
+ } else {
+ bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xfa) | 0x10);
+ }
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_setTimerB(va_list &args) {
+ int enable = va_arg(args, int);
+ int tempo = va_arg(args, int);
+
+ if (enable) {
+ bufferedWriteReg(0, 0x26, tempo & 0xff);
+ bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x0A);
+ } else {
+ bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xf5) | 0x20);
+ }
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_enableTimerA(va_list &args) {
+ bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x15);
+ return 0;
+}
+
+int TownsAudioInterface::intf_enableTimerB(va_list &args) {
+ bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x2a);
+ return 0;
+}
+
+int TownsAudioInterface::intf_loadSamples(va_list &args) {
+ uint32 dest = va_arg(args, uint32);
+ int size = va_arg(args, int);
+ uint8 *src = va_arg(args, uint8*);
+
+ if (dest >= 65536 || size == 0 || size > 65536)
+ return 3;
+ if (size + dest > 65536)
+ return 5;
+
+ int dwIndex = _numWaveTables - 1;
+ for (uint32 t = _waveTablesTotalDataSize; dwIndex && (dest < t); dwIndex--)
+ t -= _waveTables[dwIndex].size;
+
+ TownsAudio_WaveTable *s = &_waveTables[dwIndex];
+ _waveTablesTotalDataSize -= s->size;
+ s->size = size;
+ s->readData(src);
+ _waveTablesTotalDataSize += s->size;
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_reserveEffectChannels(va_list &args) {
+ int numChan = va_arg(args, int);
+ if (numChan > 8)
+ return 3;
+ if ((numChan << 13) + _waveTablesTotalDataSize > 65536)
+ return 5;
+
+ if (numChan == _numReservedChannels)
+ return 0;
+
+ if (numChan < _numReservedChannels) {
+ int c = 8 - _numReservedChannels;
+ for (int i = numChan; i; i--) {
+ uint8 f = ~_chanFlags[c--];
+ _pcmChanEffectPlaying &= f;
+ }
+ } else {
+ int c = 7 - _numReservedChannels;
+ for (int i = numChan - _numReservedChannels; i; i--) {
+ uint8 f = ~_chanFlags[c--];
+ _pcmChanKeyPressed &= f;
+ _pcmChanKeyPlaying &= f;
+ }
+ }
+
+ static const uint8 reserveChanFlags[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF };
+ _numReservedChannels = numChan;
+ _pcmChanReserved = reserveChanFlags[_numReservedChannels];
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_loadWaveTable(va_list &args) {
+ uint8 *data = va_arg(args, uint8 *);
+ if (_numWaveTables > 127)
+ return 3;
+
+ TownsAudio_WaveTable w;
+ w.readHeader(data);
+ if (!w.size)
+ return 6;
+
+ if (_waveTablesTotalDataSize + w.size > 65504)
+ return 5;
+
+ for (int i = 0; i < _numWaveTables; i++) {
+ if (_waveTables[i].id == w.id)
+ return 10;
+ }
+
+ TownsAudio_WaveTable *s = &_waveTables[_numWaveTables++];
+ s->readHeader(data);
+
+ _waveTablesTotalDataSize += s->size;
+ callback(32, _waveTablesTotalDataSize, s->size, data + 32);
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_unloadWaveTable(va_list &args) {
+ int id = va_arg(args, int);
+
+ if (id == -1) {
+ for (int i = 0; i < 128; i++)
+ _waveTables[i].clear();
+ _numWaveTables = 0;
+ _waveTablesTotalDataSize = 0;
+ } else {
+ if (_waveTables) {
+ for (int i = 0; i < _numWaveTables; i++) {
+ if (_waveTables[i].id == id) {
+ _numWaveTables--;
+ _waveTablesTotalDataSize -= _waveTables[i].size;
+ _waveTables[i].clear();
+ for (; i < _numWaveTables; i++)
+ memcpy(&_waveTables[i], &_waveTables[i + 1], sizeof(TownsAudio_WaveTable));
+ return 0;
+ }
+ return 9;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_pcmPlayEffect(va_list &args) {
+ int chan = va_arg(args, int);
+ int note = va_arg(args, int);
+ int velo = va_arg(args, int);
+ uint8 *data = va_arg(args, uint8 *);
+
+ if (chan < 0x40 || chan > 0x47)
+ return 1;
+
+ if (note & 0x80 || velo & 0x80)
+ return 3;
+
+ chan -= 0x40;
+
+ if (!(_pcmChanReserved & _chanFlags[chan]))
+ return 7;
+
+ if ((_pcmChanEffectPlaying & _chanFlags[chan]))
+ return 2;
+
+ TownsAudio_WaveTable w;
+ w.readHeader(data);
+
+ if (w.size < (w.loopStart + w.loopLen))
+ return 13;
+
+ if (!w.size)
+ return 6;
+
+ TownsAudio_PcmChannel *p = &_pcmChan[chan];
+
+ _pcmChanNote[chan] = note;
+ _pcmChanVelo[chan] = velo;
+
+ p->note = note;
+ p->velo = velo << 1;
+
+ p->loadExtData(data + 32, w.size);
+ p->setupLoop(w.loopStart, w.loopLen);
+
+ pcmCalcPhaseStep(p, &w);
+ if (p->step > 2048)
+ p->step = 2048;
+
+ _pcmChanEffectPlaying |= _chanFlags[chan];
+ _pcmChanOut |= _chanFlags[chan];
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_pcmChanOff(va_list &args) {
+ int chan = va_arg(args, int);
+ pcmChanOff(chan);
+ return 0;
+}
+
+int TownsAudioInterface::intf_pcmEffectPlaying(va_list &args) {
+ int chan = va_arg(args, int);
+ if (chan < 0x40 || chan > 0x47)
+ return 1;
+ chan -= 0x40;
+ return (_pcmChanEffectPlaying & _chanFlags[chan]) ? 1 : 0;
+}
+
+int TownsAudioInterface::intf_fmKeyOn(va_list &args) {
+ int chan = va_arg(args, int);
+ int note = va_arg(args, int);
+ int velo = va_arg(args, int);
+ return fmKeyOn(chan, note, velo);
+}
+
+int TownsAudioInterface::intf_fmKeyOff(va_list &args) {
+ int chan = va_arg(args, int);
+ return fmKeyOff(chan);
+}
+
+int TownsAudioInterface::intf_fmSetPanPos(va_list &args) {
+ int chan = va_arg(args, int);
+ int mode = va_arg(args, int);
+ return fmSetPanPos(chan, mode);
+}
+
+int TownsAudioInterface::intf_fmSetInstrument(va_list &args) {
+ int chan = va_arg(args, int);
+ int instrId = va_arg(args, int);
+ return fmSetInstrument(chan, instrId);
+}
+
+int TownsAudioInterface::intf_fmLoadInstrument(va_list &args) {
+ int instrId = va_arg(args, int);
+ uint8 *instrData = va_arg(args, uint8 *);
+ return fmLoadInstrument(instrId, instrData);
+}
+
+int TownsAudioInterface::intf_fmSetPitch(va_list &args) {
+ int chan = va_arg(args, int);
+ uint16 freq = va_arg(args, int) & 0xffff;
+ return fmSetPitch(chan, freq);
+}
+
+int TownsAudioInterface::intf_fmSetLevel(va_list &args) {
+ int chan = va_arg(args, int);
+ int lvl = va_arg(args, int);
+ return fmSetLevel(chan, lvl);
+}
+
+int TownsAudioInterface::intf_fmReset(va_list &args) {
+ fmReset();
+ return 0;
+}
+
+int TownsAudioInterface::intf_setOutputVolume(va_list &args) {
+ int chanType = va_arg(args, int);
+ int left = va_arg(args, int);
+ int right = va_arg(args, int);
+
+ if (left & 0xff80 || right & 0xff80)
+ return 3;
+
+ static const uint8 flags[] = { 0x0C, 0x30, 0x40, 0x80 };
+
+ uint8 chan = (chanType & 0x40) ? 8 : 12;
+
+ chanType &= 3;
+ left = (left & 0x7e) >> 1;
+ right = (right & 0x7e) >> 1;
+
+ if (chan)
+ _outputVolumeFlags |= flags[chanType];
+ else
+ _outputVolumeFlags &= ~flags[chanType];
+
+ if (chanType > 1) {
+ _outputLevel[chan + chanType] = left;
+ } else {
+ if (chanType == 0)
+ chan -= 8;
+ _outputLevel[chan] = left;
+ _outputLevel[chan + 1] = right;
+ }
+
+ updateOutputVolume();
+
+ return 0;
+}
+
+int TownsAudioInterface::intf_resetOutputVolume(va_list &args) {
+ memset(_outputLevel, 0, sizeof(_outputLevel));
+ _outputMuteFlags = 0;
+ _outputVolumeFlags = 0;
+ updateOutputVolume();
+ return 0;
+}
+
+int TownsAudioInterface::intf_updateOutputVolume(va_list &args) {
+ int flags = va_arg(args, int);
+ _outputMuteFlags = flags & 3;
+ updateOutputVolume();
+ return 0;
+}
+
+int TownsAudioInterface::intf_cdaToggle(va_list &args) {
+ //int mode = va_arg(args, int);
+ //_unkMask = mode ? 0x7f : 0x3f;
+ return 0;
+}
+
+int TownsAudioInterface::intf_pcmUpdateEnvelopeGenerator(va_list &args) {
+ for (int i = 0; i < 8; i++)
+ pcmUpdateEnvelopeGenerator(i);
+ return 0;
+}
+
+int TownsAudioInterface::intf_notImpl(va_list &args) {
+ return 4;
+}
+
+void TownsAudioInterface::fmReset() {
+ TownsPC98_FmSynth::reset();
+
+ _fmChanPlaying = 0;
+ memset(_fmChanNote, 0, sizeof(_fmChanNote));
+ memset(_fmChanPitch, 0, sizeof(_fmChanPitch));
+
+ memset(_fmSaveReg[0], 0, 240);
+ memset(&_fmSaveReg[0][240], 0x7f, 16);
+ memset(_fmSaveReg[1], 0, 256);
+ memset(&_fmSaveReg[1][240], 0x7f, 16);
+ _fmSaveReg[0][243] = _fmSaveReg[0][247] = _fmSaveReg[0][251] = _fmSaveReg[0][255] = _fmSaveReg[1][243] = _fmSaveReg[1][247] = _fmSaveReg[1][251] = _fmSaveReg[1][255] = 0xff;
+
+ for (int i = 0; i < 128; i++)
+ fmLoadInstrument(i, _fmDefaultInstrument);
+
+ bufferedWriteReg(0, 0x21, 0);
+ bufferedWriteReg(0, 0x2C, 0x80);
+ bufferedWriteReg(0, 0x2B, 0);
+ bufferedWriteReg(0, 0x27, 0x30);
+
+ for (int i = 0; i < 6; i++) {
+ fmKeyOff(i);
+ fmSetInstrument(i, 0);
+ fmSetLevel(i, 127);
+ }
+}
+
+int TownsAudioInterface::fmKeyOn(int chan, int note, int velo) {
+ if (chan > 5)
+ return 1;
+ if (note < 12 || note > 107 || (velo & 0x80))
+ return 3;
+ if (_fmChanPlaying & _chanFlags[chan])
+ return 2;
+
+ _fmChanPlaying |= _chanFlags[chan];
+ note -= 12;
+
+ _fmChanNote[chan] = note;
+ int16 pitch = _fmChanPitch[chan];
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ int frq = 0;
+ uint8 bl = 0;
+
+ if (note) {
+ frq = _frequency[(note - 1) % 12];
+ bl = (note - 1) / 12;
+ } else {
+ frq = 616;
+ }
+
+ frq += pitch;
+
+ if (frq < 616) {
+ if (!bl) {
+ frq = 616;
+ } else {
+ frq += 616;
+ --bl;
+ }
+ } else if (frq > 1232) {
+ if (bl == 7) {
+ frq = 15500;
+ } else {
+ frq -= 616;
+ ++bl;
+ }
+ }
+
+ frq |= (bl << 11);
+
+ bufferedWriteReg(part, chan + 0xa4, (frq >> 8) & 0xff);
+ bufferedWriteReg(part, chan + 0xa0, frq & 0xff);
+
+ velo = (velo >> 2) + 96;
+ uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7];
+ _fmSaveReg[part][0xe0 + chan] = velo;
+
+ for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) {
+ c += c;
+ if (c & 0x100) {
+ c &= 0xff;
+ bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * velo) >> 7) + 1) * _fmSaveReg[part][0xd0 + chan]) >> 7) + 1) ^ 0x7f);
+ }
+ }
+
+ uint8 v = chan;
+ if (part)
+ v |= 4;
+
+ for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
+ writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f);
+
+ writeReg(0, 0x28, v);
+
+ for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
+ writeReg(part, reg, _fmSaveReg[part][reg]);
+
+ bufferedWriteReg(0, 0x28, v | 0xf0);
+
+ return 0;
+}
+
+int TownsAudioInterface::fmKeyOff(int chan) {
+ if (chan > 5)
+ return 1;
+ _fmChanPlaying &= ~_chanFlags[chan];
+ if (chan > 2)
+ chan++;
+ bufferedWriteReg(0, 0x28, chan);
+ return 0;
+}
+
+int TownsAudioInterface::fmChanOff(int chan) {
+ if (chan > 5)
+ return 1;
+ _fmChanPlaying &= ~_chanFlags[chan];
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
+ writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f);
+
+ if (part)
+ chan += 4;
+ writeReg(0, 0x28, chan);
+ return 0;
+}
+
+int TownsAudioInterface::fmSetPanPos(int chan, int value) {
+ if (chan > 5)
+ return 1;
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ if (value > 0x40)
+ value = 0x40;
+ else if (value < 0x40)
+ value = 0x80;
+ else
+ value = 0xC0;
+
+ bufferedWriteReg(part, 0xb4 + chan, (_fmSaveReg[part][0xb4 + chan] & 0x3f) | value);
+ return 0;
+}
+
+int TownsAudioInterface::fmSetInstrument(int chan, int instrId) {
+ if (chan > 5)
+ return 1;
+ if (instrId > 127)
+ return 3;
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ uint8 *src = &_fmInstruments[instrId * 48 + 8];
+
+ uint16 c = _carrier[src[24] & 7];
+ uint8 reg = 0x30 + chan;
+
+ for (; reg < 0x40; reg += 4)
+ bufferedWriteReg(part, reg, *src++);
+
+ for (; reg < 0x50; reg += 4) {
+ uint8 v = *src++;
+ _fmSaveReg[part][0x80 + reg] = _fmSaveReg[part][reg] = v;
+ c += c;
+ if (c & 0x100) {
+ c &= 0xff;
+ v = 127;
+ }
+ writeReg(part, reg, v);
+ }
+
+ for (; reg < 0x90; reg += 4)
+ bufferedWriteReg(part, reg, *src++);
+
+ reg += 0x20;
+ bufferedWriteReg(part, reg, *src++);
+
+ uint8 v = *src++;
+ reg += 4;
+ if (v < 64)
+ v |= (_fmSaveReg[part][reg] & 0xc0);
+ bufferedWriteReg(part, reg, v);
+
+ return 0;
+}
+
+int TownsAudioInterface::fmLoadInstrument(int instrId, const uint8 *data) {
+ if (instrId > 127)
+ return 3;
+ assert(data);
+ memcpy(&_fmInstruments[instrId * 48], data, 48);
+ return 0;
+}
+
+int TownsAudioInterface::fmSetPitch(int chan, int pitch) {
+ if (chan > 5)
+ return 1;
+
+ uint8 bl = _fmChanNote[chan];
+ int frq = 0;
+
+ if (pitch < 0) {
+ if (bl) {
+ if (pitch < -8008)
+ pitch = -8008;
+ pitch *= -1;
+ pitch /= 13;
+ frq = _frequency[(bl - 1) % 12] - pitch;
+ bl = (bl - 1) / 12;
+ _fmChanPitch[chan] = -pitch;
+
+ if (frq < 616) {
+ if (bl) {
+ frq += 616;
+ bl--;
+ } else {
+ frq = 616;
+ bl = 0;
+ }
+ }
+ } else {
+ frq = 616;
+ bl = 0;
+ }
+
+ } else if (pitch > 0) {
+ if (bl < 96) {
+ if (pitch > 8008)
+ pitch = 8008;
+ pitch /= 13;
+
+ if (bl) {
+ frq = _frequency[(bl - 1) % 12] + pitch;
+ bl = (bl - 1) / 12;
+ } else {
+ frq = 616;
+ bl = 0;
+ }
+
+ _fmChanPitch[chan] = pitch;
+
+ if (frq > 1232) {
+ if (bl < 7) {
+ frq -= 616;
+ bl++;
+ } else {
+ frq = 1164;
+ bl = 7;
+ }
+ } else {
+ if (bl >= 7 && frq > 1164)
+ frq = 1164;
+ }
+
+ } else {
+ frq = 1164;
+ bl = 7;
+ }
+ } else {
+ _fmChanPitch[chan] = 0;
+ if (bl) {
+ frq = _frequency[(bl - 1) % 12];
+ bl = (bl - 1) / 12;
+ } else {
+ frq = 616;
+ bl = 0;
+ }
+ }
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ frq |= (bl << 11);
+
+ bufferedWriteReg(part, chan + 0xa4, (frq >> 8));
+ bufferedWriteReg(part, chan + 0xa0, (frq & 0xff));
+
+ return 0;
+}
+
+int TownsAudioInterface::fmSetLevel(int chan, int lvl) {
+ if (chan > 5)
+ return 1;
+ if (lvl > 127)
+ return 3;
+
+ uint8 part = chan > 2 ? 1 : 0;
+ if (chan > 2)
+ chan -= 3;
+
+ uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7];
+ _fmSaveReg[part][0xd0 + chan] = lvl;
+
+ for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) {
+ c += c;
+ if (c & 0x100) {
+ c &= 0xff;
+ bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * lvl) >> 7) + 1) * _fmSaveReg[part][0xe0 + chan]) >> 7) + 1) ^ 0x7f);
+ }
+ }
+ return 0;
+}
+
+void TownsAudioInterface::bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value) {
+ _fmSaveReg[part][regAddress] = value;
+ writeReg(part, regAddress, value);
+}
+
+void TownsAudioInterface::pcmReset() {
+ _pcmChanOut = 0;
+ _pcmChanReserved = _pcmChanKeyPressed = _pcmChanEffectPlaying = _pcmChanKeyPlaying = 0;
+ _numReservedChannels = 0;
+
+ memset(_pcmChanNote, 0, 8);
+ memset(_pcmChanVelo, 0, 8);
+ memset(_pcmChanLevel, 0, 8);
+
+ for (int i = 0; i < 8; i++)
+ _pcmChan[i].clear();
+
+ memset(_pcmInstruments, 0, 128 * 32);
+ static uint8 name[] = { 0x4E, 0x6F, 0x20, 0x44, 0x61, 0x74, 0x61, 0x21 };
+ for (int i = 0; i < 32; i++)
+ memcpy(_pcmInstruments + i * 128, name, 8);
+
+ for (int i = 0; i < 128; i++)
+ _waveTables[i].clear();
+ _numWaveTables = 0;
+ _waveTablesTotalDataSize = 0;
+
+ for (int i = 0x40; i < 0x48; i++) {
+ pcmSetInstrument(i, 0);
+ pcmSetLevel(i, 127);
+ }
+}
+
+int TownsAudioInterface::pcmKeyOn(int chan, int note, int velo) {
+ if (chan < 0x40 || chan > 0x47)
+ return 1;
+
+ if (note & 0x80 || velo & 0x80)
+ return 3;
+
+ chan -= 0x40;
+
+ if ((_pcmChanReserved & _chanFlags[chan]) || (_pcmChanKeyPressed & _chanFlags[chan]))
+ return 2;
+
+ _pcmChanNote[chan] = note;
+ _pcmChanVelo[chan] = velo;
+
+ TownsAudio_PcmChannel *p = &_pcmChan[chan];
+ p->note = note;
+
+ uint8 *instr = _pcmChan[chan].curInstrument;
+ int i = 0;
+ for (; i < 8; i++) {
+ if (note <= instr[16 + 2 * i])
+ break;
+ }
+
+ if (i == 8)
+ return 8;
+
+ int il = i << 3;
+ p->note += instr[il + 70];
+
+ p->envTotalLevel = instr[il + 64];
+ p->envAttackRate = instr[il + 65];
+ p->envDecayRate = instr[il + 66];
+ p->envSustainLevel = instr[il + 67];
+ p->envSustainRate = instr[il + 68];
+ p->envReleaseRate = instr[il + 69];
+ p->envStep = 0;
+
+ int32 id = (int32)READ_LE_UINT32(&instr[i * 4 + 32]);
+
+ for (i = 0; i < _numWaveTables; i++) {
+ if (id == _waveTables[i].id)
+ break;
+ }
+
+ if (i == _numWaveTables)
+ return 9;
+
+ TownsAudio_WaveTable *w = &_waveTables[i];
+
+ p->data = w->data;
+ p->dataEnd = w->data + w->size;
+ p->setupLoop(w->loopStart, w->loopLen);
+
+ pcmCalcPhaseStep(p, w);
+
+ uint32 lvl = _pcmChanLevel[chan] * _pcmChanVelo[chan];
+ p->envTotalLevel = ((p->envTotalLevel * lvl) >> 14) & 0xff;
+ p->envSustainLevel = ((p->envSustainLevel * lvl) >> 14) & 0xff;
+
+ p->envAttack();
+ p->velo = (p->envCurrentLevel >> 8) << 1;
+
+ _pcmChanKeyPressed |= _chanFlags[chan];
+ _pcmChanKeyPlaying |= _chanFlags[chan];
+ _pcmChanOut |= _chanFlags[chan];
+
+ return 0;
+}
+
+int TownsAudioInterface::pcmKeyOff(int chan) {
+ if (chan < 0x40 || chan > 0x47)
+ return 1;
+
+ chan -= 0x40;
+ _pcmChanKeyPressed &= ~_chanFlags[chan];
+ _pcmChan[chan].envRelease();
+ return 0;
+}
+
+int TownsAudioInterface::pcmChanOff(int chan) {
+ if (chan < 0x40 || chan > 0x47)
+ return 1;
+
+ chan -= 0x40;
+
+ _pcmChanKeyPressed &= ~_chanFlags[chan];
+ _pcmChanEffectPlaying &= ~_chanFlags[chan];
+ _pcmChanKeyPlaying &= ~_chanFlags[chan];
+ _pcmChanOut &= ~_chanFlags[chan];
+
+ return 0;
+}
+
+int TownsAudioInterface::pcmSetPanPos(int chan, int mode) {
+ if (chan > 0x47)
+ return 1;
+ if (mode & 0x80)
+ return 3;
+
+ chan -= 0x40;
+ uint8 blc = 0x77;
+
+ if (mode > 64) {
+ mode -= 64;
+ blc = ((blc ^ (mode >> 3)) + (mode << 4)) & 0xff;
+ } else if (mode < 64) {
+ mode = (mode >> 3) ^ 7;
+ blc = ((119 + mode) ^ (mode << 4)) & 0xff;
+ }
+
+ _pcmChan[chan].panLeft = blc & 0x0f;
+ _pcmChan[chan].panRight = blc >> 4;
+
+ return 0;
+}
+
+int TownsAudioInterface::pcmSetInstrument(int chan, int instrId) {
+ if (chan > 0x47)
+ return 1;
+ if (instrId > 31)
+ return 3;
+ chan -= 0x40;
+ _pcmChan[chan].curInstrument = &_pcmInstruments[instrId * 128];
+ return 0;
+}
+
+int TownsAudioInterface::pcmLoadInstrument(int instrId, const uint8 *data) {
+ if (instrId > 31)
+ return 3;
+ assert(data);
+ memcpy(&_pcmInstruments[instrId * 128], data, 128);
+ return 0;
+}
+
+int TownsAudioInterface::pcmSetPitch(int chan, int pitch) {
+ if (chan > 0x47)
+ return 1;
+
+ if (pitch < -8192 || pitch > 8191)
+ return 3;
+
+ chan -= 0x40;
+ TownsAudio_PcmChannel *p = &_pcmChan[chan];
+
+ uint32 pts = 0x4000;
+
+ if (pitch < 0)
+ pts = (0x20000000 / (-pitch + 0x2001)) >> 2;
+ else if (pitch > 0)
+ pts = (((pitch + 0x2001) << 16) / 0x2000) >> 2;
+
+ p->stepPitch = pts & 0xffff;
+ p->step = (p->stepNote * p->stepPitch) >> 14;
+
+// if (_pcmChanUnkFlag & _chanFlags[chan])
+// unk[chan] = (((p->step * 1000) << 11) / 98) / 20833;
+
+ /*else*/
+ if ((_pcmChanEffectPlaying & _chanFlags[chan]) && (p->step > 2048))
+ p->step = 2048;
+
+ return 0;
+}
+
+int TownsAudioInterface::pcmSetLevel(int chan, int lvl) {
+ if (chan > 0x47)
+ return 1;
+
+ if (lvl & 0x80)
+ return 3;
+
+ chan -= 0x40;
+ TownsAudio_PcmChannel *p = &_pcmChan[chan];
+
+ if (_pcmChanReserved & _chanFlags[chan]) {
+ _pcmChanVelo[chan] = lvl;
+ p->velo = lvl << 1;
+ } else {
+ int32 t = p->envStep * lvl;
+ if (_pcmChanLevel[chan])
+ t /= _pcmChanLevel[chan];
+ p->envStep = t;
+ t = p->envCurrentLevel * lvl;
+ if (_pcmChanLevel[chan])
+ t /= _pcmChanLevel[chan];
+ p->envCurrentLevel = t;
+ _pcmChanLevel[chan] = lvl;
+ p->velo = p->envCurrentLevel >> 8;
+ }
+
+ return 0;
+}
+
+void TownsAudioInterface::pcmUpdateEnvelopeGenerator(int chan) {
+ TownsAudio_PcmChannel *p = &_pcmChan[chan];
+ if (!p->envCurrentLevel) {
+ _pcmChanKeyPlaying &= ~_chanFlags[chan];
+ p->envState = kEnvReady;
+ }
+
+ if (!(_pcmChanKeyPlaying & _chanFlags[chan]))
+ return;
+
+ switch (p->envState) {
+ case kEnvAttacking:
+ if (((p->envCurrentLevel + p->envStep) >> 8) > p->envTotalLevel) {
+ p->envDecay();
+ return;
+ } else {
+ p->envCurrentLevel += p->envStep;
+ }
+ break;
+
+ case kEnvDecaying:
+ if (((p->envCurrentLevel - p->envStep) >> 8) < p->envSustainLevel) {
+ p->envSustain();
+ return;
+ } else {
+ p->envCurrentLevel -= p->envStep;
+ }
+ break;
+
+ case kEnvSustaining:
+ case kEnvReleasing:
+ p->envCurrentLevel -= p->envStep;
+ if (p->envCurrentLevel <= 0)
+ p->envCurrentLevel = 0;
+ break;
+
+ default:
+ break;
+ }
+ p->velo = (p->envCurrentLevel >> 8) << 1;
+}
+
+void TownsAudioInterface::pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w) {
+ int8 diff = p->note - w->baseNote;
+ uint16 r = w->rate + w->rateOffs;
+ uint16 bl = 0;
+ uint32 s = 0;
+
+ if (diff < 0) {
+ diff *= -1;
+ bl = diff % 12;
+ diff /= 12;
+ s = (r >> diff);
+ if (bl)
+ s = (s * _pcmPhase2[bl]) >> 16;
+
+ } else if (diff > 0) {
+ bl = diff % 12;
+ diff /= 12;
+ s = (r << diff);
+ if (bl)
+ s += ((s * _pcmPhase1[bl]) >> 16);
+
+ } else {
+ s = r;
+ }
+
+ p->stepNote = s & 0xffff;
+ p->step = (s * p->stepPitch) >> 14;
+}
+
+void TownsAudioInterface::updateOutputVolume() {
+ // FM Towns seems to support volumes of 0 - 63 for each channel.
+ // We recalculate sane values for our 0 to 255 volume range and
+ // balance values for our -128 to 127 volume range
+
+ // CD-AUDIO
+ uint32 maxVol = MAX(_outputLevel[12], _outputLevel[13]);
+
+ int volume = (int)(((float)(maxVol * 255) / 63.0f));
+ int balance = maxVol ? (int)( ( ((int)_outputLevel[13] - _outputLevel[12]) * 127) / (float)maxVol) : 0;
+
+ g_system->getAudioCDManager()->setVolume(volume);
+ g_system->getAudioCDManager()->setBalance(balance);
+}
+
+const uint8 TownsAudioInterface::_chanFlags[] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
+};
+
+const uint16 TownsAudioInterface::_frequency[] = {
+ 0x028C, 0x02B4, 0x02DC, 0x030A, 0x0338, 0x0368, 0x039C, 0x03D4, 0x040E, 0x044A, 0x048C, 0x04D0
+};
+
+const uint8 TownsAudioInterface::_carrier[] = {
+ 0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0
+};
+
+const uint8 TownsAudioInterface::_fmDefaultInstrument[] = {
+ 0x45, 0x4C, 0x45, 0x50, 0x49, 0x41, 0x4E, 0x4F, 0x01, 0x0A, 0x02, 0x01,
+ 0x1E, 0x32, 0x05, 0x00, 0x9C, 0xDC, 0x9C, 0xDC, 0x07, 0x03, 0x14, 0x08,
+ 0x00, 0x03, 0x05, 0x05, 0x55, 0x45, 0x27, 0xA7, 0x04, 0xC0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+const uint16 TownsAudioInterface::_pcmPhase1[] = {
+ 0x879B, 0x0F37, 0x1F58, 0x306E, 0x4288, 0x55B6, 0x6A08, 0x7F8F, 0x965E, 0xAE88, 0xC882, 0xE341
+};
+
+const uint16 TownsAudioInterface::_pcmPhase2[] = {
+ 0xFEFE, 0xF1A0, 0xE411, 0xD744, 0xCB2F, 0xBFC7, 0xB504, 0xAAE2, 0xA144, 0x9827, 0x8FAC
+};
+
+TownsAudio_PcmChannel::TownsAudio_PcmChannel() {
+ extData = 0;
+ clear();
+}
+
+TownsAudio_PcmChannel::~TownsAudio_PcmChannel() {
+ clear();
+}
+
+void TownsAudio_PcmChannel::loadExtData(uint8 *buffer, uint32 size) {
+ delete[] extData;
+ extData = new int8[size];
+ int8 *src = (int8 *)buffer;
+ int8 *dst = extData;
+ for (uint32 i = 0; i < size; i++)
+ *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
+
+ data = extData;
+ dataEnd = extData + size;
+ pos = 0;
+}
+
+void TownsAudio_PcmChannel::setupLoop(uint32 start, uint32 len) {
+ loopLen = len << 11;
+ loopEnd = loopLen ? &data[(start + loopLen) >> 11] : dataEnd;
+ pos = start;
+}
+
+void TownsAudio_PcmChannel::clear() {
+ curInstrument = 0;
+ note = 0;
+ velo = 0;
+
+ data = 0;
+ dataEnd = 0;
+ loopLen = 0;
+
+ pos = 0;
+ loopEnd = 0;
+
+ step = 0;
+ stepNote = 0x4000;
+ stepPitch = 0x4000;
+
+ panLeft = panRight = 7;
+
+ envTotalLevel = envAttackRate = envDecayRate = envSustainLevel = envSustainRate = envReleaseRate = 0;
+ envStep = envCurrentLevel = 0;
+
+ envState = kEnvReady;
+
+ delete[] extData;
+ extData = 0;
+}
+
+void TownsAudio_PcmChannel::envAttack() {
+ envState = kEnvAttacking;
+ int16 t = envTotalLevel << 8;
+ if (envAttackRate == 127) {
+ envStep = 0;
+ } else if (envAttackRate) {
+ envStep = t / envAttackRate;
+ envCurrentLevel = 1;
+ } else {
+ envCurrentLevel = t;
+ envDecay();
+ }
+}
+
+void TownsAudio_PcmChannel::envDecay() {
+ envState = kEnvDecaying;
+ int16 t = envTotalLevel - envSustainLevel;
+ if (t < 0 || envDecayRate == 127) {
+ envStep = 0;
+ } else if (envDecayRate) {
+ envStep = (t << 8) / envDecayRate;
+ } else {
+ envCurrentLevel = envSustainLevel << 8;
+ envSustain();
+ }
+}
+
+void TownsAudio_PcmChannel::envSustain() {
+ envState = kEnvSustaining;
+ if (envSustainLevel && envSustainRate)
+ envStep = (envSustainRate == 127) ? 0 : (envCurrentLevel / envSustainRate) >> 1;
+ else
+ envStep = envCurrentLevel = 1;
+}
+
+void TownsAudio_PcmChannel::envRelease() {
+ envState = kEnvReleasing;
+ if (envReleaseRate == 127)
+ envStep = 0;
+ else if (envReleaseRate)
+ envStep = envCurrentLevel / envReleaseRate;
+ else
+ envStep = envCurrentLevel = 1;
+}
+
+TownsAudio_WaveTable::TownsAudio_WaveTable() {
+ data = 0;
+ clear();
+}
+
+TownsAudio_WaveTable::~TownsAudio_WaveTable() {
+ clear();
+}
+
+void TownsAudio_WaveTable::readHeader(const uint8 *buffer) {
+ memcpy(name, buffer, 8);
+ name[8] = 0;
+ id = READ_LE_UINT32(&buffer[8]);
+ size = READ_LE_UINT32(&buffer[12]);
+ loopStart = READ_LE_UINT32(&buffer[16]);
+ loopLen = READ_LE_UINT32(&buffer[20]);
+ rate = READ_LE_UINT16(&buffer[24]);
+ rateOffs = READ_LE_UINT16(&buffer[26]);
+ baseNote = READ_LE_UINT32(&buffer[28]);
+}
+
+void TownsAudio_WaveTable::readData(const uint8 *buffer) {
+ if (!size)
+ return;
+
+ delete[] data;
+ data = new int8[size];
+
+ const int8 *src = (const int8 *)buffer;
+ int8 *dst = data;
+ for (uint32 i = 0; i < size; i++)
+ *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
+}
+
+void TownsAudio_WaveTable::clear() {
+ name[0] = name[8] = 0;
+ id = -1;
+ size = 0;
+ loopStart = 0;
+ loopLen = 0;
+ rate = 0;
+ rateOffs = 0;
+ baseNote = 0;
+ delete[] data;
+ data = 0;
+}
+
diff --git a/audio/softsynth/fmtowns_pc98/towns_audio.h b/audio/softsynth/fmtowns_pc98/towns_audio.h
new file mode 100644
index 0000000000..2819ab2d57
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_audio.h
@@ -0,0 +1,179 @@
+/* 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$
+ *
+ */
+
+#ifndef TOWNS_AUDIO_H
+#define TOWNS_AUDIO_H
+
+#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
+
+class TownsAudioInterfacePluginDriver {
+public:
+ virtual ~TownsAudioInterfacePluginDriver() {}
+ virtual void timerCallback(int timerId) = 0;
+};
+
+class TownsAudio_PcmChannel;
+class TownsAudio_WaveTable;
+
+class TownsAudioInterface : public TownsPC98_FmSynth {
+public:
+ TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver);
+ ~TownsAudioInterface();
+
+ bool init();
+
+ int callback(int command, ...);
+
+ void setMusicVolume(int volume);
+ void setSoundEffectVolume(int volume);
+ // Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
+ // The first 6 bits are the 6 fm channels. The next 8 bits are pcm channels.
+ void setSoundEffectChanMask(int mask);
+
+private:
+ void nextTickEx(int32 *buffer, uint32 bufferSize);
+
+ void timerCallbackA();
+ void timerCallbackB();
+
+ typedef int (TownsAudioInterface::*TownsAudioIntfCallback)(va_list &);
+ const TownsAudioIntfCallback *_intfOpcodes;
+
+ int intf_reset(va_list &args);
+ int intf_keyOn(va_list &args);
+ int intf_keyOff(va_list &args);
+ int intf_setPanPos(va_list &args);
+ int intf_setInstrument(va_list &args);
+ int intf_loadInstrument(va_list &args);
+ int intf_setPitch(va_list &args);
+ int intf_setLevel(va_list &args);
+ int intf_chanOff(va_list &args);
+ int intf_writeReg(va_list &args);
+ int intf_writeRegBuffer(va_list &args);
+ int intf_readRegBuffer(va_list &args);
+ int intf_setTimerA(va_list &args);
+ int intf_setTimerB(va_list &args);
+ int intf_enableTimerA(va_list &args);
+ int intf_enableTimerB(va_list &args);
+ int intf_loadSamples(va_list &args);
+ int intf_reserveEffectChannels(va_list &args);
+ int intf_loadWaveTable(va_list &args);
+ int intf_unloadWaveTable(va_list &args);
+ int intf_pcmPlayEffect(va_list &args);
+ int intf_pcmChanOff(va_list &args);
+ int intf_pcmEffectPlaying(va_list &args);
+ int intf_fmKeyOn(va_list &args);
+ int intf_fmKeyOff(va_list &args);
+ int intf_fmSetPanPos(va_list &args);
+ int intf_fmSetInstrument(va_list &args);
+ int intf_fmLoadInstrument(va_list &args);
+ int intf_fmSetPitch(va_list &args);
+ int intf_fmSetLevel(va_list &args);
+ int intf_fmReset(va_list &args);
+ int intf_setOutputVolume(va_list &args);
+ int intf_resetOutputVolume(va_list &args);
+ int intf_updateOutputVolume(va_list &args);
+ int intf_cdaToggle(va_list &args);
+ int intf_pcmUpdateEnvelopeGenerator(va_list &args);
+
+ int intf_notImpl(va_list &args);
+
+ void fmReset();
+ int fmKeyOn(int chan, int note, int velo);
+ int fmKeyOff(int chan);
+ int fmChanOff(int chan);
+ int fmSetPanPos(int chan, int mode);
+ int fmSetInstrument(int chan, int instrId);
+ int fmLoadInstrument(int instrId, const uint8 *data);
+ int fmSetPitch(int chan, int pitch);
+ int fmSetLevel(int chan, int lvl);
+
+ void bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value);
+
+ uint8 _fmChanPlaying;
+ uint8 _fmChanNote[6];
+ int16 _fmChanPitch[6];
+
+ uint8 *_fmSaveReg[2];
+ uint8 *_fmInstruments;
+
+ void pcmReset();
+ int pcmKeyOn(int chan, int note, int velo);
+ int pcmKeyOff(int chan);
+ int pcmChanOff(int chan);
+ int pcmSetPanPos(int chan, int mode);
+ int pcmSetInstrument(int chan, int instrId);
+ int pcmLoadInstrument(int instrId, const uint8 *data);
+ int pcmSetPitch(int chan, int pitch);
+ int pcmSetLevel(int chan, int lvl);
+ void pcmUpdateEnvelopeGenerator(int chan);
+
+ TownsAudio_PcmChannel *_pcmChan;
+ uint8 _pcmChanOut;
+ uint8 _pcmChanReserved;
+ uint8 _pcmChanKeyPressed;
+ uint8 _pcmChanEffectPlaying;
+ uint8 _pcmChanKeyPlaying;
+
+ uint8 _pcmChanNote[8];
+ uint8 _pcmChanVelo[8];
+ uint8 _pcmChanLevel[8];
+
+ uint8 _numReservedChannels;
+ uint8 *_pcmInstruments;
+
+ TownsAudio_WaveTable *_waveTables;
+ uint8 _numWaveTables;
+ uint32 _waveTablesTotalDataSize;
+
+ void pcmCalcPhaseStep(TownsAudio_PcmChannel *p, TownsAudio_WaveTable *w);
+
+ void updateOutputVolume();
+ uint8 _outputVolumeFlags;
+ uint8 _outputLevel[16];
+ uint8 _outputMuteFlags;
+
+ const float _baserate;
+ uint32 _timerBase;
+ uint32 _tickLength;
+ uint32 _timer;
+
+ uint16 _musicVolume;
+ uint16 _sfxVolume;
+ int _pcmSfxChanMask;
+
+ TownsAudioInterfacePluginDriver *_drv;
+ bool _ready;
+
+ static const uint8 _chanFlags[];
+ static const uint16 _frequency[];
+ static const uint8 _carrier[];
+ static const uint8 _fmDefaultInstrument[];
+ static const uint16 _pcmPhase1[];
+ static const uint16 _pcmPhase2[];
+};
+
+#endif
+
diff --git a/audio/softsynth/fmtowns_pc98/towns_euphony.cpp b/audio/softsynth/fmtowns_pc98/towns_euphony.cpp
new file mode 100644
index 0000000000..7c071c43fb
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_euphony.cpp
@@ -0,0 +1,905 @@
+/* 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 "audio/softsynth/fmtowns_pc98/towns_euphony.h"
+#include "common/endian.h"
+
+TownsEuphonyDriver::TownsEuphonyDriver(Audio::Mixer *mixer) : _activeChannels(0), _sustainChannels(0),
+ _assignedChannels(0), _paraCount(0), _command(0), _tEnable(0), _tMode(0), _tOrdr(0), _tLevel(0),
+ _tTranspose(0), _musicPos(0), _musicStart(0), _playing(false), _eventBuffer(0), _bufferedEventsCount(0),
+ _tempoControlMode(0) {
+ _para[0] = _para[1] = 0;
+ _intf = new TownsAudioInterface(mixer, this);
+ resetTempo();
+}
+
+TownsEuphonyDriver::~TownsEuphonyDriver() {
+ delete _intf;
+ delete[] _activeChannels;
+ delete[] _sustainChannels;
+ delete[] _assignedChannels;
+ delete[] _tEnable;
+ delete[] _tMode;
+ delete[] _tOrdr;
+ delete[] _tLevel;
+ delete[] _tTranspose;
+}
+
+bool TownsEuphonyDriver::init() {
+ if (!_intf->init())
+ return false;
+
+ _activeChannels = new int8[16];
+ _sustainChannels = new int8[16];
+ _assignedChannels = new ActiveChannel[128];
+ _eventBuffer = new DlEvent[64];
+
+ _tEnable = new uint8[32];
+ _tMode = new uint8[32];
+ _tOrdr = new uint8[32];
+ _tLevel = new int8[32];
+ _tTranspose = new int8[32];
+
+ reset();
+
+ return true;
+}
+
+void TownsEuphonyDriver::reset() {
+ _intf->callback(0);
+
+ _intf->callback(74);
+ _intf->callback(70);
+ _intf->callback(75, 3);
+
+ setTimerA(true, 1);
+ setTimerA(false, 1);
+ setTimerB(true, 221);
+
+ _paraCount = _command = _para[0] = _para[1] = 0;
+ memset(_sustainChannels, 0, 16);
+ memset(_activeChannels, -1, 16);
+ for (int i = 0; i < 128; i++) {
+ _assignedChannels[i].chan = _assignedChannels[i].next = -1;
+ _assignedChannels[i].note = _assignedChannels[i].sub = 0;
+ }
+
+ int e = 0;
+ for (int i = 0; i < 6; i++)
+ assignChannel(i, e++);
+ for (int i = 0x40; i < 0x48; i++)
+ assignChannel(i, e++);
+
+ resetTables();
+
+ memset(_eventBuffer, 0, 64 * sizeof(DlEvent));
+ _bufferedEventsCount = 0;
+
+ _playing = _endOfTrack = _suspendParsing = _loop = false;
+ _elapsedEvents = 0;
+ _tempoDiff = 0;
+
+ resetTempo();
+
+ if (_tempoControlMode == 1) {
+ //if (///)
+ // return;
+ setTempoIntern(_defaultTempo);
+ } else {
+ setTempoIntern(_defaultTempo);
+ }
+
+ resetControl();
+}
+
+void TownsEuphonyDriver::loadInstrument(int chanType, int id, const uint8 *data) {
+ _intf->callback(5, chanType, id, data);
+}
+
+void TownsEuphonyDriver::loadWaveTable(const uint8 *data) {
+ _intf->callback(34, data);
+}
+
+void TownsEuphonyDriver::unloadWaveTable(int id) {
+ _intf->callback(35, id);
+}
+
+void TownsEuphonyDriver::reserveSoundEffectChannels(int num) {
+ _intf->callback(33, num);
+ uint32 volMask = 0;
+
+ if (num > 8)
+ return;
+
+ for (uint32 v = 1 << 13; num; num--) {
+ volMask |= v;
+ v >>= 1;
+ }
+
+ _intf->setSoundEffectChanMask(volMask);
+}
+
+int TownsEuphonyDriver::setMusicTempo(int tempo) {
+ if (tempo > 250)
+ return 3;
+ _defaultTempo = tempo;
+ _trackTempo = tempo;
+ setTempoIntern(tempo);
+ return 0;
+}
+
+int TownsEuphonyDriver::startMusicTrack(const uint8 *data, int trackSize, int startTick) {
+ if (_playing)
+ return 2;
+
+ _musicPos = _musicStart = data;
+ _defaultBaseTickLen = _baseTickLen = startTick;
+ _musicTrackSize = trackSize;
+ _timeStampBase = _timeStampDest = 0;
+ _tickCounter = 0;
+ _playing = true;
+
+ return 0;
+}
+
+void TownsEuphonyDriver::setMusicLoop(bool loop) {
+ _loop = loop;
+}
+
+void TownsEuphonyDriver::stopParser() {
+ if (_playing) {
+ _playing = false;
+ _pulseCount = 0;
+ _endOfTrack = false;
+ flushEventBuffer();
+ resetControl();
+ }
+}
+
+void TownsEuphonyDriver::continueParsing() {
+ _suspendParsing = false;
+}
+
+void TownsEuphonyDriver::playSoundEffect(int chan, int note, int velo, const uint8 *data) {
+ _intf->callback(37, chan, note, velo, data);
+}
+
+void TownsEuphonyDriver::stopSoundEffect(int chan) {
+ _intf->callback(39, chan);
+}
+
+bool TownsEuphonyDriver::soundEffectIsPlaying(int chan) {
+ return _intf->callback(40, chan) ? true : false;
+}
+
+void TownsEuphonyDriver::chanPanPos(int chan, int mode) {
+ _intf->callback(3, chan, mode);
+}
+
+void TownsEuphonyDriver::chanPitch(int chan, int pitch) {
+ _intf->callback(7, chan, pitch);
+}
+
+void TownsEuphonyDriver::chanVolume(int chan, int vol) {
+ _intf->callback(8, chan, vol);
+}
+
+void TownsEuphonyDriver::setOutputVolume(int mode, int volLeft, int volRight) {
+ _intf->callback(67, mode, volLeft, volRight);
+}
+
+int TownsEuphonyDriver::chanEnable(int tableEntry, int val) {
+ if (tableEntry > 31)
+ return 3;
+ _tEnable[tableEntry] = val;
+ return 0;
+}
+
+int TownsEuphonyDriver::chanMode(int tableEntry, int val) {
+ if (tableEntry > 31)
+ return 3;
+ _tMode[tableEntry] = val;
+ return 0;
+}
+
+int TownsEuphonyDriver::chanOrdr(int tableEntry, int val) {
+ if (tableEntry > 31)
+ return 3;
+ if (val < 16)
+ _tOrdr[tableEntry] = val;
+ return 0;
+}
+
+int TownsEuphonyDriver::chanVolumeShift(int tableEntry, int val) {
+ if (tableEntry > 31)
+ return 3;
+ if (val <= 40)
+ _tLevel[tableEntry] = (int8)(val & 0xff);
+ return 0;
+}
+
+int TownsEuphonyDriver::chanNoteShift(int tableEntry, int val) {
+ if (tableEntry > 31)
+ return 3;
+ if (val <= 40)
+ _tTranspose[tableEntry] = (int8)(val & 0xff);
+ return 0;
+}
+
+int TownsEuphonyDriver::assignChannel(int chan, int tableEntry) {
+ if (tableEntry > 15 || chan > 127 || chan < 0)
+ return 3;
+
+ ActiveChannel *a = &_assignedChannels[chan];
+ if (a->chan == tableEntry)
+ return 0;
+
+ if (a->chan != -1) {
+ int8 *b = &_activeChannels[a->chan];
+ while (*b != chan) {
+ b = &_assignedChannels[*b].next;
+ if (*b == -1 && *b != chan)
+ return 3;
+ }
+
+ *b = a->next;
+
+ if (a->note)
+ _intf->callback(2, chan);
+
+ a->chan = a->next = -1;
+ a->note = 0;
+ }
+
+ a->next = _activeChannels[tableEntry];
+ _activeChannels[tableEntry] = chan;
+ a->chan = tableEntry;
+ a->note = a->sub = 0;
+
+ return 0;
+}
+
+void TownsEuphonyDriver::timerCallback(int timerId) {
+ switch (timerId) {
+ case 0:
+ updatePulseCount();
+ while (_pulseCount > 0) {
+ --_pulseCount;
+ updateTimeStampBase();
+ if (!_playing)
+ continue;
+ updateEventBuffer();
+ updateParser();
+ updateCheckEot();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void TownsEuphonyDriver::setMusicVolume(int volume) {
+ _intf->setMusicVolume(volume);
+}
+
+void TownsEuphonyDriver::setSoundEffectVolume(int volume) {
+ _intf->setSoundEffectVolume(volume);
+}
+
+void TownsEuphonyDriver::resetTables() {
+ memset(_tEnable, 0xff, 32);
+ memset(_tMode, 0xff, 16);
+ memset(_tMode + 16, 0, 16);
+ for (int i = 0; i < 32; i++)
+ _tOrdr[i] = i & 0x0f;
+ memset(_tLevel, 0, 32);
+ memset(_tTranspose, 0, 32);
+}
+
+void TownsEuphonyDriver::resetTempo() {
+ _defaultBaseTickLen = _baseTickLen = 0x33;
+ _pulseCount = 0;
+ _extraTimingControlRemainder = 0;
+ _extraTimingControl = 16;
+ _tempoModifier = 0;
+ _timeStampDest = 0;
+ _deltaTicks = 0;
+ _tickCounter = 0;
+ _defaultTempo = 90;
+ _trackTempo = 90;
+}
+
+void TownsEuphonyDriver::setTempoIntern(int tempo) {
+ tempo = CLIP(tempo + _tempoModifier, 0, 500);
+ if (_tempoControlMode == 0) {
+ _timerSetting = 34750 / (tempo + 30);
+ _extraTimingControl = 16;
+
+ while (_timerSetting < 126) {
+ _timerSetting <<= 1;
+ _extraTimingControl <<= 1;
+ }
+
+ while (_timerSetting > 383) {
+ _timerSetting >>= 1;
+ _extraTimingControl >>= 1;
+ }
+
+ setTimerA(true, -(_timerSetting - 2));
+
+ } else if (_tempoControlMode == 1) {
+ _timerSetting = 312500 / (tempo + 30);
+ _extraTimingControl = 16;
+ while (_timerSetting < 1105) {
+ _timerSetting <<= 1;
+ _extraTimingControl <<= 1;
+ }
+
+ } else if (_tempoControlMode == 2) {
+ _timerSetting = 625000 / (tempo + 30);
+ _extraTimingControlRemainder = 0;
+ }
+}
+
+void TownsEuphonyDriver::setTimerA(bool enable, int tempo) {
+ _intf->callback(21, enable ? 255 : 0, tempo);
+}
+
+void TownsEuphonyDriver::setTimerB(bool enable, int tempo) {
+ _intf->callback(22, enable ? 255 : 0, tempo);
+}
+
+void TownsEuphonyDriver::updatePulseCount() {
+ int tc = _extraTimingControl + _extraTimingControlRemainder;
+ _extraTimingControlRemainder = tc & 0x0f;
+ tc >>= 4;
+ _tempoDiff -= tc;
+
+ while (_tempoDiff < 0) {
+ _elapsedEvents++;
+ _tempoDiff += 4;
+ }
+
+ if (_playing && !_suspendParsing)
+ _pulseCount += tc;
+}
+
+void TownsEuphonyDriver::updateTimeStampBase() {
+ static const uint16 table[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
+ if ((uint32)(table[_baseTickLen >> 4] * ((_baseTickLen & 0x0f) + 1)) > ++_tickCounter)
+ return;
+ ++_timeStampDest;
+ _tickCounter = 0;
+ _deltaTicks = 0;
+}
+
+void TownsEuphonyDriver::updateParser() {
+ for (bool loop = true; loop;) {
+ uint8 cmd = _musicPos[0];
+
+ if (cmd == 0xff || cmd == 0xf7) {
+ jumpNextLoop();
+
+ } else if (cmd < 0x90) {
+ _endOfTrack = true;
+ flushEventBuffer();
+ loop = false;
+
+ } else if (_timeStampBase > _timeStampDest) {
+ loop = false;
+
+ } else {
+ if (_timeStampBase == _timeStampDest) {
+ uint16 timeStamp = READ_LE_UINT16(&_musicPos[2]);
+ uint8 l = (timeStamp & 0xff) + (timeStamp & 0xff);
+ timeStamp = ((timeStamp & 0xff00) | l) >> 1;
+ if (timeStamp > _tickCounter)
+ loop = false;
+ }
+
+ if (loop) {
+ if (parseNext())
+ loop = false;
+ }
+ }
+ }
+}
+
+void TownsEuphonyDriver::updateCheckEot() {
+ if (!_endOfTrack || _bufferedEventsCount)
+ return;
+ stopParser();
+}
+
+bool TownsEuphonyDriver::parseNext() {
+#define OPC(x) &TownsEuphonyDriver::evt##x
+ static const EuphonyOpcode opcodes[] = {
+ OPC(NotImpl),
+ OPC(SetupNote),
+ OPC(PolyphonicAftertouch),
+ OPC(ControlPitch),
+ OPC(InstrumentChanAftertouch),
+ OPC(InstrumentChanAftertouch),
+ OPC(ControlPitch)
+ };
+#undef OPC
+
+ uint cmd = _musicPos[0];
+ if (cmd != 0xfe && cmd != 0xfd) {
+ if (cmd >= 0xf0) {
+ cmd &= 0x0f;
+ if (cmd == 0)
+ evtLoadInstrument();
+ else if (cmd == 2)
+ evtAdvanceTimestampOffset();
+ else if (cmd == 8)
+ evtTempo();
+ else if (cmd == 12)
+ evtModeOrdrChange();
+ jumpNextLoop();
+ return false;
+
+ } else if (!(this->*opcodes[(cmd - 0x80) >> 4])()) {
+ jumpNextLoop();
+ return false;
+ }
+ }
+
+ if (cmd == 0xfd) {
+ _suspendParsing = true;
+ return true;
+ }
+
+ if (!_loop) {
+ _endOfTrack = true;
+ return true;
+ }
+
+ _endOfTrack = false;
+ _musicPos = _musicStart;
+ _timeStampBase = _timeStampDest = _tickCounter = 0;
+ _baseTickLen = _defaultBaseTickLen;
+
+ return false;
+}
+
+void TownsEuphonyDriver::jumpNextLoop() {
+ _musicPos += 6;
+ if (_musicPos >= _musicStart + _musicTrackSize)
+ _musicPos = _musicStart;
+}
+
+void TownsEuphonyDriver::updateEventBuffer() {
+ DlEvent *e = _eventBuffer;
+ for (int i = _bufferedEventsCount; i; e++) {
+ if (e->evt == 0)
+ continue;
+ if (--e->len) {
+ --i;
+ continue;
+ }
+ processBufferNote(e->mode, e->evt, e->note, e->velo);
+ e->evt = 0;
+ --i;
+ --_bufferedEventsCount;
+ }
+}
+
+void TownsEuphonyDriver::flushEventBuffer() {
+ DlEvent *e = _eventBuffer;
+ for (int i = _bufferedEventsCount; i; e++) {
+ if (e->evt == 0)
+ continue;
+ processBufferNote(e->mode, e->evt, e->note, e->velo);
+ e->evt = 0;
+ --i;
+ --_bufferedEventsCount;
+ }
+}
+
+void TownsEuphonyDriver::processBufferNote(int mode, int evt, int note, int velo) {
+ if (!velo)
+ evt &= 0x8f;
+ sendEvent(mode, evt);
+ sendEvent(mode, note);
+ sendEvent(mode, velo);
+}
+
+void TownsEuphonyDriver::resetControl() {
+ for (int i = 0; i < 32; i++) {
+ if (_tOrdr[i] > 15) {
+ for (int ii = 0; ii < 16; ii++)
+ resetControlIntern(_tMode[i], ii);
+ } else {
+ resetControlIntern(_tMode[i], _tOrdr[i]);
+ }
+ }
+}
+
+void TownsEuphonyDriver::resetControlIntern(int mode, int chan) {
+ sendEvent(mode, 0xb0 | chan);
+ sendEvent(mode, 0x40);
+ sendEvent(mode, 0);
+ sendEvent(mode, 0xb0 | chan);
+ sendEvent(mode, 0x7b);
+ sendEvent(mode, 0);
+ sendEvent(mode, 0xb0 | chan);
+ sendEvent(mode, 0x79);
+ sendEvent(mode, 0x40);
+}
+
+uint8 TownsEuphonyDriver::appendEvent(uint8 evt, uint8 chan) {
+ if (evt >= 0x80 && evt < 0xf0 && _tOrdr[chan] < 16)
+ return (evt & 0xf0) | _tOrdr[chan];
+ return evt;
+}
+
+void TownsEuphonyDriver::sendEvent(uint8 mode, uint8 command) {
+ if (mode == 0) {
+ // warning("TownsEuphonyDriver: Mode 0 not implemented");
+
+ } else if (mode == 0x10) {
+ warning("TownsEuphonyDriver: Mode 0x10 not implemented");
+
+ } else if (mode == 0xff) {
+ if (command >= 0xf0) {
+ _paraCount = 1;
+ _command = 0;
+ } else if (command >= 0x80) {
+ _paraCount = 1;
+ _command = command;
+ } else if (_command >= 0x80) {
+ switch ((_command - 0x80) >> 4) {
+ case 0:
+ if (_paraCount < 2) {
+ _paraCount++;
+ _para[0] = command;
+ } else {
+ _paraCount = 1;
+ _para[1] = command;
+ sendNoteOff();
+ }
+ break;
+
+ case 1:
+ if (_paraCount < 2) {
+ _paraCount++;
+ _para[0] = command;
+ } else {
+ _paraCount = 1;
+ _para[1] = command;
+ if (command)
+ sendNoteOn();
+ else
+ sendNoteOff();
+ }
+ break;
+
+ case 2:
+ if (_paraCount < 2) {
+ _paraCount++;
+ _para[0] = command;
+ } else {
+ _paraCount = 1;
+ }
+ break;
+
+ case 3:
+ if (_paraCount < 2) {
+ _paraCount++;
+ _para[0] = command;
+ } else {
+ _paraCount = 1;
+ _para[1] = command;
+
+ if (_para[0] == 7)
+ sendChanVolume();
+ else if (_para[0] == 10)
+ sendPanPosition();
+ else if (_para[0] == 64)
+ sendAllNotesOff();
+ }
+ break;
+
+ case 4:
+ _paraCount = 1;
+ _para[0] = command;
+ sendSetInstrument();
+ break;
+
+ case 5:
+ _paraCount = 1;
+ _para[0] = command;
+ break;
+
+ case 6:
+ if (_paraCount < 2) {
+ _paraCount++;
+ _para[0] = command;
+ } else {
+ _paraCount = 1;
+ _para[1] = command;
+ sendPitch();
+ }
+ break;
+ }
+ }
+ }
+}
+
+bool TownsEuphonyDriver::evtSetupNote() {
+ if (_musicPos[1] > 31)
+ return false;
+ if (!_tEnable[_musicPos[1]]) {
+ jumpNextLoop();
+ return (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd) ? true : false;
+ }
+ uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
+ uint8 mode = _tMode[_musicPos[1]];
+ uint8 note = _musicPos[4];
+ uint8 velo = _musicPos[5];
+
+ sendEvent(mode, evt);
+ sendEvent(mode, applyNoteShift(note));
+ sendEvent(mode, applyVolumeShift(velo));
+
+ jumpNextLoop();
+ if (_musicPos[0] == 0xfe || _musicPos[0] == 0xfd)
+ return true;
+
+ velo = _musicPos[5];
+ uint16 len = ((((_musicPos[1] << 4) | (_musicPos[2] << 8)) >> 4) & 0xff) | ((((_musicPos[3] << 4) | (_musicPos[4] << 8)) >> 4) << 8);
+
+ int i = 0;
+ for (; i < 64; i++) {
+ if (_eventBuffer[i].evt == 0)
+ break;
+ }
+
+ if (i == 64) {
+ processBufferNote(mode, evt, note, velo);
+ } else {
+ _eventBuffer[i].evt = evt;
+ _eventBuffer[i].mode = mode;
+ _eventBuffer[i].note = note;
+ _eventBuffer[i].velo = velo;
+ _eventBuffer[i].len = len ? len : 1;
+ _bufferedEventsCount++;
+ }
+
+ return false;
+}
+
+bool TownsEuphonyDriver::evtPolyphonicAftertouch() {
+ if (_musicPos[1] > 31)
+ return false;
+ if (!_tEnable[_musicPos[1]])
+ return false;
+
+ uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
+ uint8 mode = _tMode[_musicPos[1]];
+
+ sendEvent(mode, evt);
+ sendEvent(mode, applyNoteShift(_musicPos[4]));
+ sendEvent(mode, _musicPos[5]);
+
+ return false;
+}
+
+bool TownsEuphonyDriver::evtControlPitch() {
+ if (_musicPos[1] > 31)
+ return false;
+ if (!_tEnable[_musicPos[1]])
+ return false;
+
+ uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
+ uint8 mode = _tMode[_musicPos[1]];
+
+ sendEvent(mode, evt);
+ sendEvent(mode, _musicPos[4]);
+ sendEvent(mode, _musicPos[5]);
+
+ return false;
+}
+
+bool TownsEuphonyDriver::evtInstrumentChanAftertouch() {
+ if (_musicPos[1] > 31)
+ return false;
+ if (!_tEnable[_musicPos[1]])
+ return false;
+
+ uint8 evt = appendEvent(_musicPos[0], _musicPos[1]);
+ uint8 mode = _tMode[_musicPos[1]];
+
+ sendEvent(mode, evt);
+ sendEvent(mode, _musicPos[4]);
+
+ return false;
+}
+
+bool TownsEuphonyDriver::evtLoadInstrument() {
+ return false;
+}
+
+bool TownsEuphonyDriver::evtAdvanceTimestampOffset() {
+ ++_timeStampBase;
+ _baseTickLen = _musicPos[1];
+ return false;
+}
+
+bool TownsEuphonyDriver::evtTempo() {
+ uint8 l = _musicPos[4] << 1;
+ _trackTempo = (l | (_musicPos[5] << 8)) >> 1;
+ setTempoIntern(_trackTempo);
+ return false;
+}
+
+bool TownsEuphonyDriver::evtModeOrdrChange() {
+ if (_musicPos[1] > 31)
+ return false;
+ if (!_tEnable[_musicPos[1]])
+ return false;
+
+ if (_musicPos[4] == 1)
+ _tMode[_musicPos[1]] = _musicPos[5];
+ else if (_musicPos[4] == 2)
+ _tOrdr[_musicPos[1]] = _musicPos[5];
+
+ return false;
+}
+
+uint8 TownsEuphonyDriver::applyNoteShift(uint8 in) {
+ int out = _tTranspose[_musicPos[1]];
+ if (!out)
+ return in;
+ out += (in & 0x7f);
+
+ if (out > 127)
+ out -= 12;
+
+ if (out < 0)
+ out += 12;
+
+ return out & 0xff;
+}
+
+uint8 TownsEuphonyDriver::applyVolumeShift(uint8 in) {
+ int out = _tLevel[_musicPos[1]];
+ out += (in & 0x7f);
+ out = CLIP(out, 1, 127);
+
+ return out & 0xff;
+}
+
+void TownsEuphonyDriver::sendNoteOff() {
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ if (*chan == -1)
+ return;
+
+ while (_assignedChannels[*chan].note != _para[0]) {
+ chan = &_assignedChannels[*chan].next;
+ if (*chan == -1)
+ return;
+ }
+
+ if (_sustainChannels[_command & 0x0f]) {
+ _assignedChannels[*chan].note |= 0x80;
+ } else {
+ _assignedChannels[*chan].note = 0;
+ _intf->callback(2, *chan);
+ }
+}
+
+void TownsEuphonyDriver::sendNoteOn() {
+ if (!_para[0])
+ return;
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ if (*chan == -1)
+ return;
+
+ do {
+ _assignedChannels[*chan].sub++;
+ chan = &_assignedChannels[*chan].next;
+ } while (*chan != -1);
+
+ chan = &_activeChannels[_command & 0x0f];
+
+ int d = 0;
+ int c = 0;
+ bool found = false;
+
+ do {
+ if (!_assignedChannels[*chan].note) {
+ found = true;
+ break;
+ }
+ if (d <= _assignedChannels[*chan].sub) {
+ c = *chan;
+ d = _assignedChannels[*chan].sub;
+ }
+ chan = &_assignedChannels[*chan].next;
+ } while (*chan != -1);
+
+ if (found)
+ c = *chan;
+ else
+ _intf->callback(2, c);
+
+ _assignedChannels[c].note = _para[0];
+ _assignedChannels[c].sub = 0;
+ _intf->callback(1, c, _para[0], _para[1]);
+}
+
+void TownsEuphonyDriver::sendChanVolume() {
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ while (*chan != -1) {
+ _intf->callback(8, *chan, _para[1] & 0x7f);
+ chan = &_assignedChannels[*chan].next;
+ }
+}
+
+void TownsEuphonyDriver::sendPanPosition() {
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ while (*chan != -1) {
+ _intf->callback(3, *chan, _para[1] & 0x7f);
+ chan = &_assignedChannels[*chan].next;
+ }
+}
+
+void TownsEuphonyDriver::sendAllNotesOff() {
+ if (_para[1] > 63) {
+ _sustainChannels[_command & 0x0f] = -1;
+ return;
+ }
+
+ _sustainChannels[_command & 0x0f] = 0;
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ while (*chan != -1) {
+ if (_assignedChannels[*chan].note & 0x80) {
+ _assignedChannels[*chan].note = 0;
+ _intf->callback(2, *chan);
+ }
+ chan = &_assignedChannels[*chan].next;
+ }
+}
+
+void TownsEuphonyDriver::sendSetInstrument() {
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ while (*chan != -1) {
+ _intf->callback(4, *chan, _para[0]);
+ _intf->callback(7, *chan, 0);
+ chan = &_assignedChannels[*chan].next;
+ }
+}
+
+void TownsEuphonyDriver::sendPitch() {
+ int8 *chan = &_activeChannels[_command & 0x0f];
+ while (*chan != -1) {
+ _para[0] += _para[0];
+ int16 pitch = (((READ_LE_UINT16(_para)) >> 1) & 0x3fff) - 0x2000;
+ _intf->callback(7, *chan, pitch);
+ chan = &_assignedChannels[*chan].next;
+ }
+}
diff --git a/audio/softsynth/fmtowns_pc98/towns_euphony.h b/audio/softsynth/fmtowns_pc98/towns_euphony.h
new file mode 100644
index 0000000000..dc40373913
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_euphony.h
@@ -0,0 +1,187 @@
+/* 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$
+ *
+ */
+
+#ifndef TOWNS_EUP_H
+#define TOWNS_EUP_H
+
+#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
+
+class TownsEuphonyDriver : public TownsAudioInterfacePluginDriver {
+public:
+ TownsEuphonyDriver(Audio::Mixer *mixer);
+ virtual ~TownsEuphonyDriver();
+
+ bool init();
+ void reset();
+
+ void loadInstrument(int chanType, int id, const uint8 *data);
+ void loadWaveTable(const uint8 *data);
+ void unloadWaveTable(int id);
+ void reserveSoundEffectChannels(int num);
+
+ int setMusicTempo(int tempo);
+ int startMusicTrack(const uint8 *data, int trackSize, int startTick);
+ void setMusicLoop(bool loop);
+ void stopParser();
+ bool parserIsPlaying() {return _playing; }
+ void continueParsing();
+
+ void playSoundEffect(int chan, int note, int velo, const uint8 *data);
+ void stopSoundEffect(int chan);
+ bool soundEffectIsPlaying(int chan);
+
+ void chanPanPos(int chan, int mode);
+ void chanPitch(int chan, int pitch);
+ void chanVolume(int chan, int vol);
+
+ void setOutputVolume(int chanType, int volLeft, int volRight);
+
+ int chanEnable(int tableEntry, int val);
+ int chanMode(int tableEntry, int val);
+ int chanOrdr(int tableEntry, int val);
+ int chanVolumeShift(int tableEntry, int val);
+ int chanNoteShift(int tableEntry, int val);
+
+ int assignChannel(int chan, int tableEntry);
+
+ void timerCallback(int timerId);
+
+ void setMusicVolume(int volume);
+ void setSoundEffectVolume(int volume);
+
+ TownsAudioInterface *intf() {
+ return _intf;
+ }
+
+private:
+ void resetTables();
+
+ void resetTempo();
+ void setTempoIntern(int tempo);
+ void setTimerA(bool enable, int tempo);
+ void setTimerB(bool enable, int tempo);
+
+ void updatePulseCount();
+ void updateTimeStampBase();
+ void updateParser();
+ void updateCheckEot();
+
+ bool parseNext();
+ void jumpNextLoop();
+
+ void updateEventBuffer();
+ void flushEventBuffer();
+ void processBufferNote(int mode, int evt, int note, int velo);
+
+ void resetControl();
+ void resetControlIntern(int mode, int chan);
+ uint8 appendEvent(uint8 evt, uint8 chan);
+
+ void sendEvent(uint8 mode, uint8 command);
+
+ typedef bool(TownsEuphonyDriver::*EuphonyOpcode)();
+ bool evtSetupNote();
+ bool evtPolyphonicAftertouch();
+ bool evtControlPitch();
+ bool evtInstrumentChanAftertouch();
+ bool evtLoadInstrument();
+ bool evtAdvanceTimestampOffset();
+ bool evtTempo();
+ bool evtModeOrdrChange();
+ bool evtNotImpl() {
+ return false;
+ }
+
+ uint8 applyNoteShift(uint8 in);
+ uint8 applyVolumeShift(uint8 in);
+
+ void sendNoteOff();
+ void sendNoteOn();
+ void sendChanVolume();
+ void sendPanPosition();
+ void sendAllNotesOff();
+ void sendSetInstrument();
+ void sendPitch();
+
+ int8 *_activeChannels;
+ int8 *_sustainChannels;
+
+ struct ActiveChannel {
+ int8 chan;
+ int8 next;
+ uint8 note;
+ uint8 sub;
+ } *_assignedChannels;
+
+ uint8 *_tEnable;
+ uint8 *_tMode;
+ uint8 *_tOrdr;
+ int8 *_tLevel;
+ int8 *_tTranspose;
+
+ struct DlEvent {
+ uint8 evt;
+ uint8 mode;
+ uint8 note;
+ uint8 velo;
+ uint16 len;
+ } *_eventBuffer;
+ int _bufferedEventsCount;
+
+ uint8 _para[2];
+ uint8 _paraCount;
+ uint8 _command;
+
+ uint8 _defaultBaseTickLen;
+ uint8 _baseTickLen;
+ uint32 _pulseCount;
+ int _tempoControlMode;
+ int _extraTimingControlRemainder;
+ int _extraTimingControl;
+ int _timerSetting;
+ int8 _tempoDiff;
+ int _tempoModifier;
+ uint32 _timeStampDest;
+ uint32 _timeStampBase;
+ int8 _elapsedEvents;
+ uint8 _deltaTicks;
+ uint32 _tickCounter;
+ uint8 _defaultTempo;
+ int _trackTempo;
+
+ bool _loop;
+ bool _playing;
+ bool _endOfTrack;
+ bool _suspendParsing;
+
+ const uint8 *_musicStart;
+ const uint8 *_musicPos;
+ uint32 _musicTrackSize;
+
+ TownsAudioInterface *_intf;
+};
+
+#endif
+
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp
new file mode 100644
index 0000000000..79fd95ea0d
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.cpp
@@ -0,0 +1,1428 @@
+/* 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 "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
+#include "common/endian.h"
+
+class TownsPC98_MusicChannel {
+public:
+ TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id);
+ virtual ~TownsPC98_MusicChannel();
+ virtual void init();
+
+ typedef enum channelState {
+ CHS_RECALCFREQ = 0x01,
+ CHS_KEYOFF = 0x02,
+ CHS_SSGOFF = 0x04,
+ CHS_VBROFF = 0x08,
+ CHS_ALLOFF = 0x0f,
+ CHS_PROTECT = 0x40,
+ CHS_EOT = 0x80
+ } ChannelState;
+
+ virtual void loadData(uint8 *data);
+ virtual void processEvents();
+ virtual void processFrequency();
+ virtual bool processControlEvent(uint8 cmd);
+
+ virtual void keyOn();
+ void keyOff();
+
+ void setOutputLevel();
+ virtual void fadeStep();
+ virtual void reset();
+
+ const uint8 _idFlag;
+
+protected:
+ void setupVibrato();
+ bool processVibrato();
+
+ bool control_dummy(uint8 para);
+ bool control_f0_setPatch(uint8 para);
+ bool control_f1_presetOutputLevel(uint8 para);
+ bool control_f2_setKeyOffTime(uint8 para);
+ bool control_f3_setFreqLSB(uint8 para);
+ bool control_f4_setOutputLevel(uint8 para);
+ bool control_f5_setTempo(uint8 para);
+ bool control_f6_repeatSection(uint8 para);
+ bool control_f7_setupVibrato(uint8 para);
+ bool control_f8_toggleVibrato(uint8 para);
+ bool control_fa_writeReg(uint8 para);
+ virtual bool control_fb_incOutLevel(uint8 para);
+ virtual bool control_fc_decOutLevel(uint8 para);
+ bool control_fd_jump(uint8 para);
+ virtual bool control_ff_endOfTrack(uint8 para);
+
+ uint8 _ticksLeft;
+ uint8 _algorithm;
+ uint8 _instr;
+ uint8 _totalLevel;
+ uint8 _frqBlockMSB;
+ int8 _frqLSB;
+ uint8 _keyOffTime;
+ bool _hold;
+ uint8 *_dataPtr;
+ uint8 _vbrInitDelayHi;
+ uint8 _vbrInitDelayLo;
+ int16 _vbrModInitVal;
+ uint8 _vbrDuration;
+ uint8 _vbrCurDelay;
+ int16 _vbrModCurVal;
+ uint8 _vbrDurLeft;
+ uint16 _frequency;
+ uint8 _block;
+ uint8 _regOffset;
+ uint8 _flags;
+ uint8 _ssgTl;
+ uint8 _ssgStep;
+ uint8 _ssgTicksLeft;
+ uint8 _ssgTargetLvl;
+ uint8 _ssgStartLvl;
+
+ const uint8 _chanNum;
+ const uint8 _keyNum;
+ const uint8 _part;
+
+ TownsPC98_AudioDriver *_drv;
+
+ typedef bool (TownsPC98_MusicChannel::*ControlEventFunc)(uint8 para);
+ const ControlEventFunc *controlEvents;
+};
+
+class TownsPC98_MusicChannelSSG : public TownsPC98_MusicChannel {
+public:
+ TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
+ virtual ~TownsPC98_MusicChannelSSG() {}
+ void init();
+
+ virtual void loadData(uint8 *data);
+ void processEvents();
+ void processFrequency();
+ bool processControlEvent(uint8 cmd);
+
+ void keyOn();
+ void nextShape();
+
+ void protect();
+ void restore();
+ virtual void reset();
+
+ void fadeStep();
+
+protected:
+ void setOutputLevel(uint8 lvl);
+
+ bool control_f0_setPatch(uint8 para);
+ bool control_f1_setTotalLevel(uint8 para);
+ bool control_f4_setAlgorithm(uint8 para);
+ bool control_f9_loadCustomPatch(uint8 para);
+ bool control_fb_incOutLevel(uint8 para);
+ bool control_fc_decOutLevel(uint8 para);
+ bool control_ff_endOfTrack(uint8 para);
+
+ typedef bool (TownsPC98_MusicChannelSSG::*ControlEventFunc)(uint8 para);
+ const ControlEventFunc *controlEvents;
+};
+
+class TownsPC98_SfxChannel : public TownsPC98_MusicChannelSSG {
+public:
+ TownsPC98_SfxChannel(TownsPC98_AudioDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+ TownsPC98_MusicChannelSSG(driver, regOffs, flgs, num, key, prt, id) {}
+ ~TownsPC98_SfxChannel() {}
+
+ void loadData(uint8 *data);
+ void reset();
+};
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+class TownsPC98_MusicChannelPCM : public TownsPC98_MusicChannel {
+public:
+ TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
+ ~TownsPC98_MusicChannelPCM() {}
+ void init();
+
+ void loadData(uint8 *data);
+ void processEvents();
+ bool processControlEvent(uint8 cmd);
+
+private:
+ bool control_f1_prcStart(uint8 para);
+ bool control_ff_endOfTrack(uint8 para);
+
+ typedef bool (TownsPC98_MusicChannelPCM::*ControlEventFunc)(uint8 para);
+ const ControlEventFunc *controlEvents;
+};
+#endif
+
+TownsPC98_MusicChannel::TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key),
+ _part(prt), _idFlag(id), controlEvents(0) {
+
+ _ticksLeft = _algorithm = _instr = _totalLevel = _frqBlockMSB = _keyOffTime = 0;
+ _ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = _block = 0;
+ _vbrInitDelayHi = _vbrInitDelayLo = _vbrDuration = _vbrCurDelay = _vbrDurLeft = 0;
+ _frqLSB = 0;
+ _hold = false;
+ _dataPtr = 0;
+ _vbrModInitVal = _vbrModCurVal = 0;
+ _frequency = 0;
+}
+
+TownsPC98_MusicChannel::~TownsPC98_MusicChannel() {
+}
+
+void TownsPC98_MusicChannel::init() {
+#define Control(x) &TownsPC98_MusicChannel::control_##x
+ static const ControlEventFunc ctrlEvents[] = {
+ Control(f0_setPatch),
+ Control(f1_presetOutputLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setOutputLevel),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupVibrato),
+ Control(f8_toggleVibrato),
+ Control(dummy),
+ Control(fa_writeReg),
+ Control(fb_incOutLevel),
+ Control(fc_decOutLevel),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrack)
+ };
+#undef Control
+
+ controlEvents = ctrlEvents;
+}
+
+void TownsPC98_MusicChannel::keyOff() {
+ // all operators off
+ uint8 value = _keyNum & 0x0f;
+ if (_part)
+ value |= 4;
+ uint8 regAddress = 0x28;
+ _drv->writeReg(0, regAddress, value);
+ _flags |= CHS_KEYOFF;
+}
+
+void TownsPC98_MusicChannel::keyOn() {
+ // all operators on
+ uint8 value = _keyNum | 0xf0;
+ if (_part)
+ value |= 4;
+ uint8 regAddress = 0x28;
+ _drv->writeReg(0, regAddress, value);
+}
+
+void TownsPC98_MusicChannel::loadData(uint8 *data) {
+ _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
+ _ticksLeft = 1;
+ _dataPtr = data;
+ _totalLevel = 0x7F;
+
+ uint8 *tmp = _dataPtr;
+ for (bool loop = true; loop;) {
+ uint8 cmd = *tmp++;
+ if (cmd < 0xf0) {
+ tmp++;
+ } else if (cmd == 0xff) {
+ if (READ_LE_UINT16(tmp)) {
+ _drv->_looping |= _idFlag;
+ tmp += _drv->_opnFxCmdLen[cmd - 240];
+ } else
+ loop = false;
+ } else if (cmd == 0xf6) {
+ // reset repeat section countdown
+ tmp[0] = tmp[1];
+ tmp += 4;
+ } else {
+ tmp += _drv->_opnFxCmdLen[cmd - 240];
+ }
+ }
+}
+
+void TownsPC98_MusicChannel::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ if (!_hold && _ticksLeft == _keyOffTime)
+ keyOff();
+
+ if (--_ticksLeft)
+ return;
+
+ if (!_hold)
+ keyOff();
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ keyOff();
+ _hold = false;
+ } else {
+ keyOn();
+
+ if (_hold == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _hold = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+}
+
+void TownsPC98_MusicChannel::processFrequency() {
+ if (_flags & CHS_RECALCFREQ) {
+
+ _frequency = (READ_LE_UINT16(&_drv->_opnFreqTable[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB) | (((_frqBlockMSB & 0x70) >> 1) << 8);
+
+ _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8));
+ _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff));
+
+ setupVibrato();
+ }
+
+ if (!(_flags & CHS_VBROFF)) {
+ if (!processVibrato())
+ return;
+
+ _drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8));
+ _drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff));
+ }
+}
+
+void TownsPC98_MusicChannel::setupVibrato() {
+ _vbrCurDelay = _vbrInitDelayHi;
+ if (_flags & CHS_KEYOFF) {
+ _vbrModCurVal = _vbrModInitVal;
+ _vbrCurDelay += _vbrInitDelayLo;
+ }
+ _vbrDurLeft = (_vbrDuration >> 1);
+ _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+}
+
+bool TownsPC98_MusicChannel::processVibrato() {
+ if (--_vbrCurDelay)
+ return false;
+
+ _vbrCurDelay = _vbrInitDelayHi;
+ _frequency += _vbrModCurVal;
+
+ if (!--_vbrDurLeft) {
+ _vbrDurLeft = _vbrDuration;
+ _vbrModCurVal = -_vbrModCurVal;
+ }
+
+ return true;
+}
+
+bool TownsPC98_MusicChannel::processControlEvent(uint8 cmd) {
+ uint8 para = *_dataPtr++;
+ return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+void TownsPC98_MusicChannel::setOutputLevel() {
+ uint8 outopr = _drv->_opnCarrier[_algorithm];
+ uint8 reg = 0x40 + _regOffset;
+
+ for (int i = 0; i < 4; i++) {
+ if (outopr & 1)
+ _drv->writeReg(_part, reg, _totalLevel);
+ outopr >>= 1;
+ reg += 4;
+ }
+}
+
+void TownsPC98_MusicChannel::fadeStep() {
+ _totalLevel += 3;
+ if (_totalLevel > 0x7f)
+ _totalLevel = 0x7f;
+ setOutputLevel();
+}
+
+void TownsPC98_MusicChannel::reset() {
+ _hold = false;
+ _keyOffTime = 0;
+ _ticksLeft = 1;
+
+ _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
+
+ _totalLevel = 0;
+ _algorithm = 0;
+ _flags = CHS_EOT;
+ _algorithm = 0;
+
+ _block = 0;
+ _frequency = 0;
+ _frqBlockMSB = 0;
+ _frqLSB = 0;
+
+ _ssgTl = 0;
+ _ssgStartLvl = 0;
+ _ssgTargetLvl = 0;
+ _ssgStep = 0;
+ _ssgTicksLeft = 0;
+
+ _vbrInitDelayHi = 0;
+ _vbrInitDelayLo = 0;
+ _vbrModInitVal = 0;
+ _vbrDuration = 0;
+ _vbrCurDelay = 0;
+ _vbrModCurVal = 0;
+ _vbrDurLeft = 0;
+}
+
+bool TownsPC98_MusicChannel::control_f0_setPatch(uint8 para) {
+ _instr = para;
+ uint8 reg = _regOffset + 0x80;
+
+ for (int i = 0; i < 4; i++) {
+ // set release rate for each operator
+ _drv->writeReg(_part, reg, 0x0f);
+ reg += 4;
+ }
+
+ const uint8 *tptr = _drv->_patches + ((uint32)_instr << 5);
+ reg = _regOffset + 0x30;
+
+ // write registers 0x30 to 0x8f
+ for (int i = 0; i < 6; i++) {
+ _drv->writeReg(_part, reg, tptr[0]);
+ reg += 4;
+ _drv->writeReg(_part, reg, tptr[2]);
+ reg += 4;
+ _drv->writeReg(_part, reg, tptr[1]);
+ reg += 4;
+ _drv->writeReg(_part, reg, tptr[3]);
+ reg += 4;
+ tptr += 4;
+ }
+
+ reg = _regOffset + 0xB0;
+ _algorithm = tptr[0] & 7;
+ // set feedback and algorithm
+ _drv->writeReg(_part, reg, tptr[0]);
+
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f1_presetOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = _drv->_opnLvlPresets[para];
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f2_setKeyOffTime(uint8 para) {
+ _keyOffTime = para;
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f3_setFreqLSB(uint8 para) {
+ _frqLSB = (int8) para;
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f4_setOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = para;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f5_setTempo(uint8 para) {
+ _drv->setMusicTempo(para);
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f6_repeatSection(uint8 para) {
+ _dataPtr--;
+ _dataPtr[0]--;
+
+ if (*_dataPtr) {
+ // repeat section until counter has reached zero
+ _dataPtr = _drv->_trackPtr + READ_LE_UINT16(_dataPtr + 2);
+ } else {
+ // reset counter, advance to next section
+ _dataPtr[0] = _dataPtr[1];
+ _dataPtr += 4;
+ }
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f7_setupVibrato(uint8 para) {
+ _vbrInitDelayHi = _dataPtr[0];
+ _vbrInitDelayLo = para;
+ _vbrModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1);
+ _vbrDuration = _dataPtr[3];
+ _dataPtr += 4;
+ _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF | CHS_RECALCFREQ;
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_f8_toggleVibrato(uint8 para) {
+ if (para == 0x10) {
+ if (*_dataPtr++) {
+ _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF;
+ } else {
+ _flags |= CHS_VBROFF;
+ }
+ } else {
+ /* NOT IMPLEMENTED
+ uint8 skipChannels = para / 36;
+ uint8 entry = para % 36;
+ TownsPC98_AudioDriver::TownsPC98_MusicChannel *t = &chan[skipChannels];
+
+ t->unnamedEntries[entry] = *_dataPtr++;*/
+ }
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_fa_writeReg(uint8 para) {
+ _drv->writeReg(_part, para, *_dataPtr++);
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_fb_incOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ uint8 val = (_totalLevel + 3);
+ if (val > 0x7f)
+ val = 0x7f;
+
+ _totalLevel = val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_fc_decOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ int8 val = (int8)(_totalLevel - 3);
+ if (val < 0)
+ val = 0;
+
+ _totalLevel = (uint8) val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_fd_jump(uint8 para) {
+ uint8 *tmp = _drv->_trackPtr + READ_LE_UINT16(_dataPtr - 1);
+ _dataPtr = (tmp[1] == 1) ? tmp : (_dataPtr + 1);
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_dummy(uint8 para) {
+ _dataPtr--;
+ return true;
+}
+
+bool TownsPC98_MusicChannel::control_ff_endOfTrack(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackPtr + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ _drv->_finishedChannelsFlag |= _idFlag;
+ keyOff();
+ return false;
+ }
+}
+
+TownsPC98_MusicChannelSSG::TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+ TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) {
+}
+
+void TownsPC98_MusicChannelSSG::init() {
+ _algorithm = 0x80;
+
+#define Control(x) &TownsPC98_MusicChannelSSG::control_##x
+ static const ControlEventFunc ctrlEventsSSG[] = {
+ Control(f0_setPatch),
+ Control(f1_setTotalLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setAlgorithm),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupVibrato),
+ Control(f8_toggleVibrato),
+ Control(f9_loadCustomPatch),
+ Control(fa_writeReg),
+ Control(fb_incOutLevel),
+ Control(fc_decOutLevel),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrack)
+ };
+#undef Control
+
+ controlEvents = ctrlEventsSSG;
+}
+
+void TownsPC98_MusicChannelSSG::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false);
+
+ if (!_hold && _ticksLeft == _keyOffTime)
+ nextShape();
+
+ if (!--_ticksLeft) {
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ nextShape();
+ _hold = false;
+ } else {
+ if (!_hold) {
+ _instr &= 0xf0;
+ _ssgStep = _drv->_ssgPatches[_instr];
+ _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+ _ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+ _ssgStartLvl = _drv->_ssgPatches[_instr + 3];
+ _flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF;
+ }
+
+ keyOn();
+
+ if (_hold == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _hold = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+ }
+
+ if (!(_flags & CHS_SSGOFF)) {
+ if (--_ssgTicksLeft) {
+ if (!_drv->_fading)
+ setOutputLevel(_ssgStartLvl);
+ return;
+ }
+
+ _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+
+ if (_drv->_ssgPatches[_instr + 1] & 0x80) {
+ uint8 t = _ssgStartLvl - _ssgStep;
+
+ if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) {
+ if (!_drv->_fading)
+ setOutputLevel(t);
+ return;
+ }
+ } else {
+ int t = _ssgStartLvl + _ssgStep;
+ uint8 p = (uint8)(t & 0xff);
+
+ if (t < 256 && _ssgTargetLvl > p) {
+ if (!_drv->_fading)
+ setOutputLevel(p);
+ return;
+ }
+ }
+
+ setOutputLevel(_ssgTargetLvl);
+ if (_ssgStartLvl && !(_instr & 8)) {
+ _instr += 4;
+ _ssgStep = _drv->_ssgPatches[_instr];
+ _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+ _ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+ } else {
+ _flags |= CHS_SSGOFF;
+ setOutputLevel(0);
+ }
+ }
+}
+
+void TownsPC98_MusicChannelSSG::processFrequency() {
+ if (_algorithm & 0x40)
+ return;
+
+ if (_flags & CHS_RECALCFREQ) {
+ _block = _frqBlockMSB >> 4;
+ _frequency = READ_LE_UINT16(&_drv->_opnFreqTableSSG[(_frqBlockMSB & 0x0f) << 1]) + _frqLSB;
+
+ uint16 f = _frequency >> _block;
+ _drv->writeReg(_part, _regOffset << 1, f & 0xff);
+ _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
+
+ setupVibrato();
+ }
+
+ if (!(_flags & (CHS_EOT | CHS_VBROFF | CHS_SSGOFF))) {
+ if (!processVibrato())
+ return;
+
+ uint16 f = _frequency >> _block;
+ _drv->writeReg(_part, _regOffset << 1, f & 0xff);
+ _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
+ }
+}
+
+bool TownsPC98_MusicChannelSSG::processControlEvent(uint8 cmd) {
+ uint8 para = *_dataPtr++;
+ return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+void TownsPC98_MusicChannelSSG::nextShape() {
+ _instr = (_instr & 0xf0) + 0x0c;
+ _ssgStep = _drv->_ssgPatches[_instr];
+ _ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
+ _ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
+}
+
+void TownsPC98_MusicChannelSSG::keyOn() {
+ uint8 c = 0x7b;
+ uint8 t = (_algorithm & 0xC0) << 1;
+ if (_algorithm & 0x80)
+ t |= 4;
+
+ c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset));
+ t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset));
+
+ if (!(_algorithm & 0x80))
+ _drv->writeReg(_part, 6, _algorithm & 0x7f);
+
+ uint8 e = (_drv->readSSGStatus() & c) | t;
+ _drv->writeReg(_part, 7, e);
+}
+
+void TownsPC98_MusicChannelSSG::protect() {
+ _flags |= CHS_PROTECT;
+}
+
+void TownsPC98_MusicChannelSSG::restore() {
+ _flags &= ~CHS_PROTECT;
+ keyOn();
+ _drv->writeReg(_part, 8 + _regOffset, _ssgTl);
+ uint16 f = _frequency >> _block;
+ _drv->writeReg(_part, _regOffset << 1, f & 0xff);
+ _drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
+}
+
+void TownsPC98_MusicChannelSSG::loadData(uint8 *data) {
+ _drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false);
+ TownsPC98_MusicChannel::loadData(data);
+ setOutputLevel(0);
+ _algorithm = 0x80;
+}
+
+void TownsPC98_MusicChannelSSG::setOutputLevel(uint8 lvl) {
+ _ssgStartLvl = lvl;
+ uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8;
+ if (newTl == _ssgTl)
+ return;
+ _ssgTl = newTl;
+ _drv->writeReg(_part, 8 + _regOffset, _ssgTl);
+}
+
+void TownsPC98_MusicChannelSSG::reset() {
+ TownsPC98_MusicChannel::reset();
+
+ // Unlike the original we restore the default patch data. This fixes a bug
+ // where certain sound effects would bring each other out of tune (e.g. the
+ // dragon's fire in Darm's house in Kyra 1 would sound different each time
+ // you triggered another sfx by dropping an item etc.)
+ uint8 i = (10 + _regOffset) << 4;
+ const uint8 *src = &_drv->_drvTables[156];
+ _drv->_ssgPatches[i] = src[i];
+ _drv->_ssgPatches[i + 3] = src[i + 3];
+ _drv->_ssgPatches[i + 4] = src[i + 4];
+ _drv->_ssgPatches[i + 6] = src[i + 6];
+ _drv->_ssgPatches[i + 8] = src[i + 8];
+ _drv->_ssgPatches[i + 12] = src[i + 12];
+}
+
+void TownsPC98_MusicChannelSSG::fadeStep() {
+ _totalLevel--;
+ if ((int8)_totalLevel < 0)
+ _totalLevel = 0;
+ setOutputLevel(_ssgStartLvl);
+}
+
+bool TownsPC98_MusicChannelSSG::control_f0_setPatch(uint8 para) {
+ _instr = para << 4;
+ para = (para >> 3) & 0x1e;
+ if (para)
+ return control_f4_setAlgorithm(para | 0x40);
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_f1_setTotalLevel(uint8 para) {
+ if (!_drv->_fading)
+ _totalLevel = para;
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_f4_setAlgorithm(uint8 para) {
+ _algorithm = para;
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_f9_loadCustomPatch(uint8 para) {
+ _instr = (_drv->_sfxOffs + 10 + _regOffset) << 4;
+ _drv->_ssgPatches[_instr] = *_dataPtr++;
+ _drv->_ssgPatches[_instr + 3] = para;
+ _drv->_ssgPatches[_instr + 4] = *_dataPtr++;
+ _drv->_ssgPatches[_instr + 6] = *_dataPtr++;
+ _drv->_ssgPatches[_instr + 8] = *_dataPtr++;
+ _drv->_ssgPatches[_instr + 12] = *_dataPtr++;
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_fb_incOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel--;
+ if ((int8)_totalLevel < 0)
+ _totalLevel = 0;
+
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_fc_decOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ if (_totalLevel + 1 < 0x10)
+ _totalLevel++;
+
+ return true;
+}
+
+bool TownsPC98_MusicChannelSSG::control_ff_endOfTrack(uint8 para) {
+ if (!_drv->_sfxOffs) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackPtr + val;
+ return true;
+ } else {
+ // stop parsing
+ if (!_drv->_fading)
+ setOutputLevel(0);
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ _drv->_finishedSSGFlag |= _idFlag;
+ }
+ } else {
+ // end of sfx track - restore ssg music channel
+ _flags |= CHS_EOT;
+ _drv->_finishedSfxFlag |= _idFlag;
+ _drv->_ssgChannels[_chanNum]->restore();
+ }
+
+ return false;
+}
+
+void TownsPC98_SfxChannel::loadData(uint8 *data) {
+ _flags = CHS_ALLOFF;
+ _ticksLeft = 1;
+ _dataPtr = data;
+ _ssgTl = 0xff;
+ _algorithm = 0x80;
+
+ uint8 *tmp = _dataPtr;
+ for (bool loop = true; loop;) {
+ uint8 cmd = *tmp++;
+ if (cmd < 0xf0) {
+ tmp++;
+ } else if (cmd == 0xff) {
+ loop = false;
+ } else if (cmd == 0xf6) {
+ // reset repeat section countdown
+ tmp[0] = tmp[1];
+ tmp += 4;
+ } else {
+ tmp += _drv->_opnFxCmdLen[cmd - 240];
+ }
+ }
+}
+
+void TownsPC98_SfxChannel::reset() {
+ TownsPC98_MusicChannel::reset();
+
+ // Unlike the original we restore the default patch data. This fixes a bug
+ // where certain sound effects would bring each other out of tune (e.g. the
+ // dragon's fire in Darm's house in Kyra 1 would sound different each time
+ // you triggered another sfx by dropping an item etc.)
+ uint8 i = (13 + _regOffset) << 4;
+ const uint8 *src = &_drv->_drvTables[156];
+ _drv->_ssgPatches[i] = src[i];
+ _drv->_ssgPatches[i + 3] = src[i + 3];
+ _drv->_ssgPatches[i + 4] = src[i + 4];
+ _drv->_ssgPatches[i + 6] = src[i + 6];
+ _drv->_ssgPatches[i + 8] = src[i + 8];
+ _drv->_ssgPatches[i + 12] = src[i + 12];
+}
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+TownsPC98_MusicChannelPCM::TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+ TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) {
+}
+
+void TownsPC98_MusicChannelPCM::init() {
+ _algorithm = 0x80;
+
+#define Control(x) &TownsPC98_MusicChannelPCM::control_##x
+ static const ControlEventFunc ctrlEventsPCM[] = {
+ Control(dummy),
+ Control(f1_prcStart),
+ Control(dummy),
+ Control(dummy),
+ Control(dummy),
+ Control(dummy),
+ Control(f6_repeatSection),
+ Control(dummy),
+ Control(dummy),
+ Control(dummy),
+ Control(fa_writeReg),
+ Control(dummy),
+ Control(dummy),
+ Control(dummy),
+ Control(dummy),
+ Control(ff_endOfTrack)
+ };
+#undef Control
+
+ controlEvents = ctrlEventsPCM;
+}
+
+void TownsPC98_MusicChannelPCM::loadData(uint8 *data) {
+ _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
+ _ticksLeft = 1;
+ _dataPtr = data;
+ _totalLevel = 0x7F;
+}
+
+void TownsPC98_MusicChannelPCM::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ if (--_ticksLeft)
+ return;
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd == 0x80) {
+ loop = false;
+ } else if (cmd < 0xf0) {
+ _drv->writeReg(_part, 0x10, cmd);
+ } else if (!processControlEvent(cmd)) {
+ return;
+ }
+ }
+
+ _ticksLeft = *_dataPtr++;
+}
+
+bool TownsPC98_MusicChannelPCM::processControlEvent(uint8 cmd) {
+ uint8 para = *_dataPtr++;
+ return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+bool TownsPC98_MusicChannelPCM::control_f1_prcStart(uint8 para) {
+ _totalLevel = para;
+ _drv->writeReg(_part, 0x11, para);
+ return true;
+}
+
+bool TownsPC98_MusicChannelPCM::control_ff_endOfTrack(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackPtr + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ _drv->_finishedRhythmFlag |= _idFlag;
+ return false;
+ }
+}
+#endif // DISABLE_PC98_RHYTHM_CHANNEL
+
+TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type) : TownsPC98_FmSynth(mixer, type),
+ _channels(0), _ssgChannels(0), _sfxChannels(0),
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ _rhythmChannel(0),
+#endif
+ _trackPtr(0), _sfxData(0), _sfxOffs(0), _ssgPatches(0),
+ _patches(0), _sfxBuffer(0), _musicBuffer(0),
+
+ _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 108), _opnFreqTableSSG(_drvTables + 132),
+ _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == kTypeTowns ? 52 : 84)),
+
+ _updateChannelsFlag(type == kType26 ? 0x07 : 0x3F), _finishedChannelsFlag(0),
+ _updateSSGFlag(type == kTypeTowns ? 0x00 : 0x07), _finishedSSGFlag(0),
+ _updateRhythmFlag(type == kType86 ?
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ 0x01
+#else
+ 0x00
+#endif
+ : 0x00), _finishedRhythmFlag(0),
+ _updateSfxFlag(0), _finishedSfxFlag(0),
+
+ _musicTickCounter(0),
+
+ _musicVolume(255), _sfxVolume(255),
+
+ _musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) {
+
+ _sfxOffsets[0] = _sfxOffsets[1] = 0;
+}
+
+TownsPC98_AudioDriver::~TownsPC98_AudioDriver() {
+ _ready = false;
+ deinit();
+
+ if (_channels) {
+ for (int i = 0; i < _numChan; i++)
+ delete _channels[i];
+ delete[] _channels;
+ }
+
+ if (_ssgChannels) {
+ for (int i = 0; i < _numSSG; i++)
+ delete _ssgChannels[i];
+ delete[] _ssgChannels;
+ }
+
+ if (_sfxChannels) {
+ for (int i = 0; i < 2; i++)
+ delete _sfxChannels[i];
+ delete[] _sfxChannels;
+ }
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ delete _rhythmChannel;
+#endif
+
+ delete[] _ssgPatches;
+}
+
+bool TownsPC98_AudioDriver::init() {
+ if (_ready) {
+ reset();
+ return true;
+ }
+
+ TownsPC98_FmSynth::init();
+
+ setVolumeChannelMasks(-1, 0);
+
+ _channels = new TownsPC98_MusicChannel *[_numChan];
+ for (int i = 0; i < _numChan; i++) {
+ int ii = i * 6;
+ _channels[i] = new TownsPC98_MusicChannel(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _channels[i]->init();
+ }
+
+ if (_numSSG) {
+ _ssgPatches = new uint8[256];
+ memcpy(_ssgPatches, _drvTables + 156, 256);
+
+ _ssgChannels = new TownsPC98_MusicChannelSSG *[_numSSG];
+ for (int i = 0; i < _numSSG; i++) {
+ int ii = i * 6;
+ _ssgChannels[i] = new TownsPC98_MusicChannelSSG(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _ssgChannels[i]->init();
+ }
+
+ _sfxChannels = new TownsPC98_SfxChannel *[2];
+ for (int i = 0; i < 2; i++) {
+ int ii = (i + 1) * 6;
+ _sfxChannels[i] = new TownsPC98_SfxChannel(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _sfxChannels[i]->init();
+ }
+ }
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_hasPercussion) {
+ _rhythmChannel = new TownsPC98_MusicChannelPCM(this, 0, 0, 0, 0, 0, 1);
+ _rhythmChannel->init();
+ }
+#endif
+
+ setMusicTempo(84);
+ setSfxTempo(654);
+
+ _ready = true;
+
+ return true;
+}
+
+void TownsPC98_AudioDriver::loadMusicData(uint8 *data, bool loadPaused) {
+ if (!_ready) {
+ warning("TownsPC98_AudioDriver: Driver must be initialized before loading data");
+ return;
+ }
+
+ if (!data) {
+ warning("TownsPC98_AudioDriver: Invalid music file data");
+ return;
+ }
+
+ reset();
+
+ Common::StackLock lock(_mutex);
+ uint8 *src_a = _trackPtr = _musicBuffer = data;
+
+ for (uint8 i = 0; i < 3; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (int i = 0; i < _numSSG; i++) {
+ _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (uint8 i = 3; i < _numChan; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ if (_hasPercussion) {
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ _rhythmChannel->loadData(data + READ_LE_UINT16(src_a));
+#endif
+ src_a += 2;
+ }
+
+ toggleRegProtection(false);
+
+ _patches = src_a + 4;
+ _finishedChannelsFlag = _finishedSSGFlag = _finishedRhythmFlag = 0;
+
+ _musicPlaying = (loadPaused ? false : true);
+}
+
+void TownsPC98_AudioDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) {
+ if (!_ready) {
+ warning("TownsPC98_AudioDriver: Driver must be initialized before loading data");
+ return;
+ }
+
+ if (!_sfxChannels) {
+ warning("TownsPC98_AudioDriver: Sound effects not supported by this configuration");
+ return;
+ }
+
+ if (!data) {
+ warning("TownsPC98_AudioDriver: Invalid sound effects file data");
+ return;
+ }
+
+ Common::StackLock lock(_mutex);
+ _sfxData = _sfxBuffer = data;
+ _sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]);
+ _sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]);
+ _sfxPlaying = true;
+ _finishedSfxFlag = 0;
+}
+
+void TownsPC98_AudioDriver::reset() {
+ Common::StackLock lock(_mutex);
+
+ _musicPlaying = false;
+ _sfxPlaying = false;
+ _fading = false;
+ _looping = 0;
+ _musicTickCounter = 0;
+ _sfxData = 0;
+
+ TownsPC98_FmSynth::reset();
+
+ for (int i = 0; i < _numChan; i++)
+ _channels[i]->reset();
+ for (int i = 0; i < _numSSG; i++)
+ _ssgChannels[i]->reset();
+
+ if (_numSSG) {
+ for (int i = 0; i < 2; i++)
+ _sfxChannels[i]->reset();
+
+ memcpy(_ssgPatches, _drvTables + 156, 256);
+ }
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_rhythmChannel)
+ _rhythmChannel->reset();
+#endif
+}
+
+void TownsPC98_AudioDriver::fadeStep() {
+ if (!_musicPlaying)
+ return;
+
+ Common::StackLock lock(_mutex);
+ for (int j = 0; j < _numChan; j++) {
+ if (_updateChannelsFlag & _channels[j]->_idFlag)
+ _channels[j]->fadeStep();
+ }
+
+ for (int j = 0; j < _numSSG; j++) {
+ if (_updateSSGFlag & _ssgChannels[j]->_idFlag)
+ _ssgChannels[j]->fadeStep();
+ }
+
+ if (!_fading) {
+ _fading = 19;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_hasPercussion) {
+ if (_updateRhythmFlag & _rhythmChannel->_idFlag)
+ _rhythmChannel->reset();
+ }
+#endif
+ } else {
+ if (!--_fading)
+ reset();
+ }
+}
+
+void TownsPC98_AudioDriver::timerCallbackB() {
+ _sfxOffs = 0;
+
+ if (_musicPlaying) {
+ _musicTickCounter++;
+
+ for (int i = 0; i < _numChan; i++) {
+ if (_updateChannelsFlag & _channels[i]->_idFlag) {
+ _channels[i]->processEvents();
+ _channels[i]->processFrequency();
+ }
+ }
+
+ for (int i = 0; i < _numSSG; i++) {
+ if (_updateSSGFlag & _ssgChannels[i]->_idFlag) {
+ _ssgChannels[i]->processEvents();
+ _ssgChannels[i]->processFrequency();
+ }
+ }
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_hasPercussion)
+ if (_updateRhythmFlag & _rhythmChannel->_idFlag)
+ _rhythmChannel->processEvents();
+#endif
+ }
+
+ toggleRegProtection(false);
+
+ if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedRhythmFlag == _updateRhythmFlag)
+ _musicPlaying = false;
+}
+
+void TownsPC98_AudioDriver::timerCallbackA() {
+ if (_sfxChannels && _sfxPlaying) {
+ if (_sfxData)
+ startSoundEffect();
+
+ _sfxOffs = 3;
+ _trackPtr = _sfxBuffer;
+
+ for (int i = 0; i < 2; i++) {
+ if (_updateSfxFlag & _sfxChannels[i]->_idFlag) {
+ _sfxChannels[i]->processEvents();
+ _sfxChannels[i]->processFrequency();
+ }
+ }
+
+ _trackPtr = _musicBuffer;
+ }
+
+ if (_updateSfxFlag && _finishedSfxFlag == _updateSfxFlag) {
+ _sfxPlaying = false;
+ _updateSfxFlag = 0;
+ setVolumeChannelMasks(-1, 0);
+ }
+}
+
+void TownsPC98_AudioDriver::setMusicTempo(uint8 tempo) {
+ writeReg(0, 0x26, tempo);
+ writeReg(0, 0x27, 0x33);
+}
+
+void TownsPC98_AudioDriver::setSfxTempo(uint16 tempo) {
+ writeReg(0, 0x24, tempo & 0xff);
+ writeReg(0, 0x25, tempo >> 8);
+ writeReg(0, 0x27, 0x33);
+}
+
+void TownsPC98_AudioDriver::startSoundEffect() {
+ int volFlags = 0;
+
+ for (int i = 0; i < 2; i++) {
+ if (_sfxOffsets[i]) {
+ _ssgChannels[i + 1]->protect();
+ _sfxChannels[i]->reset();
+ _sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]);
+ _updateSfxFlag |= _sfxChannels[i]->_idFlag;
+ volFlags |= (_sfxChannels[i]->_idFlag << _numChan);
+ } else {
+ _ssgChannels[i + 1]->restore();
+ _updateSfxFlag &= ~_sfxChannels[i]->_idFlag;
+ }
+ }
+
+ setVolumeChannelMasks(~volFlags, volFlags);
+ _sfxData = 0;
+}
+
+const uint8 TownsPC98_AudioDriver::_drvTables[] = {
+ // channel presets
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x80, 0x01, 0x01, 0x00, 0x02,
+ 0x02, 0x80, 0x02, 0x02, 0x00, 0x04,
+ 0x00, 0x80, 0x03, 0x04, 0x01, 0x08,
+ 0x01, 0x80, 0x04, 0x05, 0x01, 0x10,
+ 0x02, 0x80, 0x05, 0x06, 0x01, 0x20,
+
+ // control event size
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05,
+ 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02,
+
+ // fmt level presets
+ 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38,
+ 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18,
+ 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90,
+
+ // carriers
+ 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F,
+
+ // pc98 level presets
+ 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25,
+ 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10,
+ 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90,
+
+ // frequencies
+ 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02,
+ 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03,
+ 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04,
+
+ // ssg frequencies
+ 0xE8, 0x0E, 0x12, 0x0E, 0x48, 0x0D, 0x89, 0x0C,
+ 0xD5, 0x0B, 0x2B, 0x0B, 0x8A, 0x0A, 0xF3, 0x09,
+ 0x64, 0x09, 0xDD, 0x08, 0x5E, 0x08, 0xE6, 0x07,
+
+ // ssg patch data
+ 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
+ 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+ 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+ 0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
+ 0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00,
+ 0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+ 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00,
+ 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00,
+ 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+
+ 0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00,
+ 0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00,
+ 0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
+ 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
+ 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00
+};
+
+#undef EUPHONY_FADEOUT_TICKS
+
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h
new file mode 100644
index 0000000000..1227e2626a
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_driver.h
@@ -0,0 +1,135 @@
+/* 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$
+ *
+ */
+
+#ifndef TOWNS_PC98_AUDIODRIVER_H
+#define TOWNS_PC98_AUDIODRIVER_H
+
+#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
+
+class TownsPC98_MusicChannel;
+class TownsPC98_MusicChannelSSG;
+class TownsPC98_SfxChannel;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+class TownsPC98_MusicChannelPCM;
+#endif
+
+class TownsPC98_AudioDriver : public TownsPC98_FmSynth {
+friend class TownsPC98_MusicChannel;
+friend class TownsPC98_MusicChannelSSG;
+friend class TownsPC98_SfxChannel;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+friend class TownsPC98_MusicChannelPCM;
+#endif
+public:
+ TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type);
+ ~TownsPC98_AudioDriver();
+
+ void loadMusicData(uint8 *data, bool loadPaused = false);
+ void loadSoundEffectData(uint8 *data, uint8 trackNum);
+ bool init();
+ void reset();
+
+ void fadeStep();
+
+ void pause() {
+ _musicPlaying = false;
+ }
+ void cont() {
+ _musicPlaying = true;
+ }
+
+ void timerCallbackB();
+ void timerCallbackA();
+
+ bool looping() {
+ return _looping == _updateChannelsFlag ? true : false;
+ }
+ bool musicPlaying() {
+ return _musicPlaying;
+ }
+
+ void setMusicVolume(int volume) {
+ _musicVolume = volume;
+ setVolumeIntern(_musicVolume, _sfxVolume);
+ }
+ void setSoundEffectVolume(int volume) {
+ _sfxVolume = volume;
+ setVolumeIntern(_musicVolume, _sfxVolume);
+ }
+
+protected:
+ void startSoundEffect();
+
+ void setMusicTempo(uint8 tempo);
+ void setSfxTempo(uint16 tempo);
+
+ TownsPC98_MusicChannel **_channels;
+ TownsPC98_MusicChannelSSG **_ssgChannels;
+ TownsPC98_SfxChannel **_sfxChannels;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ TownsPC98_MusicChannelPCM *_rhythmChannel;
+#endif
+
+ const uint8 *_opnCarrier;
+ const uint8 *_opnFreqTable;
+ const uint8 *_opnFreqTableSSG;
+ const uint8 *_opnFxCmdLen;
+ const uint8 *_opnLvlPresets;
+
+ uint8 *_musicBuffer;
+ uint8 *_sfxBuffer;
+ uint8 *_trackPtr;
+ uint8 *_patches;
+ uint8 *_ssgPatches;
+
+ uint8 _updateChannelsFlag;
+ uint8 _updateSSGFlag;
+ uint8 _updateRhythmFlag;
+ uint8 _updateSfxFlag;
+ uint8 _finishedChannelsFlag;
+ uint8 _finishedSSGFlag;
+ uint8 _finishedRhythmFlag;
+ uint8 _finishedSfxFlag;
+
+ bool _musicPlaying;
+ bool _sfxPlaying;
+ uint8 _fading;
+ uint8 _looping;
+ uint32 _musicTickCounter;
+
+ int _sfxOffs;
+ uint8 *_sfxData;
+ uint16 _sfxOffsets[2];
+
+ uint16 _musicVolume;
+ uint16 _sfxVolume;
+
+ static const uint8 _drvTables[];
+
+ bool _ready;
+};
+
+#endif
+
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
new file mode 100644
index 0000000000..f84fd841a6
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
@@ -0,0 +1,1548 @@
+/* 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 "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
+#include "common/endian.h"
+
+class TownsPC98_FmSynthOperator {
+public:
+ TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, const uint8 *rateTable,
+ const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
+ const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable);
+ ~TownsPC98_FmSynthOperator() {}
+
+ void keyOn();
+ void keyOff();
+ void frequency(int freq);
+ void updatePhaseIncrement();
+ void recalculateRates();
+ void generateOutput(int32 phasebuf, int32 *feedbuf, int32 &out);
+
+ void feedbackLevel(int32 level) {
+ _feedbackLevel = level ? level + 6 : 0;
+ }
+ void detune(int value) {
+ _detn = &_detnTbl[value << 5];
+ }
+ void multiple(uint32 value) {
+ _multiple = value ? (value << 1) : 1;
+ }
+ void attackRate(uint32 value) {
+ _specifiedAttackRate = value;
+ }
+ bool scaleRate(uint8 value);
+ void decayRate(uint32 value) {
+ _specifiedDecayRate = value;
+ recalculateRates();
+ }
+ void sustainRate(uint32 value) {
+ _specifiedSustainRate = value;
+ recalculateRates();
+ }
+ void sustainLevel(uint32 value) {
+ _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5;
+ }
+ void releaseRate(uint32 value) {
+ _specifiedReleaseRate = value;
+ recalculateRates();
+ }
+ void totalLevel(uint32 value) {
+ _totalLevel = value << 3;
+ }
+ void ampModulation(bool enable) {
+ _ampMod = enable;
+ }
+ void reset();
+
+protected:
+ EnvelopeState _state;
+ bool _playing;
+ uint32 _feedbackLevel;
+ uint32 _multiple;
+ uint32 _totalLevel;
+ uint8 _keyScale1;
+ uint8 _keyScale2;
+ uint32 _specifiedAttackRate;
+ uint32 _specifiedDecayRate;
+ uint32 _specifiedSustainRate;
+ uint32 _specifiedReleaseRate;
+ uint32 _tickCount;
+ uint32 _sustainLevel;
+
+ bool _ampMod;
+ uint32 _frequency;
+ uint8 _kcode;
+ uint32 _phase;
+ uint32 _phaseIncrement;
+ const int32 *_detn;
+
+ const uint8 *_rateTbl;
+ const uint8 *_rshiftTbl;
+ const uint8 *_adTbl;
+ const uint32 *_fTbl;
+ const uint32 *_sinTbl;
+ const int32 *_tLvlTbl;
+ const int32 *_detnTbl;
+
+ const uint32 _tickLength;
+ uint32 _timer;
+ const uint32 _rtt;
+ int32 _currentLevel;
+
+ struct EvpState {
+ uint8 rate;
+ uint8 shift;
+ } fs_a, fs_d, fs_s, fs_r;
+};
+
+TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt,
+ const uint8 *rateTable, const uint8 *shiftTable, const uint8 *attackDecayTable,
+ const uint32 *frqTable, const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) :
+ _rtt(rtt), _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable),
+ _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(timerbase * 2),
+ _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0),
+ _phase(0), _state(kEnvReady), _playing(false), _timer(0), _keyScale1(0),
+ _keyScale2(0), _currentLevel(1023), _ampMod(false), _tickCount(0) {
+
+ fs_a.rate = fs_a.shift = fs_d.rate = fs_d.shift = fs_s.rate = fs_s.shift = fs_r.rate = fs_r.shift = 0;
+
+ reset();
+}
+
+void TownsPC98_FmSynthOperator::keyOn() {
+ if (_playing)
+ return;
+
+ _playing = true;
+ _state = kEnvAttacking;
+ _phase = 0;
+}
+
+void TownsPC98_FmSynthOperator::keyOff() {
+ if (!_playing)
+ return;
+
+ _playing = false;
+ if (_state != kEnvReady)
+ _state = kEnvReleasing;
+}
+
+void TownsPC98_FmSynthOperator::frequency(int freq) {
+ uint8 block = (freq >> 11);
+ uint16 pos = (freq & 0x7ff);
+ uint8 c = pos >> 7;
+
+ _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6));
+ _frequency = _fTbl[pos << 1] >> (7 - block);
+}
+
+void TownsPC98_FmSynthOperator::updatePhaseIncrement() {
+ _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1;
+ uint8 keyscale = _kcode >> _keyScale1;
+ if (_keyScale2 != keyscale) {
+ _keyScale2 = keyscale;
+ recalculateRates();
+ }
+}
+
+void TownsPC98_FmSynthOperator::recalculateRates() {
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+
+ r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0;
+ fs_d.rate = _rateTbl[r + k];
+ fs_d.shift = _rshiftTbl[r + k];
+
+ r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0;
+ fs_s.rate = _rateTbl[r + k];
+ fs_s.shift = _rshiftTbl[r + k];
+
+ r = (_specifiedReleaseRate << 2) + 0x22;
+ fs_r.rate = _rateTbl[r + k];
+ fs_r.shift = _rshiftTbl[r + k];
+}
+
+void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int32 &out) {
+ if (_state == kEnvReady)
+ return;
+
+ _timer += _tickLength;
+ while (_timer > _rtt) {
+ _timer -= _rtt;
+ ++_tickCount;
+
+ int32 levelIncrement = 0;
+ uint32 targetTime = 0;
+ int32 targetLevel = 0;
+ EnvelopeState nextState = kEnvReady;
+
+ switch (_state) {
+ case kEnvReady:
+ return;
+ case kEnvAttacking:
+ targetLevel = 0;
+ nextState = kEnvDecaying;
+ if ((_specifiedAttackRate << 1) + _keyScale2 < 64) {
+ targetTime = (1 << fs_a.shift) - 1;
+ levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4;
+ break;
+ } else {
+ _currentLevel = targetLevel;
+ _state = nextState;
+ }
+ // Fall through
+ case kEnvDecaying:
+ targetTime = (1 << fs_d.shift) - 1;
+ nextState = kEnvSustaining;
+ targetLevel = _sustainLevel;
+ levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)];
+ break;
+ case kEnvSustaining:
+ targetTime = (1 << fs_s.shift) - 1;
+ nextState = kEnvSustaining;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)];
+ break;
+ case kEnvReleasing:
+ targetTime = (1 << fs_r.shift) - 1;
+ nextState = kEnvReady;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)];
+ break;
+ }
+
+ if (!(_tickCount & targetTime)) {
+ _currentLevel += levelIncrement;
+ if ((_state == kEnvAttacking && _currentLevel <= targetLevel) || (_state != kEnvAttacking && _currentLevel >= targetLevel)) {
+ if (_state != kEnvDecaying)
+ _currentLevel = targetLevel;
+ _state = nextState;
+ }
+ }
+ }
+
+ uint32 lvlout = _totalLevel + (uint32) _currentLevel;
+
+
+ int32 outp = 0;
+ int32 *i = &outp, *o = &outp;
+ int phaseShift = 0;
+
+ if (feed) {
+ o = &feed[0];
+ i = &feed[1];
+ phaseShift = _feedbackLevel ? ((*o + *i) << _feedbackLevel) : 0;
+ *o = *i;
+ } else {
+ phaseShift = phasebuf << 15;
+ }
+
+ if (lvlout < 832) {
+ uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000)
+ + phaseShift)) >> 16) & 0x3ff];
+ *i = ((index < 6656) ? _tLvlTbl[index] : 0);
+ } else {
+ *i = 0;
+ }
+
+ _phase += _phaseIncrement;
+ out += *o;
+}
+
+void TownsPC98_FmSynthOperator::reset() {
+ keyOff();
+ _timer = 0;
+ _keyScale2 = 0;
+ _currentLevel = 1023;
+
+ frequency(0);
+ detune(0);
+ scaleRate(0);
+ multiple(0);
+ updatePhaseIncrement();
+ attackRate(0);
+ decayRate(0);
+ releaseRate(0);
+ sustainRate(0);
+ feedbackLevel(0);
+ totalLevel(127);
+ ampModulation(false);
+}
+
+bool TownsPC98_FmSynthOperator::scaleRate(uint8 value) {
+ value = 3 - value;
+ if (_keyScale1 != value) {
+ _keyScale1 = value;
+ return true;
+ }
+
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+ return false;
+}
+
+class TownsPC98_FmSynthSquareSineSource {
+public:
+ TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt);
+ ~TownsPC98_FmSynthSquareSineSource();
+
+ void init(const int *rsTable, const int *rseTable);
+ void reset();
+ void writeReg(uint8 address, uint8 value, bool force = false);
+
+ void nextTick(int32 *buffer, uint32 bufferSize);
+
+ void setVolumeIntern(int volA, int volB) {
+ _volumeA = volA;
+ _volumeB = volB;
+ }
+ void setVolumeChannelMasks(int channelMaskA, int channelMaskB) {
+ _volMaskA = channelMaskA;
+ _volMaskB = channelMaskB;
+ }
+
+ uint8 chanEnable() const {
+ return _chanEnable;
+ }
+private:
+ void updateRegs();
+
+ uint8 _updateRequestBuf[64];
+ int _updateRequest;
+ int _rand;
+
+ int8 _evpTimer;
+ uint32 _pReslt;
+ uint8 _attack;
+
+ bool _evpUpdate, _cont;
+
+ int _evpUpdateCnt;
+ uint8 _outN;
+ int _nTick;
+
+ int32 *_tlTable;
+ int32 *_tleTable;
+
+ const uint32 _tickLength;
+ uint32 _timer;
+ const uint32 _rtt;
+
+ struct Channel {
+ int tick;
+ uint8 smp;
+ uint8 out;
+
+ uint8 frqL;
+ uint8 frqH;
+ uint8 vol;
+ } _channels[3];
+
+ uint8 _noiseGenerator;
+ uint8 _chanEnable;
+
+ uint8 **_reg;
+
+ uint16 _volumeA;
+ uint16 _volumeB;
+ int _volMaskA;
+ int _volMaskB;
+
+ bool _ready;
+};
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+class TownsPC98_FmSynthPercussionSource {
+public:
+ TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt);
+ ~TownsPC98_FmSynthPercussionSource() {
+ delete[] _reg;
+ }
+
+ void init(const uint8 *instrData = 0);
+ void reset();
+ void writeReg(uint8 address, uint8 value);
+
+ void nextTick(int32 *buffer, uint32 bufferSize);
+
+ void setVolumeIntern(int volA, int volB) {
+ _volumeA = volA;
+ _volumeB = volB;
+ }
+ void setVolumeChannelMasks(int channelMaskA, int channelMaskB) {
+ _volMaskA = channelMaskA;
+ _volMaskB = channelMaskB;
+ }
+
+private:
+ struct RhtChannel {
+ const uint8 *data;
+
+ const uint8 *start;
+ const uint8 *end;
+ const uint8 *pos;
+ uint32 size;
+ bool active;
+ uint8 level;
+
+ int8 decState;
+ uint8 decStep;
+
+ int16 samples[2];
+ int out;
+
+ uint8 startPosH;
+ uint8 startPosL;
+ uint8 endPosH;
+ uint8 endPosL;
+ };
+
+ void recalcOuput(RhtChannel *ins);
+ void advanceInput(RhtChannel *ins);
+
+ RhtChannel _rhChan[6];
+
+ uint8 _totalLevel;
+
+ const uint32 _tickLength;
+ uint32 _timer;
+ const uint32 _rtt;
+
+ uint8 **_reg;
+
+ uint16 _volumeA;
+ uint16 _volumeB;
+ int _volMaskA;
+ int _volMaskB;
+
+ bool _ready;
+};
+#endif // DISABLE_PC98_RHYTHM_CHANNEL
+
+TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt) : _tlTable(0),
+ _rtt(rtt), _tleTable(0), _updateRequest(-1), _tickLength(timerbase * 27), _ready(0), _reg(0), _rand(1), _outN(1),
+ _nTick(0), _evpUpdateCnt(0), _evpTimer(0x1f), _pReslt(0x1f), _attack(0), _cont(false), _evpUpdate(true),
+ _timer(0), _noiseGenerator(0), _chanEnable(0),
+ _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) {
+
+ memset(_channels, 0, sizeof(_channels));
+ memset(_updateRequestBuf, 0, sizeof(_updateRequestBuf));
+ _reg = new uint8 *[11];
+
+ _reg[0] = &_channels[0].frqL;
+ _reg[1] = &_channels[0].frqH;
+ _reg[2] = &_channels[1].frqL;
+ _reg[3] = &_channels[1].frqH;
+ _reg[4] = &_channels[2].frqL;
+ _reg[5] = &_channels[2].frqH;
+ _reg[6] = &_noiseGenerator;
+ _reg[7] = &_chanEnable;
+ _reg[8] = &_channels[0].vol;
+ _reg[9] = &_channels[1].vol;
+ _reg[10] = &_channels[2].vol;
+
+ reset();
+}
+
+TownsPC98_FmSynthSquareSineSource::~TownsPC98_FmSynthSquareSineSource() {
+ delete[] _tlTable;
+ delete[] _tleTable;
+ delete[] _reg;
+}
+
+void TownsPC98_FmSynthSquareSineSource::init(const int *rsTable, const int *rseTable) {
+ if (_ready) {
+ reset();
+ return;
+ }
+
+ delete[] _tlTable;
+ delete[] _tleTable;
+ _tlTable = new int32[16];
+ _tleTable = new int32[32];
+ float a, b, d;
+ d = 801.0f;
+
+ for (int i = 0; i < 16; i++) {
+ b = 1.0f / rsTable[i];
+ a = 1.0f / d + b + 1.0f / 1000.0f;
+ float v = (b / a) * 32767.0f;
+ _tlTable[i] = (int32) v;
+
+ b = 1.0f / rseTable[i];
+ a = 1.0f / d + b + 1.0f / 1000.0f;
+ v = (b / a) * 32767.0f;
+ _tleTable[i] = (int32) v;
+ }
+
+ for (int i = 16; i < 32; i++) {
+ b = 1.0f / rseTable[i];
+ a = 1.0f / d + b + 1.0f / 1000.0f;
+ float v = (b / a) * 32767.0f;
+ _tleTable[i] = (int32) v;
+ }
+
+ _ready = true;
+}
+
+void TownsPC98_FmSynthSquareSineSource::reset() {
+ _rand = 1;
+ _outN = 1;
+ _updateRequest = -1;
+ _nTick = _evpUpdateCnt = 0;
+ _evpTimer = 0x1f;
+ _pReslt = 0x1f;
+ _attack = 0;
+ _cont = false;
+ _evpUpdate = true;
+ _timer = 0;
+
+ for (int i = 0; i < 3; i++) {
+ _channels[i].tick = 0;
+ _channels[i].smp = _channels[i].out = 0;
+ }
+
+ for (int i = 0; i < 14; i++)
+ writeReg(i, 0, true);
+
+ writeReg(7, 0xbf, true);
+}
+
+void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, bool force) {
+ if (!_ready)
+ return;
+
+ if (address > 10 || *_reg[address] == value) {
+ if ((address == 11 || address == 12 || address == 13) && value)
+ warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address);
+ return;
+ }
+
+ if (!force) {
+ if (_updateRequest >= 63) {
+ warning("TownsPC98_FmSynthSquareSineSource: event buffer overflow");
+ _updateRequest = -1;
+ }
+ _updateRequestBuf[++_updateRequest] = value;
+ _updateRequestBuf[++_updateRequest] = address;
+ return;
+ }
+
+ *_reg[address] = value;
+}
+
+void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSize) {
+ if (!_ready)
+ return;
+
+ for (uint32 i = 0; i < bufferSize; i++) {
+ _timer += _tickLength;
+ while (_timer > _rtt) {
+ _timer -= _rtt;
+
+ if (++_nTick >= (_noiseGenerator & 0x1f)) {
+ if ((_rand + 1) & 2)
+ _outN ^= 1;
+
+ _rand = (((_rand & 1) ^ ((_rand >> 3) & 1)) << 16) | (_rand >> 1);
+ _nTick = 0;
+ }
+
+ for (int ii = 0; ii < 3; ii++) {
+ if (++_channels[ii].tick >= (((_channels[ii].frqH & 0x0f) << 8) | _channels[ii].frqL)) {
+ _channels[ii].tick = 0;
+ _channels[ii].smp ^= 1;
+ }
+ _channels[ii].out = (_channels[ii].smp | ((_chanEnable >> ii) & 1)) & (_outN | ((_chanEnable >> (ii + 3)) & 1));
+ }
+
+ if (_evpUpdate) {
+ if (++_evpUpdateCnt >= 0) {
+ _evpUpdateCnt = 0;
+
+ if (--_evpTimer < 0) {
+ if (_cont) {
+ _evpTimer &= 0x1f;
+ } else {
+ _evpUpdate = false;
+ _evpTimer = 0;
+ }
+ }
+ }
+ }
+ _pReslt = _evpTimer ^ _attack;
+ updateRegs();
+ }
+
+ int32 finOut = 0;
+ for (int ii = 0; ii < 3; ii++) {
+ int32 finOutTemp = ((_channels[ii].vol >> 4) & 1) ? _tleTable[_channels[ii].out ? _pReslt : 0] : _tlTable[_channels[ii].out ? (_channels[ii].vol & 0x0f) : 0];
+
+ if ((1 << ii) & _volMaskA)
+ finOutTemp = (finOutTemp * _volumeA) / Audio::Mixer::kMaxMixerVolume;
+
+ if ((1 << ii) & _volMaskB)
+ finOutTemp = (finOutTemp * _volumeB) / Audio::Mixer::kMaxMixerVolume;
+
+ finOut += finOutTemp;
+ }
+
+ finOut /= 3;
+
+ buffer[i << 1] += finOut;
+ buffer[(i << 1) + 1] += finOut;
+ }
+}
+
+void TownsPC98_FmSynthSquareSineSource::updateRegs() {
+ for (int i = 0; i < _updateRequest;) {
+ uint8 b = _updateRequestBuf[i++];
+ uint8 a = _updateRequestBuf[i++];
+ writeReg(a, b, true);
+ }
+ _updateRequest = -1;
+}
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+TownsPC98_FmSynthPercussionSource::TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt) :
+ _rtt(rtt), _tickLength(timerbase * 2), _timer(0), _ready(false), _volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) {
+
+ memset(_rhChan, 0, sizeof(RhtChannel) * 6);
+ _reg = new uint8 *[40];
+
+ _reg[0] = _reg[1] = _reg[2] = _reg[3] = _reg[4] = _reg[5] = _reg[6] = _reg[7] = _reg[8] = _reg[9] = _reg[10] = _reg[11] = _reg[12] = _reg[13] = _reg[14] = _reg[15] = 0;
+ _reg[16] = &_rhChan[0].startPosL;
+ _reg[17] = &_rhChan[1].startPosL;
+ _reg[18] = &_rhChan[2].startPosL;
+ _reg[19] = &_rhChan[3].startPosL;
+ _reg[20] = &_rhChan[4].startPosL;
+ _reg[21] = &_rhChan[5].startPosL;
+ _reg[22] = &_rhChan[0].startPosH;
+ _reg[23] = &_rhChan[1].startPosH;
+ _reg[24] = &_rhChan[2].startPosH;
+ _reg[25] = &_rhChan[3].startPosH;
+ _reg[26] = &_rhChan[4].startPosH;
+ _reg[27] = &_rhChan[5].startPosH;
+ _reg[28] = &_rhChan[0].endPosL;
+ _reg[29] = &_rhChan[1].endPosL;
+ _reg[30] = &_rhChan[2].endPosL;
+ _reg[31] = &_rhChan[3].endPosL;
+ _reg[32] = &_rhChan[4].endPosL;
+ _reg[33] = &_rhChan[5].endPosL;
+ _reg[34] = &_rhChan[0].endPosH;
+ _reg[35] = &_rhChan[1].endPosH;
+ _reg[36] = &_rhChan[2].endPosH;
+ _reg[37] = &_rhChan[3].endPosH;
+ _reg[38] = &_rhChan[4].endPosH;
+ _reg[39] = &_rhChan[5].endPosH;
+}
+
+void TownsPC98_FmSynthPercussionSource::init(const uint8 *instrData) {
+ if (_ready) {
+ reset();
+ return;
+ }
+
+ const uint8 *start = instrData;
+ const uint8 *pos = start;
+
+ if (instrData) {
+ for (int i = 0; i < 6; i++) {
+ _rhChan[i].data = start + READ_BE_UINT16(pos);
+ pos += 2;
+ _rhChan[i].size = READ_BE_UINT16(pos);
+ pos += 2;
+ }
+ reset();
+ _ready = true;
+ } else {
+ memset(_rhChan, 0, sizeof(RhtChannel) * 6);
+ _ready = false;
+ }
+}
+
+void TownsPC98_FmSynthPercussionSource::reset() {
+ _timer = 0;
+ _totalLevel = 63;
+
+ for (int i = 0; i < 6; i++) {
+ RhtChannel *s = &_rhChan[i];
+ s->pos = s->start = s->data;
+ s->end = s->data + s->size;
+ s->active = false;
+ s->level = 0;
+ s->out = 0;
+ s->decStep = 1;
+ s->decState = 0;
+ s->samples[0] = s->samples[1] = 0;
+ s->startPosH = s->startPosL = s->endPosH = s->endPosL = 0;
+ }
+}
+
+void TownsPC98_FmSynthPercussionSource::writeReg(uint8 address, uint8 value) {
+ if (!_ready)
+ return;
+
+ uint8 h = address >> 4;
+ uint8 l = address & 15;
+
+ if (address > 15)
+ *_reg[address] = value;
+
+ if (address == 0) {
+ if (value & 0x80) {
+ //key off
+ for (int i = 0; i < 6; i++) {
+ if ((value >> i) & 1)
+ _rhChan[i].active = false;
+ }
+ } else {
+ //key on
+ for (int i = 0; i < 6; i++) {
+ if ((value >> i) & 1) {
+ RhtChannel *s = &_rhChan[i];
+ s->pos = s->start;
+ s->active = true;
+ s->out = 0;
+ s->samples[0] = s->samples[1] = 0;
+ s->decStep = 1;
+ s->decState = 0;
+ }
+ }
+ }
+ } else if (address == 1) {
+ // total level
+ _totalLevel = (value & 63) ^ 63;
+ for (int i = 0; i < 6; i++)
+ recalcOuput(&_rhChan[i]);
+ } else if (!h && l & 8) {
+ // instrument level
+ l &= 7;
+ _rhChan[l].level = (value & 0x1f) ^ 0x1f;
+ recalcOuput(&_rhChan[l]);
+ } else if (h & 3) {
+ l &= 7;
+ if (h == 1) {
+ // set start offset
+ _rhChan[l].start = _rhChan[l].data + ((_rhChan[l].startPosH << 8 | _rhChan[l].startPosL) << 8);
+ } else if (h == 2) {
+ // set end offset
+ _rhChan[l].end = _rhChan[l].data + ((_rhChan[l].endPosH << 8 | _rhChan[l].endPosL) << 8) + 255;
+ }
+ }
+}
+
+void TownsPC98_FmSynthPercussionSource::nextTick(int32 *buffer, uint32 bufferSize) {
+ if (!_ready)
+ return;
+
+ for (uint32 i = 0; i < bufferSize; i++) {
+ _timer += _tickLength;
+ while (_timer > _rtt) {
+ _timer -= _rtt;
+
+ for (int ii = 0; ii < 6; ii++) {
+ RhtChannel *s = &_rhChan[ii];
+ if (s->active) {
+ recalcOuput(s);
+ if (s->decStep) {
+ advanceInput(s);
+ if (s->pos == s->end)
+ s->active = false;
+ }
+ s->decStep ^= 1;
+ }
+ }
+ }
+
+ int32 finOut = 0;
+
+ for (int ii = 0; ii < 6; ii++) {
+ if (_rhChan[ii].active)
+ finOut += _rhChan[ii].out;
+ }
+
+ finOut <<= 1;
+
+ if (1 & _volMaskA)
+ finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume;
+
+ if (1 & _volMaskB)
+ finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume;
+
+ buffer[i << 1] += finOut;
+ buffer[(i << 1) + 1] += finOut;
+ }
+}
+
+void TownsPC98_FmSynthPercussionSource::recalcOuput(RhtChannel *ins) {
+ uint32 s = _totalLevel + ins->level;
+ uint32 x = s > 62 ? 0 : (1 + (s >> 3));
+ int32 y = s > 62 ? 0 : (15 - (s & 7));
+ ins->out = ((ins->samples[ins->decStep] * y) >> x) & ~3;
+}
+
+void TownsPC98_FmSynthPercussionSource::advanceInput(RhtChannel *ins) {
+ static const int8 adjustIndex[] = { -1, -1, -1, -1, 2, 5, 7, 9 };
+
+ static const int16 stepTable[] = {
+ 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55,
+ 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337,
+ 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
+ };
+
+ uint8 cur = (int8)*ins->pos++;
+
+ for (int i = 0; i < 2; i++) {
+ int b = (2 * (cur & 7) + 1) * stepTable[ins->decState] / 8;
+ ins->samples[i] = CLIP<int16>(ins->samples[i ^ 1] + (cur & 8 ? b : -b), -2048, 2047);
+ ins->decState = CLIP<int8>(ins->decState + adjustIndex[cur & 7], 0, 48);
+ cur >>= 4;
+ }
+}
+#endif // DISABLE_PC98_RHYTHM_CHANNEL
+
+TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) :
+ _mixer(mixer),
+ _chanInternal(0), _ssg(0),
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ _prc(0),
+#endif
+ _numChan(type == kType26 ? 3 : 6), _numSSG(type == kTypeTowns ? 0 : 3),
+ _hasPercussion(type == kType86 ? true : false),
+ _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), _oprDetune(0),
+ _rtt(type == kTypeTowns ? 0x514767 : 0x5B8D80), _baserate(55125.0f / (float)mixer->getOutputRate()),
+ _volMaskA(0), _volMaskB(0), _volumeA(255), _volumeB(255),
+ _regProtectionFlag(false), _ready(false) {
+
+ memset(&_timers[0], 0, sizeof(ChipTimer));
+ memset(&_timers[1], 0, sizeof(ChipTimer));
+
+ _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback;
+ _timerbase = (uint32)(_baserate * 1000000.0f);
+}
+
+TownsPC98_FmSynth::~TownsPC98_FmSynth() {
+ if (_ready)
+ deinit();
+
+ delete _ssg;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ delete _prc;
+#endif
+ delete[] _chanInternal;
+
+ delete[] _oprRates;
+ delete[] _oprRateshift;
+ delete[] _oprFrq;
+ delete[] _oprAttackDecay;
+ delete[] _oprSinTbl;
+ delete[] _oprLevelOut;
+ delete[] _oprDetune;
+}
+
+bool TownsPC98_FmSynth::init() {
+ if (_ready) {
+ reset();
+ return true;
+ }
+
+ generateTables();
+
+ _chanInternal = new ChanInternal[_numChan];
+ for (int i = 0; i < _numChan; i++) {
+ memset(&_chanInternal[i], 0, sizeof(ChanInternal));
+ for (int j = 0; j < 4; ++j)
+ _chanInternal[i].opr[j] = new TownsPC98_FmSynthOperator(_timerbase, _rtt, _oprRates, _oprRateshift, _oprAttackDecay, _oprFrq, _oprSinTbl, _oprLevelOut, _oprDetune);
+ }
+
+ if (_numSSG) {
+ _ssg = new TownsPC98_FmSynthSquareSineSource(_timerbase, _rtt);
+ _ssg->init(&_ssgTables[0], &_ssgTables[16]);
+ }
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_hasPercussion) {
+ _prc = new TownsPC98_FmSynthPercussionSource(_timerbase, _rtt);
+ _prc->init(_percussionData);
+ }
+#endif
+
+ _timers[0].cb = &TownsPC98_FmSynth::timerCallbackA;
+ _timers[1].cb = &TownsPC98_FmSynth::timerCallbackB;
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType,
+ &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ _ready = true;
+
+ return true;
+}
+
+void TownsPC98_FmSynth::reset() {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i < _numChan; i++) {
+ for (int ii = 0; ii < 4; ii++)
+ _chanInternal[i].opr[ii]->reset();
+ memset(_chanInternal[i].feedbuf, 0, 3);
+ _chanInternal[i].algorithm = 0;
+ _chanInternal[i].frqTemp = 0;
+ _chanInternal[i].enableLeft = _chanInternal[i].enableRight = true;
+ _chanInternal[i].updateEnvelopeParameters = false;
+ }
+
+ writeReg(0, 0x27, 0x33);
+
+ if (_ssg)
+ _ssg->reset();
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->reset();
+#endif
+}
+
+void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
+ if (_regProtectionFlag || !_ready)
+ return;
+
+ static const uint8 oprOrdr[] = { 0, 2, 1, 3 };
+
+ Common::StackLock lock(_mutex);
+
+ uint8 h = regAddress & 0xf0;
+ uint8 l = (regAddress & 0x0f);
+
+ ChanInternal *c = 0;
+ TownsPC98_FmSynthOperator **co = 0;
+ TownsPC98_FmSynthOperator *o = 0;
+
+ if (regAddress > 0x2F) {
+ c = &_chanInternal[(l & 3) + 3 * part];
+ co = c->opr;
+ o = c->opr[oprOrdr[(l - (l & 3)) >> 2]];
+ } else if (regAddress == 0x28) {
+ c = &_chanInternal[(value & 3) + ((value & 4) ? 3 : 0)];
+ co = c->opr;
+ }
+
+ switch (h) {
+ case 0x00:
+ // ssg
+ if (_ssg)
+ _ssg->writeReg(l, value);
+ break;
+ case 0x10:
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ // pcm rhythm channel
+ if (_prc)
+ _prc->writeReg(l, value);
+#endif
+ break;
+ case 0x20:
+ if (l == 8) {
+ // Key on/off
+ for (int i = 0; i < 4; i++) {
+ if ((value >> (4 + i)) & 1)
+ co[oprOrdr[i]]->keyOn();
+ else
+ co[oprOrdr[i]]->keyOff();
+ }
+ } else if (l == 4) {
+ // Timer A
+ _timers[0].value = (_timers[0].value & 3) | (value << 2);
+ } else if (l == 5) {
+ // Timer A
+ _timers[0].value = (_timers[0].value & 0x3fc) | (value & 3);
+ } else if (l == 6) {
+ // Timer B
+ _timers[1].value = value & 0xff;
+ } else if (l == 7) {
+ if (value & 1) {
+ float spc = (float)(0x400 - _timers[0].value) / _baserate;
+ if (spc < 1) {
+ warning("TownsPC98_FmSynth: Invalid Timer A setting: %d", _timers[0].value);
+ spc = 1;
+ }
+
+ _timers[0].smpPerCb = (int32) spc;
+ _timers[0].smpPerCbRem = (uint32)((spc - (float)_timers[0].smpPerCb) * 1000000.0f);
+ _timers[0].smpTillCb = _timers[0].smpPerCb;
+ _timers[0].smpTillCbRem = _timers[0].smpPerCbRem;
+ _timers[0].enabled = true;
+ } else {
+ _timers[0].enabled = false;
+ }
+
+ if (value & 2) {
+ float spc = (float)(0x100 - _timers[1].value) * 16.0f / _baserate;
+ if (spc < 1) {
+ warning("TownsPC98_FmSynth: Invalid Timer B setting: %d", _timers[1].value);
+ spc = 1;
+ }
+
+ _timers[1].smpPerCb = (int32) spc;
+ _timers[1].smpPerCbRem = (uint32)((spc - (float)_timers[1].smpPerCb) * 1000000.0f);
+ _timers[1].smpTillCb = _timers[1].smpPerCb;
+ _timers[1].smpTillCbRem = _timers[1].smpPerCbRem;
+ _timers[1].enabled = true;
+ } else {
+ _timers[1].enabled = false;
+ }
+
+ if (value & 0x10) {
+ _timers[0].smpTillCb = _timers[0].smpPerCb;
+ _timers[0].smpTillCbRem = _timers[0].smpTillCbRem;
+ }
+
+ if (value & 0x20) {
+ _timers[1].smpTillCb = _timers[1].smpPerCb;
+ _timers[1].smpTillCbRem = _timers[1].smpTillCbRem;
+ }
+ } else if (l == 2) {
+ // LFO
+ if (value & 8)
+ warning("TownsPC98_FmSynth: TRYING TO USE LFO (NOT SUPPORTED)");
+ } else if (l == 10 || l == 11) {
+ // DAC
+ if (l == 11 && (value & 0x80))
+ warning("TownsPC98_FmSynth: TRYING TO USE DAC (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0x30:
+ // detune, multiple
+ o->detune((value >> 4) & 7);
+ o->multiple(value & 0x0f);
+ c->updateEnvelopeParameters = true;
+ break;
+
+ case 0x40:
+ // total level
+ o->totalLevel(value & 0x7f);
+ break;
+
+ case 0x50:
+ // rate scaling, attack rate
+ o->attackRate(value & 0x1f);
+ if (o->scaleRate(value >> 6))
+ c->updateEnvelopeParameters = true;
+ break;
+
+ case 0x60:
+ // first decay rate, amplitude modulation
+ o->decayRate(value & 0x1f);
+ o->ampModulation(value & 0x80 ? true : false);
+ break;
+
+ case 0x70:
+ // secondary decay rate
+ o->sustainRate(value & 0x1f);
+ break;
+
+ case 0x80:
+ // secondary amplitude, release rate;
+ o->sustainLevel(value >> 4);
+ o->releaseRate(value & 0x0f);
+ break;
+
+ case 0x90:
+ warning("TownsPC98_FmSynth: TRYING TO USE SSG ENVELOPE SHAPES (NOT SUPPORTED)");
+ break;
+
+ case 0xa0:
+ // frequency
+ l &= ~3;
+ if (l == 0) {
+ c->frqTemp = (c->frqTemp & 0xff00) | value;
+ c->updateEnvelopeParameters = true;
+ for (int i = 0; i < 4; i++)
+ co[i]->frequency(c->frqTemp);
+ } else if (l == 4) {
+ c->frqTemp = (c->frqTemp & 0xff) | (value << 8);
+ } else if (l == 8) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ } else if (l == 12) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0xb0:
+ l &= ~3;
+ if (l == 0) {
+ // feedback, _algorithm
+ co[0]->feedbackLevel((value >> 3) & 7);
+ c->algorithm = value & 7;
+ } else if (l == 4) {
+ // stereo, LFO sensitivity
+ c->enableLeft = value & 0x80 ? true : false;
+ c->enableRight = value & 0x40 ? true : false;
+ c->ampModSensitivity((value & 0x30) >> 4);
+ c->frqModSensitivity(value & 3);
+ }
+ break;
+
+ default:
+ warning("TownsPC98_FmSynth: UNKNOWN ADDRESS %d", regAddress);
+ }
+}
+
+int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ memset(buffer, 0, sizeof(int16) * numSamples);
+ int32 *tmp = new int32[numSamples];
+ int32 *tmpStart = tmp;
+ memset(tmp, 0, sizeof(int32) * numSamples);
+ int32 samplesLeft = numSamples >> 1;
+
+ while (_ready && samplesLeft) {
+ int32 render = samplesLeft;
+
+ for (int i = 0; i < 2; i++) {
+ if (_timers[i].enabled && _timers[i].cb) {
+ if (!_timers[i].smpTillCb) {
+ (this->*_timers[i].cb)();
+ _timers[i].smpTillCb = _timers[i].smpPerCb;
+
+ _timers[i].smpTillCbRem += _timers[i].smpPerCbRem;
+ if (_timers[i].smpTillCbRem >= _timerbase) {
+ _timers[i].smpTillCb++;
+ _timers[i].smpTillCbRem -= _timerbase;
+ }
+ }
+ render = MIN(render, _timers[i].smpTillCb);
+ }
+ }
+
+ samplesLeft -= render;
+
+ for (int i = 0; i < 2; i++) {
+ if (_timers[i].enabled && _timers[i].cb) {
+ _timers[i].smpTillCb -= render;
+ }
+ }
+
+ nextTick(tmp, render);
+
+ if (_ssg)
+ _ssg->nextTick(tmp, render);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->nextTick(tmp, render);
+#endif
+
+ nextTickEx(tmp, render);
+
+ for (int i = 0; i < render; ++i) {
+ int32 l = CLIP<int32>(tmp[i << 1], -32767, 32767);
+ buffer[i << 1] = (int16) l;
+ int32 r = CLIP<int32>(tmp[(i << 1) + 1], -32767, 32767);
+ buffer[(i << 1) + 1] = (int16) r;
+ }
+
+ buffer += (render << 1);
+ tmp += (render << 1);
+ }
+
+ delete[] tmpStart;
+ return numSamples;
+}
+
+void TownsPC98_FmSynth::deinit() {
+ _ready = false;
+ _mixer->stopHandle(_soundHandle);
+ _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback;
+}
+
+uint8 TownsPC98_FmSynth::readSSGStatus() {
+ return _ssg->chanEnable();
+}
+
+void TownsPC98_FmSynth::setVolumeIntern(int volA, int volB) {
+ Common::StackLock lock(_mutex);
+ _volumeA = CLIP<uint16>(volA, 0, Audio::Mixer::kMaxMixerVolume);
+ _volumeB = CLIP<uint16>(volB, 0, Audio::Mixer::kMaxMixerVolume);
+ if (_ssg)
+ _ssg->setVolumeIntern(_volumeA, _volumeB);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->setVolumeIntern(_volumeA, _volumeB);
+#endif
+}
+
+void TownsPC98_FmSynth::setVolumeChannelMasks(int channelMaskA, int channelMaskB) {
+ Common::StackLock lock(_mutex);
+ _volMaskA = channelMaskA;
+ _volMaskB = channelMaskB;
+ if (_ssg)
+ _ssg->setVolumeChannelMasks(_volMaskA >> _numChan, _volMaskB >> _numChan);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->setVolumeChannelMasks(_volMaskA >> (_numChan + _numSSG), _volMaskB >> (_numChan + _numSSG));
+#endif
+}
+
+void TownsPC98_FmSynth::generateTables() {
+ delete[] _oprRates;
+ _oprRates = new uint8[128];
+
+ WRITE_BE_UINT32(_oprRates + 32, _numChan == 6 ? 0x90900000 : 0x00081018);
+ WRITE_BE_UINT32(_oprRates + 36, _numChan == 6 ? 0x00001010 : 0x00081018);
+ memset(_oprRates, 0x90, 32);
+ memset(_oprRates + 96, 0x80, 32);
+ uint8 *dst = (uint8 *)_oprRates + 40;
+ for (int i = 0; i < 40; i += 4)
+ WRITE_BE_UINT32(dst + i, 0x00081018);
+ for (int i = 0; i < 48; i += 4)
+ WRITE_BE_UINT32(dst + i, 0x00081018);
+ dst += 40;
+ for (uint8 i = 0; i < 16; i ++) {
+ uint8 v = (i < 12) ? i : 12;
+ *dst++ = ((4 + v) << 3);
+ }
+
+ delete[] _oprRateshift;
+ _oprRateshift = new uint8[128];
+ memset(_oprRateshift, 0, 128);
+ dst = (uint8 *)_oprRateshift + 32;
+ for (int i = 11; i; i--) {
+ memset(dst, i, 4);
+ dst += 4;
+ }
+
+ delete[] _oprFrq;
+ _oprFrq = new uint32[0x1000];
+ for (uint32 i = 0; i < 0x1000; i++)
+ _oprFrq[i] = (uint32)(_baserate * (float)(i << 11));
+
+ delete[] _oprAttackDecay;
+ _oprAttackDecay = new uint8[152];
+ memset(_oprAttackDecay, 0, 152);
+ for (int i = 0; i < 36; i++)
+ WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]);
+
+ delete[] _oprSinTbl;
+ _oprSinTbl = new uint32[1024];
+ for (int i = 0; i < 1024; i++) {
+ double val = sin((double)(((i << 1) + 1) * PI / 1024.0));
+ double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0;
+ int32 i_dcb = (int32)(2.0 * d_dcb);
+ i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1);
+ _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1);
+ }
+
+ delete[] _oprLevelOut;
+ _oprLevelOut = new int32[0x1a00];
+ for (int i = 0; i < 256; i++) {
+ double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i)));
+ int32 val_int = ((int32) val) >> 4;
+ _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2;
+ _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1];
+ for (int ii = 1; ii < 13; ++ii) {
+ _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii;
+ _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)];
+ }
+ }
+
+ uint8 *dtt = new uint8[128];
+ memset(dtt, 0, 36);
+ memset(dtt + 36, 1, 8);
+ memcpy(dtt + 44, _detSrc, 84);
+
+ delete[] _oprDetune;
+ _oprDetune = new int32[256];
+ for (int i = 0; i < 128; i++) {
+ _oprDetune[i] = (int32)((float)dtt[i] * _baserate * 64.0);
+ _oprDetune[i + 128] = -_oprDetune[i];
+ }
+
+ delete[] dtt;
+}
+
+void TownsPC98_FmSynth::nextTick(int32 *buffer, uint32 bufferSize) {
+ if (!_ready)
+ return;
+
+ for (int i = 0; i < _numChan; i++) {
+ TownsPC98_FmSynthOperator **o = _chanInternal[i].opr;
+
+ if (_chanInternal[i].updateEnvelopeParameters) {
+ _chanInternal[i].updateEnvelopeParameters = false;
+ for (int ii = 0; ii < 4 ; ii++)
+ o[ii]->updatePhaseIncrement();
+ }
+
+ for (uint32 ii = 0; ii < bufferSize ; ii++) {
+ int32 phbuf1, phbuf2, output;
+ phbuf1 = phbuf2 = output = 0;
+
+ int32 *leftSample = &buffer[ii * 2];
+ int32 *rightSample = &buffer[ii * 2 + 1];
+ int32 *del = &_chanInternal[i].feedbuf[2];
+ int32 *feed = _chanInternal[i].feedbuf;
+
+ switch (_chanInternal[i].algorithm) {
+ case 0:
+ o[0]->generateOutput(0, feed, phbuf1);
+ o[2]->generateOutput(*del, 0, phbuf2);
+ *del = 0;
+ o[1]->generateOutput(phbuf1, 0, *del);
+ o[3]->generateOutput(phbuf2, 0, output);
+ break;
+ case 1:
+ o[0]->generateOutput(0, feed, phbuf1);
+ o[2]->generateOutput(*del, 0, phbuf2);
+ o[1]->generateOutput(0, 0, phbuf1);
+ o[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 2:
+ o[0]->generateOutput(0, feed, phbuf2);
+ o[2]->generateOutput(*del, 0, phbuf2);
+ o[1]->generateOutput(0, 0, phbuf1);
+ o[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 3:
+ o[0]->generateOutput(0, feed, phbuf2);
+ o[2]->generateOutput(0, 0, *del);
+ o[1]->generateOutput(phbuf2, 0, phbuf1);
+ o[3]->generateOutput(*del, 0, output);
+ *del = phbuf1;
+ break;
+ case 4:
+ o[0]->generateOutput(0, feed, phbuf1);
+ o[2]->generateOutput(0, 0, phbuf2);
+ o[1]->generateOutput(phbuf1, 0, output);
+ o[3]->generateOutput(phbuf2, 0, output);
+ *del = 0;
+ break;
+ case 5:
+ o[0]->generateOutput(0, feed, phbuf1);
+ o[2]->generateOutput(*del, 0, output);
+ o[1]->generateOutput(phbuf1, 0, output);
+ o[3]->generateOutput(phbuf1, 0, output);
+ *del = phbuf1;
+ break;
+ case 6:
+ o[0]->generateOutput(0, feed, phbuf1);
+ o[2]->generateOutput(0, 0, output);
+ o[1]->generateOutput(phbuf1, 0, output);
+ o[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ case 7:
+ o[0]->generateOutput(0, feed, output);
+ o[2]->generateOutput(0, 0, output);
+ o[1]->generateOutput(0, 0, output);
+ o[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ };
+
+ int32 finOut = (output << 2) / ((_numChan + _numSSG - 3) / 3);
+
+ if ((1 << i) & _volMaskA)
+ finOut = (finOut * _volumeA) / Audio::Mixer::kMaxMixerVolume;
+
+ if ((1 << i) & _volMaskB)
+ finOut = (finOut * _volumeB) / Audio::Mixer::kMaxMixerVolume;
+
+ if (_chanInternal[i].enableLeft)
+ *leftSample += finOut;
+
+ if (_chanInternal[i].enableRight)
+ *rightSample += finOut;
+ }
+ }
+}
+
+const uint32 TownsPC98_FmSynth::_adtStat[] = {
+ 0x00010001, 0x00010001, 0x00010001, 0x01010001,
+ 0x00010101, 0x00010101, 0x00010101, 0x01010101,
+ 0x01010101, 0x01010101, 0x01010102, 0x01010102,
+ 0x01020102, 0x01020102, 0x01020202, 0x01020202,
+ 0x02020202, 0x02020202, 0x02020204, 0x02020204,
+ 0x02040204, 0x02040204, 0x02040404, 0x02040404,
+ 0x04040404, 0x04040404, 0x04040408, 0x04040408,
+ 0x04080408, 0x04080408, 0x04080808, 0x04080808,
+ 0x08080808, 0x08080808, 0x10101010, 0x10101010
+};
+
+const uint8 TownsPC98_FmSynth::_detSrc[] = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05,
+ 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14,
+ 0x16, 0x16, 0x16, 0x16
+};
+
+const int TownsPC98_FmSynth::_ssgTables[] = {
+ 0x01202A, 0x0092D2, 0x006B42, 0x0053CB, 0x003DF8, 0x003053, 0x0022DA, 0x001A8C,
+ 0x00129B, 0x000DC1, 0x000963, 0x0006C9, 0x000463, 0x0002FA, 0x0001B6, 0x0000FB,
+ 0x0193B6, 0x01202A, 0x00CDB1, 0x0092D2, 0x007D7D, 0x006B42, 0x005ECD, 0x0053CB,
+ 0x00480F, 0x003DF8, 0x0036B9, 0x003053, 0x00290A, 0x0022DA, 0x001E6B, 0x001A8C,
+ 0x001639, 0x00129B, 0x000FFF, 0x000DC1, 0x000B5D, 0x000963, 0x0007FB, 0x0006C9,
+ 0x000575, 0x000463, 0x00039D, 0x0002FA, 0x000242, 0x0001B6, 0x00014C, 0x0000FB
+};
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+const uint8 TownsPC98_FmSynth::_percussionData[] = {
+ 0, 24, 1, 192, 1, 216, 2, 128, 4, 88, 23, 64, 27, 152, 1, 128, 29, 24, 2, 128, 31, 152, 0, 128, 136, 128, 128, 128, 0, 136, 97, 103, 153, 139, 34, 163, 72, 195, 27, 69, 1, 154, 137, 35, 8, 51, 169, 122, 164, 75, 133, 203, 81, 146, 168, 121, 185, 68, 202, 8, 33, 237, 49, 177, 12, 133, 140, 17, 160, 42, 161, 10, 0, 137, 176, 57,
+ 233, 41, 160, 136, 235, 65, 177, 137, 128, 26, 164, 28, 3, 157, 51, 137, 1, 152, 113, 161, 40, 146, 115, 192, 56, 5, 169, 66, 161, 56, 1, 50, 145, 59, 39, 168, 97, 1, 160, 57, 7, 153, 50, 153, 32, 2, 25, 129, 32, 20, 186, 66, 129, 24, 153, 164, 142, 130, 169, 153, 26, 242, 138, 217, 9, 128, 204, 58, 209, 172, 40, 176, 141,
+ 128, 155, 144, 203, 139, 0, 235, 9, 177, 172, 0, 185, 168, 138, 25, 240, 59, 211, 139, 19, 176, 90, 160, 17, 26, 132, 41, 1, 5, 25, 3, 50, 144, 115, 147, 42, 39, 152, 41, 3, 56, 193, 105, 130, 155, 66, 200, 26, 19, 218, 154, 49, 201, 171, 138, 176, 251, 139, 185, 172, 136, 189, 139, 145, 207, 41, 160, 171, 152, 186, 139,
+ 186, 141, 128, 218, 171, 51, 217, 170, 56, 163, 12, 4, 155, 81, 147, 42, 37, 152, 32, 54, 136, 49, 50, 48, 37, 32, 69, 0, 17, 50, 50, 83, 2, 16, 68, 20, 8, 66, 4, 154, 84, 145, 24, 33, 24, 32, 17, 18, 145, 32, 22, 168, 49, 163, 1, 33, 50, 184, 115, 129, 25, 66, 1, 24, 67, 2, 80, 35, 40, 53, 2, 65, 51, 19, 67, 37, 0, 52, 35, 49, 37,
+ 34, 49, 37, 17, 52, 17, 35, 35, 35, 34, 32, 49, 33, 152, 34, 145, 24, 24, 128, 138, 128, 184, 9, 177, 171, 168, 185, 155, 152, 172, 155, 186, 172, 185, 172, 155, 186, 173, 153, 202, 187, 185, 202, 170, 171, 202, 186, 169, 170, 170, 171, 139, 154, 171, 153, 154, 169, 10, 168, 154, 128, 168, 154, 0, 153, 152, 136, 137,
+ 128, 153, 0, 152, 8, 128, 137, 0, 136, 136, 8, 9, 8, 9, 8, 24, 153, 128, 136, 153, 144, 0, 161, 138, 1, 169, 136, 128, 160, 168, 152, 153, 138, 137, 154, 153, 153, 154, 153, 170, 168, 170, 185, 168, 169, 154, 169, 171, 153, 169, 170, 153, 152, 154, 153, 137, 169, 137, 136, 144, 152, 144, 128, 128, 144, 129, 129, 0, 33,
+ 0, 17, 17, 17, 33, 33, 18, 18, 34, 34, 34, 34, 34, 34, 35, 19, 35, 19, 35, 35, 18, 19, 18, 35, 18, 33, 0, 8, 8, 8, 8, 8, 8, 8, 160, 205, 65, 176, 171, 203, 16, 240, 95, 242, 120, 145, 156, 66, 177, 26, 19, 153, 9, 35, 35, 239, 56, 132, 138, 154, 50, 145, 203, 25, 32, 20, 237, 24, 130, 138, 160, 27, 39, 173, 50, 203, 64, 145, 139,
+ 18, 168, 48, 146, 171, 65, 18, 176, 12, 52, 128, 25, 5, 57, 240, 104, 161, 25, 129, 18, 188, 114, 160, 26, 36, 200, 154, 18, 1, 128, 186, 73, 162, 173, 32, 184, 25, 144, 137, 234, 8, 154, 32, 160, 158, 18, 187, 81, 2, 235, 41, 36, 144, 154, 17, 67, 128, 33, 160, 114, 146, 26, 37, 33, 232, 41, 130, 41, 178, 29, 50, 251, 24,
+ 1, 153, 138, 160, 76, 179, 155, 11, 0, 38, 252, 41, 146, 41, 178, 27, 193, 43, 39, 170, 136, 17, 129, 8, 49, 233, 48, 129, 11, 6, 26, 130, 136, 128, 64, 1, 248, 105, 145, 9, 16, 144, 140, 5, 25, 168, 16, 186, 48, 5, 171, 217, 57, 134, 171, 8, 34, 188, 20, 203, 41, 6, 155, 161, 89, 164, 140, 2, 136, 51, 202, 41, 131, 56, 144,
+ 8, 97, 144, 146, 13, 69, 200, 42, 130, 25, 152, 57, 6, 220, 88, 177, 26, 148, 9, 168, 8, 67, 192, 156, 65, 145, 137, 10, 4, 154, 18, 157, 67, 160, 154, 1, 50, 188, 82, 170, 82, 185, 49, 220, 97, 144, 10, 8, 16, 145, 9, 136, 18, 202, 51, 184, 141, 114, 179, 139, 24, 19, 8, 250, 121, 160, 40, 160, 10, 18, 152, 168, 42, 35, 216,
+ 187, 120, 145, 18, 156, 203, 84, 144, 9, 144, 26, 66, 161, 13, 1, 128, 17, 154, 18, 142, 6, 154, 65, 192, 29, 35, 186, 64, 192, 24, 9, 146, 56, 185, 16, 248, 121, 176, 40, 129, 136, 171, 96, 147, 140, 50, 203, 64, 144, 41, 128, 161, 187, 71, 200, 24, 129, 24, 217, 56, 20, 220, 24, 4, 169, 9, 1, 33, 201, 26, 134, 141, 51, 201,
+ 25, 16, 33, 235, 32, 144, 33, 153, 169, 99, 160, 11, 3, 136, 58, 210, 33, 203, 48, 163, 17, 219, 128, 140, 38, 8, 184, 141, 50, 131, 159, 33, 128, 153, 25, 18, 153, 88, 242, 43, 3, 9, 136, 157, 53, 202, 40, 145, 25, 2, 204, 105, 146, 156, 66, 152, 8, 153, 33, 128, 129, 136, 153, 50, 186, 55, 188, 51, 249, 64, 178, 27, 128,
+ 48, 177, 156, 18, 35, 175, 51, 189, 32, 51, 234, 155, 69, 184, 26, 2, 152, 9, 17, 136, 144, 137, 50, 235, 115, 216, 24, 2, 170, 67, 187, 49, 129, 155, 4, 27, 129, 56, 232, 43, 39, 203, 40, 3, 154, 169, 66, 184, 114, 224, 25, 2, 9, 128, 11, 35, 155, 18, 11, 202, 84, 169, 26, 5, 154, 8, 160, 98, 185, 17, 187, 50, 23, 188, 33,
+ 1, 139, 4, 154, 90, 147, 12, 3, 43, 2, 170, 171, 103, 193, 28, 132, 137, 8, 129, 24, 170, 50, 201, 42, 35, 202, 169, 52, 201, 33, 218, 40, 39, 203, 0, 40, 147, 29, 163, 139, 83, 185, 1, 4, 159, 34, 160, 12, 21, 155, 40, 129, 137, 58, 151, 13, 2, 136, 144, 16, 153, 40, 17, 131, 207, 51, 144, 140, 4, 154, 17, 146, 170, 73, 163,
+ 44, 164, 12, 152, 37, 203, 17, 128, 144, 139, 23, 154, 128, 138, 38, 216, 41, 1, 0, 233, 73, 131, 171, 49, 136, 9, 164, 46, 3, 171, 32, 0, 145, 157, 38, 187, 64, 176, 58, 134, 155, 18, 136, 217, 64, 1, 200, 140, 38, 153, 170, 66, 161, 8, 169, 65, 185, 98, 200, 41, 3, 155, 144, 58, 23, 187, 1, 145, 40, 147, 189, 32, 68, 249,
+ 1, 112, 255, 199, 195, 19, 108, 76, 187, 247, 247, 183, 40, 168, 212, 245, 199, 227, 68, 45, 59, 10, 145, 177, 198, 24, 130, 76, 26, 193, 180, 129, 0, 162, 42, 160, 199, 162, 0, 16, 152, 137, 132, 168, 195, 130, 162, 181, 227, 163, 161, 179, 211, 180, 179, 164, 128, 162, 161, 194, 164, 179, 40, 153, 195, 213, 146, 178,
+ 147, 176, 50, 186, 161, 196, 151, 58, 16, 28, 162, 160, 131, 122, 155, 33, 241, 146, 128, 40, 26, 128, 154, 36, 170, 89, 59, 9, 24, 144, 77, 161, 8, 177, 112, 139, 33, 232, 148, 24, 41, 61, 9, 26, 162, 32, 30, 58, 153, 32, 59, 73, 59, 11, 79, 137, 57, 9, 49, 30, 24, 153, 131, 25, 106, 61, 153, 73, 28, 56, 27, 41, 137, 148,
+ 76, 43, 74, 58, 13, 161, 3, 171, 149, 32, 77, 10, 74, 42, 168, 16, 0, 123, 138, 129, 162, 178, 225, 50, 140, 161, 0, 147, 10, 129, 41, 244, 210, 165, 1, 152, 24, 162, 184, 166, 32, 144, 59, 216, 132, 177, 8, 145, 67, 143, 146, 160, 183, 162, 130, 24, 192, 32, 225, 146, 144, 33, 44, 73, 30, 129, 137, 32, 76, 152, 25, 161,
+ 2, 154, 32, 177, 132, 232, 2, 136, 210, 128, 149, 177, 32, 58, 27, 168, 225, 133, 8, 44, 107, 136, 25, 136, 17, 26, 58, 46, 16, 11, 145, 17, 144, 79, 136, 144, 136, 145, 152, 33, 31, 162, 130, 200, 82, 153, 74, 137, 147, 26, 0, 13, 133, 170, 149, 16, 192, 0, 178, 0, 128, 152, 182, 150, 9, 16, 9, 137, 33, 59, 63, 10, 152, 32,
+ 179, 192, 5, 154, 228, 182, 145, 130, 144, 42, 128, 242, 2, 136, 41, 168, 17, 76, 57, 31, 129, 136, 17, 47, 8, 41, 138, 32, 138, 123, 59, 58, 10, 136, 161, 4, 46, 25, 145, 136, 129, 25, 56, 28, 91, 41, 154, 108, 9, 16, 44, 24, 137, 48, 15, 0, 194, 162, 41, 194, 56, 241, 163, 146, 0, 139, 7, 186, 150, 129, 152, 1, 208, 33, 176,
+ 136, 164, 163, 185, 7, 138, 130, 242, 162, 163, 177, 88, 136, 184, 166, 146, 0, 25, 25, 177, 199, 146, 16, 136, 9, 145, 178, 178, 0, 147, 138, 229, 18, 152, 25, 144, 163, 246, 162, 129, 129, 184, 5, 152, 178, 145, 148, 136, 146, 95, 152, 128, 144, 33, 170, 81, 11, 40, 202, 131, 0, 243, 24, 1, 11, 148, 42, 24, 163, 140,
+ 120, 9, 76, 58, 153, 145, 56, 30, 72, 46, 42, 9, 8, 57, 91, 76, 59, 26, 160, 129, 41, 76, 10, 57, 192, 163, 129, 16, 225, 2, 27, 40, 200, 48, 91, 226, 40, 145, 43, 177, 177, 182, 196, 145, 33, 184, 165, 17, 192, 163, 194, 129, 211, 128, 162, 197, 129, 0, 136, 211, 146, 8, 162, 144, 0, 167, 160, 1, 176, 150, 137, 1, 24, 243,
+ 0, 129, 145, 25, 123, 169, 130, 168, 132, 41, 63, 42, 136, 137, 120, 26, 136, 8, 24, 89, 29, 58, 177, 193, 147, 1, 26, 162, 176, 167, 180, 8, 49, 28, 29, 178, 162, 88, 43, 42, 57, 43, 61, 8, 29, 129, 128, 128, 123, 137, 24, 243, 16, 136, 16, 46, 0, 169, 149, 128, 1, 60, 153, 72, 154, 90, 25, 25, 25, 8, 91, 73, 12, 16, 137, 144,
+ 72, 11, 8, 167, 128, 129, 9, 138, 166, 193, 147, 162, 123, 137, 145, 1, 162, 26, 1, 219, 147, 129, 210, 147, 243, 1, 243, 16, 144, 145, 160, 131, 200, 4, 59, 75, 57, 218, 2, 178, 77, 24, 60, 11, 147, 10, 50, 141, 64, 27, 185, 122, 161, 41, 128, 90, 136, 24, 46, 16, 139, 16, 24, 28, 124, 9, 41, 8, 26, 121, 10, 42, 40, 139, 129,
+ 0, 201, 135, 137, 56, 176, 176, 35, 215, 145, 1, 26, 145, 144, 160, 135, 138, 1, 177, 146, 146, 161, 65, 242, 136, 164, 177, 1, 1, 186, 151, 208, 148, 129, 10, 32, 241, 145, 163, 178, 17, 168, 136, 151, 168, 2, 148, 185, 133, 176, 130, 129, 154, 163, 215, 0, 146, 136, 40, 211, 161, 131, 171, 81, 144, 170, 21, 184, 56,
+ 195, 168, 133, 177, 91, 16, 187, 5, 145, 153, 66, 172, 18, 177, 42, 120, 138, 27, 134, 26, 106, 42, 138, 146, 184, 66, 75, 46, 41, 168, 0, 145, 57, 91, 75, 27, 24, 27, 48, 169, 40, 122, 9, 109, 10, 8, 177, 146, 16, 74, 30, 129, 160, 162, 146, 41, 124, 138, 24, 145, 152, 3, 1, 14, 3, 139, 1, 192, 161, 151, 177, 122, 8, 10, 0,
+ 176, 130, 129, 27, 88, 225, 0, 2, 154, 129, 129, 193, 49, 203, 81, 153, 226, 33, 0, 30, 0, 176, 179, 18, 9, 96, 156, 162, 148, 160, 129, 2, 29, 195, 128, 0, 56, 156, 20, 232, 129, 128, 32, 10, 144, 74, 183, 9, 145, 162, 1, 162, 138, 23, 171, 1, 164, 224, 34, 43, 43, 177, 200, 135, 161, 91, 57, 154, 177, 148, 145, 146, 58,
+ 108, 136, 170, 35, 208, 177, 34, 128, 44, 129, 155, 151, 243, 16, 1, 154, 72, 193, 144, 18, 11, 122, 160, 153, 5, 192, 24, 130, 184, 132, 226, 0, 128, 153, 131, 181, 136, 65, 154, 128, 17, 170, 39, 28, 59, 144, 168, 80, 25, 47, 24, 26, 144, 32, 47, 41, 153, 161, 148, 8, 92, 9, 9, 129, 144, 33, 26, 47, 24, 137, 108, 25, 10,
+ 17, 10, 73, 75, 47, 24, 184, 48, 8, 45, 57, 138, 136, 150, 10, 48, 139, 136, 35, 203, 121, 8, 27, 179, 161, 106, 0, 29, 16, 176, 179, 3, 185, 19, 227, 41, 145, 168, 61, 197, 177, 20, 10, 57, 42, 250, 147, 196, 16, 41, 138, 24, 195, 208, 135, 137, 0, 145, 160, 2, 210, 146, 195, 177, 132, 136, 153, 167, 210, 146, 162, 40, 8,
+ 138, 148, 227, 145, 17, 137, 40, 169, 179, 130, 242, 2, 196, 9, 146, 145, 169, 167, 146, 130, 137, 136, 51, 220, 17, 163, 28, 74, 10, 76, 40, 140, 5, 137, 43, 18, 12, 107, 137, 40, 8, 201, 50, 0, 143, 3, 138, 161, 134, 138, 104, 169, 16, 162, 160, 121, 25, 28, 129, 152, 32, 56, 14, 16, 184, 146, 3, 46, 25, 176, 129, 179,
+ 193, 17, 130, 202, 135, 8, 57, 25, 154, 148, 184, 120, 9, 153, 211, 165, 24, 128, 26, 17, 242, 161, 18, 185, 81, 42, 11, 17, 12, 25, 181, 137, 66, 42, 47, 41, 184, 166, 129, 24, 91, 27, 136, 196, 0, 0, 74, 28, 178, 161, 149, 160, 32, 8, 225, 32, 128, 59, 8, 169, 50, 139, 47, 72, 186, 16, 132, 9, 122, 9, 160, 146, 144, 89, 153,
+ 10, 149, 178, 0, 121, 11, 146, 152, 162, 48, 13, 123, 177, 24, 0, 106, 27, 9, 144, 132, 12, 17, 0, 168, 0, 181, 56, 169, 129, 242, 195, 129, 17, 154, 64, 161, 244, 16, 137, 24, 144, 144, 164, 129, 75, 42, 176, 149, 9, 179, 148, 203, 4, 166, 136, 163, 128, 227, 163, 8, 57, 11, 30, 165, 0, 74, 59, 62, 9, 208, 131, 144, 40, 76,
+ 26, 27, 196, 129, 1, 25, 43, 49, 174, 67, 153, 136, 106, 152, 41, 25, 28, 2, 43, 44, 104, 45, 59, 8, 43, 128, 144, 120, 25, 12, 17, 152, 9, 130, 155, 151, 145, 74, 40, 13, 48, 192, 58, 90, 43, 43, 177, 146, 49, 31, 75, 24, 217, 131, 0, 76, 26, 152, 149, 161, 24, 74, 154, 193, 166, 145, 32, 27, 161, 164, 176, 135, 152, 24, 193,
+ 162, 146, 164, 58, 227, 193, 148, 161, 128, 18, 234, 130, 180, 145, 2, 200, 1, 163, 186, 98, 184, 129, 149, 153, 49, 42, 186, 151, 242, 129, 1, 43, 8, 177, 212, 165, 8, 40, 137, 24, 8, 144, 90, 9, 25, 48, 44, 46, 24, 138, 40, 144, 108, 58, 27, 128, 181, 128, 80, 29, 42, 152, 162, 130, 25, 106, 136, 11, 148, 8, 144, 128, 136,
+ 112, 139, 80, 153, 24, 136, 129, 46, 0, 60, 129, 208, 1, 3, 13, 57, 168, 144, 1, 242, 17, 9, 26, 2, 185, 27, 55, 140, 73, 137, 179, 16, 192, 3, 145, 143, 33, 9, 171, 135, 160, 17, 137, 10, 151, 168, 3, 178, 44, 17, 208, 144, 167, 0, 40, 155, 16, 167, 152, 18, 144, 26, 160, 199, 1, 136, 91, 136, 160, 178, 150, 161, 1, 10, 181,
+ 145, 161, 1, 145, 161, 198, 2, 9, 90, 137, 177, 160, 150, 40, 29, 129, 144, 145, 162, 57, 77, 169, 16, 148, 42, 42, 40, 141, 34, 170, 121, 154, 210, 131, 162, 107, 8, 9, 160, 195, 40, 73, 139, 18, 224, 162, 34, 139, 0, 244, 178, 163, 24, 26, 146, 194, 166, 49, 29, 42, 137, 130, 192, 16, 93, 128, 154, 19, 59, 11, 122, 11,
+ 146, 177, 120, 42, 26, 43, 164, 152, 17, 60, 63, 137, 128, 48, 10, 58, 92, 9, 59, 91, 75, 139, 32, 25, 25, 61, 74, 28, 177, 40, 130, 74, 29, 73, 168, 130, 128, 48, 14, 8, 77, 9, 25, 26, 179, 211, 32, 78, 26, 41, 152, 161, 180, 89, 59, 9, 153, 166, 160, 3, 26, 57, 106, 154, 88, 184, 40, 1, 27, 58, 73, 143, 131, 169, 3, 161, 184,
+ 122, 152, 16, 181, 145, 129, 17, 15, 129, 193, 147, 145, 192, 33, 193, 162, 183, 163, 136, 178, 129, 178, 197, 2, 41, 216, 131, 168, 163, 181, 226, 163, 178, 1, 33, 187, 166, 212, 129, 1, 27, 24, 162, 184, 151, 8, 16, 160, 144, 181, 210, 72, 168, 128, 32, 42, 25, 40, 142, 5, 185, 88, 58, 11, 58, 177, 32, 129, 63, 42, 136,
+ 186, 53, 29, 75, 58, 144, 144, 129, 77, 128, 11, 144, 133, 29, 40, 152, 24, 161, 129, 80, 155, 60, 3, 12, 89, 8, 60, 152, 152, 49, 136, 47, 57, 224, 129, 16, 41, 90, 139, 162, 147, 170, 51, 169, 27, 17, 95, 26, 26, 160, 5, 139, 48, 76, 10, 228, 146, 1, 136, 44, 161, 147, 209, 130, 137, 73, 224, 1, 162, 195, 32, 210, 177, 180,
+ 179, 148, 145, 154, 132, 242, 146, 1, 152, 32, 192, 1, 144, 155, 7, 177, 168, 5, 138, 178, 148, 152, 150, 136, 89, 152, 9, 41, 196, 145, 40, 28, 16, 8, 10, 178, 167, 24, 1, 44, 123, 137, 136, 145, 194, 48, 27, 74, 26, 192, 179, 135, 136, 88, 27, 10, 177, 163, 164, 128, 73, 24, 31, 8, 0, 192, 149, 144, 129, 9, 106, 41, 200,
+ 161, 151, 41, 138, 0, 24, 226, 162, 49, 42, 11, 90, 136, 136, 152, 17, 145, 10, 63, 40, 11, 56, 245, 162, 16, 26, 73, 11, 144, 135, 137, 58, 106, 10, 25, 8, 57, 137, 28, 33, 129, 156, 113, 10, 10, 161, 18, 8, 153, 77, 3, 217, 0, 1, 242, 128, 193, 18, 128, 75, 60, 178, 154, 37, 45, 58, 29, 144, 1, 184, 66, 41, 29, 8, 145, 10,
+ 194, 33, 148, 170, 107, 89, 139, 128, 163, 178, 16, 63, 59, 176, 144, 151, 129, 42, 74, 10, 129, 192, 2, 128, 154, 97, 192, 0, 177, 128, 178, 183, 16, 16, 155, 149, 145, 184, 84, 138, 8, 192, 161, 20, 225, 0, 130, 138, 165, 0, 28, 148, 153, 18, 209, 128, 88, 153, 89, 152, 9, 17, 9, 29, 130, 43, 122, 153, 24, 32, 202, 49,
+ 24, 43, 106, 154, 130, 193, 27, 51, 29, 28, 133, 138, 65, 11, 123, 25, 10, 40, 152, 44, 130, 26, 43, 148, 45, 73, 140, 33, 8, 153, 88, 128, 61, 144, 42, 59, 225, 128, 18, 155, 50, 75, 186, 20, 202, 120, 144, 42, 92, 176, 162, 165, 25, 2, 169, 152, 135, 185, 19, 152, 8, 146, 160, 123, 195, 137, 132, 209, 0, 16, 11, 2, 242,
+ 146, 164, 152, 73, 193, 136, 130, 178, 1, 136, 169, 23, 169, 128, 164, 242, 129, 178, 129, 32, 138, 180, 167, 153, 132, 8, 138, 2, 209, 4, 138, 1, 128, 138, 92, 136, 44, 129, 136, 162, 33, 63, 40, 141, 2, 160, 144, 106, 137, 64, 155, 17, 129, 60, 30, 146, 26, 17, 28, 48, 46, 169, 51, 154, 91, 137, 41, 26, 32, 143, 18, 138,
+ 1, 32, 28, 123, 177, 9, 181, 195, 56, 57, 14, 145, 161, 17, 17, 31, 41, 152, 145, 194, 194, 20, 153, 41, 9, 243, 129, 180, 0, 128, 45, 16, 43, 170, 135, 144, 16, 25, 42, 137, 242, 163, 194, 16, 0, 57, 14, 130, 194, 178, 16, 33, 30, 8, 59, 211, 163, 160, 5, 137, 44, 10, 17, 170, 3, 120, 9, 44, 146, 136, 131, 140, 91, 9, 171,
+ 7, 161, 32, 73, 13, 8, 161, 40, 106, 11, 25, 129, 59, 0, 49, 31, 42, 28, 40, 11, 0, 81, 176, 61, 32, 138, 25, 178, 241, 148, 136, 106, 8, 136, 128, 177, 90, 8, 155, 96, 176, 9, 18, 217, 132, 129, 10, 81, 156, 40, 178, 161, 36, 169, 76, 147, 203, 150, 0, 10, 146, 200, 147, 149, 128, 144, 148, 154, 182, 24, 0, 137, 11, 134, 211,
+ 24, 136, 129, 145, 209, 33, 8, 43, 163, 243, 88, 41, 13, 0, 160, 145, 33, 31, 32, 185, 145, 4, 155, 17, 32, 47, 161, 128, 73, 160, 44, 56, 176, 75, 74, 12, 35, 141, 104, 137, 9, 89, 152, 58, 56, 44, 41, 30, 41, 40, 157, 48, 128, 154, 88, 41, 42, 8, 14, 3, 184, 59, 120, 152, 9, 56, 10, 128, 41, 57, 227, 186, 52, 152, 62, 8, 56,
+ 242, 0, 58, 8, 156, 34, 243, 128, 24, 176, 51, 169, 58, 183, 192, 146, 164, 177, 18, 170, 7, 177, 208, 132, 161, 24, 136, 27, 147, 243, 128, 133, 10, 24, 161, 161, 178, 214, 17, 160, 25, 16, 161, 137, 165, 192, 48, 27, 72, 58, 218, 133, 162, 26, 72, 27, 10, 197, 178, 49, 138, 89, 56, 142, 1, 24, 11, 0, 44, 105, 10, 25, 0,
+ 194, 9, 3, 47, 8, 138, 147, 18, 28, 48, 202, 147, 199, 146, 25, 161, 0, 145, 194, 163, 57, 11, 146, 248, 130, 32, 57, 63, 154, 16, 48, 14, 128, 144, 209, 133, 26, 56, 154, 182, 162, 195, 18, 152, 44, 194, 180, 168, 5, 24, 137, 138, 35, 192, 232, 66, 176, 161, 24, 41, 26, 244, 129, 163, 160, 75, 129, 226, 147, 40, 145, 61,
+ 13, 130, 177, 17, 137, 112, 170, 130, 0, 136, 75, 152, 177, 241, 34, 0, 59, 156, 51, 186, 178, 91, 132, 137, 137, 122, 1, 45, 28, 50, 172, 57, 108, 8, 26, 136, 32, 152, 46, 144, 131, 171, 4, 152, 18, 141, 148, 1, 216, 32, 9, 60, 169, 66, 152, 128, 72, 90, 201, 1, 17, 201, 136, 3, 195, 26, 73, 133, 200, 176, 150, 146, 169,
+ 24, 33, 178, 184, 151, 73, 11, 28, 72, 44, 153, 82, 153, 17, 42, 57, 78, 153, 8, 160, 0, 1, 123, 11, 19, 171, 195, 18, 59, 31, 129, 10, 162, 2, 58, 96, 142, 130, 26, 75, 128, 176, 17, 180, 123, 9, 90, 137, 211, 145, 32, 26, 76, 43, 145, 130, 12, 90, 41, 27, 58, 160, 160, 128, 178, 7, 76, 59, 0, 203, 180, 147, 33, 62, 10, 0, 243,
+ 129, 146, 73, 29, 145, 144, 0, 26, 56, 153, 185, 83, 8, 76, 27, 166, 161, 193, 146, 131, 224, 145, 165, 161, 40, 168, 149, 162, 226, 2, 136, 138, 163, 131, 211, 0, 59, 146, 218, 148, 1, 192, 16, 16, 58, 248, 88, 144, 177, 136, 1, 58, 45, 9, 195, 197, 147, 48, 29, 10, 0, 162, 176, 64, 122, 9, 10, 17, 9, 153, 56, 75, 27, 31,
+ 72, 136, 9, 129, 129, 61, 45, 59, 10, 161, 18, 122, 43, 59, 41, 169, 34, 155, 130, 131, 219, 120, 162, 27, 49, 208, 160, 131, 156, 66, 12, 145, 50, 240, 16, 136, 12, 162, 40, 129, 130, 15, 129, 162, 146, 180, 83, 139, 58, 217, 129, 177, 4, 0, 169, 197, 163, 144, 242, 131, 168, 179, 179, 17, 197, 145, 178, 164, 128, 160,
+ 211, 2, 244, 163, 145, 162, 129, 212, 177, 163, 17, 208, 163, 195, 180, 57, 24, 170, 182, 164, 129, 0, 60, 60, 169, 149, 162, 177, 122, 26, 24, 136, 136, 133, 43, 27, 178, 56, 77, 24, 128, 240, 0, 2, 44, 46, 8, 128, 193, 146, 64, 27, 42, 16, 193, 25, 0, 192, 148, 11, 52, 47, 153, 147, 243, 0, 24, 73, 28, 144, 161, 150, 9,
+ 8, 73, 170, 2, 162, 25, 27, 147, 167, 131, 29, 1, 168, 200, 165, 16, 91, 137, 8, 162, 176, 35, 41, 31, 24, 169, 50, 168, 58, 123, 144, 48, 128, 13, 73, 169, 144, 16, 57, 123, 44, 200, 163, 56, 153, 80, 10, 176, 146, 57, 94, 8, 152, 131, 9, 168, 125, 26, 145, 177, 132, 137, 41, 60, 26, 144, 243, 32, 192, 34, 60, 43, 26, 16,
+ 249, 164, 16, 58, 61, 11, 130, 243, 146, 2, 42, 44, 27, 128, 165, 137, 49, 45, 28, 16, 43, 8, 211, 48, 28, 152, 105, 9, 9, 163, 161, 169, 35, 107, 42, 232, 164, 130, 168, 72, 42, 168, 210, 148, 144, 136, 129, 3, 217, 194, 50, 27, 192, 41, 210, 147, 40, 76, 226, 1, 161, 1, 155, 132, 145, 147, 171, 67, 173, 210, 132, 161, 106,
+ 137, 56, 169, 209, 131, 64, 13, 129, 9, 194, 17, 57, 61, 169, 17, 128, 40, 31, 16, 10, 162, 57, 61, 75, 139, 40, 242, 17, 58, 59, 138, 179, 144, 50, 105, 140, 179, 243, 57, 40, 26, 9, 243, 130, 24, 29, 57, 128, 210, 129, 25, 59, 91, 137, 162, 178, 72, 27, 181, 168, 19, 129, 8, 184, 231, 147, 178, 32, 28, 184, 198, 148, 144,
+ 1, 26, 128, 16, 192, 2, 26, 144, 244, 129, 0, 16, 10, 197, 177, 181, 1, 41, 9, 178, 165, 211, 129, 25, 145, 137, 210, 147, 152, 210, 163, 132, 194, 17, 91, 169, 145, 181, 130, 9, 89, 137, 152, 178, 4, 128, 9, 63, 160, 128, 106, 8, 25, 43, 10, 32, 47, 26, 123, 152, 24, 40, 25, 27, 18, 186, 35, 158, 64, 42, 216, 33, 25, 58, 58,
+ 45, 184, 147, 29, 72, 46, 9, 0, 178, 146, 58, 77, 26, 25, 209, 165, 128, 145, 17, 153, 128, 129, 148, 240, 129, 1, 40, 31, 0, 152, 242, 163, 16, 59, 44, 24, 243, 146, 128, 1, 26, 26, 179, 213, 145, 130, 176, 131, 40, 25, 145, 219, 179, 167, 8, 33, 59, 14, 176, 166, 16, 136, 74, 128, 176, 128, 149, 8, 8, 209, 148, 152, 0, 72,
+ 153, 161, 178, 35, 62, 75, 154, 163, 153, 19, 62, 170, 133, 179, 136, 89, 12, 129, 164, 144, 3, 47, 58, 193, 177, 148, 0, 61, 43, 10, 129, 17, 41, 61, 43, 25, 8, 126, 26, 25, 137, 145, 34, 44, 45, 129, 216, 179, 1, 90, 25, 137, 32, 227, 8, 16, 9, 170, 49, 31, 32, 29, 128, 145, 148, 75, 25, 75, 153, 162, 192, 35, 12, 80, 136,
+ 176, 8, 194, 24, 1, 176, 21, 154, 145, 80, 251, 130, 2, 30, 9, 8, 130, 145, 128, 98, 27, 26, 129, 136, 162, 15, 33, 168, 59, 65, 177, 77, 141, 1, 128, 168, 113, 10, 137, 178, 163, 146, 132, 74, 153, 224, 164, 33, 184, 19, 184, 228, 161, 17, 91, 152, 25, 146, 152, 44, 121, 9, 160, 145, 17, 25, 28, 93, 128, 152, 2, 25, 27, 161,
+ 210, 129, 146, 45, 179, 227, 163, 162, 9, 40, 193, 148, 179, 57, 107, 140, 196, 32, 25, 57, 47, 136, 210, 130, 24, 40, 28, 152, 210, 182, 145, 40, 8, 129, 184, 147, 147, 140, 163, 166, 160, 34, 45, 144, 194, 161, 134, 41, 46, 152, 162, 162, 3, 44, 58, 75, 209, 162, 144, 57, 129, 47, 152, 130, 59, 16, 248, 129, 17, 26, 57,
+ 9, 29, 167, 2, 60, 42, 138, 136, 209, 130, 90, 42, 42, 176, 146, 178, 120, 28, 8, 160, 145, 16, 33, 31, 1, 8, 160, 129, 128, 242, 164, 32, 152, 177, 146, 213, 196, 128, 40, 26, 160, 163, 180, 146, 108, 60, 144, 144, 136, 147, 137, 40, 90, 161, 3, 17, 219, 243, 33, 184, 130, 60, 136, 243, 178, 179, 132, 26, 8, 168, 212, 147,
+ 16, 57, 42, 31, 145, 145, 160, 32, 43, 184, 66, 45, 180, 33, 140, 226, 1, 91, 152, 16, 144, 193, 162, 48, 77, 25, 137, 153, 17, 178, 78, 0, 0, 16, 14, 90, 152, 153, 19, 129, 13, 123, 137, 129, 160, 1, 73, 44, 9, 129, 0, 153, 120, 10, 9, 162, 195, 32, 139, 28, 151, 161, 2, 128, 26, 45, 193, 146, 48, 29, 146, 153, 194, 5, 59,
+ 29, 128, 144, 195, 1, 64, 43, 208, 178, 149, 8, 9, 16, 240, 163, 129, 16, 42, 185, 181, 211, 24, 48, 45, 137, 149, 9, 24, 41, 75, 184, 177, 4, 43, 91, 128, 180, 16, 144, 29, 25, 184, 167, 1, 59, 60, 153, 148, 161, 146, 91, 42, 186, 4, 24, 145, 123, 11, 2, 178, 77, 136, 26, 25, 195, 40, 115, 61, 27, 168, 177, 3, 59, 79, 26, 25,
+ 144, 1, 48, 13, 56, 154, 248, 1, 16, 9, 129, 8, 2, 178, 31, 130, 153, 162, 20, 15, 33, 170, 56, 40, 29, 28, 128, 152, 149, 144, 56, 120, 11, 162, 212, 129, 144, 145, 59, 180, 243, 147, 145, 144, 16, 152, 48, 241, 0, 161, 176, 1, 134, 10, 129, 200, 166, 144, 128, 121, 26, 24, 177, 178, 196, 48, 75, 138, 41, 180, 195, 26, 24,
+ 89, 138, 24, 33, 187, 41, 84, 155, 57, 79, 136, 160, 210, 130, 0, 58, 58, 168, 243, 132, 27, 41, 75, 138, 3, 8, 61, 8, 29, 145, 179, 76, 24, 28, 146, 208, 2, 49, 140, 75, 196, 144, 0, 40, 44, 179, 208, 3, 176, 33, 15, 177, 2, 160, 106, 8, 160, 164, 164, 8, 73, 27, 226, 179, 161, 1, 57, 1, 196, 211, 128, 40, 156, 145, 166, 178,
+ 131, 29, 128, 145, 162, 165, 40, 27, 216, 146, 135, 144, 40, 160, 194, 177, 145, 20, 139, 200, 151, 178, 17, 136, 40, 25, 205, 130, 17, 11, 17, 129, 156, 38, 26, 25, 137, 179, 163, 11, 79, 16, 12, 146, 147, 143, 89, 25, 136, 136, 25, 48, 26, 46, 129, 40, 29, 42, 29, 8, 145, 2, 56, 27, 62, 8, 25, 212, 161, 48, 43, 144, 129,
+ 29, 145, 144, 41, 106, 10, 107, 43, 184, 131, 1, 36, 61, 13, 138, 2, 194, 1, 16, 27, 75, 186, 181, 151, 8, 1, 161, 138, 211, 129, 2, 59, 248, 129, 16, 0, 144, 63, 152, 150, 136, 24, 25, 128, 30, 161, 128, 17, 24, 225, 146, 10, 16, 0, 9, 227, 183, 129, 40, 60, 26, 162, 194, 181, 24, 90, 9, 24, 0, 176, 161, 193, 194, 35, 12, 63,
+ 8, 210, 162, 1, 32, 78, 28, 152, 164, 144, 16, 48, 45, 137, 162, 147, 168, 152, 98, 27, 43, 33, 12, 160, 165, 129, 137, 63, 41, 153, 153, 151, 16, 91, 26, 8, 8, 9, 56, 10, 46, 24, 146, 57, 168, 160, 166, 241, 129, 32, 140, 16, 145, 179, 164, 137, 113, 138, 208, 131, 26, 25, 1, 42, 178, 196, 106, 24, 171, 18, 196, 8, 18, 29,
+ 41, 194, 128, 3, 249, 57, 162, 152, 48, 184, 120, 160, 208, 33, 137, 74, 57, 187, 149, 129, 26, 35, 158, 72, 128, 168, 32, 26, 25, 180, 75, 2, 136, 15, 163, 161, 136, 120, 27, 41, 160, 128, 182, 56, 60, 25, 12, 178, 151, 128, 168, 72, 10, 152, 4, 177, 26, 147, 137, 113, 44, 42, 33, 220, 2, 152, 41, 82, 11, 210, 163, 184,
+ 133, 162, 10, 196, 128, 3, 234, 40, 149, 152, 161, 1, 44, 129, 194, 4, 225, 16, 58, 168, 24, 194, 146, 146, 154, 49, 21, 218, 33, 152, 248, 129, 194, 147, 0, 28, 1, 195, 162, 20, 140, 42, 25, 160, 198, 1, 33, 136, 142, 3, 25, 24, 141, 16, 177, 208, 112, 0, 138, 41, 160, 130, 45, 60, 32, 170, 73, 24, 75, 59, 161, 176, 49, 159,
+ 97, 26, 168, 149, 145, 32, 28, 25, 184, 211, 129, 179, 74, 73, 8, 153, 136, 193, 151, 160, 32, 48, 143, 9, 147, 181, 145, 32, 60, 9, 187, 133, 166, 144, 32, 152, 25, 136, 161, 150, 168, 145, 81, 10, 42, 0, 169, 182, 148, 136, 58, 41, 187, 182, 211, 131, 16, 137, 25, 243, 144, 129, 2, 9, 8, 202, 7, 25, 185, 21, 144, 136, 153,
+ 65, 184, 137, 56, 151, 10, 153, 49, 16, 145, 14, 56, 176, 11, 192, 19, 89, 91, 44, 168, 147, 2, 8, 147, 63, 27, 1, 136, 229, 129, 73, 26, 136, 26, 137, 81, 170, 147, 77, 72, 12, 42, 42, 192, 24, 104, 91, 26, 27, 65, 177, 27, 32, 41, 60, 14, 136, 17, 170, 150, 129, 24, 58, 11, 16, 251, 162, 19, 57, 31, 0, 152, 129, 145, 17, 61,
+ 14, 1, 129, 27, 129, 66, 169, 178, 74, 12, 11, 19, 198, 145, 75, 33, 138, 174, 133, 1, 184, 57, 40, 136, 169, 20, 1, 60, 174, 20, 154, 201, 67, 26, 162, 151, 42, 16, 138, 59, 130, 204, 20, 169, 59, 180, 59, 114, 184, 56, 178, 242, 128, 130, 43, 8, 194, 3, 229, 144, 33, 185, 144, 34, 181, 145, 168, 17, 149, 153, 74, 35, 220,
+ 129, 128, 1, 88, 59, 75, 225, 136, 130, 168, 17, 144, 12, 151, 8, 25, 179, 8, 1, 240, 16, 8, 25, 145, 211, 41, 130, 138, 115, 169, 160, 163, 168, 84, 154, 74, 0, 170, 144, 211, 149, 2, 30, 128, 137, 9, 149, 1, 144, 58, 60, 57, 153, 178, 150, 17, 29, 27, 74, 25, 195, 152, 56, 15, 1, 25, 26, 152, 149, 80, 153, 57, 73, 140, 128,
+ 160, 144, 113, 27, 56, 28, 25, 4, 42, 44, 137, 60, 171, 130, 50, 240, 8, 5, 139, 145, 1, 105, 137, 200, 80, 137, 145, 146, 178, 179, 160, 46, 16, 240, 195, 131, 128, 144, 24, 164, 198, 128, 0, 136, 137, 131, 194, 165, 177, 2, 161, 147, 11, 144, 188, 181, 148, 144, 23, 0, 28, 224, 128, 131, 192, 32, 1, 224, 1, 168, 132, 145,
+ 9, 41, 208, 58, 137, 179, 151, 145, 16, 1, 30, 8, 145, 178, 1, 47, 32, 186, 72, 169, 146, 75, 8, 41, 48, 136, 89, 13, 48, 9, 10, 124, 26, 11, 42, 32, 129, 91, 77, 16, 12, 128, 42, 57, 138, 10, 60, 2, 63, 9, 0, 93, 128, 152, 90, 8, 10, 24, 40, 44, 144, 29, 49, 188, 48, 72, 25, 30, 177, 33, 128, 186, 120, 129, 186, 133, 152, 130,
+ 24, 156, 51, 154, 8, 226, 2, 56, 155, 2, 179, 233, 167, 128, 24, 129, 176, 136, 151, 8, 184, 0, 33, 224, 152, 21, 177, 24, 10, 163, 16, 250, 17, 130, 171, 83, 137, 136, 37, 12, 56, 242, 154, 17, 160, 145, 82, 13, 3, 201, 128, 18, 137, 24, 162, 63, 162, 8, 107, 178, 128, 57, 158, 32, 24, 200, 18, 0, 106, 154, 73, 16, 248, 8,
+ 73, 137, 57, 75, 0, 128, 12, 65, 137, 59, 75, 28, 144, 129, 122, 0, 58, 140, 160, 195, 145, 105, 56, 28, 153, 145, 164, 88, 8, 28, 25, 153, 9, 162, 113, 89, 153, 136, 33, 234, 147, 128, 41, 72, 11, 138, 151, 144, 145, 16, 43, 58, 248, 130, 178, 42, 4, 40, 10, 196, 154, 147, 216, 24, 7, 136, 10, 161, 148, 210, 161, 98, 138,
+ 137, 128, 146, 176, 33, 105, 27, 43, 163, 49, 185, 6, 10, 136, 43, 67, 174, 161, 162, 151, 137, 1, 64, 200, 193, 24, 64, 200, 56, 145, 242, 24, 57, 137, 1, 128, 3, 162, 175, 80, 128, 162, 152, 25, 58, 175, 17, 17, 0, 200, 64, 168, 162, 91, 1, 154, 44, 211, 177, 35, 64, 160, 161, 144, 4, 241, 41, 209, 162, 25, 1, 3, 242, 176,
+ 134, 153, 42, 41, 136, 135, 154, 2, 130, 46, 41, 161, 153, 180, 145, 34, 26, 46, 18, 242, 137, 146, 129, 25, 128, 11, 151, 161, 40, 179, 27, 122, 168, 59, 137, 181, 50, 172, 36, 56, 15, 9, 129, 137, 128, 75, 2, 58, 12, 52, 141, 8, 24, 58, 153, 157, 122, 145, 9, 1, 80, 27, 184, 32, 74, 219, 50, 57, 168, 153, 180, 48, 28, 143,
+ 131, 144, 178, 65, 13, 48, 168, 162, 147, 155, 121, 9, 170, 5, 16, 153, 21, 29, 144, 161, 91, 0, 184, 57, 128, 137, 17, 159, 88, 178, 128, 105, 152, 9, 162, 33, 164, 141, 88, 178, 224, 1, 0, 16, 27, 185, 150, 161, 9, 4, 139, 16, 128, 160, 194, 144, 65, 180, 46, 40, 136, 27, 135, 160, 16, 44, 57, 145, 236, 2, 195, 40, 75, 177,
+ 2, 200, 179, 146, 186, 104, 50, 141, 24, 169, 165, 148, 11, 97, 10, 11, 130, 177, 49, 57, 78, 42, 154, 128, 165, 59, 33, 28, 30, 1, 136, 16, 192, 41, 128, 152, 123, 136, 24, 1, 169, 113, 10, 11, 49, 153, 14, 147, 19, 45, 43, 8, 176, 210, 148, 8, 16, 11, 96, 144, 192, 163, 150, 10, 128, 43, 26, 150, 178, 165, 24, 41, 171, 18,
+ 27, 215, 1, 8, 128, 136, 40, 35, 208, 11, 161, 193, 18, 73, 154, 133, 155, 165, 164, 10, 49, 154, 8, 199, 0, 2, 168, 64, 192, 0, 40, 162, 43, 202, 180, 150, 10, 106, 24, 185, 145, 131, 184, 113, 43, 24, 162, 187, 73, 146, 42, 81, 171, 121, 58, 155, 151, 16, 43, 32, 31, 9, 160, 146, 17, 136, 94, 10, 24, 145, 25, 9, 130, 59,
+ 65, 13, 91, 25, 169, 146, 176, 112, 42, 59, 16, 217, 130, 20, 13, 25, 9, 40, 161, 138, 68, 169, 154, 18, 62, 154, 180, 145, 135, 152, 56, 58, 155, 165, 211, 8, 40, 42, 10, 198, 1, 2, 184, 57, 184, 224, 51, 154, 27, 134, 168, 19, 202, 73, 75, 184, 35, 176, 75, 24, 25, 209, 51, 157, 19, 30, 184, 179, 3, 33, 148, 45, 232, 146,
+ 129, 168, 41, 32, 170, 149, 193, 35, 136, 16, 50, 191, 56, 146, 173, 149, 16, 24, 41, 30, 129, 168, 209, 3, 57, 31, 0, 16, 176, 147, 41, 152, 10, 17, 181, 14, 40, 144, 49, 170, 75, 97, 141, 25, 162, 146, 72, 177, 92, 137, 137, 19, 137, 153, 113, 154, 2, 41, 60, 129, 217, 2, 211, 152, 73, 42, 193, 197, 146, 147, 10, 59, 0,
+ 192, 196, 132, 41, 160, 25, 88, 169, 16, 40, 241, 1, 153, 81, 28, 10, 147, 161, 209, 88, 75, 9, 161, 162, 180, 16, 43, 57, 235, 33, 56, 156, 129, 144, 2, 135, 31, 128, 145, 136, 163, 56, 59, 154, 57, 167, 160, 105, 137, 0, 138, 163, 3, 41, 47, 185, 211, 131, 41, 41, 60, 139, 182, 146, 16, 16, 43, 242, 144, 145, 129, 16, 179,
+ 183, 1, 26, 9, 147, 240, 131, 160, 91, 74, 152, 184, 166, 178, 33, 140, 9, 4, 162, 233, 34, 136, 129, 144, 163, 60, 142, 144, 149, 128, 33, 73, 13, 161, 194, 131, 0, 26, 56, 142, 128, 163, 128, 1, 233, 56, 209, 41, 145, 194, 147, 179, 149, 64, 30, 8, 128, 216, 18, 24, 43, 43, 32, 153, 25, 74, 109, 137, 153, 48, 8, 137, 122,
+ 25, 144, 26, 43, 59, 30, 33, 41, 27, 24, 96, 153, 160, 50, 76, 27, 47, 152, 145, 163, 73, 40, 14, 152, 131, 176, 74, 90, 8, 8, 200, 67, 155, 154, 50, 49, 155, 28, 124, 177, 152, 1, 2, 17, 62, 138, 180, 176, 4, 25, 9, 177, 245, 162, 129, 40, 25, 176, 164, 130, 172, 4, 8, 181, 194, 49, 11, 168, 154, 165, 133, 152, 40, 136, 226,
+ 179, 19, 26, 185, 16, 167, 194, 16, 25, 57, 243, 136, 147, 1, 31, 25, 184, 132, 160, 33, 62, 138, 129, 130, 41, 121, 137, 153, 145, 26, 17, 107, 136, 179, 1, 61, 60, 26, 162, 168, 148, 64, 31, 25, 32, 168, 152, 64, 31, 137, 8, 129, 33, 62, 24, 137, 8, 16, 59, 47, 153, 33, 162, 91, 59, 41, 170, 145, 5, 43, 60, 41, 13, 178, 134,
+ 57, 153, 12, 194, 227, 8, 2, 128, 57, 208, 162, 19, 216, 32, 178, 25, 128, 160, 48, 194, 195, 37, 155, 10, 33, 251, 163, 146, 16, 136, 12, 166, 195, 160, 148, 129, 176, 147, 178, 150, 160, 72, 162, 162, 193, 162, 60, 200, 145, 5, 144, 25, 122, 216, 129, 161, 130, 0, 10, 73, 1, 241, 2, 9, 168, 33, 13, 161, 165, 24, 64, 203,
+ 50, 1, 14, 9, 9, 129, 161, 106, 33, 27, 13, 164, 128, 40, 41, 107, 169, 160, 33, 136, 60, 92, 168, 152, 2, 91, 57, 176, 129, 0, 144, 47, 136, 162, 164, 128, 80, 43, 154, 179, 213, 130, 74, 27, 0, 145, 145, 167, 58, 59, 160, 9, 26, 76, 8, 171, 5, 49, 28, 44, 169, 162, 183, 130, 72, 28, 144, 179, 228, 2, 25, 26, 129, 186, 151,
+ 1, 75, 128, 169, 17, 178, 15, 57, 170, 16, 166, 16, 57, 8, 139, 162, 181, 1, 8, 152, 164, 181, 41, 81, 43, 10, 242, 145, 57, 139, 89, 8, 193, 18, 154, 32, 176, 10, 165, 129, 137, 147, 177, 134, 0, 25, 25, 201, 147, 227, 129, 72, 59, 185, 167, 128, 129, 160, 91, 25, 176, 130, 147, 145, 9, 160, 5, 202, 17, 16, 186, 136, 37,
+ 177, 56, 76, 42, 169, 186, 48, 9, 145, 57, 24, 128, 41, 169, 134, 137, 145, 147, 28, 41, 168, 131, 228, 32, 27, 9, 60, 129, 178, 64, 60, 45, 25, 9, 24, 152, 49, 31, 136, 57, 42, 0, 25, 12, 181, 18, 153, 57, 96, 169, 177, 132, 153, 123, 9, 152, 129, 177, 17, 74, 43, 24, 169, 128, 121, 137, 25, 1, 139, 96, 42, 10, 146, 178, 18,
+ 44, 29, 1, 161, 164, 146, 31, 137, 146, 177, 19, 1, 10, 26, 209, 165, 146, 43, 40, 138, 240, 130, 18, 144, 25, 40, 212, 1, 58, 11, 152, 196, 147, 10, 74, 26, 152, 225, 130, 146, 58, 60, 210, 145, 16, 148, 16, 185, 192, 18, 44, 42, 57, 199, 162, 1, 9, 87, 47, 186, 215, 231, 197, 179, 180, 195, 212, 164, 32, 59, 92, 126, 62,
+ 41, 59, 76, 59, 60, 168, 179, 213, 197, 163, 72, 44, 25, 74, 126, 127, 127, 79, 26, 177, 148, 90, 27, 225, 247, 165, 0, 152, 147, 123, 138, 211, 164, 72, 126, 127, 46, 210, 196, 163, 228, 215, 64, 11, 210, 180, 1, 8, 58, 153, 1, 224, 149, 57, 76, 27, 24, 76, 42, 43, 136, 128, 243, 179, 130, 106, 60, 42, 42, 92, 28, 243, 231,
+ 147, 24, 57, 44, 58, 94, 45, 8, 57, 139, 214, 148, 40, 77, 26, 9, 16, 10, 144, 64, 62, 43, 25, 123, 59, 138, 162, 48, 63, 26, 41, 92, 60, 43, 176, 3, 59, 232, 214, 164, 16, 75, 75, 76, 60, 153, 179, 33, 62, 26, 136, 40, 75, 169, 197, 163, 129, 57, 60, 59, 75, 138, 145, 64, 63, 138, 179, 1, 42, 136, 90, 43, 176, 214, 180, 1, 25,
+ 152, 195, 129, 129, 106, 76, 60, 137, 145, 178, 2, 25, 10, 228, 130, 57, 59, 44, 41, 154, 165, 105, 76, 44, 144, 16, 76, 26, 41, 76, 26, 152, 1, 58, 26, 9, 193, 165, 16, 92, 26, 41, 77, 59, 76, 76, 60, 26, 136, 161, 130, 152, 195, 163, 211, 146, 0, 57, 11, 211, 130, 8, 25, 40, 62, 153, 162, 17, 109, 60, 153, 146, 40, 76, 60,
+ 26, 160, 179, 211, 163, 32, 60, 42, 153, 179, 194, 199, 130, 24, 58, 43, 58, 27, 128, 161, 195, 129, 226, 196, 147, 90, 59, 75, 44, 136, 128, 145, 160, 148, 123, 59, 42, 26, 41, 26, 57, 27, 192, 215, 147, 57, 59, 27, 161, 145, 213, 130, 106, 76, 43, 9, 144, 162, 129, 177, 181, 130, 136, 194, 146, 40, 10, 129, 25, 210, 146,
+ 178, 197, 196, 179, 196, 130, 8, 41, 9, 144, 178, 130, 209, 182, 17, 92, 43, 176, 147, 144, 212, 130, 136, 0, 177, 130, 73, 62, 10, 161, 130, 91, 75, 59, 43, 57, 46, 25, 41, 77, 10, 177, 164, 16, 26, 136, 210, 197, 179, 130, 128, 57, 77, 43, 25, 75, 10, 227, 179, 180, 179, 146, 128, 57, 185, 183, 163, 145, 0, 8, 8, 10, 119,
+ 114, 120, 16, 210, 244, 60, 28, 41, 25, 152, 149, 56, 161, 35, 44, 89, 27, 24, 136, 24, 164, 211, 17, 233, 176, 136, 192, 129, 179, 17, 17, 25, 0, 10, 46, 160, 132, 49, 66, 24, 132, 177, 147, 193, 56, 72, 26, 29, 232, 168, 176, 12, 137, 41, 139, 147, 9, 1, 41, 15, 91, 136, 35, 148, 21, 18, 48, 40, 1, 168, 167, 144, 0, 42, 172,
+ 177, 204, 193, 155, 232, 152, 152, 26, 152, 41, 146, 17, 6, 4, 65, 34, 35, 135, 4, 16, 32, 9, 24, 186, 176, 0, 250, 153, 204, 186, 173, 154, 153, 177, 3, 65, 41, 34, 145, 134, 35, 65, 98, 49, 50, 50, 2, 33, 169, 138, 155, 175, 170, 172, 204, 192, 138, 234, 136, 155, 136, 10, 32, 18, 5, 52, 48, 24, 162, 17, 67, 54, 66, 51, 34,
+ 131, 184, 174, 234, 153, 10, 9, 40, 0, 152, 251, 168, 142, 154, 9, 16, 33, 49, 33, 128, 154, 170, 156, 34, 54, 54, 33, 68, 0, 1, 136, 201, 137, 26, 88, 48, 35, 99, 8, 152, 189, 189, 187, 155, 171, 16, 24, 130, 145, 188, 175, 203, 144, 49, 115, 67, 67, 50, 19, 2, 1, 0, 0, 130, 131, 1, 136, 206, 216, 188, 203, 204, 187, 187,
+ 156, 153, 0, 0, 51, 17, 34, 24, 112, 20, 69, 67, 67, 34, 19, 0, 136, 169, 185, 137, 186, 232, 185, 219, 201, 203, 187, 173, 170, 154, 153, 129, 131, 6, 2, 19, 49, 49, 21, 65, 19, 53, 51, 83, 34, 16, 168, 201, 154, 172, 156, 138, 0, 1, 24, 201, 233, 186, 204, 186, 171, 137, 3, 37, 48, 24, 128, 201, 202, 202, 129, 17, 48, 21,
+ 22, 20, 19, 19, 32, 16, 2, 66, 52, 68, 4, 3, 1, 203, 235, 188, 189, 186, 171, 153, 137, 153, 170, 219, 170, 140, 9, 17, 53, 115, 50, 52, 67, 51, 51, 51, 17, 130, 0, 145, 154, 169, 188, 236, 187, 190, 203, 187, 172, 171, 138, 136, 17, 33, 18, 2, 34, 98, 98, 50, 50, 52, 66, 34, 35, 2, 19, 24, 169, 203, 203, 188, 219, 169, 154,
+ 9, 137, 171, 204, 188, 203, 184, 136, 34, 83, 50, 33, 153, 184, 170, 170, 152, 40, 57, 19, 36, 50, 50, 18, 35, 17, 2, 49, 49, 66, 66, 66, 34, 17, 168, 233, 202, 202, 170, 171, 170, 186, 219, 203, 188, 188, 154, 138, 25, 33, 68, 52, 68, 67, 67, 36, 51, 36, 18, 17, 17, 136, 8, 170, 176, 202, 188, 206, 202, 171, 172, 186, 169,
+ 153, 8, 25, 144, 128, 1, 34, 68, 52, 68, 51, 52, 34, 49, 18, 34, 2, 144, 136, 155, 140, 187, 186, 186, 154, 154, 185, 185, 153, 9, 9, 0, 24, 0, 128, 144, 168, 169, 170, 154, 154, 153, 9, 8, 16, 8, 0, 144, 19, 35, 68, 51, 52, 67, 51, 66, 34, 50, 33, 1, 144, 185, 186, 172, 204, 187, 188, 173, 172, 186, 172, 186, 154, 138, 41,
+ 33, 52, 53, 83, 50, 51, 52, 52, 37, 34, 34, 18, 16, 144, 152, 154, 187, 219, 203, 188, 173, 186, 186, 186, 170, 154, 153, 138, 144, 16, 17, 67, 82, 50, 51, 21, 34, 19, 33, 2, 18, 33, 1, 8, 153, 169, 153, 153, 136, 128, 0, 136, 154, 153, 153, 8, 8, 1, 16, 0, 169, 170, 187, 171, 171, 154, 153, 153, 152, 153, 153, 0, 16, 51, 83,
+ 66, 50, 67, 50, 51, 67, 51, 52, 35, 18, 136, 186, 219, 187, 189, 186, 171, 187, 173, 187, 188, 187, 203, 138, 9, 16, 33, 50, 52, 53, 67, 67, 147, 8, 128, 128, 128, 128, 128, 128, 128, 128, 0, 240, 255, 55, 232, 23, 220, 0, 148, 1, 9, 18, 148, 10, 189, 32, 163, 62, 160, 5, 137, 12, 149, 42, 153, 144, 34, 42, 8, 1, 138, 181,
+ 45, 136, 18, 144, 105, 138, 1, 160, 14, 128, 132, 145, 186, 37, 138, 41, 192, 48, 145, 46, 160, 33, 44, 24, 225, 16, 13, 132, 136, 137, 16, 148, 25, 170, 194, 82, 152, 136, 91, 24, 42, 169, 33, 233, 131, 179, 24, 185, 149, 16, 57, 172, 164, 18, 10, 211, 160, 147, 211, 33, 138, 243, 129, 16, 41, 193, 0, 43, 132, 155, 73,
+ 58, 145, 244, 145, 43, 35, 9, 171, 16, 110, 25, 8, 28, 74, 162, 128, 26, 27, 82, 45, 136, 153, 18, 8, 136, 8
+};
+#endif // DISABLE_PC98_RHYTHM_CHANNEL
+
+TownsPC98_FmSynth::ChanInternal::ChanInternal() {
+ memset(this, 0, sizeof(ChanInternal));
+}
+
+TownsPC98_FmSynth::ChanInternal::~ChanInternal() {
+ for (uint i = 0; i < ARRAYSIZE(opr); ++i)
+ delete opr[i];
+}
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
new file mode 100644
index 0000000000..18cca56e29
--- /dev/null
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
@@ -0,0 +1,196 @@
+/* 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$
+ *
+ */
+
+#ifndef TOWNS_PC98_FMSYNTH_H
+#define TOWNS_PC98_FMSYNTH_H
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/list.h"
+
+#ifdef __DS__
+/* This disables the rhythm channel when emulating the PC-98 type 86 sound card.
+ * The only purpose is code size reduction for certain backends.
+ * At the moment the only games which make use of the rhythm channel are the
+ * (very rare) PC-98 versions of Legend of Kyrandia 2 and Lands of Lore. Music will
+ * still be okay, just missing a couple of rhythm instruments.
+ */
+#define DISABLE_PC98_RHYTHM_CHANNEL
+#endif
+
+class TownsPC98_FmSynthOperator;
+class TownsPC98_FmSynthSquareSineSource;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+class TownsPC98_FmSynthPercussionSource;
+#endif
+
+enum EnvelopeState {
+ kEnvReady,
+ kEnvAttacking,
+ kEnvDecaying,
+ kEnvSustaining,
+ kEnvReleasing
+};
+
+class TownsPC98_FmSynth : public Audio::AudioStream {
+public:
+ enum EmuType {
+ kTypeTowns,
+ kType26,
+ kType86
+ };
+
+ TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type);
+ virtual ~TownsPC98_FmSynth();
+
+ virtual bool init();
+ virtual void reset();
+
+ void writeReg(uint8 part, uint8 regAddress, uint8 value);
+
+ // AudioStream interface
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const {
+ return true;
+ }
+ bool endOfData() const {
+ return false;
+ }
+ int getRate() const {
+ return _mixer->getOutputRate();
+ }
+
+protected:
+ void deinit();
+
+ // Implement this in your inherited class if your driver generates
+ // additional output that has to be inserted into the buffer.
+ virtual void nextTickEx(int32 *buffer, uint32 bufferSize) {}
+
+ void toggleRegProtection(bool prot) {
+ _regProtectionFlag = prot;
+ }
+ uint8 readSSGStatus();
+
+ virtual void timerCallbackA() = 0;
+ virtual void timerCallbackB() = 0;
+
+ // The audio driver can store and apply two different audio settings
+ // (usually for music and sound effects). The channel mask will determine
+ // which channels get effected by the setting. The first bits will be
+ // the normal fm channels, the next bits the ssg channels and the final
+ // bit the rhythm channel.
+ void setVolumeIntern(int volA, int volB);
+ void setVolumeChannelMasks(int channelMaskA, int channelMaskB);
+
+ const int _numChan;
+ const int _numSSG;
+ const bool _hasPercussion;
+
+ Common::Mutex _mutex;
+private:
+ void generateTables();
+ void nextTick(int32 *buffer, uint32 bufferSize);
+ void generateOutput(int32 &leftSample, int32 &rightSample, int32 *del, int32 *feed);
+
+ struct ChanInternal {
+ ChanInternal();
+ ~ChanInternal();
+
+ void ampModSensitivity(uint32 value) {
+ ampModSvty = (1 << (3 - value)) - (((value >> 1) & 1) | (value & 1));
+ }
+ void frqModSensitivity(uint32 value) {
+ frqModSvty = value << 5;
+ }
+
+ uint16 frqTemp;
+ bool enableLeft;
+ bool enableRight;
+ bool updateEnvelopeParameters;
+ int32 feedbuf[3];
+ uint8 algorithm;
+
+ uint32 ampModSvty;
+ uint32 frqModSvty;
+
+ TownsPC98_FmSynthOperator *opr[4];
+ };
+
+ TownsPC98_FmSynthSquareSineSource *_ssg;
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ TownsPC98_FmSynthPercussionSource *_prc;
+#endif
+ ChanInternal *_chanInternal;
+
+ uint8 *_oprRates;
+ uint8 *_oprRateshift;
+ uint8 *_oprAttackDecay;
+ uint32 *_oprFrq;
+ uint32 *_oprSinTbl;
+ int32 *_oprLevelOut;
+ int32 *_oprDetune;
+
+ bool _regProtectionFlag;
+
+ typedef void (TownsPC98_FmSynth::*ChipTimerProc)();
+ void idleTimerCallback() {}
+
+ struct ChipTimer {
+ bool enabled;
+ uint16 value;
+
+ int32 smpTillCb;
+ uint32 smpTillCbRem;
+ int32 smpPerCb;
+ uint32 smpPerCbRem;
+
+ ChipTimerProc cb;
+ };
+
+ ChipTimer _timers[2];
+
+ int _volMaskA, _volMaskB;
+ uint16 _volumeA, _volumeB;
+
+ const float _baserate;
+ uint32 _timerbase;
+ uint32 _rtt;
+
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ static const uint8 _percussionData[];
+#endif
+ static const uint32 _adtStat[];
+ static const uint8 _detSrc[];
+ static const int _ssgTables[];
+
+ bool _ready;
+};
+
+#endif
+
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
diff --git a/audio/softsynth/mt32/freeverb.cpp b/audio/softsynth/mt32/freeverb.cpp
new file mode 100644
index 0000000000..c62d4f2cf3
--- /dev/null
+++ b/audio/softsynth/mt32/freeverb.cpp
@@ -0,0 +1,310 @@
+/* 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$
+ *
+ */
+
+// Comb filter implementation
+//
+// Written by
+// http://www.dreampoint.co.uk
+// This code is public domain
+
+#include "audio/softsynth/mt32/freeverb.h"
+
+comb::comb() {
+ filterstore = 0;
+ bufidx = 0;
+}
+
+void comb::setbuffer(float *buf, int size) {
+ buffer = buf;
+ bufsize = size;
+}
+
+void comb::mute() {
+ for (int i = 0; i < bufsize; i++)
+ buffer[i] = 0;
+}
+
+void comb::setdamp(float val) {
+ damp1 = val;
+ damp2 = 1 - val;
+}
+
+float comb::getdamp() {
+ return damp1;
+}
+
+void comb::setfeedback(float val) {
+ feedback = val;
+}
+
+float comb::getfeedback() {
+ return feedback;
+}
+
+// Allpass filter implementation
+
+allpass::allpass() {
+ bufidx = 0;
+}
+
+void allpass::setbuffer(float *buf, int size) {
+ buffer = buf;
+ bufsize = size;
+}
+
+void allpass::mute() {
+ for (int i = 0; i < bufsize; i++)
+ buffer[i] = 0;
+}
+
+void allpass::setfeedback(float val) {
+ feedback = val;
+}
+
+float allpass::getfeedback() {
+ return feedback;
+}
+
+// Reverb model implementation
+
+revmodel::revmodel() {
+ // Tie the components to their buffers
+ combL[0].setbuffer(bufcombL1,combtuningL1);
+ combR[0].setbuffer(bufcombR1,combtuningR1);
+ combL[1].setbuffer(bufcombL2,combtuningL2);
+ combR[1].setbuffer(bufcombR2,combtuningR2);
+ combL[2].setbuffer(bufcombL3,combtuningL3);
+ combR[2].setbuffer(bufcombR3,combtuningR3);
+ combL[3].setbuffer(bufcombL4,combtuningL4);
+ combR[3].setbuffer(bufcombR4,combtuningR4);
+ combL[4].setbuffer(bufcombL5,combtuningL5);
+ combR[4].setbuffer(bufcombR5,combtuningR5);
+ combL[5].setbuffer(bufcombL6,combtuningL6);
+ combR[5].setbuffer(bufcombR6,combtuningR6);
+ combL[6].setbuffer(bufcombL7,combtuningL7);
+ combR[6].setbuffer(bufcombR7,combtuningR7);
+ combL[7].setbuffer(bufcombL8,combtuningL8);
+ combR[7].setbuffer(bufcombR8,combtuningR8);
+ allpassL[0].setbuffer(bufallpassL1,allpasstuningL1);
+ allpassR[0].setbuffer(bufallpassR1,allpasstuningR1);
+ allpassL[1].setbuffer(bufallpassL2,allpasstuningL2);
+ allpassR[1].setbuffer(bufallpassR2,allpasstuningR2);
+ allpassL[2].setbuffer(bufallpassL3,allpasstuningL3);
+ allpassR[2].setbuffer(bufallpassR3,allpasstuningR3);
+ allpassL[3].setbuffer(bufallpassL4,allpasstuningL4);
+ allpassR[3].setbuffer(bufallpassR4,allpasstuningR4);
+
+ // Set default values
+ allpassL[0].setfeedback(0.5f);
+ allpassR[0].setfeedback(0.5f);
+ allpassL[1].setfeedback(0.5f);
+ allpassR[1].setfeedback(0.5f);
+ allpassL[2].setfeedback(0.5f);
+ allpassR[2].setfeedback(0.5f);
+ allpassL[3].setfeedback(0.5f);
+ allpassR[3].setfeedback(0.5f);
+ setmode(initialmode);
+ setwet(initialwet);
+ setroomsize(initialroom);
+ setdry(initialdry);
+ setdamp(initialdamp);
+ setwidth(initialwidth);
+
+ // Buffer will be full of rubbish - so we MUST mute them
+ mute();
+}
+
+void revmodel::mute() {
+ int i;
+
+ if (getmode() >= freezemode)
+ return;
+
+ for (i = 0; i < numcombs; i++) {
+ combL[i].mute();
+ combR[i].mute();
+ }
+
+ for (i = 0; i < numallpasses; i++) {
+ allpassL[i].mute();
+ allpassR[i].mute();
+ }
+}
+
+void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) {
+ float outL, outR, input;
+
+ while (numsamples-- > 0) {
+ int i;
+
+ outL = outR = 0;
+ input = (*inputL + *inputR) * gain;
+
+ // Accumulate comb filters in parallel
+ for (i = 0; i < numcombs; i++) {
+ outL += combL[i].process(input);
+ outR += combR[i].process(input);
+ }
+
+ // Feed through allpasses in series
+ for (i = 0; i < numallpasses; i++) {
+ outL = allpassL[i].process(outL);
+ outR = allpassR[i].process(outR);
+ }
+
+ // Calculate output REPLACING anything already there
+ *outputL = outL * wet1 + outR * wet2 + *inputL * dry;
+ *outputR = outR * wet1 + outL * wet2 + *inputR * dry;
+
+ // Increment sample pointers, allowing for interleave (if any)
+ inputL += skip;
+ inputR += skip;
+ outputL += skip;
+ outputR += skip;
+ }
+}
+
+void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) {
+ float outL, outR, input;
+
+ while (numsamples-- > 0) {
+ int i;
+
+ outL = outR = 0;
+ input = (*inputL + *inputR) * gain;
+
+ // Accumulate comb filters in parallel
+ for (i = 0; i < numcombs; i++) {
+ outL += combL[i].process(input);
+ outR += combR[i].process(input);
+ }
+
+ // Feed through allpasses in series
+ for (i = 0; i < numallpasses; i++) {
+ outL = allpassL[i].process(outL);
+ outR = allpassR[i].process(outR);
+ }
+
+ // Calculate output MIXING with anything already there
+ *outputL += outL * wet1 + outR * wet2 + *inputL * dry;
+ *outputR += outR * wet1 + outL * wet2 + *inputR * dry;
+
+ // Increment sample pointers, allowing for interleave (if any)
+ inputL += skip;
+ inputR += skip;
+ outputL += skip;
+ outputR += skip;
+ }
+}
+
+void revmodel::update() {
+ // Recalculate internal values after parameter change
+
+ int i;
+
+ wet1 = wet * (width / 2 + 0.5f);
+ wet2 = wet * ((1 - width) / 2);
+
+ if (mode >= freezemode) {
+ roomsize1 = 1;
+ damp1 = 0;
+ gain = muted;
+ } else {
+ roomsize1 = roomsize;
+ damp1 = damp;
+ gain = fixedgain;
+ }
+
+ for (i = 0; i < numcombs; i++) {
+ combL[i].setfeedback(roomsize1);
+ combR[i].setfeedback(roomsize1);
+ }
+
+ for (i = 0; i < numcombs; i++) {
+ combL[i].setdamp(damp1);
+ combR[i].setdamp(damp1);
+ }
+}
+
+// The following get/set functions are not inlined, because
+// speed is never an issue when calling them, and also
+// because as you develop the reverb model, you may
+// wish to take dynamic action when they are called.
+
+void revmodel::setroomsize(float value) {
+ roomsize = (value * scaleroom) + offsetroom;
+ update();
+}
+
+float revmodel::getroomsize() {
+ return (roomsize - offsetroom) / scaleroom;
+}
+
+void revmodel::setdamp(float value) {
+ damp = value * scaledamp;
+ update();
+}
+
+float revmodel::getdamp() {
+ return damp / scaledamp;
+}
+
+void revmodel::setwet(float value) {
+ wet = value * scalewet;
+ update();
+}
+
+float revmodel::getwet() {
+ return wet / scalewet;
+}
+
+void revmodel::setdry(float value) {
+ dry = value * scaledry;
+}
+
+float revmodel::getdry() {
+ return dry / scaledry;
+}
+
+void revmodel::setwidth(float value) {
+ width = value;
+ update();
+}
+
+float revmodel::getwidth() {
+ return width;
+}
+
+void revmodel::setmode(float value) {
+ mode = value;
+ update();
+}
+
+float revmodel::getmode() {
+ if (mode >= freezemode)
+ return 1;
+ else
+ return 0;
+}
diff --git a/audio/softsynth/mt32/freeverb.h b/audio/softsynth/mt32/freeverb.h
new file mode 100644
index 0000000000..8310aca3e3
--- /dev/null
+++ b/audio/softsynth/mt32/freeverb.h
@@ -0,0 +1,244 @@
+/* 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$
+ *
+ */
+
+// Macro for killing denormalled numbers
+//
+// Written by Jezar at Dreampoint, June 2000
+// http://www.dreampoint.co.uk
+// Based on IS_DENORMAL macro by Jon Watte
+// This code is public domain
+
+#ifndef FREEVERB_H
+#define FREEVERB_H
+
+// FIXME: Fix this really ugly hack
+inline float undenormalise(void *sample) {
+ if (((*(unsigned int*)sample) & 0x7f800000) == 0)
+ return 0.0f;
+ return *(float*)sample;
+}
+
+// Comb filter class declaration
+
+class comb {
+public:
+ comb();
+ void setbuffer(float *buf, int size);
+ inline float process(float inp);
+ void mute();
+ void setdamp(float val);
+ float getdamp();
+ void setfeedback(float val);
+ float getfeedback();
+private:
+ float feedback;
+ float filterstore;
+ float damp1;
+ float damp2;
+ float *buffer;
+ int bufsize;
+ int bufidx;
+};
+
+
+// Big to inline - but crucial for speed
+
+inline float comb::process(float input) {
+ float output;
+
+ output = buffer[bufidx];
+ undenormalise(&output);
+
+ filterstore = (output * damp2) + (filterstore * damp1);
+ undenormalise(&filterstore);
+
+ buffer[bufidx] = input + (filterstore * feedback);
+
+ if (++bufidx >= bufsize)
+ bufidx = 0;
+
+ return output;
+}
+
+// Allpass filter declaration
+
+class allpass {
+public:
+ allpass();
+ void setbuffer(float *buf, int size);
+ inline float process(float inp);
+ void mute();
+ void setfeedback(float val);
+ float getfeedback();
+private:
+ float feedback;
+ float *buffer;
+ int bufsize;
+ int bufidx;
+};
+
+
+// Big to inline - but crucial for speed
+
+inline float allpass::process(float input) {
+ float output;
+ float bufout;
+
+ bufout = buffer[bufidx];
+ undenormalise(&bufout);
+
+ output = -input + bufout;
+ buffer[bufidx] = input + (bufout * feedback);
+
+ if (++bufidx >= bufsize)
+ bufidx = 0;
+
+ return output;
+}
+
+
+// Reverb model tuning values
+
+const int numcombs = 8;
+const int numallpasses = 4;
+const float muted = 0;
+const float fixedgain = 0.015f;
+const float scalewet = 3;
+const float scaledry = 2;
+const float scaledamp = 0.4f;
+const float scaleroom = 0.28f;
+const float offsetroom = 0.7f;
+const float initialroom = 0.5f;
+const float initialdamp = 0.5f;
+const float initialwet = 1 / scalewet;
+const float initialdry = 0;
+const float initialwidth = 1;
+const float initialmode = 0;
+const float freezemode = 0.5f;
+const int stereospread = 23;
+
+// These values assume 44.1KHz sample rate
+// they will probably be OK for 48KHz sample rate
+// but would need scaling for 96KHz (or other) sample rates.
+// The values were obtained by listening tests.
+const int combtuningL1 = 1116;
+const int combtuningR1 = 1116 + stereospread;
+const int combtuningL2 = 1188;
+const int combtuningR2 = 1188 + stereospread;
+const int combtuningL3 = 1277;
+const int combtuningR3 = 1277 + stereospread;
+const int combtuningL4 = 1356;
+const int combtuningR4 = 1356 + stereospread;
+const int combtuningL5 = 1422;
+const int combtuningR5 = 1422 + stereospread;
+const int combtuningL6 = 1491;
+const int combtuningR6 = 1491 + stereospread;
+const int combtuningL7 = 1557;
+const int combtuningR7 = 1557 + stereospread;
+const int combtuningL8 = 1617;
+const int combtuningR8 = 1617 + stereospread;
+const int allpasstuningL1 = 556;
+const int allpasstuningR1 = 556 + stereospread;
+const int allpasstuningL2 = 441;
+const int allpasstuningR2 = 441 + stereospread;
+const int allpasstuningL3 = 341;
+const int allpasstuningR3 = 341 + stereospread;
+const int allpasstuningL4 = 225;
+const int allpasstuningR4 = 225 + stereospread;
+
+
+// Reverb model declaration
+
+class revmodel {
+public:
+ revmodel();
+ void mute();
+ void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip);
+ void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip);
+ void setroomsize(float value);
+ float getroomsize();
+ void setdamp(float value);
+ float getdamp();
+ void setwet(float value);
+ float getwet();
+ void setdry(float value);
+ float getdry();
+ void setwidth(float value);
+ float getwidth();
+ void setmode(float value);
+ float getmode();
+private:
+ void update();
+
+ float gain;
+ float roomsize, roomsize1;
+ float damp, damp1;
+ float wet, wet1, wet2;
+ float dry;
+ float width;
+ float mode;
+
+ // The following are all declared inline
+ // to remove the need for dynamic allocation
+ // with its subsequent error-checking messiness
+
+ // Comb filters
+ comb combL[numcombs];
+ comb combR[numcombs];
+
+ // Allpass filters
+ allpass allpassL[numallpasses];
+ allpass allpassR[numallpasses];
+
+ // Buffers for the combs
+ float bufcombL1[combtuningL1];
+ float bufcombR1[combtuningR1];
+ float bufcombL2[combtuningL2];
+ float bufcombR2[combtuningR2];
+ float bufcombL3[combtuningL3];
+ float bufcombR3[combtuningR3];
+ float bufcombL4[combtuningL4];
+ float bufcombR4[combtuningR4];
+ float bufcombL5[combtuningL5];
+ float bufcombR5[combtuningR5];
+ float bufcombL6[combtuningL6];
+ float bufcombR6[combtuningR6];
+ float bufcombL7[combtuningL7];
+ float bufcombR7[combtuningR7];
+ float bufcombL8[combtuningL8];
+ float bufcombR8[combtuningR8];
+
+ // Buffers for the allpasses
+ float bufallpassL1[allpasstuningL1];
+ float bufallpassR1[allpasstuningR1];
+ float bufallpassL2[allpasstuningL2];
+ float bufallpassR2[allpasstuningR2];
+ float bufallpassL3[allpasstuningL3];
+ float bufallpassR3[allpasstuningR3];
+ float bufallpassL4[allpasstuningL4];
+ float bufallpassR4[allpasstuningR4];
+};
+
+#endif
diff --git a/audio/softsynth/mt32/i386.cpp b/audio/softsynth/mt32/i386.cpp
new file mode 100644
index 0000000000..f092189d76
--- /dev/null
+++ b/audio/softsynth/mt32/i386.cpp
@@ -0,0 +1,849 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "mt32emu.h"
+
+#ifdef MT32EMU_HAVE_X86
+
+namespace MT32Emu {
+
+#ifndef _MSC_VER
+
+#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value))
+#define cpuid_flag (1 << 21)
+
+static inline bool atti386_DetectCPUID() {
+ unsigned int result;
+
+ // Is there a cpuid?
+ result = cpuid_flag; // set test
+ eflag(result);
+ if (!(result & cpuid_flag))
+ return false;
+
+ result = 0; // clear test
+ eflag(result);
+ if (result & cpuid_flag)
+ return false;
+
+ return true;
+}
+
+static inline bool atti386_DetectSIMD() {
+ unsigned int result;
+
+ if (atti386_DetectCPUID() == false)
+ return false;
+
+ /* check cpuid */
+ __asm__ __volatile__(
+ "pushl %%ebx \n" \
+ "movl $1, %%eax \n" \
+ "cpuid \n" \
+ "movl %%edx, %0 \n" \
+ "popl %%ebx \n" \
+ : "=r"(result) : : "eax", "ecx", "edx");
+
+ if (result & (1 << 25))
+ return true;
+
+ return false;
+}
+
+static inline bool atti386_Detect3DNow() {
+ unsigned int result;
+
+ if (atti386_DetectCPUID() == false)
+ return false;
+
+ // get cpuid
+ __asm__ __volatile__(
+ "pushl %%ebx \n" \
+ "movl $0x80000001, %%eax \n" \
+ "cpuid \n" \
+ "movl %%edx, %0 \n" \
+ "popl %%ebx \n" \
+ : "=r"(result) : : "eax", "ecx", "edx");
+
+ if (result & 0x80000000)
+ return true;
+
+ return false;
+}
+
+
+static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) {
+ __asm__ __volatile__ (
+ "pushl %1 \n" \
+ "pushl %2 \n" \
+ "movss 0(%0), %%xmm1 \n" \
+ "movups 0(%1), %%xmm2 \n" \
+ "movlps 0(%2), %%xmm3 \n" \
+ " \n" \
+ "shufps $0x44, %%xmm3, %%xmm3 \n" \
+ " \n" \
+ "mulps %%xmm3, %%xmm2 \n" \
+ " \n" \
+ "subss %%xmm2, %%xmm1 \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "subss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "movss %%xmm1, 0(%2) \n" \
+ " \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "addss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "addss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "movss %%xmm3, 4(%2) \n" \
+ " \n" \
+ "addl $16, %1 \n" \
+ "addl $8, %2 \n" \
+ " \n" \
+ "movups 0(%1), %%xmm2 \n" \
+ " \n" \
+ "movlps 0(%2), %%xmm3 \n" \
+ "shufps $0x44, %%xmm3, %%xmm3 \n" \
+ " \n" \
+ "mulps %%xmm3, %%xmm2 \n" \
+ " \n" \
+ "subss %%xmm2, %%xmm1 \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "subss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "movss %%xmm1, 0(%2) \n" \
+ " \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "addss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "shufps $0x39, %%xmm2, %%xmm2 \n" \
+ "addss %%xmm2, %%xmm1 \n" \
+ " \n" \
+ "movss %%xmm3, 4(%2) \n" \
+ "movss %%xmm1, 0(%0) \n" \
+ "popl %2 \n" \
+ "popl %1 \n" \
+ : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr)
+ : "memory"
+#ifdef __SSE__
+ , "xmm1", "xmm2", "xmm3"
+#endif
+ );
+
+ return *output;
+}
+
+static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) {
+ float tmp;
+
+ __asm__ __volatile__ (
+ "movq %0, %%mm1 \n" \
+ " \n" \
+ "movl %1, %%edi \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ " \n" \
+ "movl %2, %%eax; \n" \
+ "movq 0(%%eax), %%mm3 \n" \
+ " \n" \
+ "pfmul %%mm3, %%mm2 \n" \
+ "pfsub %%mm2, %%mm1 \n" \
+ " \n" \
+ "psrlq $32, %%mm2 \n" \
+ "pfsub %%mm2, %%mm1 \n" \
+ " \n" \
+ "movd %%mm1, %3 \n" \
+ " \n" \
+ "addl $8, %%edi \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "movq 0(%%eax), %%mm3 \n" \
+ " \n" \
+ "pfmul %%mm3, %%mm2 \n" \
+ "pfadd %%mm2, %%mm1 \n" \
+ " \n" \
+ "psrlq $32, %%mm2 \n" \
+ "pfadd %%mm2, %%mm1 \n" \
+ " \n" \
+ "pushl %3 \n" \
+ "popl 0(%%eax) \n" \
+ " \n" \
+ "movd %%mm3, 4(%%eax) \n" \
+ " \n" \
+ "addl $8, %%edi \n" \
+ "addl $8, %%eax \n" \
+ " \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "movq 0(%%eax), %%mm3 \n" \
+ " \n" \
+ "pfmul %%mm3, %%mm2 \n" \
+ "pfsub %%mm2, %%mm1 \n" \
+ " \n" \
+ "psrlq $32, %%mm2 \n" \
+ "pfsub %%mm2, %%mm1 \n" \
+ " \n" \
+ "movd %%mm1, %3 \n" \
+ " \n" \
+ "addl $8, %%edi \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "movq 0(%%eax), %%mm3 \n" \
+ " \n" \
+ "pfmul %%mm3, %%mm2 \n" \
+ "pfadd %%mm2, %%mm1 \n" \
+ " \n" \
+ "psrlq $32, %%mm2 \n" \
+ "pfadd %%mm2, %%mm1 \n" \
+ " \n" \
+ "pushl %3 \n" \
+ "popl 0(%%eax) \n" \
+ "movd %%mm3, 4(%%eax) \n" \
+ " \n" \
+ "movd %%mm1, %0 \n" \
+ "femms \n" \
+ : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp)
+ : "eax", "edi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2", "mm3"
+#endif
+ );
+
+ return output;
+}
+
+static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) {
+ __asm__ __volatile__(
+ "movl %0, %%ecx \n" \
+ "movw %1, %%ax \n" \
+ "shll $16, %%eax \n" \
+ "movw %1, %%ax \n" \
+ "movd %%eax, %%mm3 \n" \
+ "movd %%eax, %%mm2 \n" \
+ "psllq $32, %%mm3 \n" \
+ "por %%mm2, %%mm3 \n" \
+ "movl %2, %%esi \n" \
+ "movl %3, %%edi \n" \
+ "1: \n" \
+ "movq 0(%%esi), %%mm1 \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "pmulhw %%mm3, %%mm1 \n" \
+ "paddw %%mm2, %%mm1 \n" \
+ "movq %%mm1, 0(%%edi) \n" \
+ " \n" \
+ "addl $8, %%esi \n" \
+ "addl $8, %%edi \n" \
+ " \n" \
+ "decl %%ecx \n" \
+ "cmpl $0, %%ecx \n" \
+ "jg 1b \n" \
+ "emms \n" \
+ : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd)
+ : "eax", "ecx", "edi", "esi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2", "mm3"
+#endif
+ );
+}
+
+static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) {
+ __asm__ __volatile__(
+ "movl %4, %%ecx \n" \
+ "shrl $1, %%ecx \n" \
+ "addl $4, %%ecx \n" \
+ "pushl %%ecx \n" \
+ " \n" \
+ "movl %0, %%esi \n" \
+ "movups 0(%%esi), %%xmm1 \n" \
+ " \n" \
+ "movl %1, %%esi \n" \
+ "movl %2, %%edi \n" \
+ "1: \n" \
+ "xorl %%eax, %%eax \n" \
+ "movw 0(%1), %%ax \n" \
+ "cwde \n" \
+ "incl %1 \n" \
+ "incl %1 \n" \
+ "movd %%eax, %%mm1 \n" \
+ "psrlq $32, %%mm1 \n" \
+ "movw 0(%1), %%ax \n" \
+ "incl %1 \n" \
+ "incl %1 \n" \
+ "movd %%eax, %%mm2 \n" \
+ "por %%mm2, %%mm1 \n" \
+ " \n" \
+ "decl %%ecx \n" \
+ "jnz 1b \n" \
+ " \n" \
+ "popl %%ecx \n" \
+ "movl %1, %%esi \n" \
+ "movl %3, %%edi \n" \
+ "incl %%esi \n" \
+ "2: \n" \
+ "decl %%ecx \n" \
+ "jnz 2b \n" \
+ : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len)
+ : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory");
+}
+
+static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) {
+ __asm__ __volatile__(
+ "movl %0, %%ecx \n" \
+ "movl %1, %%esi \n" \
+ "movl %2, %%edi \n" \
+ "1: \n" \
+ "movq 0(%%edi), %%mm1 \n" \
+ "movq 0(%%esi), %%mm2 \n" \
+ "paddw %%mm2, %%mm1 \n" \
+ "movq %%mm1, 0(%%esi) \n" \
+ "addl $8, %%edi \n" \
+ "addl $8, %%esi \n" \
+ "decl %%ecx \n" \
+ "cmpl $0, %%ecx \n" \
+ "jg 1b \n" \
+ "emms \n" \
+ : : "g"(len), "g"(buf1), "g"(buf2)
+ : "ecx", "edi", "esi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2"
+#endif
+ );
+}
+
+static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) {
+ __asm__ __volatile__(
+ "movl %0, %%ecx \n" \
+ "movl %1, %%esi \n" \
+ "movl %2, %%edi \n" \
+ "1: \n" \
+ "movq 0(%%esi), %%mm1 \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "movq %%mm1, %%mm3 \n" \
+ "pmulhw %%mm2, %%mm1 \n" \
+ "paddw %%mm3, %%mm1 \n" \
+ "movq %%mm1, 0(%%esi) \n" \
+ "addl $8, %%edi \n" \
+ "addl $8, %%esi \n" \
+ "decl %%ecx \n" \
+ "cmpl $0, %%ecx \n" \
+ "jg 1b \n" \
+ "emms \n" \
+ : : "g"(len), "g"(buf1), "g"(buf2)
+ : "ecx", "edi", "esi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2", "mm3"
+#endif
+ );
+}
+
+static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) {
+ __asm__ __volatile__(
+ "movl %0, %%ecx \n" \
+ "movl %1, %%esi \n" \
+ "movl %2, %%edi \n" \
+ "1: \n" \
+ "movq 0(%%esi), %%mm1 \n" \
+ "movq 0(%%edi), %%mm2 \n" \
+ "pmulhw %%mm2, %%mm1 \n" \
+ "movq %%mm1, 0(%%esi) \n" \
+ "addl $8, %%edi \n" \
+ "addl $8, %%esi \n" \
+ "decl %%ecx \n" \
+ "cmpl $0, %%ecx \n" \
+ "jg 1b \n" \
+ "emms \n" \
+ : : "g"(len), "g"(buf1), "g"(buf2)
+ : "ecx", "edi", "esi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2"
+#endif
+ );
+}
+
+static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) {
+ __asm__ __volatile__(
+ "movl %0, %%ecx \n" \
+ "movw %1, %%ax \n" \
+ "shll $16, %%eax \n" \
+ "movw %2, %%ax \n" \
+ "movd %%eax, %%mm1 \n" \
+ "movd %%eax, %%mm2 \n" \
+ "psllq $32, %%mm1 \n" \
+ "por %%mm2, %%mm1 \n" \
+ "movl %3, %%edi \n" \
+ "movl %4, %%esi \n" \
+ "pushl %%ebx \n" \
+ "1: \n" \
+ "movw 0(%%esi), %%bx \n" \
+ "addl $2, %%esi \n" \
+ "movw 0(%%esi), %%dx \n" \
+ "addl $2, %%esi \n" \
+ "" \
+ "movw %%dx, %%ax \n" \
+ "shll $16, %%eax \n" \
+ "movw %%dx, %%ax \n" \
+ "movd %%eax, %%mm2 \n" \
+ "psllq $32, %%mm2 \n" \
+ "movw %%bx, %%ax \n" \
+ "shll $16, %%eax \n" \
+ "movw %%bx, %%ax \n" \
+ "movd %%eax, %%mm3 \n" \
+ "por %%mm3, %%mm2 \n" \
+ "" \
+ "pmulhw %%mm1, %%mm2 \n" \
+ "movq %%mm2, 0(%%edi) \n" \
+ "addl $8, %%edi \n" \
+ "" \
+ "decl %%ecx \n" \
+ "cmpl $0, %%ecx \n" \
+ "jg 1b \n" \
+ "emms \n" \
+ "popl %%ebx \n" \
+ : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf)
+ : "eax", "ecx", "edx", "edi", "esi", "memory"
+#ifdef __MMX__
+ , "mm1", "mm2", "mm3"
+#endif
+ );
+}
+
+#endif
+
+bool DetectSIMD() {
+#ifdef _MSC_VER
+ bool found_simd;
+ __asm {
+ pushfd
+ pop eax // get EFLAGS into eax
+ mov ebx,eax // keep a copy
+ xor eax,0x200000
+ // toggle CPUID bit
+
+ push eax
+ popfd // set new EFLAGS
+ pushfd
+ pop eax // EFLAGS back into eax
+
+ xor eax,ebx
+ // have we changed the ID bit?
+
+ je NO_SIMD
+ // No, no CPUID instruction
+
+ // we could toggle the
+ // ID bit so CPUID is present
+ mov eax,1
+
+ cpuid // get processor features
+ test edx,1<<25 // check the SIMD bit
+ jz NO_SIMD
+ mov found_simd,1
+ jmp DONE
+ NO_SIMD:
+ mov found_simd,0
+ DONE:
+ }
+ return found_simd;
+#else
+ return atti386_DetectSIMD();
+#endif
+}
+
+bool Detect3DNow() {
+#ifdef _MSC_VER
+ bool found3D = false;
+ __asm {
+ pushfd
+ pop eax
+ mov edx, eax
+ xor eax, 00200000h
+ push eax
+ popfd
+ pushfd
+ pop eax
+ xor eax, edx
+ jz NO_3DNOW
+
+ mov eax, 80000000h
+ cpuid
+
+ cmp eax, 80000000h
+ jbe NO_3DNOW
+
+ mov eax, 80000001h
+ cpuid
+ test edx, 80000000h
+ jz NO_3DNOW
+ mov found3D, 1
+NO_3DNOW:
+
+ }
+ return found3D;
+#else
+ return atti386_Detect3DNow();
+#endif
+}
+
+float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) {
+ float output;
+
+ // 1st number of coefficients array is overall input scale factor, or filter gain
+ output = input * (*coef_ptr++);
+
+#ifdef _MSC_VER
+ __asm {
+
+ movss xmm1, output
+
+ mov eax, coef_ptr
+ movups xmm2, [eax]
+
+ mov eax, hist1_ptr
+ movlps xmm3, [eax]
+ shufps xmm3, xmm3, 44h
+ // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr
+
+ mulps xmm2, xmm3
+
+ subss xmm1, xmm2
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ subss xmm1, xmm2
+
+ // Store new_hist
+ movss DWORD PTR [eax], xmm1
+
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ addss xmm1, xmm2
+
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ addss xmm1, xmm2
+
+ // Store previous hist
+ movss DWORD PTR [eax+4], xmm3
+
+ add coef_ptr, 16
+ add hist1_ptr, 8
+
+ mov eax, coef_ptr
+ movups xmm2, [eax]
+
+ mov eax, hist1_ptr
+ movlps xmm3, [eax]
+ shufps xmm3, xmm3, 44h
+ // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr
+
+ mulps xmm2, xmm3
+
+ subss xmm1, xmm2
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ subss xmm1, xmm2
+
+ // Store new_hist
+ movss DWORD PTR [eax], xmm1
+
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ addss xmm1, xmm2
+
+ // Rotate elements right
+ shufps xmm2, xmm2, 39h
+ addss xmm1, xmm2
+
+ // Store previous hist
+ movss DWORD PTR [eax+4], xmm3
+
+ movss output, xmm1
+ }
+#else
+ output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr);
+#endif
+ return output;
+}
+
+float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) {
+ float output;
+
+ // 1st number of coefficients array is overall input scale factor, or filter gain
+ output = input * (*coef_ptr++);
+
+ // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE
+ // Intel does have the upper hand here.
+#ifdef _MSC_VER
+ float tmp;
+ __asm {
+ movq mm1, output
+ mov ebx, coef_ptr
+ movq mm2, [ebx]
+
+ mov eax, hist1_ptr;
+ movq mm3, [eax]
+
+ pfmul mm2, mm3
+ pfsub mm1, mm2
+
+ psrlq mm2, 32
+ pfsub mm1, mm2
+
+ // Store new hist
+ movd tmp, mm1
+
+ add ebx, 8
+ movq mm2, [ebx]
+ movq mm3, [eax]
+
+ pfmul mm2, mm3
+ pfadd mm1, mm2
+
+ psrlq mm2, 32
+ pfadd mm1, mm2
+
+ push tmp
+ pop DWORD PTR [eax]
+
+ movd DWORD PTR [eax+4], mm3
+
+ add ebx, 8
+ add eax, 8
+
+ movq mm2, [ebx]
+ movq mm3, [eax]
+
+ pfmul mm2, mm3
+ pfsub mm1, mm2
+
+ psrlq mm2, 32
+ pfsub mm1, mm2
+
+ // Store new hist
+ movd tmp, mm1
+
+ add ebx, 8
+ movq mm2, [ebx]
+ movq mm3, [eax]
+
+ pfmul mm2, mm3
+ pfadd mm1, mm2
+
+ psrlq mm2, 32
+ pfadd mm1, mm2
+
+ push tmp
+ pop DWORD PTR [eax]
+ movd DWORD PTR [eax+4], mm3
+
+ movd output, mm1
+
+ femms
+ }
+#else
+ output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr);
+#endif
+ return output;
+}
+
+#if MT32EMU_USE_MMX > 0
+
+int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) {
+ int tmplen = len >> 1;
+ if (tmplen == 0) {
+ return 0;
+ }
+#ifdef _MSC_VER
+ __asm {
+ mov ecx,tmplen
+ mov ax, leftvol
+ shl eax,16
+ mov ax, rightvol
+ movd mm1, eax
+ movd mm2, eax
+ psllq mm1, 32
+ por mm1, mm2
+ mov edi, partialBuf
+ mov esi, mixedBuf
+mmxloop1:
+ mov bx, [esi]
+ add esi,2
+ mov dx, [esi]
+ add esi,2
+
+ mov ax, dx
+ shl eax, 16
+ mov ax, dx
+ movd mm2,eax
+ psllq mm2, 32
+ mov ax, bx
+ shl eax, 16
+ mov ax, bx
+ movd mm3,eax
+ por mm2,mm3
+
+ pmulhw mm2, mm1
+ movq [edi], mm2
+ add edi, 8
+
+ dec ecx
+ cmp ecx,0
+ jg mmxloop1
+ emms
+ }
+#else
+ atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf);
+#endif
+ return tmplen << 1;
+}
+
+int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) {
+ int tmplen = len >> 2;
+ if (tmplen == 0) {
+ return 0;
+ }
+#ifdef _MSC_VER
+ __asm {
+ mov ecx, tmplen
+ mov esi, buf1
+ mov edi, buf2
+
+mixloop1:
+ movq mm1, [edi]
+ movq mm2, [esi]
+ paddw mm1,mm2
+ movq [esi],mm1
+ add edi,8
+ add esi,8
+
+ dec ecx
+ cmp ecx,0
+ jg mixloop1
+ emms
+ }
+#else
+ atti386_mixBuffers(buf1, buf2, tmplen);
+#endif
+ return tmplen << 2;
+}
+
+
+int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) {
+ int tmplen = len >> 2;
+ if (tmplen == 0) {
+ return 0;
+ }
+#ifdef _MSC_VER
+ __asm {
+ mov ecx, tmplen
+ mov esi, buf1
+ mov edi, buf2
+
+mixloop2:
+ movq mm1, [esi]
+ movq mm2, [edi]
+ movq mm3, mm1
+ pmulhw mm1, mm2
+ paddw mm1,mm3
+ movq [esi],mm1
+ add edi,8
+ add esi,8
+
+ dec ecx
+ cmp ecx,0
+ jg mixloop2
+ emms
+ }
+#else
+ atti386_mixBuffersRingMix(buf1, buf2, tmplen);
+#endif
+ return tmplen << 2;
+}
+
+int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) {
+ int tmplen = len >> 2;
+ if (tmplen == 0) {
+ return 0;
+ }
+#ifdef _MSC_VER
+ __asm {
+ mov ecx, tmplen
+ mov esi, buf1
+ mov edi, buf2
+
+mixloop3:
+ movq mm1, [esi]
+ movq mm2, [edi]
+ pmulhw mm1, mm2
+ movq [esi],mm1
+ add edi,8
+ add esi,8
+
+ dec ecx
+ cmp ecx,0
+ jg mixloop3
+ emms
+ }
+#else
+ atti386_mixBuffersRing(buf1, buf2, tmplen);
+#endif
+ return tmplen << 2;
+}
+
+int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) {
+ int tmplen = (len >> 1);
+ if (tmplen == 0) {
+ return 0;
+ }
+#ifdef _MSC_VER
+ __asm {
+ mov ecx, tmplen
+ mov ax,volume
+ shl eax,16
+ mov ax,volume
+ movd mm3,eax
+ movd mm2,eax
+ psllq mm3, 32
+ por mm3,mm2
+ mov esi, useBuf
+ mov edi, stream
+mixloop4:
+ movq mm1, [esi]
+ movq mm2, [edi]
+ pmulhw mm1, mm3
+ paddw mm1,mm2
+ movq [edi], mm1
+
+ add esi,8
+ add edi,8
+
+ dec ecx
+ cmp ecx,0
+ jg mixloop4
+ emms
+ }
+#else
+ atti386_produceOutput1(tmplen, volume, useBuf, stream);
+#endif
+ return tmplen << 1;
+}
+
+#endif
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/i386.h b/audio/softsynth/mt32/i386.h
new file mode 100644
index 0000000000..e8644411cd
--- /dev/null
+++ b/audio/softsynth/mt32/i386.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_I386_H
+#define MT32EMU_I386_H
+
+namespace MT32Emu {
+#ifdef MT32EMU_HAVE_X86
+
+// Function that detects the availablity of SSE SIMD instructions
+bool DetectSIMD();
+// Function that detects the availablity of 3DNow instructions
+bool Detect3DNow();
+
+float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr);
+float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr);
+float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr);
+
+#if MT32EMU_USE_MMX > 0
+int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf);
+int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len);
+int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len);
+int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len);
+int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume);
+#endif
+
+#endif
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk
new file mode 100644
index 0000000000..a8329bc98c
--- /dev/null
+++ b/audio/softsynth/mt32/module.mk
@@ -0,0 +1,14 @@
+MODULE := audio/softsynth/mt32
+
+MODULE_OBJS := \
+ mt32_file.o \
+ i386.o \
+ part.o \
+ partial.o \
+ partialManager.o \
+ synth.o \
+ tables.o \
+ freeverb.o
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/audio/softsynth/mt32/mt32_file.cpp b/audio/softsynth/mt32/mt32_file.cpp
new file mode 100644
index 0000000000..cdf9fa13f6
--- /dev/null
+++ b/audio/softsynth/mt32/mt32_file.cpp
@@ -0,0 +1,70 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+
+#include "mt32emu.h"
+
+namespace MT32Emu {
+
+bool File::readBit16u(Bit16u *in) {
+ Bit8u b[2];
+ if (read(&b[0], 2) != 2)
+ return false;
+ *in = ((b[0] << 8) | b[1]);
+ return true;
+}
+
+bool File::readBit32u(Bit32u *in) {
+ Bit8u b[4];
+ if (read(&b[0], 4) != 4)
+ return false;
+ *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
+ return true;
+}
+
+bool File::writeBit16u(Bit16u out) {
+ if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) {
+ return false;
+ }
+ if (!writeBit8u((Bit8u)(out & 0x00FF))) {
+ return false;
+ }
+ return true;
+}
+
+bool File::writeBit32u(Bit32u out) {
+ if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) {
+ return false;
+ }
+ if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) {
+ return false;
+ }
+ if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) {
+ return false;
+ }
+ if (!writeBit8u((Bit8u)(out & 0x000000FF))) {
+ return false;
+ }
+ return true;
+}
+
+} // End of namespace MT32Emu
+
diff --git a/audio/softsynth/mt32/mt32_file.h b/audio/softsynth/mt32/mt32_file.h
new file mode 100644
index 0000000000..e6641660ee
--- /dev/null
+++ b/audio/softsynth/mt32/mt32_file.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_FILE_H
+#define MT32EMU_FILE_H
+
+#include "common/scummsys.h"
+
+namespace MT32Emu {
+
+class File {
+public:
+ enum OpenMode {
+ OpenMode_read = 0,
+ OpenMode_write = 1
+ };
+ virtual ~File() {}
+ virtual void close() = 0;
+ virtual size_t read(void *in, size_t size) = 0;
+ virtual bool readBit8u(Bit8u *in) = 0;
+ virtual bool readBit16u(Bit16u *in);
+ virtual bool readBit32u(Bit32u *in);
+ virtual size_t write(const void *out, size_t size) = 0;
+ virtual bool writeBit8u(Bit8u out) = 0;
+ // Note: May write a single byte to the file before failing
+ virtual bool writeBit16u(Bit16u out);
+ // Note: May write some (<4) bytes to the file before failing
+ virtual bool writeBit32u(Bit32u out);
+ virtual bool isEOF() = 0;
+};
+
+} // End of namespace MT32Emu
+
+#endif
diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h
new file mode 100644
index 0000000000..6eedf04bc0
--- /dev/null
+++ b/audio/softsynth/mt32/mt32emu.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_MT32EMU_H
+#define MT32EMU_MT32EMU_H
+
+// Debugging
+// Show the instruments played
+#define MT32EMU_MONITOR_INSTRUMENTS 1
+// Shows number of partials MT-32 is playing, and on which parts
+#define MT32EMU_MONITOR_PARTIALS 0
+// Determines how the waveform cache file is handled (must be regenerated after sampling rate change)
+#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache
+//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generate but don't save cache
+//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache
+//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache
+
+// Configuration
+// The maximum number of partials playing simultaneously
+#define MT32EMU_MAX_PARTIALS 32
+// The maximum number of notes playing simultaneously per part.
+// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial.
+#define MT32EMU_MAX_POLY 32
+// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow.
+#define MT32EMU_ACCURATENOTES 0
+
+#if (defined (_MSC_VER) && defined(_M_IX86))
+#define MT32EMU_HAVE_X86
+#elif defined(__GNUC__)
+#if __GNUC__ >= 3 && defined(__i386__)
+#define MT32EMU_HAVE_X86
+#endif
+#endif
+
+#ifdef MT32EMU_HAVE_X86
+#define MT32EMU_USE_MMX 1
+#else
+#define MT32EMU_USE_MMX 0
+#endif
+
+#include "freeverb.h"
+
+#include "structures.h"
+#include "i386.h"
+#include "mt32_file.h"
+#include "tables.h"
+#include "partial.h"
+#include "partialManager.h"
+#include "part.h"
+#include "synth.h"
+
+#endif
diff --git a/audio/softsynth/mt32/part.cpp b/audio/softsynth/mt32/part.cpp
new file mode 100644
index 0000000000..eb087f7ea0
--- /dev/null
+++ b/audio/softsynth/mt32/part.cpp
@@ -0,0 +1,633 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <math.h>
+
+#include "mt32emu.h"
+
+namespace MT32Emu {
+
+static const Bit8u PartialStruct[13] = {
+ 0, 0, 2, 2, 1, 3,
+ 3, 0, 3, 0, 2, 1, 3 };
+
+static const Bit8u PartialMixStruct[13] = {
+ 0, 1, 0, 1, 1, 0,
+ 1, 3, 3, 2, 2, 2, 2 };
+
+static const float floatKeyfollow[17] = {
+ -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f,
+ 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f,
+ 5.0f/4.0f, 3.0f/2.0f, 2.0f,
+ 1.0009765625f, 1.0048828125f
+};
+
+//FIXME:KG: Put this dpoly stuff somewhere better
+bool dpoly::isActive() const {
+ return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL;
+}
+
+Bit32u dpoly::getAge() const {
+ for (int i = 0; i < 4; i++) {
+ if (partials[i] != NULL) {
+ return partials[i]->age;
+ }
+ }
+ return 0;
+}
+
+RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
+ strcpy(name, "Rhythm");
+ rhythmTemp = &synth->mt32ram.rhythmSettings[0];
+ refresh();
+}
+
+Part::Part(Synth *useSynth, unsigned int usePartNum) {
+ this->synth = useSynth;
+ this->partNum = usePartNum;
+ patchCache[0].dirty = true;
+ holdpedal = false;
+ patchTemp = &synth->mt32ram.patchSettings[partNum];
+ if (usePartNum == 8) {
+ // Nasty hack for rhythm
+ timbreTemp = NULL;
+ } else {
+ sprintf(name, "Part %d", partNum + 1);
+ timbreTemp = &synth->mt32ram.timbreSettings[partNum];
+ }
+ currentInstr[0] = 0;
+ currentInstr[10] = 0;
+ expression = 127;
+ volumeMult = 0;
+ volumesetting.leftvol = 32767;
+ volumesetting.rightvol = 32767;
+ bend = 0.0f;
+ memset(polyTable,0,sizeof(polyTable));
+ memset(patchCache, 0, sizeof(patchCache));
+}
+
+void Part::setHoldPedal(bool pedalval) {
+ if (holdpedal && !pedalval) {
+ holdpedal = false;
+ stopPedalHold();
+ } else {
+ holdpedal = pedalval;
+ }
+}
+
+void RhythmPart::setBend(unsigned int midiBend) {
+ synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend);
+ return;
+}
+
+void Part::setBend(unsigned int midiBend) {
+ // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0
+ if (midiBend <= 0x2000) {
+ bend = ((signed int)midiBend - 0x2000) / (float)0x2000;
+ } else {
+ bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF;
+ }
+ // Loop through all partials to update their bend
+ for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
+ for (int j = 0; j < 4; j++) {
+ if (polyTable[i].partials[j] != NULL) {
+ polyTable[i].partials[j]->setBend(bend);
+ }
+ }
+ }
+}
+
+void RhythmPart::setModulation(unsigned int midiModulation) {
+ synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation);
+}
+
+void Part::setModulation(unsigned int midiModulation) {
+ // Just a bloody guess, as always, before I get things figured out
+ for (int t = 0; t < 4; t++) {
+ if (patchCache[t].playPartial) {
+ int newrate = (patchCache[t].modsense * midiModulation) >> 7;
+ //patchCache[t].lfoperiod = lfotable[newrate];
+ patchCache[t].lfodepth = newrate;
+ //FIXME:KG: timbreTemp->partial[t].lfo.depth =
+ }
+ }
+}
+
+void RhythmPart::refresh() {
+ updateVolume();
+ // (Re-)cache all the mapped timbres ahead of time
+ for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) {
+ int drumTimbreNum = rhythmTemp[drumNum].timbre;
+ if (drumTimbreNum >= 127) // 94 on MT-32
+ continue;
+ Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14...
+ // FIXME:KG: Panning cache should be backed up to partials using it, too
+ if (pan < 7) {
+ drumPan[drumNum].leftvol = pan * 4681;
+ drumPan[drumNum].rightvol = 32767;
+ } else {
+ drumPan[drumNum].rightvol = (14 - pan) * 4681;
+ drumPan[drumNum].leftvol = 32767;
+ }
+ PatchCache *cache = drumCache[drumNum];
+ backupCacheToPartials(cache);
+ for (int t = 0; t < 4; t++) {
+ // Common parameters, stored redundantly
+ cache[t].dirty = true;
+ cache[t].pitchShift = 0.0f;
+ cache[t].benderRange = 0.0f;
+ cache[t].pansetptr = &drumPan[drumNum];
+ cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
+ }
+ }
+}
+
+void Part::refresh() {
+ updateVolume();
+ backupCacheToPartials(patchCache);
+ for (int t = 0; t < 4; t++) {
+ // Common parameters, stored redundantly
+ patchCache[t].dirty = true;
+ patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f;
+ patchCache[t].benderRange = patchTemp->patch.benderRange;
+ patchCache[t].pansetptr = &volumesetting;
+ patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
+ }
+ memcpy(currentInstr, timbreTemp->common.name, 10);
+}
+
+const char *Part::getCurrentInstr() const {
+ return &currentInstr[0];
+}
+
+void RhythmPart::refreshTimbre(unsigned int absTimbreNum) {
+ for (int m = 0; m < 85; m++) {
+ if (rhythmTemp[m].timbre == absTimbreNum - 128)
+ drumCache[m][0].dirty = true;
+ }
+}
+
+void Part::refreshTimbre(unsigned int absTimbreNum) {
+ if (getAbsTimbreNum() == absTimbreNum) {
+ memcpy(currentInstr, timbreTemp->common.name, 10);
+ patchCache[0].dirty = true;
+ }
+}
+
+int Part::fixBiaslevel(int srcpnt, int *dir) {
+ int noteat = srcpnt & 0x3F;
+ int outnote;
+ if (srcpnt < 64)
+ *dir = 0;
+ else
+ *dir = 1;
+ outnote = 33 + noteat;
+ //synth->printDebug("Bias note %d, dir %d", outnote, *dir);
+
+ return outnote;
+}
+
+int Part::fixKeyfollow(int srckey) {
+ if (srckey>=0 && srckey<=16) {
+ int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116};
+ return keyfix[srckey];
+ } else {
+ //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey);
+ return 256;
+ }
+}
+
+void Part::abortPoly(dpoly *poly) {
+ if (!poly->isPlaying) {
+ return;
+ }
+ for (int i = 0; i < 4; i++) {
+ Partial *partial = poly->partials[i];
+ if (partial != NULL) {
+ partial->deactivate();
+ }
+ }
+ poly->isPlaying = false;
+}
+
+void Part::setPatch(const PatchParam *patch) {
+ patchTemp->patch = *patch;
+}
+
+void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
+ synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name);
+}
+
+void Part::setTimbre(TimbreParam *timbre) {
+ *timbreTemp = *timbre;
+}
+
+unsigned int RhythmPart::getAbsTimbreNum() const {
+ synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name);
+ return 0;
+}
+
+unsigned int Part::getAbsTimbreNum() const {
+ return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum;
+}
+
+void RhythmPart::setProgram(unsigned int patchNum) {
+ synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
+}
+
+void Part::setProgram(unsigned int patchNum) {
+ setPatch(&synth->mt32ram.patches[patchNum]);
+ setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
+
+ refresh();
+
+ allSoundOff(); //FIXME:KG: Is this correct?
+}
+
+void Part::backupCacheToPartials(PatchCache cache[4]) {
+ // check if any partials are still playing with the old patch cache
+ // if so then duplicate the cached data from the part to the partial so that
+ // we can change the part's cache without affecting the partial.
+ // We delay this until now to avoid a copy operation with every note played
+ for (int m = 0; m < MT32EMU_MAX_POLY; m++) {
+ for (int i = 0; i < 4; i++) {
+ Partial *partial = polyTable[m].partials[i];
+ if (partial != NULL && partial->patchCache == &cache[i]) {
+ partial->cachebackup = cache[i];
+ partial->patchCache = &partial->cachebackup;
+ }
+ }
+ }
+}
+
+void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
+ backupCacheToPartials(cache);
+ int partialCount = 0;
+ for (int t = 0; t < 4; t++) {
+ cache[t].PCMPartial = false;
+ if (((timbre->common.pmute >> t) & 0x1) == 1) {
+ cache[t].playPartial = true;
+ partialCount++;
+ } else {
+ cache[t].playPartial = false;
+ continue;
+ }
+
+ // Calculate and cache common parameters
+
+ cache[t].pcm = timbre->partial[t].wg.pcmwave;
+ cache[t].useBender = (timbre->partial[t].wg.bender == 1);
+
+ switch (t) {
+ case 0:
+ cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false;
+ cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12];
+ cache[t].structurePosition = 0;
+ cache[t].structurePair = 1;
+ break;
+ case 1:
+ cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false;
+ cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12];
+ cache[t].structurePosition = 1;
+ cache[t].structurePair = 0;
+ break;
+ case 2:
+ cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false;
+ cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34];
+ cache[t].structurePosition = 0;
+ cache[t].structurePair = 3;
+ break;
+ case 3:
+ cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false;
+ cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34];
+ cache[t].structurePosition = 1;
+ cache[t].structurePair = 2;
+ break;
+ default:
+ break;
+ }
+
+ cache[t].waveform = timbre->partial[t].wg.waveform;
+ cache[t].pulsewidth = timbre->partial[t].wg.pulsewid;
+ cache[t].pwsens = timbre->partial[t].wg.pwvelo;
+ if (timbre->partial[t].wg.keyfollow > 16) {
+ synth->printDebug("Bad keyfollow value in timbre!");
+ cache[t].pitchKeyfollow = 1.0f;
+ } else {
+ cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow];
+ }
+
+ cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f;
+ cache[t].pitchEnv = timbre->partial[t].env;
+ cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f);
+ cache[t].pitchsustain = cache[t].pitchEnv.level[3];
+
+ // Calculate and cache TVA envelope stuff
+ cache[t].ampEnv = timbre->partial[t].tva;
+ cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f);
+
+ cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]);
+ cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1;
+ cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]);
+ cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2;
+ cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf;
+
+ // Calculate and cache filter stuff
+ cache[t].filtEnv = timbre->partial[t].tvf;
+ cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow);
+ cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27);
+ cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir);
+ cache[t].tvfblevel = cache[t].filtEnv.biaslevel;
+ cache[t].filtsustain = cache[t].filtEnv.envlevel[3];
+
+ // Calculate and cache LFO stuff
+ cache[t].lfodepth = timbre->partial[t].lfo.depth;
+ cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate];
+ cache[t].lforate = timbre->partial[t].lfo.rate;
+ cache[t].modsense = timbre->partial[t].lfo.modsense;
+ }
+ for (int t = 0; t < 4; t++) {
+ // Common parameters, stored redundantly
+ cache[t].dirty = false;
+ cache[t].partialCount = partialCount;
+ cache[t].sustain = (timbre->common.nosustain == 0);
+ }
+ //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform);
+
+#if MT32EMU_MONITOR_INSTRUMENTS == 1
+ synth->printDebug("%s (%s): Recached timbre", name, currentInstr);
+ for (int i = 0; i < 4; i++) {
+ synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform);
+ }
+#endif
+}
+
+const char *Part::getName() const {
+ return name;
+}
+
+void Part::updateVolume() {
+ volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127];
+}
+
+int Part::getVolume() const {
+ // FIXME: Use the mappings for this in the control ROM
+ return patchTemp->outlevel * 127 / 100;
+}
+
+void Part::setVolume(int midiVolume) {
+ // FIXME: Use the mappings for this in the control ROM
+ patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127);
+ updateVolume();
+ synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
+}
+
+void Part::setExpression(int midiExpression) {
+ expression = midiExpression;
+ updateVolume();
+}
+
+void RhythmPart::setPan(unsigned int midiPan)
+{
+ // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct?
+ synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan);
+}
+
+void Part::setPan(unsigned int midiPan) {
+ // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100%
+ // (But this makes the range somewhat skewed)
+ // Check against the real thing
+ // NOTE: Panning is inverted compared to GM.
+ if (midiPan < 64) {
+ volumesetting.leftvol = (Bit16s)(midiPan * 512);
+ volumesetting.rightvol = 32767;
+ } else if (midiPan == 64) {
+ volumesetting.leftvol = 32767;
+ volumesetting.rightvol = 32767;
+ } else {
+ volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520);
+ volumesetting.leftvol = 32767;
+ }
+ patchTemp->panpot = (Bit8u)(midiPan * 14 / 127);
+ //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
+}
+
+void RhythmPart::playNote(unsigned int key, int vel) {
+ if (key < 24 || key > 108)/*> 87 on MT-32)*/ {
+ synth->printDebug("%s: Attempted to play invalid key %d", name, key);
+ return;
+ }
+ int drumNum = key - 24;
+ int drumTimbreNum = rhythmTemp[drumNum].timbre;
+ if (drumTimbreNum >= 127) { // 94 on MT-32
+ synth->printDebug("%s: Attempted to play unmapped key %d", name, key);
+ return;
+ }
+ int absTimbreNum = drumTimbreNum + 128;
+ TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
+ memcpy(currentInstr, timbre->common.name, 10);
+#if MT32EMU_MONITOR_INSTRUMENTS == 1
+ synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key);
+#endif
+ if (drumCache[drumNum][0].dirty) {
+ cacheTimbre(drumCache[drumNum], timbre);
+ }
+ playPoly(drumCache[drumNum], key, MIDDLEC, vel);
+}
+
+void Part::playNote(unsigned int key, int vel) {
+ int freqNum = key;
+ if (freqNum < 12) {
+ synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key);
+ freqNum += 12;
+ } else if (freqNum > 108) {
+ synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key);
+ while (freqNum > 108) {
+ freqNum -= 12;
+ }
+ }
+ // POLY1 mode, Single Assign
+ // Haven't found any software that uses any of the other poly modes
+ // FIXME:KG: Should this also apply to rhythm?
+ for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) {
+ if (polyTable[i].isActive() && (polyTable[i].key == key)) {
+ //AbortPoly(&polyTable[i]);
+ stopNote(key);
+ break;
+ }
+ }
+#if MT32EMU_MONITOR_INSTRUMENTS == 1
+ synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key);
+#endif
+ if (patchCache[0].dirty) {
+ cacheTimbre(patchCache, timbreTemp);
+ }
+ playPoly(patchCache, key, freqNum, vel);
+}
+
+void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) {
+ unsigned int needPartials = cache[0].partialCount;
+ unsigned int freePartials = synth->partialManager->getFreePartialCount();
+
+ if (freePartials < needPartials) {
+ if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) {
+ synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount());
+ return;
+ }
+ }
+ // Find free poly
+ int m;
+ for (m = 0; m < MT32EMU_MAX_POLY; m++) {
+ if (!polyTable[m].isActive()) {
+ break;
+ }
+ }
+ if (m == MT32EMU_MAX_POLY) {
+ synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel);
+ return;
+ }
+
+ dpoly *tpoly = &polyTable[m];
+
+ tpoly->isPlaying = true;
+ tpoly->key = key;
+ tpoly->isDecay = false;
+ tpoly->freqnum = freqNum;
+ tpoly->vel = vel;
+ tpoly->pedalhold = false;
+
+ bool allnull = true;
+ for (int x = 0; x < 4; x++) {
+ if (cache[x].playPartial) {
+ tpoly->partials[x] = synth->partialManager->allocPartial(partNum);
+ allnull = false;
+ } else {
+ tpoly->partials[x] = NULL;
+ }
+ }
+
+ if (allnull)
+ synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr);
+
+ tpoly->sustain = cache[0].sustain;
+ tpoly->volumeptr = &volumeMult;
+
+ for (int x = 0; x < 4; x++) {
+ if (tpoly->partials[x] != NULL) {
+ tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]);
+ tpoly->partials[x]->setBend(bend);
+ }
+ }
+}
+
+static void startDecayPoly(dpoly *tpoly) {
+ if (tpoly->isDecay) {
+ return;
+ }
+ tpoly->isDecay = true;
+
+ for (int t = 0; t < 4; t++) {
+ Partial *partial = tpoly->partials[t];
+ if (partial == NULL)
+ continue;
+ partial->startDecayAll();
+ }
+ tpoly->isPlaying = false;
+}
+
+void Part::allNotesOff() {
+ // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B)
+ // should treat the hold pedal as usual.
+ // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal.
+ // The latter controller is not implemented on the MT-32 (according to the docs).
+ for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
+ dpoly *tpoly = &polyTable[q];
+ if (tpoly->isPlaying) {
+ if (holdpedal)
+ tpoly->pedalhold = true;
+ else if (tpoly->sustain)
+ startDecayPoly(tpoly);
+ }
+ }
+}
+
+void Part::allSoundOff() {
+ for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
+ dpoly *tpoly = &polyTable[q];
+ if (tpoly->isPlaying) {
+ startDecayPoly(tpoly);
+ }
+ }
+}
+
+void Part::stopPedalHold() {
+ for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
+ dpoly *tpoly;
+ tpoly = &polyTable[q];
+ if (tpoly->isActive() && tpoly->pedalhold)
+ stopNote(tpoly->key);
+ }
+}
+
+void Part::stopNote(unsigned int key) {
+ // Non-sustaining instruments ignore stop commands.
+ // They die away eventually anyway
+
+#if MT32EMU_MONITOR_INSTRUMENTS == 1
+ synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
+#endif
+
+ if (key != 255) {
+ for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
+ dpoly *tpoly = &polyTable[q];
+ if (tpoly->isPlaying && tpoly->key == key) {
+ if (holdpedal)
+ tpoly->pedalhold = true;
+ else if (tpoly->sustain)
+ startDecayPoly(tpoly);
+ }
+ }
+ return;
+ }
+
+ // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first
+ // This is simplest
+ int oldest = -1;
+ Bit32u oldage = 0;
+
+ for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
+ dpoly *tpoly = &polyTable[q];
+
+ if (tpoly->isPlaying && !tpoly->isDecay) {
+ if (tpoly->getAge() >= oldage) {
+ oldage = tpoly->getAge();
+ oldest = q;
+ }
+ }
+ }
+
+ if (oldest != -1) {
+ startDecayPoly(&polyTable[oldest]);
+ }
+}
+
+}
diff --git a/audio/softsynth/mt32/part.h b/audio/softsynth/mt32/part.h
new file mode 100644
index 0000000000..54c4999653
--- /dev/null
+++ b/audio/softsynth/mt32/part.h
@@ -0,0 +1,113 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_PART_H
+#define MT32EMU_PART_H
+
+namespace MT32Emu {
+
+class PartialManager;
+class Synth;
+
+class Part {
+private:
+ // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8)
+ MemParams::PatchTemp *patchTemp;
+ TimbreParam *timbreTemp;
+
+ // 0=Part 1, .. 7=Part 8, 8=Rhythm
+ unsigned int partNum;
+
+ bool holdpedal;
+
+ StereoVolume volumesetting;
+
+ PatchCache patchCache[4];
+
+ float bend; // -1.0 .. +1.0
+
+ dpoly polyTable[MT32EMU_MAX_POLY];
+
+ void abortPoly(dpoly *poly);
+
+ static int fixKeyfollow(int srckey);
+ static int fixBiaslevel(int srcpnt, int *dir);
+
+ void setPatch(const PatchParam *patch);
+
+protected:
+ Synth *synth;
+ char name[8]; // "Part 1".."Part 8", "Rhythm"
+ char currentInstr[11];
+ int expression;
+ Bit32u volumeMult;
+
+ void updateVolume();
+ void backupCacheToPartials(PatchCache cache[4]);
+ void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre);
+ void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel);
+ const char *getName() const;
+
+public:
+ Part(Synth *synth, unsigned int usePartNum);
+ virtual ~Part() {}
+ virtual void playNote(unsigned int key, int vel);
+ void stopNote(unsigned int key);
+ void allNotesOff();
+ void allSoundOff();
+ int getVolume() const;
+ void setVolume(int midiVolume);
+ void setExpression(int midiExpression);
+ virtual void setPan(unsigned int midiPan);
+ virtual void setBend(unsigned int midiBend);
+ virtual void setModulation(unsigned int midiModulation);
+ virtual void setProgram(unsigned int midiProgram);
+ void setHoldPedal(bool pedalval);
+ void stopPedalHold();
+ virtual void refresh();
+ virtual void refreshTimbre(unsigned int absTimbreNum);
+ virtual void setTimbre(TimbreParam *timbre);
+ virtual unsigned int getAbsTimbreNum() const;
+ const char *getCurrentInstr() const;
+};
+
+class RhythmPart: public Part {
+ // Pointer to the area of the MT-32's memory dedicated to rhythm
+ const MemParams::RhythmTemp *rhythmTemp;
+
+ // This caches the timbres/settings in use by the rhythm part
+ PatchCache drumCache[85][4];
+ StereoVolume drumPan[85];
+public:
+ RhythmPart(Synth *synth, unsigned int usePartNum);
+ void refresh();
+ void refreshTimbre(unsigned int timbreNum);
+ void setTimbre(TimbreParam *timbre);
+ void playNote(unsigned int key, int vel);
+ unsigned int getAbsTimbreNum() const;
+ void setPan(unsigned int midiPan);
+ void setBend(unsigned int midiBend);
+ void setModulation(unsigned int midiModulation);
+ void setProgram(unsigned int patchNum);
+};
+
+}
+#endif
diff --git a/audio/softsynth/mt32/partial.cpp b/audio/softsynth/mt32/partial.cpp
new file mode 100644
index 0000000000..5ba9ef6145
--- /dev/null
+++ b/audio/softsynth/mt32/partial.cpp
@@ -0,0 +1,968 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "mt32emu.h"
+
+#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__)
+// Older versions of Mac OS X didn't supply a powf function, so using it
+// will cause a binary incompatibility when trying to run a binary built
+// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide
+// powf, floorf, fabsf etc. at all.
+// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in
+// libstdc++. math/stubs.o should be empty, but it comes with a symbol for
+// powf, resulting in a linker error because of multiple definitions.
+// Hence we re-define them here. The only potential drawback is that it
+// might be a little bit slower this way.
+#define powf(x,y) ((float)pow(x,y))
+#define floorf(x) ((float)floor(x))
+#define fabsf(x) ((float)fabs(x))
+#endif
+
+#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y)))
+#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y)))
+#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point)
+#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point))
+
+using namespace MT32Emu;
+
+Partial::Partial(Synth *useSynth) {
+ this->synth = useSynth;
+ ownerPart = -1;
+ poly = NULL;
+ pair = NULL;
+#if MT32EMU_ACCURATENOTES == 1
+ for (int i = 0; i < 3; i++) {
+ noteLookupStorage.waveforms[i] = new Bit16s[65536];
+ }
+ noteLookup = &noteLookupStorage;
+#endif
+}
+
+Partial::~Partial() {
+#if MT32EMU_ACCURATENOTES == 1
+ for (int i = 0; i < 3; i++) {
+ delete[] noteLookupStorage.waveforms[i];
+ }
+ delete[] noteLookupStorage.wavTable;
+#endif
+}
+
+int Partial::getOwnerPart() const {
+ return ownerPart;
+}
+
+bool Partial::isActive() {
+ return ownerPart > -1;
+}
+
+const dpoly *Partial::getDpoly() const {
+ return this->poly;
+}
+
+void Partial::activate(int part) {
+ // This just marks the partial as being assigned to a part
+ ownerPart = part;
+}
+
+void Partial::deactivate() {
+ ownerPart = -1;
+ if (poly != NULL) {
+ for (int i = 0; i < 4; i++) {
+ if (poly->partials[i] == this) {
+ poly->partials[i] = NULL;
+ break;
+ }
+ }
+ if (pair != NULL) {
+ pair->pair = NULL;
+ }
+ }
+}
+
+void Partial::initKeyFollow(int key) {
+ // Setup partial keyfollow
+ // Note follow relative to middle C
+
+ // Calculate keyfollow for pitch
+#if 1
+ float rel = key == -1 ? 0.0f : (key - MIDDLEC);
+ float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift;
+ //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)?
+ //int newKey = (int)(rel * patchCache->pitchKeyfollow);
+ //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift;
+#else
+ float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC);
+ float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch;
+#endif
+#if MT32EMU_ACCURATENOTES == 1
+ noteVal = newPitch;
+ synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch);
+#else
+ float newPitchInt;
+ float newPitchFract = modff(newPitch, &newPitchInt);
+ if (newPitchFract > 0.5f) {
+ newPitchInt += 1.0f;
+ newPitchFract -= 1.0f;
+ }
+ noteVal = (int)newPitchInt;
+ fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f);
+ synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch, noteVal, fineShift);
+#endif
+ // FIXME:KG: Raise/lower by octaves until in the supported range.
+ while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108?
+ noteVal -= 12;
+ while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12?
+ noteVal += 12;
+ // Calculate keyfollow for filter
+ int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096;
+ if (keyfollow > 108)
+ keyfollow = 108;
+ else if (keyfollow < -108)
+ keyfollow = -108;
+ filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108];
+ realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108];
+}
+
+int Partial::getKey() const {
+ if (poly == NULL) {
+ return -1;
+ } else {
+ return poly->key;
+ }
+}
+
+void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) {
+ if (usePoly == NULL || useCache == NULL) {
+ synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK");
+ return;
+ }
+ patchCache = useCache;
+ poly = usePoly;
+ mixType = patchCache->structureMix;
+ structurePosition = patchCache->structurePosition;
+
+ play = true;
+ initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal
+#if MT32EMU_ACCURATENOTES == 0
+ noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE];
+#else
+ Tables::initNote(synth, &noteLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL);
+#endif
+ keyLookup = &synth->tables.keyLookups[poly->freqnum - 12];
+
+ if (patchCache->PCMPartial) {
+ pcmNum = patchCache->pcm;
+ if (synth->controlROMMap->pcmCount > 128) {
+ // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter.
+ if (patchCache->waveform > 1) {
+ pcmNum += 128;
+ }
+ }
+ pcmWave = &synth->pcmWaves[pcmNum];
+ } else {
+ pcmWave = NULL;
+ }
+
+ lfoPos = 0;
+ pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel];
+ if (pulsewidth > 100) {
+ pulsewidth = 100;
+ } else if (pulsewidth < 0) {
+ pulsewidth = 0;
+ }
+
+ for (int e = 0; e < 3; e++) {
+ envs[e].envpos = 0;
+ envs[e].envstat = -1;
+ envs[e].envbase = 0;
+ envs[e].envdist = 0;
+ envs[e].envsize = 0;
+ envs[e].sustaining = false;
+ envs[e].decaying = false;
+ envs[e].prevlevel = 0;
+ envs[e].counter = 0;
+ envs[e].count = 0;
+ }
+ ampEnvVal = 0;
+ pitchEnvVal = 0;
+ pitchSustain = false;
+ loopPos = 0;
+ partialOff.pcmoffset = partialOff.pcmplace = 0;
+ pair = pairPartial;
+ useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2);
+ age = 0;
+ alreadyOutputed = false;
+ memset(history,0,sizeof(history));
+}
+
+Bit16s *Partial::generateSamples(long length) {
+ if (!isActive() || alreadyOutputed) {
+ return NULL;
+ }
+ if (poly == NULL) {
+ synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!");
+ return NULL;
+ }
+
+ alreadyOutputed = true;
+
+ // Generate samples
+
+ Bit16s *partialBuf = &myBuffer[0];
+ Bit32u volume = *poly->volumeptr;
+ while (length--) {
+ Bit32s envval;
+ Bit32s sample = 0;
+ if (!envs[EnvelopeType_amp].sustaining) {
+ if (envs[EnvelopeType_amp].count <= 0) {
+ Bit32u ampval = getAmpEnvelope();
+ if (!play) {
+ deactivate();
+ break;
+ }
+ if (ampval > 100) {
+ ampval = 100;
+ }
+
+ ampval = synth->tables.volumeMult[ampval];
+ ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8);
+ //if (envs[EnvelopeType_amp].sustaining)
+ ampEnvVal = ampval;
+ }
+ --envs[EnvelopeType_amp].count;
+ }
+
+ unsigned int lfoShift = 0x1000;
+ if (pitchSustain) {
+ // Calculate LFO position
+ // LFO does not kick in completely until pitch envelope sustains
+ if (patchCache->lfodepth > 0) {
+ lfoPos++;
+ if (lfoPos >= patchCache->lfoperiod)
+ lfoPos = 0;
+ int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16);
+ int lfoatr = synth->tables.sintable[lfoatm];
+ lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr];
+ }
+ } else {
+ // Calculate Pitch envelope
+ envval = getPitchEnvelope();
+ int pd = patchCache->pitchEnv.depth;
+ pitchEnvVal = synth->tables.pitchEnvVal[pd][envval];
+ }
+
+ int delta;
+
+ // Wrap positions or end if necessary
+ if (patchCache->PCMPartial) {
+ // PCM partial
+
+ delta = noteLookup->wavTable[pcmNum];
+ int len = pcmWave->len;
+ if (partialOff.pcmplace >= len) {
+ if (pcmWave->loop) {
+ //partialOff.pcmplace = partialOff.pcmoffset = 0;
+ partialOff.pcmplace %= len;
+ } else {
+ play = false;
+ deactivate();
+ break;
+ }
+ }
+ } else {
+ // Synthesis partial
+ delta = 0x10000;
+ partialOff.pcmplace %= (Bit16u)noteLookup->div2;
+ }
+
+ // Build delta for position of next sample
+ // Fix delta code
+ Bit32u tdelta = delta;
+#if MT32EMU_ACCURATENOTES == 0
+ tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12);
+#endif
+ tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12);
+ tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12);
+ tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12);
+ delta = (int)tdelta;
+
+ // Get waveform - either PCM or synthesized sawtooth or square
+ if (ampEnvVal > 0) {
+ if (patchCache->PCMPartial) {
+ // Render PCM sample
+ int ra, rb, dist;
+ Bit32u taddr;
+ Bit32u pcmAddr = pcmWave->addr;
+ if (delta < 0x10000) {
+ // Linear sound interpolation
+ taddr = pcmAddr + partialOff.pcmplace;
+ ra = synth->pcmROMData[taddr];
+ taddr++;
+ if (taddr == pcmAddr + pcmWave->len) {
+ // Past end of PCM
+ if (pcmWave->loop) {
+ rb = synth->pcmROMData[pcmAddr];
+ } else {
+ rb = 0;
+ }
+ } else {
+ rb = synth->pcmROMData[taddr];
+ }
+ dist = rb - ra;
+ sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8));
+ } else {
+ // Sound decimation
+ // The right way to do it is to use a lowpass filter on the waveform before selecting
+ // a point. This is too slow. The following approximates this as fast as possible
+ int idelta = delta >> 16;
+ taddr = pcmAddr + partialOff.pcmplace;
+ ra = synth->pcmROMData[taddr++];
+ for (int ix = 0; ix < idelta - 1; ix++) {
+ if (taddr == pcmAddr + pcmWave->len) {
+ // Past end of PCM
+ if (pcmWave->loop) {
+ taddr = pcmAddr;
+ } else {
+ // Behave as if all subsequent samples were 0
+ break;
+ }
+ }
+ ra += synth->pcmROMData[taddr++];
+ }
+ sample = ra / idelta;
+ }
+ } else {
+ // Render synthesised sample
+ int toff = partialOff.pcmplace;
+ int minorplace = partialOff.pcmoffset >> 14;
+ Bit32s filterInput;
+ Bit32s filtval = getFiltEnvelope();
+
+ //synth->printDebug("Filtval: %d", filtval);
+
+ if ((patchCache->waveform & 1) == 0) {
+ // Square waveform. Made by combining two pregenerated bandlimited
+ // sawtooth waveforms
+ Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0];
+ int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7);
+ Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0];
+ Bit16s pa = noteLookup->waveforms[0][ofsA];
+ Bit16s pb = noteLookup->waveforms[0][ofsB];
+ filterInput = pa - pb;
+ // Non-bandlimited squarewave
+ /*
+ ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8);
+ if (toff < ofs)
+ sample = 1 * WGAMP;
+ else
+ sample = -1 * WGAMP;
+ */
+ } else {
+ // Sawtooth. Made by combining the full cosine and half cosine according
+ // to how it looks on the MT-32. What it really does it takes the
+ // square wave and multiplies it by a full cosine
+ int waveoff = (toff << 2) + minorplace;
+ if (toff < noteLookup->sawTable[pulsewidth])
+ filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]];
+ else
+ filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]];
+ // This is the correct way
+ // Seems slow to me (though bandlimited) -- doesn't seem to
+ // sound any better though
+ /*
+ //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8;
+
+ Bit32u ofs = toff % (noteLookup->div2 >> 1);
+
+ Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9);
+ ofs3 = ofs3 % (noteLookup->div2 >> 1);
+
+ pa = noteLookup->waveforms[0][ofs];
+ pb = noteLookup->waveforms[0][ofs3];
+ sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2;
+ */
+ }
+
+ //Very exact filter
+ if (filtval > ((FILTERGRAN * 15) / 16))
+ filtval = ((FILTERGRAN * 15) / 16);
+ sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]);
+ if (sample < -32768) {
+ synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample);
+ sample = -32768;
+ }
+ else if (sample > 32767) {
+ synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample);
+ sample = 32767;
+ }
+ }
+ }
+
+ // Add calculated delta to our waveform offset
+ Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset);
+ absOff += delta;
+ partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16);
+ partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF);
+
+ // Put volume envelope over generated sample
+ sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9);
+ sample = FIXEDPOINT_SMULT(sample, volume, 7);
+ envs[EnvelopeType_amp].envpos++;
+ envs[EnvelopeType_pitch].envpos++;
+ envs[EnvelopeType_filt].envpos++;
+
+ *partialBuf++ = (Bit16s)sample;
+ }
+ // We may have deactivated and broken out of the loop before the end of the buffer,
+ // if so then fill the remainder with 0s.
+ if (++length > 0)
+ memset(partialBuf, 0, length * 2);
+ return &myBuffer[0];
+}
+
+void Partial::setBend(float factor) {
+ if (!patchCache->useBender || factor == 0.0f) {
+ bendShift = 4096;
+ return;
+ }
+ // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB.
+ // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs.
+ float bendSemitones = factor * patchCache->benderRange; // -24 .. 24
+ float mult = powf(2.0f, bendSemitones / 12.0f);
+ synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", (double)factor, (double)patchCache->benderRange, (double)bendSemitones, (double)mult);
+ bendShift = (int)(mult * 4096.0f);
+}
+
+Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) {
+ if (buf1 == NULL)
+ return buf2;
+ if (buf2 == NULL)
+ return buf1;
+
+ Bit16s *outBuf = buf1;
+#if MT32EMU_USE_MMX >= 1
+ // KG: This seems to be fine
+ int donelen = i386_mixBuffers(buf1, buf2, len);
+ len -= donelen;
+ buf1 += donelen;
+ buf2 += donelen;
+#endif
+ while (len--) {
+ *buf1 = *buf1 + *buf2;
+ buf1++, buf2++;
+ }
+ return outBuf;
+}
+
+Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) {
+ if (buf1 == NULL)
+ return NULL;
+ if (buf2 == NULL) {
+ Bit16s *outBuf = buf1;
+ while (len--) {
+ if (*buf1 < -8192)
+ *buf1 = -8192;
+ else if (*buf1 > 8192)
+ *buf1 = 8192;
+ buf1++;
+ }
+ return outBuf;
+ }
+
+ Bit16s *outBuf = buf1;
+#if MT32EMU_USE_MMX >= 1
+ // KG: This seems to be fine
+ int donelen = i386_mixBuffersRingMix(buf1, buf2, len);
+ len -= donelen;
+ buf1 += donelen;
+ buf2 += donelen;
+#endif
+ while (len--) {
+ float a, b;
+ a = ((float)*buf1) / 8192.0f;
+ b = ((float)*buf2) / 8192.0f;
+ a = (a * b) + a;
+ if (a > 1.0f)
+ a = 1.0f;
+ if (a < -1.0f)
+ a = -1.0f;
+ *buf1 = (Bit16s)(a * 8192.0f);
+ buf1++;
+ buf2++;
+ //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i];
+ }
+ return outBuf;
+}
+
+Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) {
+ if (buf1 == NULL) {
+ return NULL;
+ }
+ if (buf2 == NULL) {
+ return NULL;
+ }
+
+ Bit16s *outBuf = buf1;
+#if MT32EMU_USE_MMX >= 1
+ // FIXME:KG: Not really checked as working
+ int donelen = i386_mixBuffersRing(buf1, buf2, len);
+ len -= donelen;
+ buf1 += donelen;
+ buf2 += donelen;
+#endif
+ while (len--) {
+ float a, b;
+ a = ((float)*buf1) / 8192.0f;
+ b = ((float)*buf2) / 8192.0f;
+ a *= b;
+ if (a > 1.0f)
+ a = 1.0f;
+ if (a < -1.0f)
+ a = -1.0f;
+ *buf1 = (Bit16s)(a * 8192.0f);
+ buf1++;
+ buf2++;
+ }
+ return outBuf;
+}
+
+void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) {
+ if (buf2 == NULL) {
+ while (len--) {
+ *outBuf++ = *buf1++;
+ *outBuf++ = 0;
+ }
+ } else if (buf1 == NULL) {
+ while (len--) {
+ *outBuf++ = 0;
+ *outBuf++ = *buf2++;
+ }
+ } else {
+ while (len--) {
+ *outBuf++ = *buf1++;
+ *outBuf++ = *buf2++;
+ }
+ }
+}
+
+bool Partial::produceOutput(Bit16s *partialBuf, long length) {
+ if (!isActive() || alreadyOutputed)
+ return false;
+ if (poly == NULL) {
+ synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!");
+ return false;
+ }
+
+ Bit16s *pairBuf = NULL;
+ // Check for dependant partial
+ if (pair != NULL) {
+ if (!pair->alreadyOutputed) {
+ // Note: pair may have become NULL after this
+ pairBuf = pair->generateSamples(length);
+ }
+ } else if (useNoisePair) {
+ // Generate noise for pairless ring mix
+ pairBuf = synth->tables.noiseBuf;
+ }
+
+ Bit16s *myBuf = generateSamples(length);
+
+ if (myBuf == NULL && pairBuf == NULL)
+ return false;
+
+ Bit16s *p1buf, *p2buf;
+
+ if (structurePosition == 0 || pairBuf == NULL) {
+ p1buf = myBuf;
+ p2buf = pairBuf;
+ } else {
+ p2buf = myBuf;
+ p1buf = pairBuf;
+ }
+
+ //synth->printDebug("mixType: %d", mixType);
+
+ Bit16s *mixedBuf;
+ switch (mixType) {
+ case 0:
+ // Standard sound mix
+ mixedBuf = mixBuffers(p1buf, p2buf, length);
+ break;
+
+ case 1:
+ // Ring modulation with sound mix
+ mixedBuf = mixBuffersRingMix(p1buf, p2buf, length);
+ break;
+
+ case 2:
+ // Ring modulation alone
+ mixedBuf = mixBuffersRing(p1buf, p2buf, length);
+ break;
+
+ case 3:
+ // Stereo mixing. One partial to one speaker channel, one to another.
+ // FIXME:KG: Surely we should be multiplying by the left/right volumes here?
+ mixBuffersStereo(p1buf, p2buf, partialBuf, length);
+ return true;
+
+ default:
+ mixedBuf = mixBuffers(p1buf, p2buf, length);
+ break;
+ }
+
+ if (mixedBuf == NULL)
+ return false;
+
+ Bit16s leftvol, rightvol;
+ leftvol = patchCache->pansetptr->leftvol;
+ rightvol = patchCache->pansetptr->rightvol;
+
+#if MT32EMU_USE_MMX >= 2
+ // FIXME:KG: This appears to introduce crackle
+ int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf);
+ length -= donelen;
+ mixedBuf += donelen;
+ partialBuf += donelen * 2;
+#endif
+ while (length--) {
+ *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 15);
+ *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15);
+ mixedBuf++;
+ }
+ return true;
+}
+
+Bit32s Partial::getFiltEnvelope() {
+ int reshigh;
+
+ int cutoff, depth;
+
+ EnvelopeStatus *tStat = &envs[EnvelopeType_filt];
+
+ if (tStat->decaying) {
+ reshigh = tStat->envbase;
+ reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize));
+ if (tStat->envpos >= tStat->envsize)
+ reshigh = 0;
+ } else {
+ if (tStat->envstat==4) {
+ reshigh = patchCache->filtsustain;
+ if (!poly->sustain) {
+ startDecay(EnvelopeType_filt, reshigh);
+ }
+ } else {
+ if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) {
+ if (tStat->envstat==-1)
+ tStat->envbase = 0;
+ else
+ tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat];
+ tStat->envstat++;
+ tStat->envpos = 0;
+ if (tStat->envstat == 3) {
+ tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]];
+ } else {
+ Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat];
+ if (tStat->envstat > 1) {
+ int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]);
+ if (envTime > synth->tables.envDeltaMaxTime[envDiff]) {
+ envTime = synth->tables.envDeltaMaxTime[envDiff];
+ }
+ }
+
+ tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8;
+ }
+
+ tStat->envsize++;
+ tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase;
+ }
+
+ reshigh = tStat->envbase;
+ reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize));
+
+ }
+ tStat->prevlevel = reshigh;
+ }
+
+ cutoff = patchCache->filtEnv.cutoff;
+
+ //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2;
+
+ depth = patchCache->filtEnv.envdepth;
+
+ //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7;
+ depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8);
+
+ int bias = patchCache->tvfbias;
+ int dist;
+
+ if (bias != 0) {
+ //FIXME:KG: Is this really based on pitch (as now), or key pressed?
+ //synth->printDebug("Cutoff before %d", cutoff);
+ if (patchCache->tvfdir == 0) {
+ if (noteVal < bias) {
+ dist = bias - noteVal;
+ cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8);
+ }
+ } else {
+ // > Bias
+ if (noteVal > bias) {
+ dist = noteVal - bias;
+ cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8);
+ }
+
+ }
+ //synth->printDebug("Cutoff after %d", cutoff);
+ }
+
+ depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8;
+ reshigh = (reshigh * depth) >> 7;
+
+ Bit32s tmp;
+
+ cutoff *= filtVal;
+ cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct?
+
+ reshigh *= filtVal;
+ reshigh /= realVal; //FIXME:KG: As above for cutoff
+
+ if (patchCache->waveform == 1) {
+ reshigh = (reshigh * 65) / 100;
+ }
+
+ if (cutoff > 100)
+ cutoff = 100;
+ else if (cutoff < 0)
+ cutoff = 0;
+ if (reshigh > 100)
+ reshigh = 100;
+ else if (reshigh < 0)
+ reshigh = 0;
+ tmp = noteLookup->nfiltTable[cutoff][reshigh];
+ //tmp *= keyfollow;
+ //tmp /= realfollow;
+
+ //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256);
+ return tmp;
+}
+
+bool Partial::shouldReverb() {
+ if (!isActive())
+ return false;
+ return patchCache->reverb;
+}
+
+Bit32u Partial::getAmpEnvelope() {
+ Bit32s tc;
+
+ EnvelopeStatus *tStat = &envs[EnvelopeType_amp];
+
+ if (!play)
+ return 0;
+
+ if (tStat->decaying) {
+ tc = tStat->envbase;
+ tc += (tStat->envdist * tStat->envpos) / tStat->envsize;
+ if (tc < 0)
+ tc = 0;
+ if ((tStat->envpos >= tStat->envsize) || (tc == 0)) {
+ play = false;
+ // Don't have to worry about prevlevel storage or anything, this partial's about to die
+ return 0;
+ }
+ } else {
+ if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) {
+ if (tStat->envstat == -1)
+ tStat->envbase = 0;
+ else
+ tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat];
+ tStat->envstat++;
+ tStat->envpos = 0;
+ if (tStat->envstat == 4) {
+ //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize);
+ tc = patchCache->ampEnv.envlevel[3];
+ if (!poly->sustain)
+ startDecay(EnvelopeType_amp, tc);
+ else
+ tStat->sustaining = true;
+ goto PastCalc;
+ }
+ Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat];
+ tStat->envdist = targetLevel - tStat->envbase;
+ Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat];
+ if (targetLevel == 0) {
+ tStat->envsize = synth->tables.envDecayTime[envTime];
+ } else {
+ int envLevelDelta = abs(tStat->envdist);
+ if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) {
+ envTime = synth->tables.envDeltaMaxTime[envLevelDelta];
+ }
+ tStat->envsize = synth->tables.envTime[envTime];
+ }
+
+ // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L)
+ tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8);
+
+ switch (tStat->envstat) {
+ case 0:
+ //Spot for velocity time follow
+ //Only used for first attack
+ tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8);
+ //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize);
+ break;
+ default:
+ synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat);
+ break;
+ }
+
+ tStat->envsize++;
+
+ if (tStat->envdist != 0) {
+ tStat->counter = abs(tStat->envsize / tStat->envdist);
+ //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist);
+ } else {
+ tStat->counter = 0;
+ //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist);
+ }
+ }
+ tc = tStat->envbase;
+ tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize));
+ tStat->count = tStat->counter;
+PastCalc:
+ tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100;
+ }
+
+ // Prevlevel storage is bottle neck
+ tStat->prevlevel = tc;
+
+ //Bias level crap stuff now
+
+ for (int i = 0; i < 2; i++) {
+ if (patchCache->ampblevel[i]!=0) {
+ int bias = patchCache->ampbias[i];
+ if (patchCache->ampdir[i]==0) {
+ // < Bias
+ if (noteVal < bias) {
+ int dist = bias - noteVal;
+ tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8);
+ }
+ } else {
+ // > Bias
+ if (noteVal > bias) {
+ int dist = noteVal - bias;
+ tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8);
+ }
+ }
+ }
+ }
+ if (tc < 0) {
+ synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc);
+ tc = 0;
+ }
+ return (Bit32u)tc;
+}
+
+Bit32s Partial::getPitchEnvelope() {
+ EnvelopeStatus *tStat = &envs[EnvelopeType_pitch];
+
+ Bit32s tc;
+ pitchSustain = false;
+ if (tStat->decaying) {
+ if (tStat->envpos >= tStat->envsize)
+ tc = patchCache->pitchEnv.level[4];
+ else {
+ tc = tStat->envbase;
+ tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize));
+ }
+ } else {
+ if (tStat->envstat==3) {
+ tc = patchCache->pitchsustain;
+ if (poly->sustain)
+ pitchSustain = true;
+ else
+ startDecay(EnvelopeType_pitch, tc);
+ } else {
+ if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) {
+ tStat->envstat++;
+
+ tStat->envbase = patchCache->pitchEnv.level[tStat->envstat];
+
+ Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat];
+ int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]);
+ if (envTime > synth->tables.envDeltaMaxTime[envDiff]) {
+ envTime = synth->tables.envDeltaMaxTime[envDiff];
+ }
+
+ tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8;
+
+ tStat->envpos = 0;
+ tStat->envsize++;
+ tStat->envdist = patchCache->pitchEnv.level[tStat->envstat + 1] - tStat->envbase;
+ }
+ tc = tStat->envbase;
+ tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize));
+ }
+ tStat->prevlevel = tc;
+ }
+ return tc;
+}
+
+void Partial::startDecayAll() {
+ startDecay(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel);
+ startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel);
+ startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel);
+ pitchSustain = false;
+}
+
+void Partial::startDecay(EnvelopeType envnum, Bit32s startval) {
+ EnvelopeStatus *tStat = &envs[envnum];
+
+ tStat->sustaining = false;
+ tStat->decaying = true;
+ tStat->envpos = 0;
+ tStat->envbase = startval;
+
+ switch (envnum) {
+ case EnvelopeType_amp:
+ tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8);
+ tStat->envdist = -startval;
+ break;
+ case EnvelopeType_filt:
+ tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8);
+ tStat->envdist = -startval;
+ break;
+ case EnvelopeType_pitch:
+ tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8);
+ tStat->envdist = patchCache->pitchEnv.level[4] - startval;
+ break;
+ default:
+ break;
+ }
+ tStat->envsize++;
+}
diff --git a/audio/softsynth/mt32/partial.h b/audio/softsynth/mt32/partial.h
new file mode 100644
index 0000000000..93d8bcd985
--- /dev/null
+++ b/audio/softsynth/mt32/partial.h
@@ -0,0 +1,148 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_PARTIAL_H
+#define MT32EMU_PARTIAL_H
+
+namespace MT32Emu {
+
+class Synth;
+struct NoteLookup;
+
+enum EnvelopeType {
+ EnvelopeType_amp = 0,
+ EnvelopeType_filt = 1,
+ EnvelopeType_pitch = 2
+};
+
+struct EnvelopeStatus {
+ Bit32s envpos;
+ Bit32s envstat;
+ Bit32s envbase;
+ Bit32s envdist;
+ Bit32s envsize;
+
+ bool sustaining;
+ bool decaying;
+ Bit32s prevlevel;
+
+ Bit32s counter;
+ Bit32s count;
+};
+
+// Class definition of MT-32 partials. 32 in all.
+class Partial {
+private:
+ Synth *synth;
+
+ int ownerPart; // -1 if unassigned
+ int mixType;
+ int structurePosition; // 0 or 1 of a structure pair
+ bool useNoisePair;
+
+ Bit16s myBuffer[MAX_SAMPLE_OUTPUT];
+
+ // Keyfollowed note value
+#if MT32EMU_ACCURATENOTES == 1
+ NoteLookup noteLookupStorage;
+ float noteVal;
+#else
+ int noteVal;
+ int fineShift;
+#endif
+ const NoteLookup *noteLookup; // LUTs for this noteVal
+ const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key
+
+ // Keyfollowed filter values
+ int realVal;
+ int filtVal;
+
+ // Only used for PCM partials
+ int pcmNum;
+ PCMWaveEntry *pcmWave;
+
+ int pulsewidth;
+
+ Bit32u lfoPos;
+ soundaddr partialOff;
+
+ Bit32u ampEnvVal;
+ Bit32u pitchEnvVal;
+
+ float history[32];
+
+ bool pitchSustain;
+
+ int loopPos;
+
+ dpoly *poly;
+
+ int bendShift;
+
+ Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len);
+ Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len);
+ Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len);
+ void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len);
+
+ Bit32s getFiltEnvelope();
+ Bit32u getAmpEnvelope();
+ Bit32s getPitchEnvelope();
+
+ void initKeyFollow(int freqNum);
+
+public:
+ const PatchCache *patchCache;
+ EnvelopeStatus envs[3];
+ bool play;
+
+ PatchCache cachebackup;
+
+ Partial *pair;
+ bool alreadyOutputed;
+ Bit32u age;
+
+ Partial(Synth *synth);
+ ~Partial();
+
+ int getOwnerPart() const;
+ int getKey() const;
+ const dpoly *getDpoly() const;
+ bool isActive();
+ void activate(int part);
+ void deactivate(void);
+ void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial);
+ void startDecay(EnvelopeType envnum, Bit32s startval);
+ void startDecayAll();
+ void setBend(float factor);
+ bool shouldReverb();
+
+ // Returns true only if data written to buffer
+ // This function (unlike the one below it) returns processed stereo samples
+ // made from combining this single partial with its pair, if it has one.
+ bool produceOutput(Bit16s * partialBuf, long length);
+
+ // This function produces mono sample output using the partial's private internal buffer
+ Bit16s *generateSamples(long length);
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/partialManager.cpp b/audio/softsynth/mt32/partialManager.cpp
new file mode 100644
index 0000000000..3d3b6302db
--- /dev/null
+++ b/audio/softsynth/mt32/partialManager.cpp
@@ -0,0 +1,272 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "mt32emu.h"
+
+using namespace MT32Emu;
+
+PartialManager::PartialManager(Synth *useSynth) {
+ this->synth = useSynth;
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
+ partialTable[i] = new Partial(synth);
+}
+
+PartialManager::~PartialManager(void) {
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
+ delete partialTable[i];
+}
+
+void PartialManager::getPerPartPartialUsage(int usage[9]) {
+ memset(usage, 0, 9 * sizeof (int));
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialTable[i]->isActive())
+ usage[partialTable[i]->getOwnerPart()]++;
+ }
+}
+
+void PartialManager::clearAlreadyOutputed() {
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
+ partialTable[i]->alreadyOutputed = false;
+}
+
+void PartialManager::ageAll() {
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
+ partialTable[i]->age++;
+}
+
+bool PartialManager::shouldReverb(int i) {
+ return partialTable[i]->shouldReverb();
+}
+
+bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) {
+ return partialTable[i]->produceOutput(buffer, bufferLength);
+}
+
+void PartialManager::deactivateAll() {
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ partialTable[i]->deactivate();
+ }
+}
+
+unsigned int PartialManager::setReserve(Bit8u *rset) {
+ unsigned int pr = 0;
+ for (int x = 0; x < 9; x++) {
+ for (int y = 0; y < rset[x]; y++) {
+ partialReserveTable[pr] = x;
+ pr++;
+ }
+ }
+ return pr;
+}
+
+Partial *PartialManager::allocPartial(int partNum) {
+ Partial *outPartial = NULL;
+
+ // Use the first inactive partial reserved for the specified part (if there are any)
+ // Otherwise, use the last inactive partial, if any
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (!partialTable[i]->isActive()) {
+ outPartial = partialTable[i];
+ if (partialReserveTable[i] == partNum)
+ break;
+ }
+ }
+ if (outPartial != NULL) {
+ outPartial->activate(partNum);
+ outPartial->age = 0;
+ }
+ return outPartial;
+}
+
+unsigned int PartialManager::getFreePartialCount(void) {
+ int count = 0;
+ memset(partialPart, 0, sizeof(partialPart));
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (!partialTable[i]->isActive())
+ count++;
+ else
+ partialPart[partialTable[i]->getOwnerPart()]++;
+ }
+ return count;
+}
+
+/*
+bool PartialManager::freePartials(unsigned int needed, int partNum) {
+ int i;
+ int myPartPrior = (int)mt32ram.system.reserveSettings[partNum];
+ if (myPartPrior<partialPart[partNum]) {
+ //This can have more parts, must kill off those with less priority
+ int most, mostPart;
+ while (needed > 0) {
+ int selectPart = -1;
+ //Find the worst offender with more partials than allocated and kill them
+ most = -1;
+ mostPart = -1;
+ int diff;
+
+ for (i=0;i<9;i++) {
+ diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i];
+
+ if (diff>0) {
+ if (diff>most) {
+ most = diff;
+ mostPart = i;
+ }
+ }
+ }
+ selectPart = mostPart;
+ if (selectPart == -1) {
+ // All parts are within the allocated limits, you suck
+ // Look for first partial not of this part that's decaying perhaps?
+ return false;
+ }
+ bool found;
+ int oldest;
+ int oldnum;
+ while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) {
+ oldest = -1;
+ oldnum = -1;
+ found = false;
+ for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialTable[i]->isActive) {
+ if (partialTable[i]->ownerPart == selectPart) {
+ found = true;
+ if (partialTable[i]->age > oldest) {
+ oldest = partialTable[i]->age;
+ oldnum = i;
+ }
+ }
+ }
+ }
+ if (!found) break;
+ partialTable[oldnum]->deactivate();
+ --partialPart[selectPart];
+ --needed;
+ }
+
+ }
+ return true;
+
+ } else {
+ //This part has reached its max, must kill off its own
+ bool found;
+ int oldest;
+ int oldnum;
+ while (needed > 0) {
+ oldest = -1;
+ oldnum = -1;
+ found = false;
+ for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialTable[i]->isActive) {
+ if (partialTable[i]->ownerPart == partNum) {
+ found = true;
+ if (partialTable[i]->age > oldest) {
+ oldest = partialTable[i]->age;
+ oldnum = i;
+ }
+ }
+ }
+ }
+ if (!found) break;
+ partialTable[oldnum]->deactivate();
+ --needed;
+ }
+ // Couldn't free enough partials, sorry
+ if (needed>0) return false;
+ return true;
+ }
+
+}
+*/
+bool PartialManager::freePartials(unsigned int needed, int partNum) {
+ if (needed == 0) {
+ return true;
+ }
+ // Reclaim partials reserved for this part
+ // Kill those that are already decaying first
+ /*
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialReserveTable[i] == partNum) {
+ if (partialTable[i]->ownerPart != partNum) {
+ if (partialTable[i]->partCache->envs[AMPENV].decaying) {
+ partialTable[i]->isActive = false;
+ --needed;
+ if (needed == 0)
+ return true;
+ }
+ }
+ }
+ }*/
+ // Then kill those with the lowest part priority -- oldest at the moment
+ while (needed > 0) {
+ Bit32u prior = 0;
+ int priornum = -1;
+
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) {
+ /*
+ if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) {
+ prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart];
+ priornum = i;
+ }*/
+ if (partialTable[i]->age >= prior) {
+ prior = partialTable[i]->age;
+ priornum = i;
+ }
+ }
+ }
+ if (priornum != -1) {
+ partialTable[priornum]->deactivate();
+ --needed;
+ } else {
+ break;
+ }
+ }
+
+ // Kill off the oldest partials within this part
+ while (needed > 0) {
+ Bit32u oldest = 0;
+ int oldlist = -1;
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) {
+ if (partialTable[i]->age >= oldest) {
+ oldest = partialTable[i]->age;
+ oldlist = i;
+ }
+ }
+ }
+ if (oldlist != -1) {
+ partialTable[oldlist]->deactivate();
+ --needed;
+ } else {
+ break;
+ }
+ }
+ return needed == 0;
+}
+
+const Partial *PartialManager::getPartial(unsigned int partialNum) const {
+ if (partialNum > MT32EMU_MAX_PARTIALS - 1)
+ return NULL;
+ return partialTable[partialNum];
+}
diff --git a/audio/softsynth/mt32/partialManager.h b/audio/softsynth/mt32/partialManager.h
new file mode 100644
index 0000000000..b10f93ff02
--- /dev/null
+++ b/audio/softsynth/mt32/partialManager.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_PARTIALMANAGER_H
+#define MT32EMU_PARTIALMANAGER_H
+
+namespace MT32Emu {
+
+class Synth;
+
+class PartialManager {
+private:
+ Synth *synth; // Only used for sending debug output
+
+ Partial *partialTable[MT32EMU_MAX_PARTIALS];
+ Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS];
+ Bit32s partialPart[9]; // The count of partials played per part
+
+public:
+
+ PartialManager(Synth *synth);
+ ~PartialManager();
+ Partial *allocPartial(int partNum);
+ unsigned int getFreePartialCount(void);
+ bool freePartials(unsigned int needed, int partNum);
+ unsigned int setReserve(Bit8u *rset);
+ void deactivateAll();
+ void ageAll();
+ bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength);
+ bool shouldReverb(int i);
+ void clearAlreadyOutputed();
+ void getPerPartPartialUsage(int usage[9]);
+ const Partial *getPartial(unsigned int partialNum) const;
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/structures.h b/audio/softsynth/mt32/structures.h
new file mode 100644
index 0000000000..ef58c1d20f
--- /dev/null
+++ b/audio/softsynth/mt32/structures.h
@@ -0,0 +1,284 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_STRUCTURES_H
+#define MT32EMU_STRUCTURES_H
+
+namespace MT32Emu {
+
+const unsigned int MAX_SAMPLE_OUTPUT = 4096;
+
+// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it
+// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output
+#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f))
+#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f))
+
+#ifdef _MSC_VER
+#define MT32EMU_ALIGN_PACKED __declspec(align(1))
+typedef unsigned __int64 Bit64u;
+typedef signed __int64 Bit64s;
+#else
+#define MT32EMU_ALIGN_PACKED __attribute__((packed))
+typedef unsigned long long Bit64u;
+typedef signed long long Bit64s;
+#endif
+
+typedef unsigned int Bit32u;
+typedef signed int Bit32s;
+typedef unsigned short int Bit16u;
+typedef signed short int Bit16s;
+typedef unsigned char Bit8u;
+typedef signed char Bit8s;
+
+// The following structures represent the MT-32's memory
+// Since sysex allows this memory to be written to in blocks of bytes,
+// we keep this packed so that we can copy data into the various
+// banks directly
+#if defined(_MSC_VER) || defined (__MINGW32__)
+#pragma pack(push, 1)
+#else
+#pragma pack(1)
+#endif
+
+struct TimbreParam {
+ struct commonParam {
+ char name[10];
+ Bit8u pstruct12; // 1&2 0-12 (1-13)
+ Bit8u pstruct34; // #3&4 0-12 (1-13)
+ Bit8u pmute; // 0-15 (0000-1111)
+ Bit8u nosustain; // 0-1(Normal, No sustain)
+ } MT32EMU_ALIGN_PACKED common;
+
+ struct partialParam {
+ struct wgParam {
+ Bit8u coarse; // 0-96 (C1,C#1-C9)
+ Bit8u fine; // 0-100 (-50 to +50 (cents?))
+ Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2)
+ Bit8u bender; // 0,1 (ON/OFF)
+ Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2)
+ Bit8u pcmwave; // 0-127 (1-128)
+ Bit8u pulsewid; // 0-100
+ Bit8u pwvelo; // 0-14 (-7 - +7)
+ } MT32EMU_ALIGN_PACKED wg;
+
+ struct envParam {
+ Bit8u depth; // 0-10
+ Bit8u sensitivity; // 1-100
+ Bit8u timekeyfollow; // 0-4
+ Bit8u time[4]; // 1-100
+ Bit8u level[5]; // 1-100 (-50 - +50)
+ } MT32EMU_ALIGN_PACKED env;
+
+ struct lfoParam {
+ Bit8u rate; // 0-100
+ Bit8u depth; // 0-100
+ Bit8u modsense; // 0-100
+ } MT32EMU_ALIGN_PACKED lfo;
+
+ struct tvfParam {
+ Bit8u cutoff; // 0-100
+ Bit8u resonance; // 0-30
+ Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2)
+ Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C)
+ Bit8u biaslevel; // 0-14 (-7 - +7)
+ Bit8u envdepth; // 0-100
+ Bit8u envsense; // 0-100
+ Bit8u envdkf; // DEPTH KEY FOLL0W 0-4
+ Bit8u envtkf; // TIME KEY FOLLOW 0-4
+ Bit8u envtime[5]; // 1-100
+ Bit8u envlevel[4]; // 1-100
+ } MT32EMU_ALIGN_PACKED tvf;
+
+ struct tvaParam {
+ Bit8u level; // 0-100
+ Bit8u velosens; // 0-100
+ Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C)
+ Bit8u biaslevel1; // 0-12 (-12 - 0)
+ Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C)
+ Bit8u biaslevel2; // 0-12 (-12 - 0)
+ Bit8u envtkf; // TIME KEY FOLLOW 0-4
+ Bit8u envvkf; // VELOS KEY FOLL0W 0-4
+ Bit8u envtime[5]; // 1-100
+ Bit8u envlevel[4]; // 1-100
+ } MT32EMU_ALIGN_PACKED tva;
+ } MT32EMU_ALIGN_PACKED partial[4];
+} MT32EMU_ALIGN_PACKED;
+
+struct PatchParam {
+ Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm)
+ Bit8u timbreNum; // TIMBRE NUMBER 0-63
+ Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones)
+ Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents)
+ Bit8u benderRange; // BENDER RANGE 0-24
+ Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4)
+ Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
+ Bit8u dummy; // (DUMMY)
+} MT32EMU_ALIGN_PACKED;
+
+struct MemParams {
+ // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8.
+ // The LAPC-I documentation specified an additional area for rhythm at the end,
+ // where all parameters but fine tune, assign mode and output level are ignored
+ struct PatchTemp {
+ PatchParam patch;
+ Bit8u outlevel; // OUTPUT LEVEL 0-100
+ Bit8u panpot; // PANPOT 0-14 (R-L)
+ Bit8u dummyv[6];
+ } MT32EMU_ALIGN_PACKED;
+
+ PatchTemp patchSettings[9];
+
+ struct RhythmTemp {
+ Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF)
+ Bit8u outlevel; // OUTPUT LEVEL 0-100
+ Bit8u panpot; // PANPOT 0-14 (R-L)
+ Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
+ } MT32EMU_ALIGN_PACKED;
+
+ RhythmTemp rhythmSettings[85];
+
+ TimbreParam timbreSettings[8];
+
+ PatchParam patches[128];
+
+ // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above
+ struct PaddedTimbre {
+ TimbreParam timbre;
+ Bit8u padding[10];
+ } MT32EMU_ALIGN_PACKED;
+
+ PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
+
+ struct SystemArea {
+ Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz
+ Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay)
+ Bit8u reverbTime; // REVERB TIME 0-7 (1-8)
+ Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8)
+ Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32
+ Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF)
+ Bit8u masterVol; // MASTER VOLUME 0-100
+ } MT32EMU_ALIGN_PACKED;
+
+ SystemArea system;
+};
+
+#if defined(_MSC_VER) || defined (__MINGW32__)
+#pragma pack(pop)
+#else
+#pragma pack()
+#endif
+
+struct PCMWaveEntry {
+ Bit32u addr;
+ Bit32u len;
+ double tune;
+ bool loop;
+};
+
+struct soundaddr {
+ Bit16u pcmplace;
+ Bit16u pcmoffset;
+};
+
+struct StereoVolume {
+ Bit16s leftvol;
+ Bit16s rightvol;
+};
+
+// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings
+struct PatchCache {
+ bool playPartial;
+ bool PCMPartial;
+ int pcm;
+ char waveform;
+ int pulsewidth;
+ int pwsens;
+
+ float pitch;
+
+ int lfodepth;
+ int lforate;
+ Bit32u lfoperiod;
+ int modsense;
+
+ float pitchKeyfollow;
+
+ int filtkeyfollow;
+
+ int tvfbias;
+ int tvfblevel;
+ int tvfdir;
+
+ int ampbias[2];
+ int ampblevel[2];
+ int ampdir[2];
+
+ int ampdepth;
+ int amplevel;
+
+ bool useBender;
+ float benderRange; // 0.0, 1.0, .., 24.0 (semitones)
+
+ TimbreParam::partialParam::envParam pitchEnv;
+ TimbreParam::partialParam::tvaParam ampEnv;
+ TimbreParam::partialParam::tvfParam filtEnv;
+
+ Bit32s pitchsustain;
+ Bit32s filtsustain;
+
+ Bit32u structureMix;
+ int structurePosition;
+ int structurePair;
+
+ // The following fields are actually common to all partials in the timbre
+ bool dirty;
+ Bit32u partialCount;
+ bool sustain;
+ float pitchShift;
+ bool reverb;
+ const StereoVolume *pansetptr;
+};
+
+class Partial; // Forward reference for class defined in partial.h
+
+struct dpoly {
+ bool isPlaying;
+
+ unsigned int key;
+ int freqnum;
+ int vel;
+
+ bool isDecay;
+
+ const Bit32u *volumeptr;
+
+ Partial *partials[4];
+
+ bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal
+ bool sustain;
+
+ bool isActive() const;
+ Bit32u getAge() const;
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/synth.cpp b/audio/softsynth/mt32/synth.cpp
new file mode 100644
index 0000000000..16460795a5
--- /dev/null
+++ b/audio/softsynth/mt32/synth.cpp
@@ -0,0 +1,1198 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "mt32emu.h"
+
+#include "common/str.h"
+
+#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__)
+// Older versions of Mac OS X didn't supply a powf function, so using it
+// will cause a binary incompatibility when trying to run a binary built
+// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide
+// powf, floorf, fabsf etc. at all.
+// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in
+// libstdc++. math/stubs.o should be empty, but it comes with a symbol for
+// powf, resulting in a linker error because of multiple definitions.
+// Hence we re-define them here. The only potential drawback is that it
+// might be a little bit slower this way.
+#define powf(x,y) ((float)pow(x,y))
+#define floorf(x) ((float)floor(x))
+#define fabsf(x) ((float)fabs(x))
+#endif
+
+namespace MT32Emu {
+
+const int MAX_SYSEX_SIZE = 512;
+
+const ControlROMMap ControlROMMaps[5] = {
+ // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog
+ {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0
+ {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0
+ {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1
+ {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod
+ {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L
+ // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp)
+};
+
+float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) {
+ float *hist2_ptr;
+ float output,new_hist;
+
+ hist2_ptr = hist1_ptr + 1; // next history
+
+ // 1st number of coefficients array is overall input scale factor, or filter gain
+ output = input * (*coef_ptr++);
+
+ output = output - *hist1_ptr * (*coef_ptr++);
+ new_hist = output - *hist2_ptr * (*coef_ptr++); // poles
+
+ output = new_hist + *hist1_ptr * (*coef_ptr++);
+ output = output + *hist2_ptr * (*coef_ptr++); // zeros
+
+ *hist2_ptr++ = *hist1_ptr;
+ *hist1_ptr++ = new_hist;
+ hist1_ptr++;
+ hist2_ptr++;
+
+ // i = 1
+ output = output - *hist1_ptr * (*coef_ptr++);
+ new_hist = output - *hist2_ptr * (*coef_ptr++); // poles
+
+ output = new_hist + *hist1_ptr * (*coef_ptr++);
+ output = output + *hist2_ptr * (*coef_ptr++); // zeros
+
+ *hist2_ptr++ = *hist1_ptr;
+ *hist1_ptr++ = new_hist;
+
+ return(output);
+}
+
+Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) {
+ for (unsigned int i = 0; i < len; i++) {
+ checksum = checksum + data[i];
+ }
+ checksum = checksum & 0x7f;
+ if (checksum)
+ checksum = 0x80 - checksum;
+ return checksum;
+}
+
+Synth::Synth() {
+ isOpen = false;
+ reverbModel = NULL;
+ partialManager = NULL;
+ memset(parts, 0, sizeof(parts));
+}
+
+Synth::~Synth() {
+ close(); // Make sure we're closed and everything is freed
+}
+
+int Synth::report(ReportType type, const void *data) {
+ if (myProp.report != NULL) {
+ return myProp.report(myProp.userData, type, data);
+ }
+ return 0;
+}
+
+void Synth::printDebug(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ if (myProp.printDebug != NULL) {
+ myProp.printDebug(myProp.userData, fmt, ap);
+ } else {
+ vprintf(fmt, ap);
+ printf("\n");
+ }
+ va_end(ap);
+}
+
+void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) {
+ // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters
+ delete reverbModel;
+ reverbModel = new revmodel();
+
+ switch (newRevMode) {
+ case 0:
+ reverbModel->setroomsize(.1f);
+ reverbModel->setdamp(.75f);
+ break;
+ case 1:
+ reverbModel->setroomsize(.5f);
+ reverbModel->setdamp(.5f);
+ break;
+ case 2:
+ reverbModel->setroomsize(.5f);
+ reverbModel->setdamp(.1f);
+ break;
+ case 3:
+ reverbModel->setroomsize(1.0f);
+ reverbModel->setdamp(.75f);
+ break;
+ default:
+ reverbModel->setroomsize(.1f);
+ reverbModel->setdamp(.5f);
+ break;
+ }
+ reverbModel->setdry(1);
+ reverbModel->setwet((float)newRevLevel / 8.0f);
+ reverbModel->setwidth((float)newRevTime / 8.0f);
+}
+
+File *Synth::openFile(const char *filename, File::OpenMode mode) {
+ // It should never happen that openFile is NULL in our use case.
+ // Just to cover the case where something is horrible wrong we
+ // use an assert here.
+ assert(myProp.openFile != NULL);
+ return myProp.openFile(myProp.userData, filename, mode);
+}
+
+void Synth::closeFile(File *file) {
+ if (myProp.closeFile != NULL) {
+ myProp.closeFile(myProp.userData, file);
+ } else {
+ file->close();
+ delete file;
+ }
+}
+
+bool Synth::loadPreset(File *file) {
+ bool inSys = false;
+ Bit8u sysexBuf[MAX_SYSEX_SIZE];
+ Bit16u syslen = 0;
+ bool rc = true;
+ for (;;) {
+ Bit8u c;
+ if (!file->readBit8u(&c)) {
+ if (!file->isEOF()) {
+ rc = false;
+ }
+ break;
+ }
+ sysexBuf[syslen] = c;
+ if (inSys) {
+ syslen++;
+ if (c == 0xF7) {
+ playSysex(&sysexBuf[0], syslen);
+ inSys = false;
+ syslen = 0;
+ } else if (syslen == MAX_SYSEX_SIZE) {
+ printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE);
+ inSys = false;
+ syslen = 0;
+ }
+ } else if (c == 0xF0) {
+ syslen++;
+ inSys = true;
+ }
+ }
+ return rc;
+}
+
+bool Synth::loadControlROM(const char *filename) {
+ File *file = openFile(filename, File::OpenMode_read); // ROM File
+ if (file == NULL) {
+ return false;
+ }
+ bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE);
+
+ closeFile(file);
+ if (!rc)
+ return rc;
+
+ // Control ROM successfully loaded, now check whether it's a known type
+ controlROMMap = NULL;
+ for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) {
+ if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) {
+ controlROMMap = &ControlROMMaps[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Synth::loadPCMROM(const char *filename) {
+ File *file = openFile(filename, File::OpenMode_read); // ROM File
+ if (file == NULL) {
+ return false;
+ }
+ bool rc = true;
+ int i;
+ for (i = 0; i < pcmROMSize; i++) {
+ Bit8u s;
+ if (!file->readBit8u(&s)) {
+ if (!file->isEOF()) {
+ rc = false;
+ }
+ break;
+ }
+ Bit8u c;
+ if (!file->readBit8u(&c)) {
+ if (!file->isEOF()) {
+ rc = false;
+ } else {
+ printDebug("PCM ROM file has an odd number of bytes! Ignoring last");
+ }
+ break;
+ }
+
+ short e;
+ int bit;
+ int u;
+ int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8};
+
+ e = 0;
+ for (u = 0; u < 15; u++) {
+ if (order[u] < 8)
+ bit = (s >> (7 - order[u])) & 0x1;
+ else
+ bit = (c >> (7 - (order[u] - 8))) & 0x1;
+ e = e | (short)(bit << (15 - u));
+ }
+
+ /*
+ //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2;
+ if (e<0)
+ e = -32767 - e;
+ int ut = abs(e);
+ int dif = 0x7fff - ut;
+ x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000));
+ e = (int)((float)e * (x/3200));
+ */
+
+ // File is companded (dB?), convert to linear PCM
+ // MINDB = -96
+ // MAXDB = -15
+ float testval;
+ testval = (float)((~e) & 0x7fff);
+ testval = -(testval / 400.00f);
+ //testval = -(testval / 341.32291666666666666666666666667);
+ float vol = powf(8, testval / 20) * 32767.0f;
+
+ if (e > 0)
+ vol = -vol;
+
+ pcmROMData[i] = (Bit16s)vol;
+ }
+ if (i != pcmROMSize) {
+ printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i);
+ rc = false;
+ }
+ closeFile(file);
+ return rc;
+}
+
+bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
+ ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress];
+ for (int i = 0; i < count; i++) {
+ int rAddr = tps[i].pos * 0x800;
+ int rLenExp = (tps[i].len & 0x70) >> 4;
+ int rLen = 0x800 << rLenExp;
+ bool rLoop = (tps[i].len & 0x80) != 0;
+ //Bit8u rFlag = tps[i].len & 0x0F;
+ Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB;
+ // The number below is confirmed to a reasonable degree of accuracy on CM-32L
+ double STANDARDFREQ = 442.0;
+ float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0));
+ //printDebug("%f,%d,%d", (double)pTune, tps[i].pitchCoarse, tps[i].pitchFine);
+ if (rAddr + rLen > pcmROMSize) {
+ printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen);
+ return false;
+ }
+ pcmWaves[i].addr = rAddr;
+ pcmWaves[i].len = rLen;
+ pcmWaves[i].loop = rLoop;
+ pcmWaves[i].tune = rTune;
+ }
+ return false;
+}
+
+bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) {
+ if (memLen < sizeof(TimbreParam::commonParam)) {
+ return false;
+ }
+ TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre;
+ memcpy(&timbre->common, mem, 14);
+ unsigned int memPos = 14;
+ char drumname[11];
+ memset(drumname, 0, 11);
+ memcpy(drumname, timbre->common.name, 10);
+ for (int t = 0; t < 4; t++) {
+ if (((timbre->common.pmute >> t) & 0x1) == 0x1) {
+ if (memPos + 58 >= memLen) {
+ return false;
+ }
+ memcpy(&timbre->partial[t], mem + memPos, 58);
+ memPos += 58;
+ }
+ }
+ return true;
+}
+
+bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) {
+ const Bit8u *drumMap = &controlROMData[mapAddress];
+ int timbreNum = 192;
+ for (Bit16u i = 0; i < count * 2; i += 2) {
+ Bit16u address = (drumMap[i + 1] << 8) | drumMap[i];
+ /*
+ // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is)
+ if (address >= CONTROL_ROM_SIZE) {
+ printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address);
+ return false;
+ }
+ */
+ if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) {
+ printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) {
+ for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) {
+ Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i];
+ if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) {
+ printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address);
+ return false;
+ }
+ address = address + offset;
+ TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre;
+ memcpy(timbre, &controlROMData[address], sizeof(TimbreParam));
+ }
+ return true;
+}
+
+bool Synth::open(SynthProperties &useProp) {
+ if (isOpen)
+ return false;
+
+ myProp = useProp;
+ if (useProp.baseDir != NULL) {
+ myProp.baseDir = new char[strlen(useProp.baseDir) + 1];
+ strcpy(myProp.baseDir, useProp.baseDir);
+ }
+
+ // This is to help detect bugs
+ memset(&mt32ram, '?', sizeof(mt32ram));
+
+ printDebug("Loading Control ROM");
+ if (!loadControlROM("CM32L_CONTROL.ROM")) {
+ if (!loadControlROM("MT32_CONTROL.ROM")) {
+ printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM");
+ report(ReportType_errorControlROM, NULL);
+ return false;
+ }
+ }
+
+ // 512KB PCM ROM for MT-32, etc.
+ // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500
+ // Note that the size below is given in samples (16-bit), not bytes
+ pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
+ pcmROMData = new Bit16s[pcmROMSize];
+
+ printDebug("Loading PCM ROM");
+ if (!loadPCMROM("CM32L_PCM.ROM")) {
+ if (!loadPCMROM("MT32_PCM.ROM")) {
+ printDebug("Init Error - Missing MT32_PCM.ROM");
+ report(ReportType_errorPCMROM, NULL);
+ return false;
+ }
+ }
+
+ printDebug("Initialising Timbre Bank A");
+ if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) {
+ return false;
+ }
+
+ printDebug("Initialising Timbre Bank B");
+ if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) {
+ return false;
+ }
+
+ printDebug("Initialising Timbre Bank R");
+ if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) {
+ return false;
+ }
+
+ printDebug("Initialising Timbre Bank M");
+ // CM-64 seems to initialise all bytes in this bank to 0.
+ memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64);
+
+ partialManager = new PartialManager(this);
+
+ pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount];
+
+ printDebug("Initialising PCM List");
+ initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount);
+
+ printDebug("Initialising Rhythm Temp");
+ memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4);
+
+ printDebug("Initialising Patches");
+ for (Bit8u i = 0; i < 128; i++) {
+ PatchParam *patch = &mt32ram.patches[i];
+ patch->timbreGroup = i / 64;
+ patch->timbreNum = i % 64;
+ patch->keyShift = 24;
+ patch->fineTune = 50;
+ patch->benderRange = 12;
+ patch->assignMode = 0;
+ patch->reverbSwitch = 1;
+ patch->dummy = 0;
+ }
+
+ printDebug("Initialising System");
+ // The MT-32 manual claims that "Standard pitch" is 442Hz.
+ mt32ram.system.masterTune = 0x40; // Confirmed on CM-64 as 0x4A, but SCUMM games use 0x40 and we don't want to initialise twice
+ mt32ram.system.reverbMode = 0; // Confirmed
+ mt32ram.system.reverbTime = 5; // Confirmed
+ mt32ram.system.reverbLevel = 3; // Confirmed
+ memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed
+ for (Bit8u i = 0; i < 9; i++) {
+ // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9}
+ // An alternative configuration can be selected by holding "Master Volume"
+ // and pressing "PART button 1" on the real MT-32's frontpanel.
+ // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9}
+ mt32ram.system.chanAssign[i] = i + 1;
+ }
+ mt32ram.system.masterVol = 100; // Confirmed
+ if (!refreshSystem())
+ return false;
+
+ for (int i = 0; i < 8; i++) {
+ mt32ram.patchSettings[i].outlevel = 80;
+ mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i];
+ memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv));
+ parts[i] = new Part(this, i);
+ parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]);
+ }
+ parts[8] = new RhythmPart(this, 8);
+
+ // For resetting mt32 mid-execution
+ mt32default = mt32ram;
+
+ iirFilter = &iir_filter_normal;
+
+#ifdef MT32EMU_HAVE_X86
+ bool availableSSE = DetectSIMD();
+ bool available3DNow = Detect3DNow();
+
+ if (availableSSE)
+ report(ReportType_availableSSE, NULL);
+ if (available3DNow)
+ report(ReportType_available3DNow, NULL);
+
+ if (available3DNow) {
+ printDebug("Detected and using SIMD (AMD 3DNow) extensions");
+ iirFilter = &iir_filter_3dnow;
+ report(ReportType_using3DNow, NULL);
+ } else if (availableSSE) {
+ printDebug("Detected and using SIMD (Intel SSE) extensions");
+ iirFilter = &iir_filter_sse;
+ report(ReportType_usingSSE, NULL);
+ }
+#endif
+
+ isOpen = true;
+ isEnabled = false;
+
+ printDebug("*** Initialisation complete ***");
+ return true;
+}
+
+void Synth::close(void) {
+ if (!isOpen)
+ return;
+
+ tables.freeNotes();
+ if (partialManager != NULL) {
+ delete partialManager;
+ partialManager = NULL;
+ }
+
+ if (reverbModel != NULL) {
+ delete reverbModel;
+ reverbModel = NULL;
+ }
+
+ for (int i = 0; i < 9; i++) {
+ if (parts[i] != NULL) {
+ delete parts[i];
+ parts[i] = NULL;
+ }
+ }
+ if (myProp.baseDir != NULL) {
+ delete myProp.baseDir;
+ myProp.baseDir = NULL;
+ }
+
+ delete[] pcmWaves;
+ delete[] pcmROMData;
+ isOpen = false;
+}
+
+void Synth::playMsg(Bit32u msg) {
+ // FIXME: Implement active sensing
+ unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4);
+ unsigned char chan = (unsigned char) (msg & 0x00000F);
+ unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8);
+ unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16);
+ isEnabled = true;
+
+ //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note);
+
+ signed char part = chantable[chan];
+ if (part < 0 || part > 8) {
+ printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity);
+ return;
+ }
+ playMsgOnPart(part, code, note, velocity);
+}
+
+void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) {
+ Bit32u bend;
+
+ //printDebug("Synth::playMsg(0x%02x)",msg);
+ switch (code) {
+ case 0x8:
+ //printDebug("Note OFF - Part %d", part);
+ // The MT-32 ignores velocity for note off
+ parts[part]->stopNote(note);
+ break;
+ case 0x9:
+ //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity);
+ if (velocity == 0) {
+ // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40
+ parts[part]->stopNote(note);
+ } else {
+ parts[part]->playNote(note, velocity);
+ }
+ break;
+ case 0xB: // Control change
+ switch (note) {
+ case 0x01: // Modulation
+ //printDebug("Modulation: %d", velocity);
+ parts[part]->setModulation(velocity);
+ break;
+ case 0x07: // Set volume
+ //printDebug("Volume set: %d", velocity);
+ parts[part]->setVolume(velocity);
+ break;
+ case 0x0A: // Pan
+ //printDebug("Pan set: %d", velocity);
+ parts[part]->setPan(velocity);
+ break;
+ case 0x0B:
+ //printDebug("Expression set: %d", velocity);
+ parts[part]->setExpression(velocity);
+ break;
+ case 0x40: // Hold (sustain) pedal
+ //printDebug("Hold pedal set: %d", velocity);
+ parts[part]->setHoldPedal(velocity>=64);
+ break;
+
+ case 0x79: // Reset all controllers
+ //printDebug("Reset all controllers");
+ //FIXME: Check for accuracy against real thing
+ parts[part]->setVolume(100);
+ parts[part]->setExpression(127);
+ parts[part]->setPan(64);
+ parts[part]->setBend(0x2000);
+ parts[part]->setHoldPedal(false);
+ break;
+
+ case 0x7B: // All notes off
+ //printDebug("All notes off");
+ parts[part]->allNotesOff();
+ break;
+
+ default:
+ printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity);
+ break;
+ }
+
+ break;
+ case 0xC: // Program change
+ //printDebug("Program change %01x", note);
+ parts[part]->setProgram(note);
+ break;
+ case 0xE: // Pitch bender
+ bend = (velocity << 7) | (note);
+ //printDebug("Pitch bender %02x", bend);
+ parts[part]->setBend(bend);
+ break;
+ default:
+ printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity);
+ break;
+ }
+
+ //midiOutShortMsg(m_out, msg);
+}
+
+void Synth::playSysex(const Bit8u *sysex, Bit32u len) {
+ if (len < 2) {
+ printDebug("playSysex: Message is too short for sysex (%d bytes)", len);
+ }
+ if (sysex[0] != 0xF0) {
+ printDebug("playSysex: Message lacks start-of-sysex (0xF0)");
+ return;
+ }
+ // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len.
+ Bit32u endPos;
+ for (endPos = 1; endPos < len; endPos++)
+ {
+ if (sysex[endPos] == 0xF7)
+ break;
+ }
+ if (endPos == len) {
+ printDebug("playSysex: Message lacks end-of-sysex (0xf7)");
+ return;
+ }
+ playSysexWithoutFraming(sysex + 1, endPos - 1);
+}
+
+void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) {
+ if (len < 4) {
+ printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len);
+ return;
+ }
+ if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) {
+ printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]);
+ return;
+ }
+ if (sysex[2] == SYSEX_MDL_D50) {
+ printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]);
+ return;
+ }
+ else if (sysex[2] != SYSEX_MDL_MT32) {
+ printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]);
+ return;
+ }
+ playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4);
+}
+
+void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) {
+ if (device > 0x10) {
+ // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels
+ printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device);
+ return;
+ }
+ if (len < 4) {
+ printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len);
+ return;
+ }
+ unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0);
+ if (checksum != sysex[len - 1]) {
+ printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum);
+ return;
+ }
+ len -= 1; // Exclude checksum
+ switch (command) {
+ case SYSEX_CMD_DT1:
+ writeSysex(device, sysex, len);
+ break;
+ case SYSEX_CMD_RQ1:
+ readSysex(device, sysex, len);
+ break;
+ default:
+ printDebug("playSysexWithoutFraming: Unsupported command %02x", command);
+ return;
+ }
+}
+
+void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) {
+}
+
+const MemoryRegion memoryRegions[8] = {
+ {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9},
+ {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85},
+ {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8},
+ {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128},
+ {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64},
+ {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1},
+ {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1},
+ {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1}
+};
+
+const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion);
+
+void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) {
+ Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]);
+ addr = MT32EMU_MEMADDR(addr);
+ sysex += 3;
+ len -= 3;
+ //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr));
+ // NOTE: Please keep both lower and upper bounds in each check, for ease of reading
+
+ // Process channel-specific sysex by converting it to device-global
+ if (device < 0x10) {
+ printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr));
+ if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) {
+ int offset;
+ if (chantable[device] == -1) {
+ printDebug(" (Channel not mapped to a partial... 0 offset)");
+ offset = 0;
+ } else if (chantable[device] == 8) {
+ printDebug(" (Channel mapped to rhythm... 0 offset)");
+ offset = 0;
+ } else {
+ offset = chantable[device] * sizeof(MemParams::PatchTemp);
+ printDebug(" (Setting extra offset to %d)", offset);
+ }
+ addr += MT32EMU_MEMADDR(0x030000) + offset;
+ } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) {
+ addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000);
+ } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) {
+ int offset;
+ if (chantable[device] == -1) {
+ printDebug(" (Channel not mapped to a partial... 0 offset)");
+ offset = 0;
+ } else if (chantable[device] == 8) {
+ printDebug(" (Channel mapped to rhythm... 0 offset)");
+ offset = 0;
+ } else {
+ offset = chantable[device] * sizeof(TimbreParam);
+ printDebug(" (Setting extra offset to %d)", offset);
+ }
+ addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset;
+ } else {
+ printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr));
+ return;
+ }
+ }
+
+ // Process device-global sysex (possibly converted from channel-specific sysex above)
+ for (;;) {
+ // Find the appropriate memory region
+ int regionNum;
+ const MemoryRegion *region = NULL; // Initialised to please compiler
+ for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) {
+ region = &memoryRegions[regionNum];
+ if (region->contains(addr)) {
+ writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex);
+ break;
+ }
+ }
+ if (regionNum == NUM_REGIONS) {
+ printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len);
+ break;
+ }
+ Bit32u next = region->next(addr, len);
+ if (next == 0) {
+ break;
+ }
+ addr += next;
+ sysex += next;
+ len -= next;
+ }
+}
+
+void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) {
+ int regionNum;
+ const MemoryRegion *region = NULL;
+ for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) {
+ region = &memoryRegions[regionNum];
+ if (region->contains(addr)) {
+ readMemoryRegion(region, addr, len, data);
+ break;
+ }
+ }
+}
+
+void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) {
+ unsigned int first = region->firstTouched(addr);
+ //unsigned int last = region->lastTouched(addr, len);
+ unsigned int off = region->firstTouchedOffset(addr);
+ len = region->getClampedLen(addr, len);
+
+ unsigned int m;
+
+ switch (region->type) {
+ case MR_PatchTemp:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m];
+ break;
+ case MR_RhythmTemp:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m];
+ break;
+ case MR_TimbreTemp:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m];
+ break;
+ case MR_Patches:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m];
+ break;
+ case MR_Timbres:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m];
+ break;
+ case MR_System:
+ for (m = 0; m < len; m++)
+ data[m] = ((Bit8u *)&mt32ram.system)[m + off];
+ break;
+ default:
+ for (m = 0; m < len; m += 2) {
+ data[m] = 0xff;
+ if (m + 1 < len) {
+ data[m+1] = (Bit8u)region->type;
+ }
+ }
+ // TODO: Don't care about the others ATM
+ break;
+ }
+
+}
+
+void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) {
+ unsigned int first = region->firstTouched(addr);
+ unsigned int last = region->lastTouched(addr, len);
+ unsigned int off = region->firstTouchedOffset(addr);
+ switch (region->type) {
+ case MR_PatchTemp:
+ for (unsigned int m = 0; m < len; m++) {
+ ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m];
+ }
+ //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len);
+
+ for (unsigned int i = first; i <= last; i++) {
+ int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum;
+ char timbreName[11];
+ memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10);
+ timbreName[10] = 0;
+ printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel);
+ if (parts[i] != NULL) {
+ if (i != 8) {
+ // Note: Confirmed on CM-64 that we definitely *should* update the timbre here,
+ // but only in the case that the sysex actually writes to those values
+ if (i == first && off > 2) {
+ printDebug(" (Not updating timbre, since those values weren't touched)");
+ } else {
+ parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre);
+ }
+ }
+ parts[i]->refresh();
+ }
+ }
+ break;
+ case MR_RhythmTemp:
+ for (unsigned int m = 0; m < len; m++)
+ ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m];
+ for (unsigned int i = first; i <= last; i++) {
+ int timbreNum = mt32ram.rhythmSettings[i].timbre;
+ char timbreName[11];
+ if (timbreNum < 94) {
+ memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10);
+ timbreName[10] = 0;
+ } else {
+ strcpy(timbreName, "[None]");
+ }
+ printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName);
+ }
+ if (parts[8] != NULL) {
+ parts[8]->refresh();
+ }
+ break;
+ case MR_TimbreTemp:
+ for (unsigned int m = 0; m < len; m++)
+ ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m];
+ for (unsigned int i = first; i <= last; i++) {
+ char instrumentName[11];
+ memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10);
+ instrumentName[10] = 0;
+ printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName);
+ if (parts[i] != NULL) {
+ parts[i]->refresh();
+ }
+ }
+ break;
+ case MR_Patches:
+ for (unsigned int m = 0; m < len; m++)
+ ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m];
+ for (unsigned int i = first; i <= last; i++) {
+ PatchParam *patch = &mt32ram.patches[i];
+ int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum;
+ char instrumentName[11];
+ memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10);
+ instrumentName[10] = 0;
+ Bit8u *n = (Bit8u *)patch;
+ printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]);
+ // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using
+ // based on a timbre match (but many patches could have the same timbre!)
+ // If this refresh is really correct, we should store the patch number in use by each part.
+ /*
+ for (int part = 0; part < 8; part++) {
+ if (parts[part] != NULL) {
+ int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum;
+ if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) {
+ parts[part]->setPatch(patch);
+ parts[part]->RefreshPatch();
+ }
+ }
+ }
+ */
+ }
+ break;
+ case MR_Timbres:
+ // Timbres
+ first += 128;
+ last += 128;
+ for (unsigned int m = 0; m < len; m++)
+ ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m];
+ for (unsigned int i = first; i <= last; i++) {
+ char instrumentName[11];
+ memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10);
+ instrumentName[10] = 0;
+ printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName);
+ // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)...
+ // Does the real MT-32 automatically do this?
+ for (unsigned int part = 0; part < 9; part++) {
+ if (parts[part] != NULL) {
+ parts[part]->refreshTimbre(i);
+ }
+ }
+ }
+ break;
+ case MR_System:
+ for (unsigned int m = 0; m < len; m++)
+ ((Bit8u *)&mt32ram.system)[m + off] = data[m];
+
+ report(ReportType_devReconfig, NULL);
+
+ printDebug("WRITE-SYSTEM:");
+ refreshSystem();
+ break;
+ case MR_Display:
+ char buf[MAX_SYSEX_SIZE];
+ memcpy(&buf, &data[0], len);
+ buf[len] = 0;
+ printDebug("WRITE-LCD: %s", buf);
+ report(ReportType_lcdMessage, buf);
+ break;
+ case MR_Reset:
+ printDebug("RESET");
+ report(ReportType_devReset, NULL);
+ partialManager->deactivateAll();
+ mt32ram = mt32default;
+ for (int i = 0; i < 9; i++) {
+ parts[i]->refresh();
+ }
+ isEnabled = false;
+ break;
+ }
+}
+
+bool Synth::refreshSystem() {
+ memset(chantable, -1, sizeof(chantable));
+
+ for (unsigned int i = 0; i < 9; i++) {
+ //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]);
+ if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) {
+ parts[i]->allSoundOff();
+ } else {
+ chantable[(int)mt32ram.system.chanAssign[i]] = (char)i;
+ }
+ }
+ //FIXME:KG: This is just an educated guess.
+ // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here)
+ // The MT-32 documentation claims a range of 432.1Hz-457.6Hz
+ masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f));
+ printDebug(" Master Tune: %f", (double)masterTune);
+ printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel);
+ report(ReportType_newReverbMode, &mt32ram.system.reverbMode);
+ report(ReportType_newReverbTime, &mt32ram.system.reverbTime);
+ report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel);
+
+ if (myProp.useDefaultReverb) {
+ initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel);
+ } else {
+ initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel);
+ }
+
+ Bit8u *rset = mt32ram.system.reserveSettings;
+ printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]);
+ int pr = partialManager->setReserve(rset);
+ if (pr != 32)
+ printDebug(" (Partial Reserve Table with less than 32 partials reserved!)");
+ rset = mt32ram.system.chanAssign;
+ printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]);
+ printDebug(" Master volume: %d", mt32ram.system.masterVol);
+ masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100);
+ if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) {
+ report(ReportType_errorSampleRate, NULL);
+ return false;
+ }
+ return true;
+}
+
+bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) {
+ // Sysex header
+ if (!file->writeBit8u(0xF0))
+ return false;
+ if (!file->writeBit8u(0x41))
+ return false;
+ if (!file->writeBit8u(0x10))
+ return false;
+ if (!file->writeBit8u(0x16))
+ return false;
+ if (!file->writeBit8u(0x12))
+ return false;
+
+ char lsb = (char)(address & 0x7f);
+ char isb = (char)((address >> 7) & 0x7f);
+ char msb = (char)(((address >> 14) & 0x7f) | 0x08);
+
+ //Address
+ if (!file->writeBit8u(msb))
+ return false;
+ if (!file->writeBit8u(isb))
+ return false;
+ if (!file->writeBit8u(lsb))
+ return false;
+
+ //Data
+ if (file->write(timbre, 246) != 246)
+ return false;
+
+ //Checksum
+ unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb);
+ if (!file->writeBit8u(checksum))
+ return false;
+
+ //End of sysex
+ if (!file->writeBit8u(0xF7))
+ return false;
+ return true;
+}
+
+int Synth::dumpTimbres(const char *filename, int start, int len) {
+ File *file = openFile(filename, File::OpenMode_write);
+ if (file == NULL)
+ return -1;
+
+ for (int timbreNum = start; timbreNum < start + len; timbreNum++) {
+ int useaddr = (timbreNum - start) * 256;
+ TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre;
+ if (!dumpTimbre(file, timbre, useaddr))
+ break;
+ }
+ closeFile(file);
+ return 0;
+}
+
+void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) {
+#if MT32EMU_USE_MMX > 2
+ //FIXME:KG: This appears to introduce crackle
+ int donelen = i386_produceOutput1(useBuf, stream, len, volume);
+ len -= donelen;
+ stream += donelen * 2;
+ useBuf += donelen * 2;
+#endif
+ int end = len * 2;
+ while (end--) {
+ *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15);
+ stream++;
+ }
+}
+
+void Synth::render(Bit16s *stream, Bit32u len) {
+ memset(stream, 0, len * sizeof (Bit16s) * 2);
+ if (!isEnabled)
+ return;
+ while (len > 0) {
+ Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len;
+ doRender(stream, thisLen);
+ len -= thisLen;
+ stream += 2 * thisLen;
+ }
+}
+
+void Synth::doRender(Bit16s *stream, Bit32u len) {
+ partialManager->ageAll();
+
+ if (myProp.useReverb) {
+ for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialManager->shouldReverb(i)) {
+ if (partialManager->produceOutput(i, &tmpBuffer[0], len)) {
+ ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume);
+ }
+ }
+ }
+ Bit32u m = 0;
+ for (unsigned int i = 0; i < len; i++) {
+ sndbufl[i] = (float)stream[m] / 32767.0f;
+ m++;
+ sndbufr[i] = (float)stream[m] / 32767.0f;
+ m++;
+ }
+ reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1);
+ m=0;
+ for (unsigned int i = 0; i < len; i++) {
+ stream[m] = (Bit16s)(outbufl[i] * 32767.0f);
+ m++;
+ stream[m] = (Bit16s)(outbufr[i] * 32767.0f);
+ m++;
+ }
+ for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (!partialManager->shouldReverb(i)) {
+ if (partialManager->produceOutput(i, &tmpBuffer[0], len)) {
+ ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume);
+ }
+ }
+ }
+ } else {
+ for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (partialManager->produceOutput(i, &tmpBuffer[0], len))
+ ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume);
+ }
+ }
+
+ partialManager->clearAlreadyOutputed();
+
+#if MT32EMU_MONITOR_PARTIALS == 1
+ samplepos += len;
+ if (samplepos > myProp.SampleRate * 5) {
+ samplepos = 0;
+ int partialUsage[9];
+ partialManager->GetPerPartPartialUsage(partialUsage);
+ printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]);
+ printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount());
+ }
+#endif
+}
+
+const Partial *Synth::getPartial(unsigned int partialNum) const {
+ return partialManager->getPartial(partialNum);
+}
+
+const Part *Synth::getPart(unsigned int partNum) const {
+ if (partNum > 8)
+ return NULL;
+ return parts[partNum];
+}
+
+}
diff --git a/audio/softsynth/mt32/synth.h b/audio/softsynth/mt32/synth.h
new file mode 100644
index 0000000000..3fc303d322
--- /dev/null
+++ b/audio/softsynth/mt32/synth.h
@@ -0,0 +1,300 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_SYNTH_H
+#define MT32EMU_SYNTH_H
+
+#include "common/scummsys.h"
+
+class revmodel;
+
+namespace MT32Emu {
+
+class File;
+class TableInitialiser;
+class Partial;
+class PartialManager;
+class Part;
+
+enum ReportType {
+ // Errors
+ ReportType_errorControlROM = 1,
+ ReportType_errorPCMROM,
+ ReportType_errorSampleRate,
+
+ // Progress
+ ReportType_progressInit,
+
+ // HW spec
+ ReportType_availableSSE,
+ ReportType_available3DNow,
+ ReportType_usingSSE,
+ ReportType_using3DNow,
+
+ // General info
+ ReportType_lcdMessage,
+ ReportType_devReset,
+ ReportType_devReconfig,
+ ReportType_newReverbMode,
+ ReportType_newReverbTime,
+ ReportType_newReverbLevel
+};
+
+struct SynthProperties {
+ // Sample rate to use in mixing
+ int sampleRate;
+
+ // Flag to activate reverb. True = use reverb, False = no reverb
+ bool useReverb;
+ // True to use software set reverb settings, False to set reverb settings in
+ // following parameters
+ bool useDefaultReverb;
+ // When not using the default settings, this specifies one of the 4 reverb types
+ // 1 = Room 2 = Hall 3 = Plate 4 = Tap
+ unsigned char reverbType;
+ // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement)
+ unsigned char reverbTime;
+ // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement)
+ unsigned char reverbLevel;
+ // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
+ // Not used if "openFile" is set. May be NULL in any case.
+ char *baseDir;
+ // This is used as the first argument to all callbacks
+ void *userData;
+ // Callback for reporting various errors and information. May be NULL
+ int (*report)(void *userData, ReportType type, const void *reportData);
+ // Callback for debug messages, in vprintf() format
+ void (*printDebug)(void *userData, const char *fmt, va_list list);
+ // Callback for providing an implementation of File, opened and ready for use
+ // May be NULL, in which case a default implementation will be used.
+ File *(*openFile)(void *userData, const char *filename, File::OpenMode mode);
+ // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
+ void (*closeFile)(void *userData, File *file);
+};
+
+// This is the specification of the Callback routine used when calling the RecalcWaveforms
+// function
+typedef void (*recalcStatusCallback)(int percDone);
+
+// This external function recreates the base waveform file (waveforms.raw) using a specifed
+// sampling rate. The callback routine provides interactivity to let the user know what
+// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the
+// callback routine, no status is reported.
+bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack);
+
+typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr);
+
+const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
+
+const Bit8u SYSEX_MDL_MT32 = 0x16;
+const Bit8u SYSEX_MDL_D50 = 0x14;
+
+const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1
+const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1
+const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data
+const Bit8u SYSEX_CMD_RQD = 0x41; // Request data
+const Bit8u SYSEX_CMD_DAT = 0x42; // Data set
+const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge
+const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
+const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
+const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
+
+const unsigned int CONTROL_ROM_SIZE = 64 * 1024;
+
+struct ControlROMPCMStruct
+{
+ Bit8u pos;
+ Bit8u len;
+ Bit8u pitchLSB;
+ Bit8u pitchMSB;
+};
+
+struct ControlROMMap {
+ Bit16u idPos;
+ Bit16u idLen;
+ const char *idBytes;
+ Bit16u pcmTable;
+ Bit16u pcmCount;
+ Bit16u timbreAMap;
+ Bit16u timbreAOffset;
+ Bit16u timbreBMap;
+ Bit16u timbreBOffset;
+ Bit16u timbreRMap;
+ Bit16u timbreRCount;
+ Bit16u rhythmSettings;
+ Bit16u rhythmSettingsCount;
+ Bit16u reserveSettings;
+ Bit16u panSettings;
+ Bit16u programSettings;
+};
+
+enum MemoryRegionType {
+ MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
+};
+
+class MemoryRegion {
+public:
+ MemoryRegionType type;
+ Bit32u startAddr, entrySize, entries;
+
+ int lastTouched(Bit32u addr, Bit32u len) const {
+ return (offset(addr) + len - 1) / entrySize;
+ }
+ int firstTouchedOffset(Bit32u addr) const {
+ return offset(addr) % entrySize;
+ }
+ int firstTouched(Bit32u addr) const {
+ return offset(addr) / entrySize;
+ }
+ Bit32u regionEnd() const {
+ return startAddr + entrySize * entries;
+ }
+ bool contains(Bit32u addr) const {
+ return addr >= startAddr && addr < regionEnd();
+ }
+ int offset(Bit32u addr) const {
+ return addr - startAddr;
+ }
+ Bit32u getClampedLen(Bit32u addr, Bit32u len) const {
+ if (addr + len > regionEnd())
+ return regionEnd() - addr;
+ return len;
+ }
+ Bit32u next(Bit32u addr, Bit32u len) const {
+ if (addr + len > regionEnd()) {
+ return regionEnd() - addr;
+ }
+ return 0;
+ }
+};
+
+
+class Synth {
+friend class Part;
+friend class RhythmPart;
+friend class Partial;
+friend class Tables;
+private:
+ bool isEnabled;
+
+ iir_filter_type iirFilter;
+
+ PCMWaveEntry *pcmWaves; // Array
+
+ const ControlROMMap *controlROMMap;
+ Bit8u controlROMData[CONTROL_ROM_SIZE];
+ Bit16s *pcmROMData;
+ int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
+
+ Bit8s chantable[32];
+
+ #if MT32EMU_MONITOR_PARTIALS == 1
+ static Bit32s samplepos = 0;
+ #endif
+
+ Tables tables;
+
+ MemParams mt32ram, mt32default;
+
+ revmodel *reverbModel;
+
+ float masterTune;
+ Bit16u masterVolume;
+
+ bool isOpen;
+
+ PartialManager *partialManager;
+ Part *parts[9];
+
+ Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2];
+ float sndbufl[MAX_SAMPLE_OUTPUT];
+ float sndbufr[MAX_SAMPLE_OUTPUT];
+ float outbufl[MAX_SAMPLE_OUTPUT];
+ float outbufr[MAX_SAMPLE_OUTPUT];
+
+ SynthProperties myProp;
+
+ bool loadPreset(File *file);
+ void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel);
+ void doRender(Bit16s * stream, Bit32u len);
+
+ void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
+ void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
+ void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
+ void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
+
+ bool loadControlROM(const char *filename);
+ bool loadPCMROM(const char *filename);
+ bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr);
+ int dumpTimbres(const char *filename, int start, int len);
+
+ bool initPCMList(Bit16u mapAddress, Bit16u count);
+ bool initRhythmTimbres(Bit16u mapAddress, Bit16u count);
+ bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre);
+ bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen);
+ bool refreshSystem();
+
+protected:
+ int report(ReportType type, const void *reportData);
+ File *openFile(const char *filename, File::OpenMode mode);
+ void closeFile(File *file);
+ void printDebug(const char *fmt, ...) GCC_PRINTF(2, 3);
+
+public:
+ static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
+
+ Synth();
+ ~Synth();
+
+ // Used to initialise the MT-32. Must be called before any other function.
+ // Returns true if initialization was sucessful, otherwise returns false.
+ bool open(SynthProperties &useProp);
+
+ // Closes the MT-32 and deallocates any memory used by the synthesizer
+ void close(void);
+
+ // Sends a 4-byte MIDI message to the MT-32 for immediate playback
+ void playMsg(Bit32u msg);
+ void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity);
+
+ // Sends a string of Sysex commands to the MT-32 for immediate interpretation
+ // The length is in bytes
+ void playSysex(const Bit8u *sysex, Bit32u len);
+ void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len);
+ void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len);
+ void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
+
+ // This callback routine is used to have the MT-32 generate samples to the specified
+ // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo,
+ // one sample is 4 bytes)
+ void render(Bit16s * stream, Bit32u len);
+
+ const Partial *getPartial(unsigned int partialNum) const;
+
+ void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
+
+ // partNum should be 0..7 for Part 1..8, or 8 for Rhythm
+ const Part *getPart(unsigned int partNum) const;
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/tables.cpp b/audio/softsynth/mt32/tables.cpp
new file mode 100644
index 0000000000..eba4d2a520
--- /dev/null
+++ b/audio/softsynth/mt32/tables.cpp
@@ -0,0 +1,757 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "mt32emu.h"
+
+#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__)
+// Older versions of Mac OS X didn't supply a powf function, so using it
+// will cause a binary incompatibility when trying to run a binary built
+// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide
+// powf, floorf, fabsf etc. at all.
+// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in
+// libstdc++. math/stubs.o should be empty, but it comes with a symbol for
+// powf, resulting in a linker error because of multiple definitions.
+// Hence we re-define them here. The only potential drawback is that it
+// might be a little bit slower this way.
+#define powf(x,y) ((float)pow(x,y))
+#define floorf(x) ((float)floor(x))
+#define fabsf(x) ((float)fabs(x))
+#endif
+
+#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x))
+
+namespace MT32Emu {
+
+//Amplitude time velocity follow exponential coefficients
+static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637};
+static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539};
+
+// These are division constants for the TVF depth key follow
+static const Bit32u depexp[5] = {3000, 950, 485, 255, 138};
+
+//Envelope time keyfollow exponential coefficients
+static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215};
+static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067};
+
+// Begin filter stuff
+
+// Pre-warp the coefficients of a numerator or denominator.
+// Note that a0 is assumed to be 1, so there is no wrapping
+// of it.
+static void prewarp(double *a1, double *a2, double fc, double fs) {
+ double wp;
+
+ wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs);
+
+ *a2 = *a2 / (wp * wp);
+ *a1 = *a1 / wp;
+}
+
+// Transform the numerator and denominator coefficients
+// of s-domain biquad section into corresponding
+// z-domain coefficients.
+//
+// Store the 4 IIR coefficients in array pointed by coef
+// in following order:
+// beta1, beta2 (denominator)
+// alpha1, alpha2 (numerator)
+//
+// Arguments:
+// a0-a2 - s-domain numerator coefficients
+// b0-b2 - s-domain denominator coefficients
+// k - filter gain factor. initially set to 1
+// and modified by each biquad section in such
+// a way, as to make it the coefficient by
+// which to multiply the overall filter gain
+// in order to achieve a desired overall filter gain,
+// specified in initial value of k.
+// fs - sampling rate (Hz)
+// coef - array of z-domain coefficients to be filled in.
+//
+// Return:
+// On return, set coef z-domain coefficients
+static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) {
+ double ad, bd;
+
+ // alpha (Numerator in s-domain)
+ ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0;
+ // beta (Denominator in s-domain)
+ bd = 4. * b2 * fs * fs + 2. * b1* fs + b0;
+
+ // update gain constant for this section
+ *k *= ad/bd;
+
+ // Denominator
+ *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1
+ *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2
+
+ // Nominator
+ *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1
+ *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2
+}
+
+// a0-a2: numerator coefficients
+// b0-b2: denominator coefficients
+// fc: Filter cutoff frequency
+// fs: sampling rate
+// k: overall gain factor
+// coef: pointer to 4 iir coefficients
+static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) {
+ // Calculate a1 and a2 and overwrite the original values
+ prewarp(a1, a2, fc, fs);
+ prewarp(b1, b2, fc, fs);
+ bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef);
+}
+
+static void initFilter(float fs, float fc, float *icoeff, float Q) {
+ float *coef;
+ double a0, a1, a2, b0, b1, b2;
+
+ double k = 1.5; // Set overall filter gain factor
+ coef = icoeff + 1; // Skip k, or gain
+
+ // Section 1
+ a0 = 1.0;
+ a1 = 0;
+ a2 = 0;
+ b0 = 1.0;
+ b1 = 0.765367 / Q; // Divide by resonance or Q
+ b2 = 1.0;
+ szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
+ coef += 4; // Point to next filter section
+
+ // Section 2
+ a0 = 1.0;
+ a1 = 0;
+ a2 = 0;
+ b0 = 1.0;
+ b1 = 1.847759 / Q;
+ b2 = 1.0;
+ szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
+
+ icoeff[0] = (float)k;
+}
+
+void Tables::initFiltCoeff(float samplerate) {
+ for (int j = 0; j < FILTERGRAN; j++) {
+ for (int res = 0; res < 31; res++) {
+ float tres = resonanceFactor[res];
+ initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres);
+ }
+ }
+}
+
+void Tables::initEnvelopes(float samplerate) {
+ for (int lf = 0; lf <= 100; lf++) {
+ float elf = (float)lf;
+
+ // General envelope
+ // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter,
+ // when all other times were 0 and all levels were 100. Note that variations occur depending on the level
+ // delta of the section, which we're not fully emulating.
+ float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f;
+ int samples = (int)(seconds * samplerate);
+ envTime[lf] = samples;
+
+ // Cap on envelope times depending on the level delta
+ if (elf == 0) {
+ envDeltaMaxTime[lf] = 63;
+ } else {
+ float cap = 11.0f * (float)log(elf) + 64;
+ if (cap > 100.0f) {
+ cap = 100.0f;
+ }
+ envDeltaMaxTime[lf] = (int)cap;
+ }
+
+
+ // This (approximately) represents the time durations when the target level is 0.
+ // Not sure why this is a special case, but it's seen to be from the real thing.
+ seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f;
+ envDecayTime[lf] = (int)(seconds * samplerate);
+
+ // I am certain of this: Verified by hand LFO log
+ lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f));
+ }
+}
+
+void Tables::initMT32ConstantTables(Synth *synth) {
+ int lf;
+ synth->printDebug("Initialising Pitch Tables");
+ for (lf = -108; lf <= 108; lf++) {
+ tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f)));
+ //synth->printDebug("KT %d = %d", f, keytable[f+108]);
+ }
+
+ for (int res = 0; res < 31; res++) {
+ resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f;
+ }
+
+ int period = 65536;
+
+ for (int ang = 0; ang < period; ang++) {
+ int halfang = (period / 2);
+ int angval = ang % halfang;
+ float tval = (((float)angval / (float)halfang) - 0.5f) * 2;
+ if (ang >= halfang)
+ tval = -tval;
+ sintable[ang] = (Bit16s)(tval * 50.0f) + 50;
+ }
+
+ int velt, dep;
+ float tempdep;
+ for (velt = 0; velt < 128; velt++) {
+ for (dep = 0; dep < 5; dep++) {
+ if (dep > 0) {
+ float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]);
+ tempdep = 256.0f * ff;
+ envTimeVelfollowMult[dep][velt] = (int)tempdep;
+ //if ((velt % 16) == 0) {
+ // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep);
+ //}
+ } else
+ envTimeVelfollowMult[dep][velt] = 256;
+ }
+
+ for (dep = -7; dep < 8; dep++) {
+ float fldep = (float)abs(dep) / 7.0f;
+ fldep = powf(fldep,2.5f);
+ if (dep < 0)
+ fldep = fldep * -1.0f;
+ pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0);
+ }
+ }
+
+ for (dep = 0; dep <= 100; dep++) {
+ for (velt = 0; velt < 128; velt++) {
+ float fdep = (float)dep * 0.000347013f; // Another MT-32 constant
+ float fv = ((float)velt - 64.0f)/7.26f;
+ float flogdep = powf(10, fdep * fv);
+ float fbase;
+
+ if (velt > 64)
+ synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0);
+ else {
+ //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96));
+ fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f));
+ synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0);
+ }
+ //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]);
+ }
+ }
+
+ for (lf = 0; lf < 128; lf++) {
+ float veloFract = lf / 127.0f;
+ for (int velsens = 0; velsens <= 100; velsens++) {
+ float sensFract = (velsens - 50) / 50.0f;
+ if (velsens < 50) {
+ tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8);
+ } else {
+ tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8);
+ }
+ }
+ }
+
+ for (lf = 0; lf <= 100; lf++) {
+ // Converts the 0-100 range used by the MT-32 to volume multiplier
+ volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7);
+ }
+
+ for (lf = 0; lf <= 100; lf++) {
+ float mv = lf / 100.0f;
+ float pt = mv - 0.5f;
+ if (pt < 0)
+ pt = 0;
+
+ // Original (CC version)
+ //pwFactor[lf] = (int)(pt * 210.04f) + 128;
+
+ // Approximation from sample comparison
+ pwFactor[lf] = (int)(pt * 179.0f) + 128;
+ }
+
+ for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) {
+ int myRand;
+ myRand = rand();
+ //myRand = ((myRand - 16383) * 7168) >> 16;
+ // This one is slower but works with all values of RAND_MAX
+ myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2));
+ //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason
+ noiseBuf[i] = (Bit16s)myRand;
+ }
+
+ float tdist;
+ float padjtable[51];
+ for (lf = 0; lf <= 50; lf++) {
+ if (lf == 0)
+ padjtable[lf] = 7;
+ else if (lf == 1)
+ padjtable[lf] = 6;
+ else if (lf == 2)
+ padjtable[lf] = 5;
+ else if (lf == 3)
+ padjtable[lf] = 4;
+ else if (lf == 4)
+ padjtable[lf] = 4 - (0.333333f);
+ else if (lf == 5)
+ padjtable[lf] = 4 - (0.333333f * 2);
+ else if (lf == 6)
+ padjtable[lf] = 3;
+ else if ((lf > 6) && (lf <= 12)) {
+ tdist = (lf-6.0f) / 6.0f;
+ padjtable[lf] = 3.0f - tdist;
+ } else if ((lf > 12) && (lf <= 25)) {
+ tdist = (lf - 12.0f) / 13.0f;
+ padjtable[lf] = 2.0f - tdist;
+ } else {
+ tdist = (lf - 25.0f) / 25.0f;
+ padjtable[lf] = 1.0f - tdist;
+ }
+ //synth->printDebug("lf %d = padj %f", lf, (double)padjtable[lf]);
+ }
+
+ float lfp, depf, finalval, tlf;
+ int depat, pval, depti;
+ for (lf = 0; lf <= 10; lf++) {
+ // I believe the depth is cubed or something
+
+ for (depat = 0; depat <= 100; depat++) {
+ if (lf > 0) {
+ depti = abs(depat - 50);
+ tlf = (float)lf - padjtable[depti];
+ if (tlf < 0)
+ tlf = 0;
+ lfp = (float)exp(0.713619942f * tlf) / 407.4945111f;
+
+ if (depat < 50)
+ finalval = 4096.0f * powf(2, -lfp);
+ else
+ finalval = 4096.0f * powf(2, lfp);
+ pval = (int)finalval;
+
+ pitchEnvVal[lf][depat] = pval;
+ //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, (double)tlf, (double)lfp);
+ } else {
+ pitchEnvVal[lf][depat] = 4096;
+ //synth->printDebug("lf %d depat %d pval 4096", lf, depat);
+ }
+ }
+ }
+ for (lf = 0; lf <= 100; lf++) {
+ // It's linear - verified on MT-32 - one of the few things linear
+ lfp = ((float)lf * 0.1904f) / 310.55f;
+
+ for (depat = 0; depat <= 100; depat++) {
+ depf = ((float)depat - 50.0f) / 50.0f;
+ //finalval = pow(2, lfp * depf * .5);
+ finalval = 4096.0f + (4096.0f * lfp * depf);
+
+ pval = (int)finalval;
+
+ lfoShift[lf][depat] = pval;
+
+ //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval);
+ }
+ }
+
+ for (lf = 0; lf <= 12; lf++) {
+ for (int distval = 0; distval < 128; distval++) {
+ float amplog, dval;
+ if (lf == 0) {
+ amplog = 0;
+ dval = 1;
+ tvaBiasMult[lf][distval] = 256;
+ } else {
+ /*
+ amplog = powf(1.431817011f, (float)lf) / FLOAT_PI;
+ dval = ((128.0f - (float)distval) / 128.0f);
+ amplog = exp(amplog);
+ dval = powf(amplog, dval) / amplog;
+ tvaBiasMult[lf][distval] = (int)(dval * 256.0);
+ */
+ // Lets assume for a second it's linear
+
+ // Distance of full volume reduction
+ amplog = (float)(12.0f / (float)lf) * 24.0f;
+ if (distval > amplog) {
+ tvaBiasMult[lf][distval] = 0;
+ } else {
+ dval = (amplog - (float)distval) / amplog;
+ tvaBiasMult[lf][distval] = (int)(dval * 256.0f);
+ }
+ }
+ //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvaBiasMult[lf][distval],(double)amplog);
+ }
+ }
+
+ for (lf = 0; lf <= 14; lf++) {
+ for (int distval = 0; distval < 128; distval++) {
+ float filval = fabsf((float)((lf - 7) * 12) / 7.0f);
+ float amplog, dval;
+ if (lf == 7) {
+ amplog = 0;
+ dval = 1;
+ tvfBiasMult[lf][distval] = 256;
+ } else {
+ //amplog = pow(1.431817011, filval) / FLOAT_PI;
+ amplog = powf(1.531817011f, filval) / FLOAT_PI;
+ dval = (128.0f - (float)distval) / 128.0f;
+ amplog = (float)exp(amplog);
+ dval = powf(amplog,dval)/amplog;
+ if (lf < 8) {
+ tvfBiasMult[lf][distval] = (int)(dval * 256.0f);
+ } else {
+ dval = powf(dval, 0.3333333f);
+ if (dval < 0.01f)
+ dval = 0.01f;
+ dval = 1 / dval;
+ tvfBiasMult[lf][distval] = (int)(dval * 256.0f);
+ }
+ }
+ //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvfBiasMult[lf][distval],(double)amplog);
+ }
+ }
+}
+
+// Per-note table initialisation follows
+
+static void initSaw(NoteLookup *noteLookup, Bit32s div2) {
+ int tmpdiv = div2 << 16;
+ for (int rsaw = 0; rsaw <= 100; rsaw++) {
+ float fsaw;
+ if (rsaw < 50)
+ fsaw = 50.0f;
+ else
+ fsaw = (float)rsaw;
+
+ //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132
+ float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f;
+ noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16;
+ //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]);
+ }
+}
+
+static void initDep(KeyLookup *keyLookup, float f) {
+ for (int dep = 0; dep < 5; dep++) {
+ if (dep == 0) {
+ keyLookup->envDepthMult[dep] = 256;
+ keyLookup->envTimeMult[dep] = 256;
+ } else {
+ float depfac = 3000.0f;
+ float ff, tempdep;
+ depfac = (float)depexp[dep];
+
+ ff = (f - (float)MIDDLEC) / depfac;
+ tempdep = powf(2, ff) * 256.0f;
+ keyLookup->envDepthMult[dep] = (int)tempdep;
+
+ ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]);
+ keyLookup->envTimeMult[dep] = (int)(ff * 256.0f);
+ }
+ }
+ //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", (double)f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]);
+}
+
+Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) {
+ Bit32s x = (Bit32s)(input * ampVal);
+ if (x < -ampVal - 1) {
+ synth->printDebug("%s==%d<-WGAMP-1!", n, x);
+ x = (Bit32s)(-ampVal - 1);
+ } else if (x > ampVal) {
+ synth->printDebug("%s==%d>WGAMP!", n, x);
+ x = (Bit32s)ampVal;
+ }
+ return (Bit16s)x;
+}
+
+File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) {
+ int iDiv2 = (int)div2;
+ noteLookup->waveformSize[0] = iDiv2 << 1;
+ noteLookup->waveformSize[1] = iDiv2 << 1;
+ noteLookup->waveformSize[2] = iDiv2 << 2;
+ for (int i = 0; i < 3; i++) {
+ if (noteLookup->waveforms[i] == NULL) {
+ noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]];
+ }
+ }
+ if (file != NULL) {
+ for (int i = 0; i < 3 && file != NULL; i++) {
+ size_t len = noteLookup->waveformSize[i];
+ for (unsigned int j = 0; j < len; j++) {
+ if (!file->readBit16u((Bit16u *)&noteLookup->waveforms[i][j])) {
+ synth->printDebug("Error reading wave file cache!");
+ file->close();
+ file = NULL;
+ break;
+ }
+ }
+ }
+ }
+ if (file == NULL) {
+ double sd = DOUBLE_PI / div2;
+
+ for (int fa = 0; fa < (iDiv2 << 1); fa++) {
+ // sa ranges from 0 to 2PI
+ double sa = fa * sd;
+
+ // Calculate a sample for the bandlimited sawtooth wave
+ double saw = 0.0;
+ int sincs = iDiv2 >> 1;
+ double sinus = 1.0;
+ for (int sincNum = 1; sincNum <= sincs; sincNum++) {
+ saw += sin(sinus * sa) / sinus;
+ sinus++;
+ }
+
+ // This works pretty well
+ // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude
+ noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84);
+ noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0));
+ noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI));
+ noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI));
+ }
+ }
+ return file;
+}
+
+static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) {
+ for (int tr = 0; tr <= 200; tr++) {
+ float ftr = (float)tr;
+
+ // Verified exact on MT-32
+ if (tr > 100)
+ ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f);
+
+ // I think this is the one
+ float brsq = powf(10.0f, (tr / 50.0f) - 1.0f);
+ noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN);
+ if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16))
+ noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16);
+
+ float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f;
+ noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN);
+ if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16))
+ noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16);
+ }
+}
+
+static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) {
+ for (int cf = 0; cf <= 100; cf++) {
+ float cfmult = (float)cf;
+
+ for (int tf = 0;tf <= 100; tf++) {
+ float tfadd = (float)tf;
+
+ //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f;
+ //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f));
+
+ float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f);
+
+ noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN);
+ if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16))
+ noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16);
+ }
+ }
+}
+
+File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) {
+ float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0));
+ float div2 = rate * 2.0f / freq;
+ noteLookup->div2 = (int)div2;
+
+ if (noteLookup->div2 == 0)
+ noteLookup->div2 = 1;
+
+ initSaw(noteLookup, noteLookup->div2);
+
+ //synth->printDebug("Note %f; freq=%f, div=%f", (double)note, (double)freq, (double) rate / freq);
+ file = initWave(synth, noteLookup, WGAMP, div2, file);
+
+ // Create the pitch tables
+ if (noteLookup->wavTable == NULL)
+ noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount];
+ double rateMult = 32000.0 / rate;
+ double tuner = freq * 65536.0f;
+ for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) {
+ noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult);
+ }
+
+ initFiltTable(noteLookup, freq, rate);
+ initNFiltTable(noteLookup, freq, rate);
+ return file;
+}
+
+bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) {
+ const char *NoteNames[12] = {
+ "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B "
+ };
+ char filename[64];
+ int intRate = (int)rate;
+ char version[4] = {0, 0, 0, 5};
+ sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, (double)masterTune);
+
+ File *file = NULL;
+ char header[20];
+ memcpy(header, "MT32WAVE", 8);
+ int pos = 8;
+ // Version...
+ for (int i = 0; i < 4; i++)
+ header[pos++] = version[i];
+ header[pos++] = (char)((intRate >> 24) & 0xFF);
+ header[pos++] = (char)((intRate >> 16) & 0xFF);
+ header[pos++] = (char)((intRate >> 8) & 0xFF);
+ header[pos++] = (char)(intRate & 0xFF);
+ int intTuning = (int)masterTune;
+ header[pos++] = (char)((intTuning >> 8) & 0xFF);
+ header[pos++] = (char)(intTuning & 0xFF);
+ header[pos++] = 0;
+ header[pos] = (char)((masterTune - intTuning) * 10);
+#if MT32EMU_WAVECACHEMODE < 2
+ bool reading = false;
+ file = synth->openFile(filename, File::OpenMode_read);
+ if (file != NULL) {
+ char fileHeader[20];
+ if (file->read(fileHeader, 20) == 20) {
+ if (memcmp(fileHeader, header, 20) == 0) {
+ Bit16u endianCheck;
+ if (file->readBit16u(&endianCheck)) {
+ if (endianCheck == 1) {
+ reading = true;
+ } else {
+ synth->printDebug("Endian check in %s does not match expected", filename);
+ }
+ } else {
+ synth->printDebug("Unable to read endian check in %s", filename);
+ }
+ } else {
+ synth->printDebug("Header of %s does not match expected", filename);
+ }
+ } else {
+ synth->printDebug("Error reading 16 bytes of %s", filename);
+ }
+ if (!reading) {
+ file->close();
+ file = NULL;
+ }
+ } else {
+ synth->printDebug("Unable to open %s for reading", filename);
+ }
+#endif
+
+ float progress = 0.0f;
+ bool abort = false;
+ synth->report(ReportType_progressInit, &progress);
+ for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) {
+ synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2);
+ NoteLookup *noteLookup = &noteLookups[f - LOWEST_NOTE];
+ file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file);
+ progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES;
+ abort = synth->report(ReportType_progressInit, &progress) != 0;
+ if (abort)
+ break;
+ }
+
+#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2
+ if (file == NULL) {
+ file = synth->openFile(filename, File::OpenMode_write);
+ if (file != NULL) {
+ if (file->write(header, 20) == 20 && file->writeBit16u(1)) {
+ for (int f = 0; f < NUM_NOTES; f++) {
+ for (int i = 0; i < 3 && file != NULL; i++) {
+ int len = noteLookups[f].waveformSize[i];
+ for (int j = 0; j < len; j++) {
+ if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) {
+ synth->printDebug("Error writing waveform cache file");
+ file->close();
+ file = NULL;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename);
+ }
+ } else {
+ synth->printDebug("Unable to open %s for writing - won't be created", filename);
+ }
+ }
+#endif
+
+ if (file != NULL)
+ synth->closeFile(file);
+ return !abort;
+}
+
+void Tables::freeNotes() {
+ for (int t = 0; t < 3; t++) {
+ for (int m = 0; m < NUM_NOTES; m++) {
+ if (noteLookups[m].waveforms[t] != NULL) {
+ delete[] noteLookups[m].waveforms[t];
+ noteLookups[m].waveforms[t] = NULL;
+ noteLookups[m].waveformSize[t] = 0;
+ }
+ if (noteLookups[m].wavTable != NULL) {
+ delete[] noteLookups[m].wavTable;
+ noteLookups[m].wavTable = NULL;
+ }
+ }
+ }
+ initialisedMasterTune = 0.0f;
+}
+
+Tables::Tables() {
+ initialisedSampleRate = 0.0f;
+ initialisedMasterTune = 0.0f;
+ memset(&noteLookups, 0, sizeof(noteLookups));
+}
+
+bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) {
+ if (sampleRate <= 0.0f) {
+ synth->printDebug("Bad sampleRate (%f <= 0.0f)", (double)sampleRate);
+ return false;
+ }
+ if (initialisedSampleRate == 0.0f) {
+ initMT32ConstantTables(synth);
+ }
+ if (initialisedSampleRate != sampleRate) {
+ initFiltCoeff(sampleRate);
+ initEnvelopes(sampleRate);
+ for (int key = 12; key <= 108; key++) {
+ initDep(&keyLookups[key - 12], (float)key);
+ }
+ }
+ if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) {
+ freeNotes();
+ if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) {
+ return false;
+ }
+ initialisedSampleRate = sampleRate;
+ initialisedMasterTune = masterTune;
+ }
+ return true;
+}
+
+}
diff --git a/audio/softsynth/mt32/tables.h b/audio/softsynth/mt32/tables.h
new file mode 100644
index 0000000000..d9af5114b2
--- /dev/null
+++ b/audio/softsynth/mt32/tables.h
@@ -0,0 +1,116 @@
+/* Copyright (c) 2003-2005 Various contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MT32EMU_TABLES_H
+#define MT32EMU_TABLES_H
+
+namespace MT32Emu {
+
+// Mathematical constants
+const double DOUBLE_PI = 3.1415926535897932384626433832795;
+const double DOUBLE_LN = 2.3025850929940456840179914546844;
+const float FLOAT_PI = 3.1415926535897932384626433832795f;
+const float FLOAT_LN = 2.3025850929940456840179914546844f;
+
+// Filter settings
+const int FILTERGRAN = 512;
+
+// Amplitude of waveform generator
+// FIXME: This value is the amplitude possible whilst avoiding
+// overdriven values immediately after filtering when playing
+// back SQ3MT.MID. Needs to be checked.
+const int WGAMP = 12382;
+
+const int MIDDLEC = 60;
+const int MIDDLEA = 69; // By this I mean "A above middle C"
+
+// FIXME:KG: may only need to do 12 to 108
+// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow
+// and adjustment for timbre pitch, so the results can be outside that range.
+// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range,
+// or something else altogether?
+const int LOWEST_NOTE = 12;
+const int HIGHEST_NOTE = 127;
+const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT
+
+class Synth;
+
+struct NoteLookup {
+ Bit32u div2;
+ Bit32u *wavTable;
+ Bit32s sawTable[101];
+ int filtTable[2][201];
+ int nfiltTable[101][101];
+ Bit16s *waveforms[3];
+ Bit32u waveformSize[3];
+};
+
+struct KeyLookup {
+ Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed
+ Bit32s envDepthMult[5];
+};
+
+class Tables {
+ float initialisedSampleRate;
+ float initialisedMasterTune;
+ void initMT32ConstantTables(Synth *synth);
+ static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input);
+ static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file);
+ bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning);
+ void initEnvelopes(float sampleRate);
+ void initFiltCoeff(float samplerate);
+public:
+ // Constant LUTs
+ Bit32s tvfKeyfollowMult[217];
+ Bit32s tvfVelfollowMult[128][101];
+ Bit32s tvfBiasMult[15][128];
+ Bit32u tvaVelfollowMult[128][101];
+ Bit32s tvaBiasMult[13][128];
+ Bit16s noiseBuf[MAX_SAMPLE_OUTPUT];
+ Bit16s sintable[65536];
+ Bit32s pitchEnvVal[16][101];
+ Bit32s envTimeVelfollowMult[5][128];
+ Bit32s pwVelfollowAdd[15][128];
+ float resonanceFactor[31];
+ Bit32u lfoShift[101][101];
+ Bit32s pwFactor[101];
+ Bit32s volumeMult[101];
+
+ // LUTs varying with sample rate
+ Bit32u envTime[101];
+ Bit32u envDeltaMaxTime[101];
+ Bit32u envDecayTime[101];
+ Bit32u lfoPeriod[101];
+ float filtCoeff[FILTERGRAN][31][8];
+
+ // Various LUTs for each note and key
+ NoteLookup noteLookups[NUM_NOTES];
+ KeyLookup keyLookups[97];
+
+ Tables();
+ bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune);
+ File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file);
+ void freeNotes();
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/opl/dbopl.cpp b/audio/softsynth/opl/dbopl.cpp
new file mode 100644
index 0000000000..47e263b6b9
--- /dev/null
+++ b/audio/softsynth/opl/dbopl.cpp
@@ -0,0 +1,1536 @@
+/*
+ * Copyright (C) 2002-2010 The DOSBox 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator.
+ Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2
+ Except for the table generation it's all integer math
+ Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms
+ The generation was based on the MAME implementation but tried to have it use less memory and be faster in general
+ MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times
+
+ //TODO Don't delay first operator 1 sample in opl3 mode
+ //TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter
+ //TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though?
+ //TODO Check if having the same accuracy in all frequency multipliers sounds better or not
+
+ //DUNNO Keyon in 4op, switch to 2op without keyoff.
+*/
+
+// Last synch with DOSBox SVN trunk r3556
+
+#include "dbopl.h"
+
+#ifndef DISABLE_DOSBOX_OPL
+
+namespace OPL {
+namespace DOSBox {
+
+#ifndef PI
+#define PI 3.14159265358979323846
+#endif
+
+namespace DBOPL {
+
+#define OPLRATE ((double)(14318180.0 / 288.0))
+#define TREMOLO_TABLE 52
+
+//Try to use most precision for frequencies
+//Else try to keep different waves in synch
+//#define WAVE_PRECISION 1
+#ifndef WAVE_PRECISION
+//Wave bits available in the top of the 32bit range
+//Original adlib uses 10.10, we use 10.22
+#define WAVE_BITS 10
+#else
+//Need some extra bits at the top to have room for octaves and frequency multiplier
+//We support to 8 times lower rate
+//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits
+#define WAVE_BITS 14
+#endif
+#define WAVE_SH ( 32 - WAVE_BITS )
+#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 )
+
+//Use the same accuracy as the waves
+#define LFO_SH ( WAVE_SH - 10 )
+//LFO is controlled by our tremolo 256 sample limit
+#define LFO_MAX ( 256 << ( LFO_SH ) )
+
+
+//Maximum amount of attenuation bits
+//Envelope goes to 511, 9 bits
+#if (DBOPL_WAVE == WAVE_TABLEMUL )
+//Uses the value directly
+#define ENV_BITS ( 9 )
+#else
+//Add 3 bits here for more accuracy and would have to be shifted up either way
+#define ENV_BITS ( 9 )
+#endif
+//Limits of the envelope with those bits and when the envelope goes silent
+#define ENV_MIN 0
+#define ENV_EXTRA ( ENV_BITS - 9 )
+#define ENV_MAX ( 511 << ENV_EXTRA )
+#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) )
+#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT )
+
+//Attack/decay/release rate counter shift
+#define RATE_SH 24
+#define RATE_MASK ( ( 1 << RATE_SH ) - 1 )
+//Has to fit within 16bit lookuptable
+#define MUL_SH 16
+
+//Check some ranges
+#if ENV_EXTRA > 3
+#error Too many envelope bits
+#endif
+
+
+//How much to substract from the base value for the final attenuation
+static const Bit8u KslCreateTable[16] = {
+ //0 will always be be lower than 7 * 8
+ 64, 32, 24, 19,
+ 16, 12, 11, 10,
+ 8, 6, 5, 4,
+ 3, 2, 1, 0,
+};
+
+#define M(_X_) ((Bit8u)( (_X_) * 2))
+static const Bit8u FreqCreateTable[16] = {
+ M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ),
+ M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15)
+};
+#undef M
+
+//We're not including the highest attack rate, that gets a special value
+static const Bit8u AttackSamplesTable[13] = {
+ 69, 55, 46, 40,
+ 35, 29, 23, 20,
+ 19, 15, 11, 10,
+ 9
+};
+//On a real opl these values take 8 samples to reach and are based upon larger tables
+static const Bit8u EnvelopeIncreaseTable[13] = {
+ 4, 5, 6, 7,
+ 8, 10, 12, 14,
+ 16, 20, 24, 28,
+ 32,
+};
+
+#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
+static Bit16u ExpTable[ 256 ];
+#endif
+
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+//PI table used by WAVEHANDLER
+static Bit16u SinTable[ 512 ];
+#endif
+
+#if ( DBOPL_WAVE > WAVE_HANDLER )
+//Layout of the waveform table in 512 entry intervals
+//With overlapping waves we reduce the table to half it's size
+
+// | |//\\|____|WAV7|//__|/\ |____|/\/\|
+// |\\//| | |WAV7| | \/| | |
+// |06 |0126|17 |7 |3 |4 |4 5 |5 |
+
+//6 is just 0 shifted and masked
+
+static Bit16s WaveTable[ 8 * 512 ];
+//Distance into WaveTable the wave starts
+static const Bit16u WaveBaseTable[8] = {
+ 0x000, 0x200, 0x200, 0x800,
+ 0xa00, 0xc00, 0x100, 0x400,
+
+};
+//Mask the counter with this
+static const Bit16u WaveMaskTable[8] = {
+ 1023, 1023, 511, 511,
+ 1023, 1023, 512, 1023,
+};
+
+//Where to start the counter on at keyon
+static const Bit16u WaveStartTable[8] = {
+ 512, 0, 0, 0,
+ 0, 512, 512, 256,
+};
+#endif
+
+#if ( DBOPL_WAVE == WAVE_TABLEMUL )
+static Bit16u MulTable[ 384 ];
+#endif
+
+static Bit8u KslTable[ 8 * 16 ];
+static Bit8u TremoloTable[ TREMOLO_TABLE ];
+//Start of a channel behind the chip struct start
+static Bit16u ChanOffsetTable[32];
+//Start of an operator behind the chip struct start
+static Bit16u OpOffsetTable[64];
+
+//The lower bits are the shift of the operator vibrato value
+//The highest bit is right shifted to generate -1 or 0 for negation
+//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0
+static const Bit8s VibratoTable[ 8 ] = {
+ 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00,
+ 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80
+};
+
+//Shift strength for the ksl value determined by ksl strength
+static const Bit8u KslShiftTable[4] = {
+ 31,1,2,0
+};
+
+//Generate a table index and table shift value using input value from a selected rate
+static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) {
+ if ( val < 13 * 4 ) { //Rate 0 - 12
+ shift = 12 - ( val >> 2 );
+ index = val & 3;
+ } else if ( val < 15 * 4 ) { //rate 13 - 14
+ shift = 0;
+ index = val - 12 * 4;
+ } else { //rate 15 and up
+ shift = 0;
+ index = 12;
+ }
+}
+
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+/*
+ Generate the different waveforms out of the sine/exponetial table using handlers
+*/
+static inline Bits MakeVolume( Bitu wave, Bitu volume ) {
+ Bitu total = wave + volume;
+ Bitu index = total & 0xff;
+ Bitu sig = ExpTable[ index ];
+ Bitu exp = total >> 8;
+#if 0
+ //Check if we overflow the 31 shift limit
+ if ( exp >= 32 ) {
+ LOG_MSG( "WTF %d %d", total, exp );
+ }
+#endif
+ return (sig >> exp);
+}
+
+static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) {
+ Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+ Bitu wave = SinTable[i & 511];
+ return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) {
+ Bit32u wave = SinTable[i & 511];
+ wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+ return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) {
+ Bitu wave = SinTable[i & 511];
+ return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) {
+ Bitu wave = SinTable[i & 255];
+ wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 );
+ return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) {
+ //Twice as fast
+ i <<= 1;
+ Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+ Bitu wave = SinTable[i & 511];
+ wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+ return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) {
+ //Twice as fast
+ i <<= 1;
+ Bitu wave = SinTable[i & 511];
+ wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 );
+ return MakeVolume( wave, volume );
+}
+static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) {
+ Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0
+ return (MakeVolume( 0, volume ) ^ neg) - neg;
+}
+static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) {
+ //Negative is reversed here
+ Bits neg = (( i >> 9) & 1) - 1;
+ Bitu wave = (i << 3);
+ //When negative the volume also runs backwards
+ wave = ((wave ^ neg) - neg) & 4095;
+ return (MakeVolume( wave, volume ) ^ neg) - neg;
+}
+
+static const WaveHandler WaveHandlerTable[8] = {
+ WaveForm0, WaveForm1, WaveForm2, WaveForm3,
+ WaveForm4, WaveForm5, WaveForm6, WaveForm7
+};
+
+#endif
+
+/*
+ Operator
+*/
+
+//We zero out when rate == 0
+inline void Operator::UpdateAttack( const Chip* chip ) {
+ Bit8u rate = reg60 >> 4;
+ if ( rate ) {
+ Bit8u val = (rate << 2) + ksr;
+ attackAdd = chip->attackRates[ val ];
+ rateZero &= ~(1 << ATTACK);
+ } else {
+ attackAdd = 0;
+ rateZero |= (1 << ATTACK);
+ }
+}
+inline void Operator::UpdateDecay( const Chip* chip ) {
+ Bit8u rate = reg60 & 0xf;
+ if ( rate ) {
+ Bit8u val = (rate << 2) + ksr;
+ decayAdd = chip->linearRates[ val ];
+ rateZero &= ~(1 << DECAY);
+ } else {
+ decayAdd = 0;
+ rateZero |= (1 << DECAY);
+ }
+}
+inline void Operator::UpdateRelease( const Chip* chip ) {
+ Bit8u rate = reg80 & 0xf;
+ if ( rate ) {
+ Bit8u val = (rate << 2) + ksr;
+ releaseAdd = chip->linearRates[ val ];
+ rateZero &= ~(1 << RELEASE);
+ if ( !(reg20 & MASK_SUSTAIN ) ) {
+ rateZero &= ~( 1 << SUSTAIN );
+ }
+ } else {
+ rateZero |= (1 << RELEASE);
+ releaseAdd = 0;
+ if ( !(reg20 & MASK_SUSTAIN ) ) {
+ rateZero |= ( 1 << SUSTAIN );
+ }
+ }
+}
+
+inline void Operator::UpdateAttenuation( ) {
+ Bit8u kslBase = (Bit8u)((chanData >> SHIFT_KSLBASE) & 0xff);
+ Bit32u tl = reg40 & 0x3f;
+ Bit8u kslShift = KslShiftTable[ reg40 >> 6 ];
+ //Make sure the attenuation goes to the right bits
+ totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max
+ totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift;
+}
+
+void Operator::UpdateFrequency( ) {
+ Bit32u freq = chanData & (( 1 << 10 ) - 1);
+ Bit32u block = (chanData >> 10) & 0xff;
+#ifdef WAVE_PRECISION
+ block = 7 - block;
+ waveAdd = ( freq * freqMul ) >> block;
+#else
+ waveAdd = ( freq << block ) * freqMul;
+#endif
+ if ( reg20 & MASK_VIBRATO ) {
+ vibStrength = (Bit8u)(freq >> 7);
+
+#ifdef WAVE_PRECISION
+ vibrato = ( vibStrength * freqMul ) >> block;
+#else
+ vibrato = ( vibStrength << block ) * freqMul;
+#endif
+ } else {
+ vibStrength = 0;
+ vibrato = 0;
+ }
+}
+
+void Operator::UpdateRates( const Chip* chip ) {
+ //Mame seems to reverse this where enabling ksr actually lowers
+ //the rate, but pdf manuals says otherwise?
+ Bit8u newKsr = (Bit8u)((chanData >> SHIFT_KEYCODE) & 0xff);
+ if ( !( reg20 & MASK_KSR ) ) {
+ newKsr >>= 2;
+ }
+ if ( ksr == newKsr )
+ return;
+ ksr = newKsr;
+ UpdateAttack( chip );
+ UpdateDecay( chip );
+ UpdateRelease( chip );
+}
+
+INLINE Bit32s Operator::RateForward( Bit32u add ) {
+ rateIndex += add;
+ Bit32s ret = rateIndex >> RATE_SH;
+ rateIndex = rateIndex & RATE_MASK;
+ return ret;
+}
+
+template< Operator::State yes>
+Bits Operator::TemplateVolume( ) {
+ Bit32s vol = volume;
+ Bit32s change;
+ switch ( yes ) {
+ case OFF:
+ return ENV_MAX;
+ case ATTACK:
+ change = RateForward( attackAdd );
+ if ( !change )
+ return vol;
+ vol += ( (~vol) * change ) >> 3;
+ if ( vol < ENV_MIN ) {
+ volume = ENV_MIN;
+ rateIndex = 0;
+ SetState( DECAY );
+ return ENV_MIN;
+ }
+ break;
+ case DECAY:
+ vol += RateForward( decayAdd );
+ if ( GCC_UNLIKELY(vol >= sustainLevel) ) {
+ //Check if we didn't overshoot max attenuation, then just go off
+ if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
+ volume = ENV_MAX;
+ SetState( OFF );
+ return ENV_MAX;
+ }
+ //Continue as sustain
+ rateIndex = 0;
+ SetState( SUSTAIN );
+ }
+ break;
+ case SUSTAIN:
+ if ( reg20 & MASK_SUSTAIN ) {
+ return vol;
+ }
+ //In sustain phase, but not sustaining, do regular release
+ case RELEASE:
+ vol += RateForward( releaseAdd );
+ if ( GCC_UNLIKELY(vol >= ENV_MAX) ) {
+ volume = ENV_MAX;
+ SetState( OFF );
+ return ENV_MAX;
+ }
+ break;
+ }
+ volume = vol;
+ return vol;
+}
+
+static const VolumeHandler VolumeHandlerTable[5] = {
+ &Operator::TemplateVolume< Operator::OFF >,
+ &Operator::TemplateVolume< Operator::RELEASE >,
+ &Operator::TemplateVolume< Operator::SUSTAIN >,
+ &Operator::TemplateVolume< Operator::DECAY >,
+ &Operator::TemplateVolume< Operator::ATTACK >
+};
+
+INLINE Bitu Operator::ForwardVolume() {
+ return currentLevel + (this->*volHandler)();
+}
+
+
+INLINE Bitu Operator::ForwardWave() {
+ waveIndex += waveCurrent;
+ return waveIndex >> WAVE_SH;
+}
+
+void Operator::Write20( const Chip* chip, Bit8u val ) {
+ Bit8u change = (reg20 ^ val );
+ if ( !change )
+ return;
+ reg20 = val;
+ //Shift the tremolo bit over the entire register, saved a branch, YES!
+ tremoloMask = (Bit8s)(val) >> 7;
+ tremoloMask &= ~(( 1 << ENV_EXTRA ) -1);
+ //Update specific features based on changes
+ if ( change & MASK_KSR ) {
+ UpdateRates( chip );
+ }
+ //With sustain enable the volume doesn't change
+ if ( reg20 & MASK_SUSTAIN || ( !releaseAdd ) ) {
+ rateZero |= ( 1 << SUSTAIN );
+ } else {
+ rateZero &= ~( 1 << SUSTAIN );
+ }
+ //Frequency multiplier or vibrato changed
+ if ( change & (0xf | MASK_VIBRATO) ) {
+ freqMul = chip->freqMul[ val & 0xf ];
+ UpdateFrequency();
+ }
+}
+
+void Operator::Write40( const Chip* /*chip*/, Bit8u val ) {
+ if (!(reg40 ^ val ))
+ return;
+ reg40 = val;
+ UpdateAttenuation( );
+}
+
+void Operator::Write60( const Chip* chip, Bit8u val ) {
+ Bit8u change = reg60 ^ val;
+ reg60 = val;
+ if ( change & 0x0f ) {
+ UpdateDecay( chip );
+ }
+ if ( change & 0xf0 ) {
+ UpdateAttack( chip );
+ }
+}
+
+void Operator::Write80( const Chip* chip, Bit8u val ) {
+ Bit8u change = (reg80 ^ val );
+ if ( !change )
+ return;
+ reg80 = val;
+ Bit8u sustain = val >> 4;
+ //Turn 0xf into 0x1f
+ sustain |= ( sustain + 1) & 0x10;
+ sustainLevel = sustain << ( ENV_BITS - 5 );
+ if ( change & 0x0f ) {
+ UpdateRelease( chip );
+ }
+}
+
+void Operator::WriteE0( const Chip* chip, Bit8u val ) {
+ if ( !(regE0 ^ val) )
+ return;
+ //in opl3 mode you can always selet 7 waveforms regardless of waveformselect
+ Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) );
+ regE0 = val;
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+ waveHandler = WaveHandlerTable[ waveForm ];
+#else
+ waveBase = WaveTable + WaveBaseTable[ waveForm ];
+ waveStart = WaveStartTable[ waveForm ] << WAVE_SH;
+ waveMask = WaveMaskTable[ waveForm ];
+#endif
+}
+
+INLINE void Operator::SetState( Bit8u s ) {
+ state = s;
+ volHandler = VolumeHandlerTable[ s ];
+}
+
+INLINE bool Operator::Silent() const {
+ if ( !ENV_SILENT( totalLevel + volume ) )
+ return false;
+ if ( !(rateZero & ( 1 << state ) ) )
+ return false;
+ return true;
+}
+
+INLINE void Operator::Prepare( const Chip* chip ) {
+ currentLevel = totalLevel + (chip->tremoloValue & tremoloMask);
+ waveCurrent = waveAdd;
+ if ( vibStrength >> chip->vibratoShift ) {
+ Bit32s add = vibrato >> chip->vibratoShift;
+ //Sign extend over the shift value
+ Bit32s neg = chip->vibratoSign;
+ //Negate the add with -1 or 0
+ add = ( add ^ neg ) - neg;
+ waveCurrent += add;
+ }
+}
+
+void Operator::KeyOn( Bit8u mask ) {
+ if ( !keyOn ) {
+ //Restart the frequency generator
+#if ( DBOPL_WAVE > WAVE_HANDLER )
+ waveIndex = waveStart;
+#else
+ waveIndex = 0;
+#endif
+ rateIndex = 0;
+ SetState( ATTACK );
+ }
+ keyOn |= mask;
+}
+
+void Operator::KeyOff( Bit8u mask ) {
+ keyOn &= ~mask;
+ if ( !keyOn ) {
+ if ( state != OFF ) {
+ SetState( RELEASE );
+ }
+ }
+}
+
+INLINE Bits Operator::GetWave( Bitu index, Bitu vol ) {
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+ return waveHandler( index, vol << ( 3 - ENV_EXTRA ) );
+#elif ( DBOPL_WAVE == WAVE_TABLEMUL )
+ return (waveBase[ index & waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH;
+#elif ( DBOPL_WAVE == WAVE_TABLELOG )
+ Bit32s wave = waveBase[ index & waveMask ];
+ Bit32u total = ( wave & 0x7fff ) + ( vol << ( 3 - ENV_EXTRA ) );
+ Bit32s sig = ExpTable[ total & 0xff ];
+ Bit32u exp = total >> 8;
+ Bit32s neg = wave >> 16;
+ return ((sig ^ neg) - neg) >> exp;
+#else
+#error "No valid wave routine"
+#endif
+}
+
+INLINE Bits Operator::GetSample( Bits modulation ) {
+ Bitu vol = ForwardVolume();
+ if ( ENV_SILENT( vol ) ) {
+ //Simply forward the wave
+ waveIndex += waveCurrent;
+ return 0;
+ } else {
+ Bitu index = ForwardWave();
+ index += modulation;
+ return GetWave( index, vol );
+ }
+}
+
+Operator::Operator() {
+ chanData = 0;
+ freqMul = 0;
+ waveIndex = 0;
+ waveAdd = 0;
+ waveCurrent = 0;
+ keyOn = 0;
+ ksr = 0;
+ reg20 = 0;
+ reg40 = 0;
+ reg60 = 0;
+ reg80 = 0;
+ regE0 = 0;
+ SetState( OFF );
+ rateZero = (1 << OFF);
+ sustainLevel = ENV_MAX;
+ currentLevel = ENV_MAX;
+ totalLevel = ENV_MAX;
+ volume = ENV_MAX;
+ releaseAdd = 0;
+}
+
+/*
+ Channel
+*/
+
+Channel::Channel() {
+ old[0] = old[1] = 0;
+ chanData = 0;
+ regB0 = 0;
+ regC0 = 0;
+ maskLeft = -1;
+ maskRight = -1;
+ feedback = 31;
+ fourMask = 0;
+ synthHandler = &Channel::BlockTemplate< sm2FM >;
+}
+
+void Channel::SetChanData( const Chip* chip, Bit32u data ) {
+ Bit32u change = chanData ^ data;
+ chanData = data;
+ Op( 0 )->chanData = data;
+ Op( 1 )->chanData = data;
+ //Since a frequency update triggered this, always update frequency
+ Op( 0 )->UpdateFrequency();
+ Op( 1 )->UpdateFrequency();
+ if ( change & ( 0xff << SHIFT_KSLBASE ) ) {
+ Op( 0 )->UpdateAttenuation();
+ Op( 1 )->UpdateAttenuation();
+ }
+ if ( change & ( 0xff << SHIFT_KEYCODE ) ) {
+ Op( 0 )->UpdateRates( chip );
+ Op( 1 )->UpdateRates( chip );
+ }
+}
+
+void Channel::UpdateFrequency( const Chip* chip, Bit8u fourOp ) {
+ //Extrace the frequency bits
+ Bit32u data = chanData & 0xffff;
+ Bit32u kslBase = KslTable[ data >> 6 ];
+ Bit32u keyCode = ( data & 0x1c00) >> 9;
+ if ( chip->reg08 & 0x40 ) {
+ keyCode |= ( data & 0x100)>>8; /* notesel == 1 */
+ } else {
+ keyCode |= ( data & 0x200)>>9; /* notesel == 0 */
+ }
+ //Add the keycode and ksl into the highest bits of chanData
+ data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE );
+ ( this + 0 )->SetChanData( chip, data );
+ if ( fourOp & 0x3f ) {
+ ( this + 1 )->SetChanData( chip, data );
+ }
+}
+
+void Channel::WriteA0( const Chip* chip, Bit8u val ) {
+ Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask;
+ //Don't handle writes to silent fourop channels
+ if ( fourOp > 0x80 )
+ return;
+ Bit32u change = (chanData ^ val ) & 0xff;
+ if ( change ) {
+ chanData ^= change;
+ UpdateFrequency( chip, fourOp );
+ }
+}
+
+void Channel::WriteB0( const Chip* chip, Bit8u val ) {
+ Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask;
+ //Don't handle writes to silent fourop channels
+ if ( fourOp > 0x80 )
+ return;
+ Bitu change = (chanData ^ ( val << 8 ) ) & 0x1f00;
+ if ( change ) {
+ chanData ^= change;
+ UpdateFrequency( chip, fourOp );
+ }
+ //Check for a change in the keyon/off state
+ if ( !(( val ^ regB0) & 0x20))
+ return;
+ regB0 = val;
+ if ( val & 0x20 ) {
+ Op(0)->KeyOn( 0x1 );
+ Op(1)->KeyOn( 0x1 );
+ if ( fourOp & 0x3f ) {
+ ( this + 1 )->Op(0)->KeyOn( 1 );
+ ( this + 1 )->Op(1)->KeyOn( 1 );
+ }
+ } else {
+ Op(0)->KeyOff( 0x1 );
+ Op(1)->KeyOff( 0x1 );
+ if ( fourOp & 0x3f ) {
+ ( this + 1 )->Op(0)->KeyOff( 1 );
+ ( this + 1 )->Op(1)->KeyOff( 1 );
+ }
+ }
+}
+
+void Channel::WriteC0( const Chip* chip, Bit8u val ) {
+ Bit8u change = val ^ regC0;
+ if ( !change )
+ return;
+ regC0 = val;
+ feedback = ( val >> 1 ) & 7;
+ if ( feedback ) {
+ //We shift the input to the right 10 bit wave index value
+ feedback = 9 - feedback;
+ } else {
+ feedback = 31;
+ }
+ //Select the new synth mode
+ if ( chip->opl3Active ) {
+ //4-op mode enabled for this channel
+ if ( (chip->reg104 & fourMask) & 0x3f ) {
+ Channel* chan0, *chan1;
+ //Check if it's the 2nd channel in a 4-op
+ if ( !(fourMask & 0x80 ) ) {
+ chan0 = this;
+ chan1 = this + 1;
+ } else {
+ chan0 = this - 1;
+ chan1 = this;
+ }
+
+ Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 );
+ switch ( synth ) {
+ case 0:
+ chan0->synthHandler = &Channel::BlockTemplate< sm3FMFM >;
+ break;
+ case 1:
+ chan0->synthHandler = &Channel::BlockTemplate< sm3AMFM >;
+ break;
+ case 2:
+ chan0->synthHandler = &Channel::BlockTemplate< sm3FMAM >;
+ break;
+ case 3:
+ chan0->synthHandler = &Channel::BlockTemplate< sm3AMAM >;
+ break;
+ }
+ //Disable updating percussion channels
+ } else if ((fourMask & 0x40) && ( chip->regBD & 0x20) ) {
+
+ //Regular dual op, am or fm
+ } else if ( val & 1 ) {
+ synthHandler = &Channel::BlockTemplate< sm3AM >;
+ } else {
+ synthHandler = &Channel::BlockTemplate< sm3FM >;
+ }
+ maskLeft = ( val & 0x10 ) ? -1 : 0;
+ maskRight = ( val & 0x20 ) ? -1 : 0;
+ //opl2 active
+ } else {
+ //Disable updating percussion channels
+ if ( (fourMask & 0x40) && ( chip->regBD & 0x20 ) ) {
+
+ //Regular dual op, am or fm
+ } else if ( val & 1 ) {
+ synthHandler = &Channel::BlockTemplate< sm2AM >;
+ } else {
+ synthHandler = &Channel::BlockTemplate< sm2FM >;
+ }
+ }
+}
+
+void Channel::ResetC0( const Chip* chip ) {
+ Bit8u val = regC0;
+ regC0 ^= 0xff;
+ WriteC0( chip, val );
+}
+
+template< bool opl3Mode>
+INLINE void Channel::GeneratePercussion( Chip* chip, Bit32s* output ) {
+ Channel* chan = this;
+
+ //BassDrum
+ Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback;
+ old[0] = old[1];
+ old[1] = Op(0)->GetSample( mod );
+
+ //When bassdrum is in AM mode first operator is ignoed
+ if ( chan->regC0 & 1 ) {
+ mod = 0;
+ } else {
+ mod = old[0];
+ }
+ Bit32s sample = Op(1)->GetSample( mod );
+
+
+ //Precalculate stuff used by other outputs
+ Bit32u noiseBit = chip->ForwardNoise() & 0x1;
+ Bit32u c2 = Op(2)->ForwardWave();
+ Bit32u c5 = Op(5)->ForwardWave();
+ Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00;
+
+ //Hi-Hat
+ Bit32u hhVol = Op(2)->ForwardVolume();
+ if ( !ENV_SILENT( hhVol ) ) {
+ Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 )));
+ sample += Op(2)->GetWave( hhIndex, hhVol );
+ }
+ //Snare Drum
+ Bit32u sdVol = Op(3)->ForwardVolume();
+ if ( !ENV_SILENT( sdVol ) ) {
+ Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 );
+ sample += Op(3)->GetWave( sdIndex, sdVol );
+ }
+ //Tom-tom
+ sample += Op(4)->GetSample( 0 );
+
+ //Top-Cymbal
+ Bit32u tcVol = Op(5)->ForwardVolume();
+ if ( !ENV_SILENT( tcVol ) ) {
+ Bit32u tcIndex = (1 + phaseBit) << 8;
+ sample += Op(5)->GetWave( tcIndex, tcVol );
+ }
+ sample <<= 1;
+ if ( opl3Mode ) {
+ output[0] += sample;
+ output[1] += sample;
+ } else {
+ output[0] += sample;
+ }
+}
+
+template<SynthMode mode>
+Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) {
+ switch( mode ) {
+ case sm2AM:
+ case sm3AM:
+ if ( Op(0)->Silent() && Op(1)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 1);
+ }
+ break;
+ case sm2FM:
+ case sm3FM:
+ if ( Op(1)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 1);
+ }
+ break;
+ case sm3FMFM:
+ if ( Op(3)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 2);
+ }
+ break;
+ case sm3AMFM:
+ if ( Op(0)->Silent() && Op(3)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 2);
+ }
+ break;
+ case sm3FMAM:
+ if ( Op(1)->Silent() && Op(3)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 2);
+ }
+ break;
+ case sm3AMAM:
+ if ( Op(0)->Silent() && Op(2)->Silent() && Op(3)->Silent() ) {
+ old[0] = old[1] = 0;
+ return (this + 2);
+ }
+ break;
+ case sm2Percussion:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm3Percussion:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm4Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm6Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ }
+ //Init the operators with the the current vibrato and tremolo values
+ Op( 0 )->Prepare( chip );
+ Op( 1 )->Prepare( chip );
+ if ( mode > sm4Start ) {
+ Op( 2 )->Prepare( chip );
+ Op( 3 )->Prepare( chip );
+ }
+ if ( mode > sm6Start ) {
+ Op( 4 )->Prepare( chip );
+ Op( 5 )->Prepare( chip );
+ }
+ for ( Bitu i = 0; i < samples; i++ ) {
+ //Early out for percussion handlers
+ if ( mode == sm2Percussion ) {
+ GeneratePercussion<false>( chip, output + i );
+ continue; //Prevent some unitialized value bitching
+ } else if ( mode == sm3Percussion ) {
+ GeneratePercussion<true>( chip, output + i * 2 );
+ continue; //Prevent some unitialized value bitching
+ }
+
+ //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise
+ Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback;
+ old[0] = old[1];
+ old[1] = Op(0)->GetSample( mod );
+ Bit32s sample;
+ Bit32s out0 = old[0];
+ if ( mode == sm2AM || mode == sm3AM ) {
+ sample = out0 + Op(1)->GetSample( 0 );
+ } else if ( mode == sm2FM || mode == sm3FM ) {
+ sample = Op(1)->GetSample( out0 );
+ } else if ( mode == sm3FMFM ) {
+ Bits next = Op(1)->GetSample( out0 );
+ next = Op(2)->GetSample( next );
+ sample = Op(3)->GetSample( next );
+ } else if ( mode == sm3AMFM ) {
+ sample = out0;
+ Bits next = Op(1)->GetSample( 0 );
+ next = Op(2)->GetSample( next );
+ sample += Op(3)->GetSample( next );
+ } else if ( mode == sm3FMAM ) {
+ sample = Op(1)->GetSample( out0 );
+ Bits next = Op(2)->GetSample( 0 );
+ sample += Op(3)->GetSample( next );
+ } else if ( mode == sm3AMAM ) {
+ sample = out0;
+ Bits next = Op(1)->GetSample( 0 );
+ sample += Op(2)->GetSample( next );
+ sample += Op(3)->GetSample( 0 );
+ }
+ switch( mode ) {
+ case sm2AM:
+ case sm2FM:
+ output[ i ] += sample;
+ break;
+ case sm3AM:
+ case sm3FM:
+ case sm3FMFM:
+ case sm3AMFM:
+ case sm3FMAM:
+ case sm3AMAM:
+ output[ i * 2 + 0 ] += sample & maskLeft;
+ output[ i * 2 + 1 ] += sample & maskRight;
+ break;
+ case sm2Percussion:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm3Percussion:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm4Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm6Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ }
+ }
+ switch( mode ) {
+ case sm2AM:
+ case sm2FM:
+ case sm3AM:
+ case sm3FM:
+ return ( this + 1 );
+ case sm3FMFM:
+ case sm3AMFM:
+ case sm3FMAM:
+ case sm3AMAM:
+ return( this + 2 );
+ case sm2Percussion:
+ case sm3Percussion:
+ return( this + 3 );
+ case sm4Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ case sm6Start:
+ // This case was not handled in the DOSBox code either
+ // thus we leave this blank.
+ // TODO: Consider checking this.
+ break;
+ }
+ return 0;
+}
+
+/*
+ Chip
+*/
+
+Chip::Chip() {
+ reg08 = 0;
+ reg04 = 0;
+ regBD = 0;
+ reg104 = 0;
+ opl3Active = 0;
+}
+
+INLINE Bit32u Chip::ForwardNoise() {
+ noiseCounter += noiseAdd;
+ Bitu count = noiseCounter >> LFO_SH;
+ noiseCounter &= WAVE_MASK;
+ for ( ; count > 0; --count ) {
+ //Noise calculation from mame
+ noiseValue ^= ( 0x800302 ) & ( 0 - (noiseValue & 1 ) );
+ noiseValue >>= 1;
+ }
+ return noiseValue;
+}
+
+INLINE Bit32u Chip::ForwardLFO( Bit32u samples ) {
+ //Current vibrato value, runs 4x slower than tremolo
+ vibratoSign = ( VibratoTable[ vibratoIndex >> 2] ) >> 7;
+ vibratoShift = ( VibratoTable[ vibratoIndex >> 2] & 7) + vibratoStrength;
+ tremoloValue = TremoloTable[ tremoloIndex ] >> tremoloStrength;
+
+ //Check hom many samples there can be done before the value changes
+ Bit32u todo = LFO_MAX - lfoCounter;
+ Bit32u count = (todo + lfoAdd - 1) / lfoAdd;
+ if ( count > samples ) {
+ count = samples;
+ lfoCounter += count * lfoAdd;
+ } else {
+ lfoCounter += count * lfoAdd;
+ lfoCounter &= (LFO_MAX - 1);
+ //Maximum of 7 vibrato value * 4
+ vibratoIndex = ( vibratoIndex + 1 ) & 31;
+ //Clip tremolo to the the table size
+ if ( tremoloIndex + 1 < TREMOLO_TABLE )
+ ++tremoloIndex;
+ else
+ tremoloIndex = 0;
+ }
+ return count;
+}
+
+
+void Chip::WriteBD( Bit8u val ) {
+ Bit8u change = regBD ^ val;
+ if ( !change )
+ return;
+ regBD = val;
+ //TODO could do this with shift and xor?
+ vibratoStrength = (val & 0x40) ? 0x00 : 0x01;
+ tremoloStrength = (val & 0x80) ? 0x00 : 0x02;
+ if ( val & 0x20 ) {
+ //Drum was just enabled, make sure channel 6 has the right synth
+ if ( change & 0x20 ) {
+ if ( opl3Active ) {
+ chan[6].synthHandler = &Channel::BlockTemplate< sm3Percussion >;
+ } else {
+ chan[6].synthHandler = &Channel::BlockTemplate< sm2Percussion >;
+ }
+ }
+ //Bass Drum
+ if ( val & 0x10 ) {
+ chan[6].op[0].KeyOn( 0x2 );
+ chan[6].op[1].KeyOn( 0x2 );
+ } else {
+ chan[6].op[0].KeyOff( 0x2 );
+ chan[6].op[1].KeyOff( 0x2 );
+ }
+ //Hi-Hat
+ if ( val & 0x1 ) {
+ chan[7].op[0].KeyOn( 0x2 );
+ } else {
+ chan[7].op[0].KeyOff( 0x2 );
+ }
+ //Snare
+ if ( val & 0x8 ) {
+ chan[7].op[1].KeyOn( 0x2 );
+ } else {
+ chan[7].op[1].KeyOff( 0x2 );
+ }
+ //Tom-Tom
+ if ( val & 0x4 ) {
+ chan[8].op[0].KeyOn( 0x2 );
+ } else {
+ chan[8].op[0].KeyOff( 0x2 );
+ }
+ //Top Cymbal
+ if ( val & 0x2 ) {
+ chan[8].op[1].KeyOn( 0x2 );
+ } else {
+ chan[8].op[1].KeyOff( 0x2 );
+ }
+ //Toggle keyoffs when we turn off the percussion
+ } else if ( change & 0x20 ) {
+ //Trigger a reset to setup the original synth handler
+ chan[6].ResetC0( this );
+ chan[6].op[0].KeyOff( 0x2 );
+ chan[6].op[1].KeyOff( 0x2 );
+ chan[7].op[0].KeyOff( 0x2 );
+ chan[7].op[1].KeyOff( 0x2 );
+ chan[8].op[0].KeyOff( 0x2 );
+ chan[8].op[1].KeyOff( 0x2 );
+ }
+}
+
+
+#define REGOP( _FUNC_ ) \
+ index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \
+ if ( OpOffsetTable[ index ] ) { \
+ Operator* regOp = (Operator*)( ((char *)this ) + OpOffsetTable[ index ] ); \
+ regOp->_FUNC_( this, val ); \
+ }
+
+#define REGCHAN( _FUNC_ ) \
+ index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \
+ if ( ChanOffsetTable[ index ] ) { \
+ Channel* regChan = (Channel*)( ((char *)this ) + ChanOffsetTable[ index ] ); \
+ regChan->_FUNC_( this, val ); \
+ }
+
+void Chip::WriteReg( Bit32u reg, Bit8u val ) {
+ Bitu index;
+ switch ( (reg & 0xf0) >> 4 ) {
+ case 0x00 >> 4:
+ if ( reg == 0x01 ) {
+ waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0;
+ } else if ( reg == 0x104 ) {
+ //Only detect changes in lowest 6 bits
+ if ( !((reg104 ^ val) & 0x3f) )
+ return;
+ //Always keep the highest bit enabled, for checking > 0x80
+ reg104 = 0x80 | ( val & 0x3f );
+ } else if ( reg == 0x105 ) {
+ //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register
+ if ( !((opl3Active ^ val) & 1 ) )
+ return;
+ opl3Active = ( val & 1 ) ? 0xff : 0;
+ //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers
+ for ( int i = 0; i < 18;i++ ) {
+ chan[i].ResetC0( this );
+ }
+ } else if ( reg == 0x08 ) {
+ reg08 = val;
+ }
+ case 0x10 >> 4:
+ break;
+ case 0x20 >> 4:
+ case 0x30 >> 4:
+ REGOP( Write20 );
+ break;
+ case 0x40 >> 4:
+ case 0x50 >> 4:
+ REGOP( Write40 );
+ break;
+ case 0x60 >> 4:
+ case 0x70 >> 4:
+ REGOP( Write60 );
+ break;
+ case 0x80 >> 4:
+ case 0x90 >> 4:
+ REGOP( Write80 );
+ break;
+ case 0xa0 >> 4:
+ REGCHAN( WriteA0 );
+ break;
+ case 0xb0 >> 4:
+ if ( reg == 0xbd ) {
+ WriteBD( val );
+ } else {
+ REGCHAN( WriteB0 );
+ }
+ break;
+ case 0xc0 >> 4:
+ REGCHAN( WriteC0 );
+ case 0xd0 >> 4:
+ break;
+ case 0xe0 >> 4:
+ case 0xf0 >> 4:
+ REGOP( WriteE0 );
+ break;
+ }
+}
+
+
+Bit32u Chip::WriteAddr( Bit32u port, Bit8u val ) {
+ switch ( port & 3 ) {
+ case 0:
+ return val;
+ case 2:
+ if ( opl3Active || (val == 0x05) )
+ return 0x100 | val;
+ else
+ return val;
+ }
+ return 0;
+}
+
+void Chip::GenerateBlock2( Bitu total, Bit32s* output ) {
+ while ( total > 0 ) {
+ Bit32u samples = ForwardLFO( total );
+ memset(output, 0, sizeof(Bit32s) * samples);
+ int count = 0;
+ for( Channel* ch = chan; ch < chan + 9; ) {
+ count++;
+ ch = (ch->*(ch->synthHandler))( this, samples, output );
+ }
+ total -= samples;
+ output += samples;
+ }
+}
+
+void Chip::GenerateBlock3( Bitu total, Bit32s* output ) {
+ while ( total > 0 ) {
+ Bit32u samples = ForwardLFO( total );
+ memset(output, 0, sizeof(Bit32s) * 2 * samples);
+ int count = 0;
+ for( Channel* ch = chan; ch < chan + 18; ) {
+ count++;
+ ch = (ch->*(ch->synthHandler))( this, samples, output );
+ }
+ total -= samples;
+ output += samples * 2;
+ }
+}
+
+void Chip::Setup( Bit32u rate ) {
+ double scale = OPLRATE / (double)rate;
+
+ //Noise counter is run at the same precision as general waves
+ noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
+ noiseCounter = 0;
+ noiseValue = 1; //Make sure it triggers the noise xor the first time
+ //The low frequency oscillation counter
+ //Every time his overflows vibrato and tremoloindex are increased
+ lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) );
+ lfoCounter = 0;
+ vibratoIndex = 0;
+ tremoloIndex = 0;
+
+ //With higher octave this gets shifted up
+ //-1 since the freqCreateTable = *2
+#ifdef WAVE_PRECISION
+ double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10));
+ for ( int i = 0; i < 16; i++ ) {
+ freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] );
+ }
+#else
+ Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10)));
+ for ( int i = 0; i < 16; i++ ) {
+ freqMul[i] = freqScale * FreqCreateTable[ i ];
+ }
+#endif
+
+ //-3 since the real envelope takes 8 steps to reach the single value we supply
+ for ( Bit8u i = 0; i < 76; i++ ) {
+ Bit8u index, shift;
+ EnvelopeSelect( i, index, shift );
+ linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 )));
+ }
+ //Generate the best matching attack rate
+ for ( Bit8u i = 0; i < 62; i++ ) {
+ Bit8u index, shift;
+ EnvelopeSelect( i, index, shift );
+ //Original amount of samples the attack would take
+ Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale);
+
+ Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 )));
+ Bit32s bestAdd = guessAdd;
+ Bit32u bestDiff = 1 << 30;
+ for( Bit32u passes = 0; passes < 16; passes ++ ) {
+ Bit32s volume = ENV_MAX;
+ Bit32s samples = 0;
+ Bit32u count = 0;
+ while ( volume > 0 && samples < original * 2 ) {
+ count += guessAdd;
+ Bit32s change = count >> RATE_SH;
+ count &= RATE_MASK;
+ if ( GCC_UNLIKELY(change) ) { // less than 1 %
+ volume += ( ~volume * change ) >> 3;
+ }
+ samples++;
+
+ }
+ Bit32s diff = original - samples;
+ Bit32u lDiff = labs( diff );
+ //Init last on first pass
+ if ( lDiff < bestDiff ) {
+ bestDiff = lDiff;
+ bestAdd = guessAdd;
+ if ( !bestDiff )
+ break;
+ }
+ //Below our target
+ if ( diff < 0 ) {
+ //Better than the last time
+ Bit32s mul = ((original - diff) << 12) / original;
+ guessAdd = ((guessAdd * mul) >> 12);
+ guessAdd++;
+ } else if ( diff > 0 ) {
+ Bit32s mul = ((original - diff) << 12) / original;
+ guessAdd = (guessAdd * mul) >> 12;
+ guessAdd--;
+ }
+ }
+ attackRates[i] = bestAdd;
+ }
+ for ( Bit8u i = 62; i < 76; i++ ) {
+ //This should provide instant volume maximizing
+ attackRates[i] = 8 << RATE_SH;
+ }
+ //Setup the channels with the correct four op flags
+ //Channels are accessed through a table so they appear linear here
+ chan[ 0].fourMask = 0x00 | ( 1 << 0 );
+ chan[ 1].fourMask = 0x80 | ( 1 << 0 );
+ chan[ 2].fourMask = 0x00 | ( 1 << 1 );
+ chan[ 3].fourMask = 0x80 | ( 1 << 1 );
+ chan[ 4].fourMask = 0x00 | ( 1 << 2 );
+ chan[ 5].fourMask = 0x80 | ( 1 << 2 );
+
+ chan[ 9].fourMask = 0x00 | ( 1 << 3 );
+ chan[10].fourMask = 0x80 | ( 1 << 3 );
+ chan[11].fourMask = 0x00 | ( 1 << 4 );
+ chan[12].fourMask = 0x80 | ( 1 << 4 );
+ chan[13].fourMask = 0x00 | ( 1 << 5 );
+ chan[14].fourMask = 0x80 | ( 1 << 5 );
+
+ //mark the percussion channels
+ chan[ 6].fourMask = 0x40;
+ chan[ 7].fourMask = 0x40;
+ chan[ 8].fourMask = 0x40;
+
+ //Clear Everything in opl3 mode
+ WriteReg( 0x105, 0x1 );
+ for ( int i = 0; i < 512; i++ ) {
+ if ( i == 0x105 )
+ continue;
+ WriteReg( i, 0xff );
+ WriteReg( i, 0x0 );
+ }
+ WriteReg( 0x105, 0x0 );
+ //Clear everything in opl2 mode
+ for ( int i = 0; i < 255; i++ ) {
+ WriteReg( i, 0xff );
+ WriteReg( i, 0x0 );
+ }
+}
+
+static bool doneTables = false;
+void InitTables( void ) {
+ if ( doneTables )
+ return;
+ doneTables = true;
+#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG )
+ //Exponential volume table, same as the real adlib
+ for ( int i = 0; i < 256; i++ ) {
+ //Save them in reverse
+ ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 );
+ ExpTable[i] += 1024; //or remove the -1 oh well :)
+ //Preshift to the left once so the final volume can shift to the right
+ ExpTable[i] *= 2;
+ }
+#endif
+#if ( DBOPL_WAVE == WAVE_HANDLER )
+ //Add 0.5 for the trunc rounding of the integer cast
+ //Do a PI sinetable instead of the original 0.5 PI
+ for ( int i = 0; i < 512; i++ ) {
+ SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
+ }
+#endif
+#if ( DBOPL_WAVE == WAVE_TABLEMUL )
+ //Multiplication based tables
+ for ( int i = 0; i < 384; i++ ) {
+ int s = i * 8;
+ //TODO maybe keep some of the precision errors of the original table?
+ double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH ));
+ MulTable[i] = (Bit16u)(val);
+ }
+
+ //Sine Wave Base
+ for ( int i = 0; i < 512; i++ ) {
+ WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084);
+ WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ];
+ }
+ //Exponential wave
+ for ( int i = 0; i < 256; i++ ) {
+ WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 );
+ WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ];
+ }
+#endif
+#if ( DBOPL_WAVE == WAVE_TABLELOG )
+ //Sine Wave Base
+ for ( int i = 0; i < 512; i++ ) {
+ WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 );
+ WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i];
+ }
+ //Exponential wave
+ for ( int i = 0; i < 256; i++ ) {
+ WaveTable[ 0x700 + i ] = i * 8;
+ WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8;
+ }
+#endif
+
+ // | |//\\|____|WAV7|//__|/\ |____|/\/\|
+ // |\\//| | |WAV7| | \/| | |
+ // |06 |0126|27 |7 |3 |4 |4 5 |5 |
+
+#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL ))
+ for ( int i = 0; i < 256; i++ ) {
+ //Fill silence gaps
+ WaveTable[ 0x400 + i ] = WaveTable[0];
+ WaveTable[ 0x500 + i ] = WaveTable[0];
+ WaveTable[ 0x900 + i ] = WaveTable[0];
+ WaveTable[ 0xc00 + i ] = WaveTable[0];
+ WaveTable[ 0xd00 + i ] = WaveTable[0];
+ //Replicate sines in other pieces
+ WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ];
+ //double speed sines
+ WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ];
+ WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ];
+ WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ];
+ WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ];
+ }
+#endif
+
+ //Create the ksl table
+ for ( int oct = 0; oct < 8; oct++ ) {
+ int base = oct * 8;
+ for ( int i = 0; i < 16; i++ ) {
+ int val = base - KslCreateTable[i];
+ if ( val < 0 )
+ val = 0;
+ //*4 for the final range to match attenuation range
+ KslTable[ oct * 16 + i ] = val * 4;
+ }
+ }
+ //Create the Tremolo table, just increase and decrease a triangle wave
+ for ( Bit8u i = 0; i < TREMOLO_TABLE / 2; i++ ) {
+ Bit8u val = i << ENV_EXTRA;
+ TremoloTable[i] = val;
+ TremoloTable[TREMOLO_TABLE - 1 - i] = val;
+ }
+ //Create a table with offsets of the channels from the start of the chip
+ DBOPL::Chip* chip = 0;
+ for ( Bitu i = 0; i < 32; i++ ) {
+ Bitu index = i & 0xf;
+ if ( index >= 9 ) {
+ ChanOffsetTable[i] = 0;
+ continue;
+ }
+ //Make sure the four op channels follow eachother
+ if ( index < 6 ) {
+ index = (index % 3) * 2 + ( index / 3 );
+ }
+ //Add back the bits for highest ones
+ if ( i >= 16 )
+ index += 9;
+ Bitu blah = reinterpret_cast<size_t>( &(chip->chan[ index ]) );
+ ChanOffsetTable[i] = blah;
+ }
+ //Same for operators
+ for ( Bitu i = 0; i < 64; i++ ) {
+ if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) {
+ OpOffsetTable[i] = 0;
+ continue;
+ }
+ Bitu chNum = (i / 8) * 3 + (i % 8) % 3;
+ //Make sure we use 16 and up for the 2nd range to match the chanoffset gap
+ if ( chNum >= 12 )
+ chNum += 16 - 12;
+ Bitu opNum = ( i % 8 ) / 3;
+ DBOPL::Channel* chan = 0;
+ Bitu blah = reinterpret_cast<size_t>( &(chan->op[opNum]) );
+ OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah;
+ }
+#if 0
+ //Stupid checks if table's are correct
+ for ( Bitu i = 0; i < 18; i++ ) {
+ Bit32u find = (Bit16u)( &(chip->chan[ i ]) );
+ for ( Bitu c = 0; c < 32; c++ ) {
+ if ( ChanOffsetTable[c] == find ) {
+ find = 0;
+ break;
+ }
+ }
+ if ( find ) {
+ find = find;
+ }
+ }
+ for ( Bitu i = 0; i < 36; i++ ) {
+ Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) );
+ for ( Bitu c = 0; c < 64; c++ ) {
+ if ( OpOffsetTable[c] == find ) {
+ find = 0;
+ break;
+ }
+ }
+ if ( find ) {
+ find = find;
+ }
+ }
+#endif
+}
+
+} //Namespace DBOPL
+} // End of namespace DOSBox
+} // End of namespace OPL
+
+#endif // !DISABLE_DOSBOX_OPL
diff --git a/audio/softsynth/opl/dbopl.h b/audio/softsynth/opl/dbopl.h
new file mode 100644
index 0000000000..87d1045fab
--- /dev/null
+++ b/audio/softsynth/opl/dbopl.h
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2002-2010 The DOSBox 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+// Last synch with DOSBox SVN trunk r3556
+
+#ifndef SOUND_SOFTSYNTH_OPL_DBOPL_H
+#define SOUND_SOFTSYNTH_OPL_DBOPL_H
+
+#include "common/scummsys.h"
+
+#ifndef DISABLE_DOSBOX_OPL
+
+namespace OPL {
+namespace DOSBox {
+
+//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume
+#define WAVE_HANDLER 10
+//Use a logarithmic wavetable with an exponential table for volume
+#define WAVE_TABLELOG 11
+//Use a linear wavetable with a multiply table for volume
+#define WAVE_TABLEMUL 12
+
+//Select the type of wave generator routine
+#define DBOPL_WAVE WAVE_TABLEMUL
+
+namespace DBOPL {
+
+// Type aliases for the DBOPL code
+typedef int Bits;
+typedef uint Bitu;
+
+typedef int8 Bit8s;
+typedef uint8 Bit8u;
+
+typedef int16 Bit16s;
+typedef uint16 Bit16u;
+
+typedef int32 Bit32s;
+typedef uint32 Bit32u;
+
+#define DB_FASTCALL
+#define GCC_UNLIKELY(x) (x)
+#define INLINE inline
+// -------------------------------
+
+struct Chip;
+struct Operator;
+struct Channel;
+
+#if (DBOPL_WAVE == WAVE_HANDLER)
+typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume );
+#endif
+
+typedef Bits ( DBOPL::Operator::*VolumeHandler) ( );
+typedef Channel* ( DBOPL::Channel::*SynthHandler) ( Chip* chip, Bit32u samples, Bit32s* output );
+
+//Different synth modes that can generate blocks of data
+typedef enum {
+ sm2AM,
+ sm2FM,
+ sm3AM,
+ sm3FM,
+ sm4Start,
+ sm3FMFM,
+ sm3AMFM,
+ sm3FMAM,
+ sm3AMAM,
+ sm6Start,
+ sm2Percussion,
+ sm3Percussion
+} SynthMode;
+
+//Shifts for the values contained in chandata variable
+enum {
+ SHIFT_KSLBASE = 16,
+ SHIFT_KEYCODE = 24
+};
+
+struct Operator {
+public:
+ //Masks for operator 20 values
+ enum {
+ MASK_KSR = 0x10,
+ MASK_SUSTAIN = 0x20,
+ MASK_VIBRATO = 0x40,
+ MASK_TREMOLO = 0x80
+ };
+
+ typedef enum {
+ OFF,
+ RELEASE,
+ SUSTAIN,
+ DECAY,
+ ATTACK
+ } State;
+
+ VolumeHandler volHandler;
+
+#if (DBOPL_WAVE == WAVE_HANDLER)
+ WaveHandler waveHandler; //Routine that generate a wave
+#else
+ Bit16s* waveBase;
+ Bit32u waveMask;
+ Bit32u waveStart;
+#endif
+ Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index
+ Bit32u waveAdd; //The base frequency without vibrato
+ Bit32u waveCurrent; //waveAdd + vibratao
+
+ Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this
+ Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove?
+ Bit32u vibrato; //Scaled up vibrato strength
+ Bit32s sustainLevel; //When stopping at sustain level stop here
+ Bit32s totalLevel; //totalLevel is added to every generated volume
+ Bit32u currentLevel; //totalLevel + tremolo
+ Bit32s volume; //The currently active volume
+
+ Bit32u attackAdd; //Timers for the different states of the envelope
+ Bit32u decayAdd;
+ Bit32u releaseAdd;
+ Bit32u rateIndex; //Current position of the evenlope
+
+ Bit8u rateZero; //Bits for the different states of the envelope having no changes
+ Bit8u keyOn; //Bitmask of different values that can generate keyon
+ //Registers, also used to check for changes
+ Bit8u reg20, reg40, reg60, reg80, regE0;
+ //Active part of the envelope we're in
+ Bit8u state;
+ //0xff when tremolo is enabled
+ Bit8u tremoloMask;
+ //Strength of the vibrato
+ Bit8u vibStrength;
+ //Keep track of the calculated KSR so we can check for changes
+ Bit8u ksr;
+private:
+ void SetState( Bit8u s );
+ void UpdateAttack( const Chip* chip );
+ void UpdateRelease( const Chip* chip );
+ void UpdateDecay( const Chip* chip );
+public:
+ void UpdateAttenuation();
+ void UpdateRates( const Chip* chip );
+ void UpdateFrequency( );
+
+ void Write20( const Chip* chip, Bit8u val );
+ void Write40( const Chip* chip, Bit8u val );
+ void Write60( const Chip* chip, Bit8u val );
+ void Write80( const Chip* chip, Bit8u val );
+ void WriteE0( const Chip* chip, Bit8u val );
+
+ bool Silent() const;
+ void Prepare( const Chip* chip );
+
+ void KeyOn( Bit8u mask);
+ void KeyOff( Bit8u mask);
+
+ template< State state>
+ Bits TemplateVolume( );
+
+ Bit32s RateForward( Bit32u add );
+ Bitu ForwardWave();
+ Bitu ForwardVolume();
+
+ Bits GetSample( Bits modulation );
+ Bits GetWave( Bitu index, Bitu vol );
+public:
+ Operator();
+};
+
+struct Channel {
+ Operator op[2];
+ inline Operator* Op( Bitu index ) {
+ return &( ( this + (index >> 1) )->op[ index & 1 ]);
+ }
+ SynthHandler synthHandler;
+ Bit32u chanData; //Frequency/octave and derived values
+ Bit32s old[2]; //Old data for feedback
+
+ Bit8u feedback; //Feedback shift
+ Bit8u regB0; //Register values to check for changes
+ Bit8u regC0;
+ //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel
+ Bit8u fourMask;
+ Bit8s maskLeft; //Sign extended values for both channel's panning
+ Bit8s maskRight;
+
+ //Forward the channel data to the operators of the channel
+ void SetChanData( const Chip* chip, Bit32u data );
+ //Change in the chandata, check for new values and if we have to forward to operators
+ void UpdateFrequency( const Chip* chip, Bit8u fourOp );
+ void WriteA0( const Chip* chip, Bit8u val );
+ void WriteB0( const Chip* chip, Bit8u val );
+ void WriteC0( const Chip* chip, Bit8u val );
+ void ResetC0( const Chip* chip );
+
+ //call this for the first channel
+ template< bool opl3Mode >
+ void GeneratePercussion( Chip* chip, Bit32s* output );
+
+ //Generate blocks of data in specific modes
+ template<SynthMode mode>
+ Channel* BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output );
+ Channel();
+};
+
+struct Chip {
+ //This is used as the base counter for vibrato and tremolo
+ Bit32u lfoCounter;
+ Bit32u lfoAdd;
+
+
+ Bit32u noiseCounter;
+ Bit32u noiseAdd;
+ Bit32u noiseValue;
+
+ //Frequency scales for the different multiplications
+ Bit32u freqMul[16];
+ //Rates for decay and release for rate of this chip
+ Bit32u linearRates[76];
+ //Best match attack rates for the rate of this chip
+ Bit32u attackRates[76];
+
+ //18 channels with 2 operators each
+ Channel chan[18];
+
+ Bit8u reg104;
+ Bit8u reg08;
+ Bit8u reg04;
+ Bit8u regBD;
+ Bit8u vibratoIndex;
+ Bit8u tremoloIndex;
+ Bit8s vibratoSign;
+ Bit8u vibratoShift;
+ Bit8u tremoloValue;
+ Bit8u vibratoStrength;
+ Bit8u tremoloStrength;
+ //Mask for allowed wave forms
+ Bit8u waveFormMask;
+ //0 or -1 when enabled
+ Bit8s opl3Active;
+
+ //Return the maximum amount of samples before and LFO change
+ Bit32u ForwardLFO( Bit32u samples );
+ Bit32u ForwardNoise();
+
+ void WriteBD( Bit8u val );
+ void WriteReg(Bit32u reg, Bit8u val );
+
+ Bit32u WriteAddr( Bit32u port, Bit8u val );
+
+ void GenerateBlock2( Bitu samples, Bit32s* output );
+ void GenerateBlock3( Bitu samples, Bit32s* output );
+
+ void Generate( Bit32u samples );
+ void Setup( Bit32u r );
+
+ Chip();
+};
+
+void InitTables();
+
+} //Namespace
+} // End of namespace DOSBox
+} // End of namespace OPL
+
+#endif // !DISABLE_DOSBOX_OPL
+
+#endif
diff --git a/audio/softsynth/opl/dosbox.cpp b/audio/softsynth/opl/dosbox.cpp
new file mode 100644
index 0000000000..29993ce3d8
--- /dev/null
+++ b/audio/softsynth/opl/dosbox.cpp
@@ -0,0 +1,335 @@
+/* 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$
+ */
+
+/*
+ * Based on AdLib emulation code of DOSBox
+ * Copyright (C) 2002-2009 The DOSBox Team
+ * Licensed under GPLv2+
+ * http://www.dosbox.com
+ */
+
+#ifndef DISABLE_DOSBOX_OPL
+
+#include "dosbox.h"
+#include "dbopl.h"
+
+#include "common/system.h"
+#include "common/scummsys.h"
+
+#include <math.h>
+#include <string.h>
+
+namespace OPL {
+namespace DOSBox {
+
+Timer::Timer() {
+ masked = false;
+ overflow = false;
+ enabled = false;
+ counter = 0;
+ delay = 0;
+}
+
+void Timer::update(double time) {
+ if (!enabled || !delay)
+ return;
+ double deltaStart = time - startTime;
+ // Only set the overflow flag when not masked
+ if (deltaStart >= 0 && !masked)
+ overflow = 1;
+}
+
+void Timer::reset(double time) {
+ overflow = false;
+ if (!delay || !enabled)
+ return;
+ double delta = (time - startTime);
+ double rem = fmod(delta, delay);
+ double next = delay - rem;
+ startTime = time + next;
+}
+
+void Timer::stop() {
+ enabled = false;
+}
+
+void Timer::start(double time, int scale) {
+ //Don't enable again
+ if (enabled)
+ return;
+ enabled = true;
+ delay = 0.001 * (256 - counter) * scale;
+ startTime = time + delay;
+}
+
+bool Chip::write(uint32 reg, uint8 val) {
+ switch (reg) {
+ case 0x02:
+ timer[0].counter = val;
+ return true;
+ case 0x03:
+ timer[1].counter = val;
+ return true;
+ case 0x04:
+ double time = g_system->getMillis() / 1000.0;
+
+ if (val & 0x80) {
+ timer[0].reset(time);
+ timer[1].reset(time);
+ } else {
+ timer[0].update(time);
+ timer[1].update(time);
+
+ if (val & 0x1)
+ timer[0].start(time, 80);
+ else
+ timer[0].stop();
+
+ timer[0].masked = (val & 0x40) > 0;
+
+ if (timer[0].masked)
+ timer[0].overflow = false;
+
+ if (val & 0x2)
+ timer[1].start(time, 320);
+ else
+ timer[1].stop();
+
+ timer[1].masked = (val & 0x20) > 0;
+
+ if (timer[1].masked)
+ timer[1].overflow = false;
+ }
+ return true;
+ }
+ return false;
+}
+
+uint8 Chip::read() {
+ double time = g_system->getMillis() / 1000.0;
+
+ timer[0].update(time);
+ timer[1].update(time);
+
+ uint8 ret = 0;
+ // Overflow won't be set if a channel is masked
+ if (timer[0].overflow) {
+ ret |= 0x40;
+ ret |= 0x80;
+ }
+ if (timer[1].overflow) {
+ ret |= 0x20;
+ ret |= 0x80;
+ }
+ return ret;
+}
+
+OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) {
+}
+
+OPL::~OPL() {
+ free();
+}
+
+void OPL::free() {
+ delete _emulator;
+ _emulator = 0;
+}
+
+bool OPL::init(int rate) {
+ free();
+
+ memset(&_reg, 0, sizeof(_reg));
+ memset(_chip, 0, sizeof(_chip));
+
+ _emulator = new DBOPL::Chip();
+ if (!_emulator)
+ return false;
+
+ DBOPL::InitTables();
+ _emulator->Setup(rate);
+
+ if (_type == Config::kDualOpl2) {
+ // Setup opl3 mode in the hander
+ _emulator->WriteReg(0x105, 1);
+ }
+
+ _rate = rate;
+ return true;
+}
+
+void OPL::reset() {
+ init(_rate);
+}
+
+void OPL::write(int port, int val) {
+ if (port&1) {
+ switch (_type) {
+ case Config::kOpl2:
+ case Config::kOpl3:
+ if (!_chip[0].write(_reg.normal, val))
+ _emulator->WriteReg(_reg.normal, val);
+ break;
+ case Config::kDualOpl2:
+ // Not a 0x??8 port, then write to a specific port
+ if (!(port & 0x8)) {
+ byte index = (port & 2) >> 1;
+ dualWrite(index, _reg.dual[index], val);
+ } else {
+ //Write to both ports
+ dualWrite(0, _reg.dual[0], val);
+ dualWrite(1, _reg.dual[1], val);
+ }
+ break;
+ }
+ } else {
+ // Ask the handler to write the address
+ // Make sure to clip them in the right range
+ switch (_type) {
+ case Config::kOpl2:
+ _reg.normal = _emulator->WriteAddr(port, val) & 0xff;
+ break;
+ case Config::kOpl3:
+ _reg.normal = _emulator->WriteAddr(port, val) & 0x1ff;
+ break;
+ case Config::kDualOpl2:
+ // Not a 0x?88 port, when write to a specific side
+ if (!(port & 0x8)) {
+ byte index = (port & 2) >> 1;
+ _reg.dual[index] = val & 0xff;
+ } else {
+ _reg.dual[0] = val & 0xff;
+ _reg.dual[1] = val & 0xff;
+ }
+ break;
+ }
+ }
+}
+
+byte OPL::read(int port) {
+ switch (_type) {
+ case Config::kOpl2:
+ if (!(port & 1))
+ //Make sure the low bits are 6 on opl2
+ return _chip[0].read() | 0x6;
+ break;
+ case Config::kOpl3:
+ if (!(port & 1))
+ return _chip[0].read();
+ break;
+ case Config::kDualOpl2:
+ // Only return for the lower ports
+ if (port & 1)
+ return 0xff;
+ // Make sure the low bits are 6 on opl2
+ return _chip[(port >> 1) & 1].read() | 0x6;
+ }
+ return 0;
+}
+
+void OPL::writeReg(int r, int v) {
+ byte tempReg = 0;
+ switch (_type) {
+ case Config::kOpl2:
+ case Config::kDualOpl2:
+ case Config::kOpl3:
+ // We can't use _handler->writeReg here directly, since it would miss timer changes.
+
+ // Backup old setup register
+ tempReg = _reg.normal;
+
+ // We need to set the register we want to write to via port 0x388
+ write(0x388, r);
+ // Do the real writing to the register
+ write(0x389, v);
+ // Restore the old register
+ write(0x388, tempReg);
+ break;
+ };
+}
+
+void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) {
+ // Make sure you don't use opl3 features
+ // Don't allow write to disable opl3
+ if (reg == 5)
+ return;
+
+ // Only allow 4 waveforms
+ if (reg >= 0xE0 && reg <= 0xE8)
+ val &= 3;
+
+ // Write to the timer?
+ if (_chip[index].write(reg, val))
+ return;
+
+ // Enabling panning
+ if (reg >= 0xC0 && reg <= 0xC8) {
+ val &= 15;
+ val |= index ? 0xA0 : 0x50;
+ }
+
+ uint32 fullReg = reg + (index ? 0x100 : 0);
+ _emulator->WriteReg(fullReg, val);
+}
+
+void OPL::readBuffer(int16 *buffer, int length) {
+ // For stereo OPL cards, we divide the sample count by 2,
+ // to match stereo AudioStream behavior.
+ if (_type != Config::kOpl2)
+ length >>= 1;
+
+ const uint bufferLength = 512;
+ int32 tempBuffer[bufferLength * 2];
+
+ if (_emulator->opl3Active) {
+ while (length > 0) {
+ const uint readSamples = MIN<uint>(length, bufferLength);
+
+ _emulator->GenerateBlock3(readSamples, tempBuffer);
+
+ for (uint i = 0; i < (readSamples << 1); ++i)
+ buffer[i] = tempBuffer[i];
+
+ buffer += (readSamples << 1);
+ length -= readSamples;
+ }
+ } else {
+ while (length > 0) {
+ const uint readSamples = MIN<uint>(length, bufferLength << 1);
+
+ _emulator->GenerateBlock2(readSamples, tempBuffer);
+
+ for (uint i = 0; i < readSamples; ++i)
+ buffer[i] = tempBuffer[i];
+
+ buffer += readSamples;
+ length -= readSamples;
+ }
+ }
+}
+
+} // End of namespace DOSBox
+} // End of namespace OPL
+
+#endif // !DISABLE_DOSBOX_ADLIB
diff --git a/audio/softsynth/opl/dosbox.h b/audio/softsynth/opl/dosbox.h
new file mode 100644
index 0000000000..1e92c7f7c9
--- /dev/null
+++ b/audio/softsynth/opl/dosbox.h
@@ -0,0 +1,110 @@
+/* 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$
+ */
+
+/*
+ * Based on OPL emulation code of DOSBox
+ * Copyright (C) 2002-2009 The DOSBox Team
+ * Licensed under GPLv2+
+ * http://www.dosbox.com
+ */
+
+#ifndef SOUND_SOFTSYNTH_OPL_DOSBOX_H
+#define SOUND_SOFTSYNTH_OPL_DOSBOX_H
+
+#ifndef DISABLE_DOSBOX_OPL
+
+#include "audio/fmopl.h"
+
+namespace OPL {
+namespace DOSBox {
+
+struct Timer {
+ double startTime;
+ double delay;
+ bool enabled, overflow, masked;
+ uint8 counter;
+
+ Timer();
+
+ //Call update before making any further changes
+ void update(double time);
+
+ //On a reset make sure the start is in sync with the next cycle
+ void reset(double time);
+
+ void stop();
+
+ void start(double time, int scale);
+};
+
+struct Chip {
+ //Last selected register
+ Timer timer[2];
+ //Check for it being a write to the timer
+ bool write(uint32 addr, uint8 val);
+ //Read the current timer state, will use current double
+ uint8 read();
+};
+
+namespace DBOPL {
+struct Chip;
+} // end of namespace DBOPL
+
+class OPL : public ::OPL::OPL {
+private:
+ Config::OplType _type;
+ uint _rate;
+
+ DBOPL::Chip *_emulator;
+ Chip _chip[2];
+ union {
+ uint16 normal;
+ uint8 dual[2];
+ } _reg;
+
+ void free();
+ void dualWrite(uint8 index, uint8 reg, uint8 val);
+public:
+ OPL(Config::OplType type);
+ ~OPL();
+
+ bool init(int rate);
+ void reset();
+
+ void write(int a, int v);
+ byte read(int a);
+
+ void writeReg(int r, int v);
+
+ void readBuffer(int16 *buffer, int length);
+ bool isStereo() const { return _type != Config::kOpl2; }
+};
+
+} // End of namespace DOSBox
+} // End of namespace OPL
+
+#endif // !DISABLE_DOSBOX_OPL
+
+#endif
+
diff --git a/audio/softsynth/opl/mame.cpp b/audio/softsynth/opl/mame.cpp
new file mode 100644
index 0000000000..c875080e8f
--- /dev/null
+++ b/audio/softsynth/opl/mame.cpp
@@ -0,0 +1,1234 @@
+/* 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$
+ *
+ * LGPL licensed version of MAMEs fmopl (V0.37a modified) by
+ * Tatsuyuki Satoh. Included from LGPL'ed AdPlug.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <math.h>
+
+#include "mame.h"
+
+#if defined (_WIN32_WCE) || defined (__SYMBIAN32__) || defined(__GP32__) || defined(GP2X) || defined (__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__)
+#include "common/config-manager.h"
+#endif
+
+#if defined(__DS__)
+#include "dsmain.h"
+#endif
+
+namespace OPL {
+namespace MAME {
+
+OPL::~OPL() {
+ MAME::OPLDestroy(_opl);
+ _opl = 0;
+}
+
+bool OPL::init(int rate) {
+ if (_opl)
+ MAME::OPLDestroy(_opl);
+
+ _opl = MAME::makeAdLibOPL(rate);
+ return (_opl != 0);
+}
+
+void OPL::reset() {
+ MAME::OPLResetChip(_opl);
+}
+
+void OPL::write(int a, int v) {
+ MAME::OPLWrite(_opl, a, v);
+}
+
+byte OPL::read(int a) {
+ return MAME::OPLRead(_opl, a);
+}
+
+void OPL::writeReg(int r, int v) {
+ MAME::OPLWriteReg(_opl, r, v);
+}
+
+void OPL::readBuffer(int16 *buffer, int length) {
+ MAME::YM3812UpdateOne(_opl, buffer, length);
+}
+
+/* -------------------- preliminary define section --------------------- */
+/* attack/decay rate time rate */
+#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */
+#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */
+
+#define FREQ_BITS 24 /* frequency turn */
+
+/* counter bits = 20 , octerve 7 */
+#define FREQ_RATE (1<<(FREQ_BITS-20))
+#define TL_BITS (FREQ_BITS+2)
+
+/* final output shift , limit minimum and maximum */
+#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */
+#define OPL_MAXOUT (0x7fff<<OPL_OUTSB)
+#define OPL_MINOUT (-0x8000<<OPL_OUTSB)
+
+/* -------------------- quality selection --------------------- */
+
+/* sinwave entries */
+/* used static memory = SIN_ENT * 4 (byte) */
+#ifdef __DS__
+#define SIN_ENT_SHIFT 8
+#else
+#define SIN_ENT_SHIFT 11
+#endif
+#define SIN_ENT (1<<SIN_ENT_SHIFT)
+
+/* output level entries (envelope,sinwave) */
+/* envelope counter lower bits */
+int ENV_BITS;
+/* envelope output entries */
+int EG_ENT;
+
+/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */
+/* used static memory = EG_ENT*4 (byte) */
+int EG_OFF; /* OFF */
+int EG_DED;
+int EG_DST; /* DECAY START */
+int EG_AED;
+#define EG_AST 0 /* ATTACK START */
+
+#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */
+
+/* LFO table entries */
+#define VIB_ENT 512
+#define VIB_SHIFT (32-9)
+#define AMS_ENT 512
+#define AMS_SHIFT (32-9)
+
+#define VIB_RATE_SHIFT 8
+#define VIB_RATE (1<<VIB_RATE_SHIFT)
+
+/* -------------------- local defines , macros --------------------- */
+
+/* register number to channel number , slot offset */
+#define SLOT1 0
+#define SLOT2 1
+
+/* envelope phase */
+#define ENV_MOD_RR 0x00
+#define ENV_MOD_DR 0x01
+#define ENV_MOD_AR 0x02
+
+/* -------------------- tables --------------------- */
+static const int slot_array[32] = {
+ 0, 2, 4, 1, 3, 5,-1,-1,
+ 6, 8,10, 7, 9,11,-1,-1,
+ 12,14,16,13,15,17,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1
+};
+
+static uint KSL_TABLE[8 * 16];
+
+static const double KSL_TABLE_SEED[8 * 16] = {
+ /* OCT 0 */
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 0.000, 0.000, 0.000,
+ /* OCT 1 */
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 0.750, 1.125, 1.500,
+ 1.875, 2.250, 2.625, 3.000,
+ /* OCT 2 */
+ 0.000, 0.000, 0.000, 0.000,
+ 0.000, 1.125, 1.875, 2.625,
+ 3.000, 3.750, 4.125, 4.500,
+ 4.875, 5.250, 5.625, 6.000,
+ /* OCT 3 */
+ 0.000, 0.000, 0.000, 1.875,
+ 3.000, 4.125, 4.875, 5.625,
+ 6.000, 6.750, 7.125, 7.500,
+ 7.875, 8.250, 8.625, 9.000,
+ /* OCT 4 */
+ 0.000, 0.000, 3.000, 4.875,
+ 6.000, 7.125, 7.875, 8.625,
+ 9.000, 9.750, 10.125, 10.500,
+ 10.875, 11.250, 11.625, 12.000,
+ /* OCT 5 */
+ 0.000, 3.000, 6.000, 7.875,
+ 9.000, 10.125, 10.875, 11.625,
+ 12.000, 12.750, 13.125, 13.500,
+ 13.875, 14.250, 14.625, 15.000,
+ /* OCT 6 */
+ 0.000, 6.000, 9.000, 10.875,
+ 12.000, 13.125, 13.875, 14.625,
+ 15.000, 15.750, 16.125, 16.500,
+ 16.875, 17.250, 17.625, 18.000,
+ /* OCT 7 */
+ 0.000, 9.000, 12.000, 13.875,
+ 15.000, 16.125, 16.875, 17.625,
+ 18.000, 18.750, 19.125, 19.500,
+ 19.875, 20.250, 20.625, 21.000
+};
+
+/* sustain level table (3db per step) */
+/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/
+
+static int SL_TABLE[16];
+
+static const uint SL_TABLE_SEED[16] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31
+};
+
+#define TL_MAX (EG_ENT * 2) /* limit(tl + ksr + envelope) + sinwave */
+/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */
+/* TL_TABLE[ 0 to TL_MAX ] : plus section */
+/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */
+static int *TL_TABLE;
+
+/* pointers to TL_TABLE with sinwave output offset */
+static int **SIN_TABLE;
+
+/* LFO table */
+static int *AMS_TABLE;
+static int *VIB_TABLE;
+
+/* envelope output curve table */
+/* attack + decay + OFF */
+//static int ENV_CURVE[2*EG_ENT+1];
+//static int ENV_CURVE[2 * 4096 + 1]; // to keep it static ...
+static int *ENV_CURVE;
+
+
+/* multiple table */
+#define ML(a) (int)(a * 2)
+static const uint MUL_TABLE[16]= {
+/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */
+ ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00),
+ ML(8.00), ML(9.00), ML(10.00), ML(10.00),ML(12.00),ML(12.00),ML(15.00),ML(15.00)
+};
+#undef ML
+
+/* dummy attack / decay rate ( when rate == 0 ) */
+static int RATE_0[16]=
+{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+/* -------------------- static state --------------------- */
+
+/* lock level of common table */
+static int num_lock = 0;
+
+/* work table */
+static void *cur_chip = NULL; /* current chip point */
+/* currenct chip state */
+/* static OPLSAMPLE *bufL,*bufR; */
+static OPL_CH *S_CH;
+static OPL_CH *E_CH;
+OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2;
+
+static int outd[1];
+static int ams;
+static int vib;
+int *ams_table;
+int *vib_table;
+static int amsIncr;
+static int vibIncr;
+static int feedback2; /* connect for SLOT 2 */
+
+/* --------------------- rebuild tables ------------------- */
+
+#define SC_KSL(mydb) ((uint) (mydb / (EG_STEP / 2)))
+#define SC_SL(db) (int)(db * ((3 / EG_STEP) * (1 << ENV_BITS))) + EG_DST
+
+void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM) {
+ int i;
+
+ ENV_BITS = ENV_BITS_PARAM;
+ EG_ENT = EG_ENT_PARAM;
+ EG_OFF = ((2 * EG_ENT)<<ENV_BITS); /* OFF */
+ EG_DED = EG_OFF;
+ EG_DST = (EG_ENT << ENV_BITS); /* DECAY START */
+ EG_AED = EG_DST;
+ //EG_STEP = (96.0/EG_ENT);
+
+ for (i = 0; i < ARRAYSIZE(KSL_TABLE_SEED); i++)
+ KSL_TABLE[i] = SC_KSL(KSL_TABLE_SEED[i]);
+
+ for (i = 0; i < ARRAYSIZE(SL_TABLE_SEED); i++)
+ SL_TABLE[i] = SC_SL(SL_TABLE_SEED[i]);
+}
+
+#undef SC_KSL
+#undef SC_SL
+
+/* --------------------- subroutines --------------------- */
+
+/* status set and IRQ handling */
+inline void OPL_STATUS_SET(FM_OPL *OPL, int flag) {
+ /* set status flag */
+ OPL->status |= flag;
+ if (!(OPL->status & 0x80)) {
+ if (OPL->status & OPL->statusmask) { /* IRQ on */
+ OPL->status |= 0x80;
+ /* callback user interrupt handler (IRQ is OFF to ON) */
+ if (OPL->IRQHandler)
+ (OPL->IRQHandler)(OPL->IRQParam,1);
+ }
+ }
+}
+
+/* status reset and IRQ handling */
+inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) {
+ /* reset status flag */
+ OPL->status &= ~flag;
+ if ((OPL->status & 0x80)) {
+ if (!(OPL->status & OPL->statusmask)) {
+ OPL->status &= 0x7f;
+ /* callback user interrupt handler (IRQ is ON to OFF) */
+ if (OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0);
+ }
+ }
+}
+
+/* IRQ mask set */
+inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) {
+ OPL->statusmask = flag;
+ /* IRQ handling check */
+ OPL_STATUS_SET(OPL,0);
+ OPL_STATUS_RESET(OPL,0);
+}
+
+/* ----- key on ----- */
+inline void OPL_KEYON(OPL_SLOT *SLOT) {
+ /* sin wave restart */
+ SLOT->Cnt = 0;
+ /* set attack */
+ SLOT->evm = ENV_MOD_AR;
+ SLOT->evs = SLOT->evsa;
+ SLOT->evc = EG_AST;
+ SLOT->eve = EG_AED;
+}
+
+/* ----- key off ----- */
+inline void OPL_KEYOFF(OPL_SLOT *SLOT) {
+ if (SLOT->evm > ENV_MOD_RR) {
+ /* set envelope counter from envleope output */
+
+ // WORKAROUND: The Kyra engine does something very strange when
+ // starting a new song. For each channel:
+ //
+ // * The release rate is set to "fastest".
+ // * Any note is keyed off.
+ // * A very low-frequency note is keyed on.
+ //
+ // Usually, what happens next is that the real notes is keyed
+ // on immediately, in which case there's no problem.
+ //
+ // However, if the note is again keyed off (because the channel
+ // begins on a rest rather than a note), the envelope counter
+ // was moved from the very lowest point on the attack curve to
+ // the very highest point on the release curve.
+ //
+ // Again, this might not be a problem, if the release rate is
+ // still set to "fastest". But in many cases, it had already
+ // been increased. And, possibly because of inaccuracies in the
+ // envelope generator, that would cause the note to "fade out"
+ // for quite a long time.
+ //
+ // What we really need is a way to find the correct starting
+ // point for the envelope counter, and that may be what the
+ // commented-out line below is meant to do. For now, simply
+ // handle the pathological case.
+
+ if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST)
+ SLOT->evc = EG_DED;
+ else if (!(SLOT->evc & EG_DST))
+ //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST;
+ SLOT->evc = EG_DST;
+ SLOT->eve = EG_DED;
+ SLOT->evs = SLOT->evsr;
+ SLOT->evm = ENV_MOD_RR;
+ }
+}
+
+/* ---------- calcrate Envelope Generator & Phase Generator ---------- */
+
+/* return : envelope output */
+inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) {
+ /* calcrate envelope generator */
+ if ((SLOT->evc += SLOT->evs) >= SLOT->eve) {
+ switch (SLOT->evm) {
+ case ENV_MOD_AR: /* ATTACK -> DECAY1 */
+ /* next DR */
+ SLOT->evm = ENV_MOD_DR;
+ SLOT->evc = EG_DST;
+ SLOT->eve = SLOT->SL;
+ SLOT->evs = SLOT->evsd;
+ break;
+ case ENV_MOD_DR: /* DECAY -> SL or RR */
+ SLOT->evc = SLOT->SL;
+ SLOT->eve = EG_DED;
+ if (SLOT->eg_typ) {
+ SLOT->evs = 0;
+ } else {
+ SLOT->evm = ENV_MOD_RR;
+ SLOT->evs = SLOT->evsr;
+ }
+ break;
+ case ENV_MOD_RR: /* RR -> OFF */
+ SLOT->evc = EG_OFF;
+ SLOT->eve = EG_OFF + 1;
+ SLOT->evs = 0;
+ break;
+ }
+ }
+ /* calcrate envelope */
+ return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0);
+}
+
+/* set algorythm connection */
+static void set_algorythm(OPL_CH *CH) {
+ int *carrier = &outd[0];
+ CH->connect1 = CH->CON ? carrier : &feedback2;
+ CH->connect2 = carrier;
+}
+
+/* ---------- frequency counter for operater update ---------- */
+inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) {
+ int ksr;
+
+ /* frequency step counter */
+ SLOT->Incr = CH->fc * SLOT->mul;
+ ksr = CH->kcode >> SLOT->KSR;
+
+ if (SLOT->ksr != ksr) {
+ SLOT->ksr = ksr;
+ /* attack , decay rate recalcration */
+ SLOT->evsa = SLOT->AR[ksr];
+ SLOT->evsd = SLOT->DR[ksr];
+ SLOT->evsr = SLOT->RR[ksr];
+ }
+ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl);
+}
+
+/* set multi,am,vib,EG-TYP,KSR,mul */
+inline void set_mul(FM_OPL *OPL, int slot, int v) {
+ OPL_CH *CH = &OPL->P_CH[slot>>1];
+ OPL_SLOT *SLOT = &CH->SLOT[slot & 1];
+
+ SLOT->mul = MUL_TABLE[v & 0x0f];
+ SLOT->KSR = (v & 0x10) ? 0 : 2;
+ SLOT->eg_typ = (v & 0x20) >> 5;
+ SLOT->vib = (v & 0x40);
+ SLOT->ams = (v & 0x80);
+ CALC_FCSLOT(CH, SLOT);
+}
+
+/* set ksl & tl */
+inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) {
+ OPL_CH *CH = &OPL->P_CH[slot>>1];
+ OPL_SLOT *SLOT = &CH->SLOT[slot & 1];
+ int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */
+
+ SLOT->ksl = ksl ? 3-ksl : 31;
+ SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */
+
+ if (!(OPL->mode & 0x80)) { /* not CSM latch total level */
+ SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl);
+ }
+}
+
+/* set attack rate & decay rate */
+inline void set_ar_dr(FM_OPL *OPL, int slot, int v) {
+ OPL_CH *CH = &OPL->P_CH[slot>>1];
+ OPL_SLOT *SLOT = &CH->SLOT[slot & 1];
+ int ar = v >> 4;
+ int dr = v & 0x0f;
+
+ SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0;
+ SLOT->evsa = SLOT->AR[SLOT->ksr];
+ if (SLOT->evm == ENV_MOD_AR)
+ SLOT->evs = SLOT->evsa;
+
+ SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0;
+ SLOT->evsd = SLOT->DR[SLOT->ksr];
+ if (SLOT->evm == ENV_MOD_DR)
+ SLOT->evs = SLOT->evsd;
+}
+
+/* set sustain level & release rate */
+inline void set_sl_rr(FM_OPL *OPL, int slot, int v) {
+ OPL_CH *CH = &OPL->P_CH[slot>>1];
+ OPL_SLOT *SLOT = &CH->SLOT[slot & 1];
+ int sl = v >> 4;
+ int rr = v & 0x0f;
+
+ SLOT->SL = SL_TABLE[sl];
+ if (SLOT->evm == ENV_MOD_DR)
+ SLOT->eve = SLOT->SL;
+ SLOT->RR = &OPL->DR_TABLE[rr<<2];
+ SLOT->evsr = SLOT->RR[SLOT->ksr];
+ if (SLOT->evm == ENV_MOD_RR)
+ SLOT->evs = SLOT->evsr;
+}
+
+/* operator output calcrator */
+
+#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env]
+/* ---------- calcrate one of channel ---------- */
+inline void OPL_CALC_CH(OPL_CH *CH) {
+ uint env_out;
+ OPL_SLOT *SLOT;
+
+ feedback2 = 0;
+ /* SLOT 1 */
+ SLOT = &CH->SLOT[SLOT1];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if (env_out < (uint)(EG_ENT - 1)) {
+ /* PG */
+ if (SLOT->vib)
+ SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT;
+ else
+ SLOT->Cnt += SLOT->Incr;
+ /* connection */
+ if (CH->FB) {
+ int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB;
+ CH->op1_out[1] = CH->op1_out[0];
+ *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1);
+ } else {
+ *CH->connect1 += OP_OUT(SLOT, env_out, 0);
+ }
+ } else {
+ CH->op1_out[1] = CH->op1_out[0];
+ CH->op1_out[0] = 0;
+ }
+ /* SLOT 2 */
+ SLOT = &CH->SLOT[SLOT2];
+ env_out=OPL_CALC_SLOT(SLOT);
+ if (env_out < (uint)(EG_ENT - 1)) {
+ /* PG */
+ if (SLOT->vib)
+ SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT;
+ else
+ SLOT->Cnt += SLOT->Incr;
+ /* connection */
+ outd[0] += OP_OUT(SLOT, env_out, feedback2);
+ }
+}
+
+/* ---------- calcrate rythm block ---------- */
+#define WHITE_NOISE_db 6.0
+inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) {
+ uint env_tam, env_sd, env_top, env_hh;
+ // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)),
+ // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to
+ // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024,
+ // or 128, so we can safely avoid any FP ops.
+ int whitenoise = OPL->rnd.getRandomBit() * (EG_ENT>>4);
+
+ int tone8;
+
+ OPL_SLOT *SLOT;
+ int env_out;
+
+ /* BD : same as FM serial mode and output level is large */
+ feedback2 = 0;
+ /* SLOT 1 */
+ SLOT = &CH[6].SLOT[SLOT1];
+ env_out = OPL_CALC_SLOT(SLOT);
+ if (env_out < EG_ENT-1) {
+ /* PG */
+ if (SLOT->vib)
+ SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT;
+ else
+ SLOT->Cnt += SLOT->Incr;
+ /* connection */
+ if (CH[6].FB) {
+ int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB;
+ CH[6].op1_out[1] = CH[6].op1_out[0];
+ feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1);
+ }
+ else {
+ feedback2 = OP_OUT(SLOT, env_out, 0);
+ }
+ } else {
+ feedback2 = 0;
+ CH[6].op1_out[1] = CH[6].op1_out[0];
+ CH[6].op1_out[0] = 0;
+ }
+ /* SLOT 2 */
+ SLOT = &CH[6].SLOT[SLOT2];
+ env_out = OPL_CALC_SLOT(SLOT);
+ if (env_out < EG_ENT-1) {
+ /* PG */
+ if (SLOT->vib)
+ SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT;
+ else
+ SLOT->Cnt += SLOT->Incr;
+ /* connection */
+ outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2;
+ }
+
+ // SD (17) = mul14[fnum7] + white noise
+ // TAM (15) = mul15[fnum8]
+ // TOP (18) = fnum6(mul18[fnum8]+whitenoise)
+ // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise
+ env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise;
+ env_tam =OPL_CALC_SLOT(SLOT8_1);
+ env_top = OPL_CALC_SLOT(SLOT8_2);
+ env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise;
+
+ /* PG */
+ if (SLOT7_1->vib)
+ SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1);
+ else
+ SLOT7_1->Cnt += 2 * SLOT7_1->Incr;
+ if (SLOT7_2->vib)
+ SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3);
+ else
+ SLOT7_2->Cnt += (CH[7].fc * 8);
+ if (SLOT8_1->vib)
+ SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT;
+ else
+ SLOT8_1->Cnt += SLOT8_1->Incr;
+ if (SLOT8_2->vib)
+ SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4);
+ else
+ SLOT8_2->Cnt += (CH[8].fc * 48);
+
+ tone8 = OP_OUT(SLOT8_2,whitenoise,0 );
+
+ /* SD */
+ if (env_sd < (uint)(EG_ENT - 1))
+ outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8;
+ /* TAM */
+ if (env_tam < (uint)(EG_ENT - 1))
+ outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2;
+ /* TOP-CY */
+ if (env_top < (uint)(EG_ENT - 1))
+ outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2;
+ /* HH */
+ if (env_hh < (uint)(EG_ENT-1))
+ outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2;
+}
+
+/* ----------- initialize time tabls ----------- */
+static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) {
+ int i;
+ double rate;
+
+ /* make attack rate & decay rate tables */
+ for (i = 0; i < 4; i++)
+ OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0;
+ for (i = 4; i <= 60; i++) {
+ rate = OPL->freqbase; /* frequency rate */
+ if (i < 60)
+ rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */
+ rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */
+ rate *= (double)(EG_ENT << ENV_BITS);
+ OPL->AR_TABLE[i] = (int)(rate / ARRATE);
+ OPL->DR_TABLE[i] = (int)(rate / DRRATE);
+ }
+ for (i = 60; i < 76; i++) {
+ OPL->AR_TABLE[i] = EG_AED-1;
+ OPL->DR_TABLE[i] = OPL->DR_TABLE[60];
+ }
+}
+
+/* ---------- generic table initialize ---------- */
+static int OPLOpenTable(void) {
+ int s,t;
+ double rate;
+ int i,j;
+ double pom;
+
+#ifdef __DS__
+ DS::fastRamReset();
+
+ TL_TABLE = (int *) DS::fastRamAlloc(TL_MAX * 2 * sizeof(int *));
+ SIN_TABLE = (int **) DS::fastRamAlloc(SIN_ENT * 4 * sizeof(int *));
+#else
+
+ /* allocate dynamic tables */
+ if ((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL)
+ return 0;
+
+ if ((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) {
+ free(TL_TABLE);
+ return 0;
+ }
+#endif
+
+ if ((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) {
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ return 0;
+ }
+
+ if ((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) {
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ free(AMS_TABLE);
+ return 0;
+ }
+ /* make total level table */
+ for (t = 0; t < EG_ENT - 1; t++) {
+ rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */
+ TL_TABLE[ t] = (int)rate;
+ TL_TABLE[TL_MAX + t] = -TL_TABLE[t];
+ }
+ /* fill volume off area */
+ for (t = EG_ENT - 1; t < TL_MAX; t++) {
+ TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0;
+ }
+
+ /* make sinwave table (total level offet) */
+ /* degree 0 = degree 180 = off */
+ SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1];
+ for (s = 1;s <= SIN_ENT / 4; s++) {
+ pom = sin(2 * PI * s / SIN_ENT); /* sin */
+ pom = 20 * log10(1 / pom); /* decibel */
+ j = int(pom / EG_STEP); /* TL_TABLE steps */
+
+ /* degree 0 - 90 , degree 180 - 90 : plus section */
+ SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j];
+ /* degree 180 - 270 , degree 360 - 270 : minus section */
+ SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j];
+ }
+ for (s = 0;s < SIN_ENT; s++) {
+ SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT];
+ SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)];
+ SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s];
+ }
+
+
+ ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1));
+
+ /* envelope counter -> envelope output table */
+ for (i=0; i < EG_ENT; i++) {
+ /* ATTACK curve */
+ pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT;
+ /* if (pom >= EG_ENT) pom = EG_ENT-1; */
+ ENV_CURVE[i] = (int)pom;
+ /* DECAY ,RELEASE curve */
+ ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i;
+ }
+ /* off */
+ ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1;
+ /* make LFO ams table */
+ for (i=0; i < AMS_ENT; i++) {
+ pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */
+ AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */
+ AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */
+ }
+ /* make LFO vibrate table */
+ for (i=0; i < VIB_ENT; i++) {
+ /* 100cent = 1seminote = 6% ?? */
+ pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */
+ VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */
+ VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */
+ }
+ return 1;
+}
+
+static void OPLCloseTable(void) {
+ free(TL_TABLE);
+ free(SIN_TABLE);
+ free(AMS_TABLE);
+ free(VIB_TABLE);
+ free(ENV_CURVE);
+}
+
+/* CSM Key Controll */
+inline void CSMKeyControll(OPL_CH *CH) {
+ OPL_SLOT *slot1 = &CH->SLOT[SLOT1];
+ OPL_SLOT *slot2 = &CH->SLOT[SLOT2];
+ /* all key off */
+ OPL_KEYOFF(slot1);
+ OPL_KEYOFF(slot2);
+ /* total level latch */
+ slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl);
+ slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl);
+ /* key on */
+ CH->op1_out[0] = CH->op1_out[1] = 0;
+ OPL_KEYON(slot1);
+ OPL_KEYON(slot2);
+}
+
+/* ---------- opl initialize ---------- */
+static void OPL_initalize(FM_OPL *OPL) {
+ int fn;
+
+ /* frequency base */
+ OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0;
+ /* Timer base time */
+ OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 );
+ /* make time tables */
+ init_timetables(OPL, OPL_ARRATE, OPL_DRRATE);
+ /* make fnumber -> increment counter table */
+ for (fn=0; fn < 1024; fn++) {
+ OPL->FN_TABLE[fn] = (uint)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2);
+ }
+ /* LFO freq.table */
+ OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0);
+ OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0);
+}
+
+/* ---------- write a OPL registers ---------- */
+void OPLWriteReg(FM_OPL *OPL, int r, int v) {
+ OPL_CH *CH;
+ int slot;
+ uint block_fnum;
+
+ switch (r & 0xe0) {
+ case 0x00: /* 00-1f:controll */
+ switch (r & 0x1f) {
+ case 0x01:
+ /* wave selector enable */
+ if (OPL->type&OPL_TYPE_WAVESEL) {
+ OPL->wavesel = v & 0x20;
+ if (!OPL->wavesel) {
+ /* preset compatible mode */
+ int c;
+ for (c = 0; c < OPL->max_ch; c++) {
+ OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0];
+ OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0];
+ }
+ }
+ }
+ return;
+ case 0x02: /* Timer 1 */
+ OPL->T[0] = (256-v) * 4;
+ break;
+ case 0x03: /* Timer 2 */
+ OPL->T[1] = (256-v) * 16;
+ return;
+ case 0x04: /* IRQ clear / mask and Timer enable */
+ if (v & 0x80) { /* IRQ flag clear */
+ OPL_STATUS_RESET(OPL, 0x7f);
+ } else { /* set IRQ mask ,timer enable*/
+ uint8 st1 = v & 1;
+ uint8 st2 = (v >> 1) & 1;
+ /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */
+ OPL_STATUS_RESET(OPL, v & 0x78);
+ OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01);
+ /* timer 2 */
+ if (OPL->st[1] != st2) {
+ double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0;
+ OPL->st[1] = st2;
+ if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval);
+ }
+ /* timer 1 */
+ if (OPL->st[0] != st1) {
+ double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0;
+ OPL->st[0] = st1;
+ if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval);
+ }
+ }
+ return;
+ }
+ break;
+ case 0x20: /* am,vib,ksr,eg type,mul */
+ slot = slot_array[r&0x1f];
+ if (slot == -1)
+ return;
+ set_mul(OPL,slot,v);
+ return;
+ case 0x40:
+ slot = slot_array[r&0x1f];
+ if (slot == -1)
+ return;
+ set_ksl_tl(OPL,slot,v);
+ return;
+ case 0x60:
+ slot = slot_array[r&0x1f];
+ if (slot == -1)
+ return;
+ set_ar_dr(OPL,slot,v);
+ return;
+ case 0x80:
+ slot = slot_array[r&0x1f];
+ if (slot == -1)
+ return;
+ set_sl_rr(OPL,slot,v);
+ return;
+ case 0xa0:
+ switch (r) {
+ case 0xbd:
+ /* amsep,vibdep,r,bd,sd,tom,tc,hh */
+ {
+ uint8 rkey = OPL->rythm ^ v;
+ OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0];
+ OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0];
+ OPL->rythm = v & 0x3f;
+ if (OPL->rythm & 0x20) {
+ /* BD key on/off */
+ if (rkey & 0x10) {
+ if (v & 0x10) {
+ OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0;
+ OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]);
+ OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]);
+ } else {
+ OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]);
+ OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]);
+ }
+ }
+ /* SD key on/off */
+ if (rkey & 0x08) {
+ if (v & 0x08)
+ OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]);
+ else
+ OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]);
+ }/* TAM key on/off */
+ if (rkey & 0x04) {
+ if (v & 0x04)
+ OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]);
+ else
+ OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]);
+ }
+ /* TOP-CY key on/off */
+ if (rkey & 0x02) {
+ if (v & 0x02)
+ OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]);
+ else
+ OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]);
+ }
+ /* HH key on/off */
+ if (rkey & 0x01) {
+ if (v & 0x01)
+ OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]);
+ else
+ OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]);
+ }
+ }
+ }
+ return;
+
+ default:
+ break;
+ }
+ /* keyon,block,fnum */
+ if ((r & 0x0f) > 8)
+ return;
+ CH = &OPL->P_CH[r & 0x0f];
+ if (!(r&0x10)) { /* a0-a8 */
+ block_fnum = (CH->block_fnum & 0x1f00) | v;
+ } else { /* b0-b8 */
+ int keyon = (v >> 5) & 1;
+ block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff);
+ if (CH->keyon != keyon) {
+ if ((CH->keyon=keyon)) {
+ CH->op1_out[0] = CH->op1_out[1] = 0;
+ OPL_KEYON(&CH->SLOT[SLOT1]);
+ OPL_KEYON(&CH->SLOT[SLOT2]);
+ } else {
+ OPL_KEYOFF(&CH->SLOT[SLOT1]);
+ OPL_KEYOFF(&CH->SLOT[SLOT2]);
+ }
+ }
+ }
+ /* update */
+ if (CH->block_fnum != block_fnum) {
+ int blockRv = 7 - (block_fnum >> 10);
+ int fnum = block_fnum & 0x3ff;
+ CH->block_fnum = block_fnum;
+ CH->ksl_base = KSL_TABLE[block_fnum >> 6];
+ CH->fc = OPL->FN_TABLE[fnum] >> blockRv;
+ CH->kcode = CH->block_fnum >> 9;
+ if ((OPL->mode & 0x40) && CH->block_fnum & 0x100)
+ CH->kcode |=1;
+ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]);
+ CALC_FCSLOT(CH,&CH->SLOT[SLOT2]);
+ }
+ return;
+ case 0xc0:
+ /* FB,C */
+ if ((r & 0x0f) > 8)
+ return;
+ CH = &OPL->P_CH[r&0x0f];
+ {
+ int feedback = (v >> 1) & 7;
+ CH->FB = feedback ? (8 + 1) - feedback : 0;
+ CH->CON = v & 1;
+ set_algorythm(CH);
+ }
+ return;
+ case 0xe0: /* wave type */
+ slot = slot_array[r & 0x1f];
+ if (slot == -1)
+ return;
+ CH = &OPL->P_CH[slot>>1];
+ if (OPL->wavesel) {
+ CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT];
+ }
+ return;
+ }
+}
+
+/* lock/unlock for common table */
+static int OPL_LockTable(void) {
+ num_lock++;
+ if (num_lock>1)
+ return 0;
+ /* first time */
+ cur_chip = NULL;
+ /* allocate total level table (128kb space) */
+ if (!OPLOpenTable()) {
+ num_lock--;
+ return -1;
+ }
+ return 0;
+}
+
+static void OPL_UnLockTable(void) {
+ if (num_lock)
+ num_lock--;
+ if (num_lock)
+ return;
+ /* last time */
+ cur_chip = NULL;
+ OPLCloseTable();
+}
+
+/*******************************************************************************/
+/* YM3812 local section */
+/*******************************************************************************/
+
+/* ---------- update one of chip ----------- */
+void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) {
+ int i;
+ int data;
+ int16 *buf = buffer;
+ uint amsCnt = OPL->amsCnt;
+ uint vibCnt = OPL->vibCnt;
+ uint8 rythm = OPL->rythm & 0x20;
+ OPL_CH *CH, *R_CH;
+
+
+ if ((void *)OPL != cur_chip) {
+ cur_chip = (void *)OPL;
+ /* channel pointers */
+ S_CH = OPL->P_CH;
+ E_CH = &S_CH[9];
+ /* rythm slot */
+ SLOT7_1 = &S_CH[7].SLOT[SLOT1];
+ SLOT7_2 = &S_CH[7].SLOT[SLOT2];
+ SLOT8_1 = &S_CH[8].SLOT[SLOT1];
+ SLOT8_2 = &S_CH[8].SLOT[SLOT2];
+ /* LFO state */
+ amsIncr = OPL->amsIncr;
+ vibIncr = OPL->vibIncr;
+ ams_table = OPL->ams_table;
+ vib_table = OPL->vib_table;
+ }
+ R_CH = rythm ? &S_CH[6] : E_CH;
+ for (i = 0; i < length; i++) {
+ /* channel A channel B channel C */
+ /* LFO */
+ ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT];
+ vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT];
+ outd[0] = 0;
+ /* FM part */
+ for (CH = S_CH; CH < R_CH; CH++)
+ OPL_CALC_CH(CH);
+ /* Rythn part */
+ if (rythm)
+ OPL_CALC_RH(OPL, S_CH);
+ /* limit check */
+ data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT);
+ /* store to sound buffer */
+ buf[i] = data >> OPL_OUTSB;
+ }
+
+ OPL->amsCnt = amsCnt;
+ OPL->vibCnt = vibCnt;
+}
+
+/* ---------- reset a chip ---------- */
+void OPLResetChip(FM_OPL *OPL) {
+ int c,s;
+ int i;
+
+ /* reset chip */
+ OPL->mode = 0; /* normal mode */
+ OPL_STATUS_RESET(OPL, 0x7f);
+ /* reset with register write */
+ OPLWriteReg(OPL, 0x01,0); /* wabesel disable */
+ OPLWriteReg(OPL, 0x02,0); /* Timer1 */
+ OPLWriteReg(OPL, 0x03,0); /* Timer2 */
+ OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */
+ for (i = 0xff; i >= 0x20; i--)
+ OPLWriteReg(OPL,i,0);
+ /* reset OPerator parameter */
+ for (c = 0; c < OPL->max_ch; c++) {
+ OPL_CH *CH = &OPL->P_CH[c];
+ /* OPL->P_CH[c].PAN = OPN_CENTER; */
+ for (s = 0; s < 2; s++) {
+ /* wave table */
+ CH->SLOT[s].wavetable = &SIN_TABLE[0];
+ /* CH->SLOT[s].evm = ENV_MOD_RR; */
+ CH->SLOT[s].evc = EG_OFF;
+ CH->SLOT[s].eve = EG_OFF + 1;
+ CH->SLOT[s].evs = 0;
+ }
+ }
+}
+
+/* ---------- Create a virtual YM3812 ---------- */
+/* 'rate' is sampling rate and 'bufsiz' is the size of the */
+FM_OPL *OPLCreate(int type, int clock, int rate) {
+ char *ptr;
+ FM_OPL *OPL;
+ int state_size;
+ int max_ch = 9; /* normaly 9 channels */
+
+ if (OPL_LockTable() == -1)
+ return NULL;
+ /* allocate OPL state space */
+ state_size = sizeof(FM_OPL);
+ state_size += sizeof(OPL_CH) * max_ch;
+
+ /* allocate memory block */
+ ptr = (char *)calloc(state_size, 1);
+ if (ptr == NULL)
+ return NULL;
+
+ /* clear */
+ memset(ptr, 0, state_size);
+ OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL);
+ OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch;
+
+ /* set channel state pointer */
+ OPL->type = type;
+ OPL->clock = clock;
+ OPL->rate = rate;
+ OPL->max_ch = max_ch;
+
+ /* init grobal tables */
+ OPL_initalize(OPL);
+
+ /* reset chip */
+ OPLResetChip(OPL);
+ return OPL;
+}
+
+/* ---------- Destroy one of vietual YM3812 ---------- */
+void OPLDestroy(FM_OPL *OPL) {
+ OPL_UnLockTable();
+ free(OPL);
+}
+
+/* ---------- Option handlers ---------- */
+void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) {
+ OPL->TimerHandler = TimerHandler;
+ OPL->TimerParam = channelOffset;
+}
+
+void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) {
+ OPL->IRQHandler = IRQHandler;
+ OPL->IRQParam = param;
+}
+
+void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) {
+ OPL->UpdateHandler = UpdateHandler;
+ OPL->UpdateParam = param;
+}
+
+/* ---------- YM3812 I/O interface ---------- */
+int OPLWrite(FM_OPL *OPL,int a,int v) {
+ if (!(a & 1)) { /* address port */
+ OPL->address = v & 0xff;
+ } else { /* data port */
+ if (OPL->UpdateHandler)
+ OPL->UpdateHandler(OPL->UpdateParam,0);
+ OPLWriteReg(OPL, OPL->address,v);
+ }
+ return OPL->status >> 7;
+}
+
+unsigned char OPLRead(FM_OPL *OPL,int a) {
+ if (!(a & 1)) { /* status port */
+ return OPL->status & (OPL->statusmask | 0x80);
+ }
+ /* data port */
+ switch (OPL->address) {
+ case 0x05: /* KeyBoard IN */
+ warning("OPL:read unmapped KEYBOARD port");
+ return 0;
+ case 0x19: /* I/O DATA */
+ warning("OPL:read unmapped I/O port");
+ return 0;
+ case 0x1a: /* PCM-DATA */
+ return 0;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int OPLTimerOver(FM_OPL *OPL, int c) {
+ if (c) { /* Timer B */
+ OPL_STATUS_SET(OPL, 0x20);
+ } else { /* Timer A */
+ OPL_STATUS_SET(OPL, 0x40);
+ /* CSM mode key,TL controll */
+ if (OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */
+ int ch;
+ if (OPL->UpdateHandler)
+ OPL->UpdateHandler(OPL->UpdateParam,0);
+ for (ch = 0; ch < 9; ch++)
+ CSMKeyControll(&OPL->P_CH[ch]);
+ }
+ }
+ /* reload timer */
+ if (OPL->TimerHandler)
+ (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase);
+ return OPL->status >> 7;
+}
+
+FM_OPL *makeAdLibOPL(int rate) {
+ // We need to emulate one YM3812 chip
+ int env_bits = FMOPL_ENV_BITS_HQ;
+ int eg_ent = FMOPL_EG_ENT_HQ;
+#if defined (_WIN32_WCE) || defined(__SYMBIAN32__) || defined(__GP32__) || defined (GP2X) || defined(__MAEMO__) || defined(__DS__) || defined (__MINT__) || defined(__N64__)
+ if (ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) {
+ env_bits = FMOPL_ENV_BITS_HQ;
+ eg_ent = FMOPL_EG_ENT_HQ;
+ } else if (ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")) {
+ env_bits = FMOPL_ENV_BITS_MQ;
+ eg_ent = FMOPL_EG_ENT_MQ;
+ } else {
+ env_bits = FMOPL_ENV_BITS_LQ;
+ eg_ent = FMOPL_EG_ENT_LQ;
+ }
+#endif
+
+ OPLBuildTables(env_bits, eg_ent);
+ return OPLCreate(OPL_TYPE_YM3812, 3579545, rate);
+}
+
+} // End of namespace MAME
+} // End of namespace OPL
+
diff --git a/audio/softsynth/opl/mame.h b/audio/softsynth/opl/mame.h
new file mode 100644
index 0000000000..58ef5f18dc
--- /dev/null
+++ b/audio/softsynth/opl/mame.h
@@ -0,0 +1,202 @@
+/* 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$
+ *
+ * LGPL licensed version of MAMEs fmopl (V0.37a modified) by
+ * Tatsuyuki Satoh. Included from LGPL'ed AdPlug.
+ */
+
+
+#ifndef SOUND_SOFTSYNTH_OPL_MAME_H
+#define SOUND_SOFTSYNTH_OPL_MAME_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/random.h"
+
+#include "audio/fmopl.h"
+
+namespace OPL {
+namespace MAME {
+
+enum {
+ FMOPL_ENV_BITS_HQ = 16,
+ FMOPL_ENV_BITS_MQ = 8,
+ FMOPL_ENV_BITS_LQ = 8,
+ FMOPL_EG_ENT_HQ = 4096,
+ FMOPL_EG_ENT_MQ = 1024,
+ FMOPL_EG_ENT_LQ = 128
+};
+
+
+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);
+
+#define OPL_TYPE_WAVESEL 0x01 /* waveform select */
+
+/* Saving is necessary for member of the 'R' mark for suspend/resume */
+/* ---------- OPL one of slot ---------- */
+typedef struct fm_opl_slot {
+ int TL; /* total level :TL << 8 */
+ int TLL; /* adjusted now TL */
+ uint8 KSR; /* key scale rate :(shift down bit) */
+ int *AR; /* attack rate :&AR_TABLE[AR<<2] */
+ int *DR; /* decay rate :&DR_TABLE[DR<<2] */
+ int SL; /* sustain level :SL_TABLE[SL] */
+ int *RR; /* release rate :&DR_TABLE[RR<<2] */
+ uint8 ksl; /* keyscale level :(shift down bits) */
+ uint8 ksr; /* key scale rate :kcode>>KSR */
+ uint mul; /* multiple :ML_TABLE[ML] */
+ uint Cnt; /* frequency count */
+ uint Incr; /* frequency step */
+
+ /* envelope generator state */
+ uint8 eg_typ;/* envelope type flag */
+ uint8 evm; /* envelope phase */
+ int evc; /* envelope counter */
+ int eve; /* envelope counter end point */
+ int evs; /* envelope counter step */
+ int evsa; /* envelope step for AR :AR[ksr] */
+ int evsd; /* envelope step for DR :DR[ksr] */
+ int evsr; /* envelope step for RR :RR[ksr] */
+
+ /* LFO */
+ uint8 ams; /* ams flag */
+ uint8 vib; /* vibrate flag */
+ /* wave selector */
+ int **wavetable;
+} OPL_SLOT;
+
+/* ---------- OPL one of channel ---------- */
+typedef struct fm_opl_channel {
+ OPL_SLOT SLOT[2];
+ uint8 CON; /* connection type */
+ uint8 FB; /* feed back :(shift down bit)*/
+ int *connect1; /* slot1 output pointer */
+ int *connect2; /* slot2 output pointer */
+ int op1_out[2]; /* slot1 output for selfeedback */
+
+ /* phase generator state */
+ uint block_fnum; /* block+fnum */
+ uint8 kcode; /* key code : KeyScaleCode */
+ uint fc; /* Freq. Increment base */
+ uint ksl_base; /* KeyScaleLevel Base step */
+ uint8 keyon; /* key on/off flag */
+} OPL_CH;
+
+/* OPL state */
+typedef struct fm_opl_f {
+ uint8 type; /* chip type */
+ int clock; /* master clock (Hz) */
+ int rate; /* sampling rate (Hz) */
+ double freqbase; /* frequency base */
+ double TimerBase; /* Timer base time (==sampling time) */
+ uint8 address; /* address register */
+ uint8 status; /* status flag */
+ uint8 statusmask; /* status mask */
+ uint mode; /* Reg.08 : CSM , notesel,etc. */
+
+ /* Timer */
+ int T[2]; /* timer counter */
+ uint8 st[2]; /* timer enable */
+
+ /* FM channel slots */
+ OPL_CH *P_CH; /* pointer of CH */
+ int max_ch; /* maximum channel */
+
+ /* Rythm sention */
+ uint8 rythm; /* Rythm mode , key flag */
+
+ /* time tables */
+ int AR_TABLE[76]; /* atttack rate tables */
+ int DR_TABLE[76]; /* decay rate tables */
+ uint FN_TABLE[1024];/* fnumber -> increment counter */
+
+ /* LFO */
+ int *ams_table;
+ int *vib_table;
+ int amsCnt;
+ int amsIncr;
+ int vibCnt;
+ int vibIncr;
+
+ /* wave selector enable flag */
+ uint8 wavesel;
+
+ /* external event callback handler */
+ 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 */
+
+ Common::RandomSource rnd;
+} FM_OPL;
+
+/* ---------- Generic interface section ---------- */
+#define OPL_TYPE_YM3526 (0)
+#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL)
+
+void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM);
+
+FM_OPL *OPLCreate(int type, int clock, int rate);
+void OPLDestroy(FM_OPL *OPL);
+void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset);
+void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param);
+void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param);
+
+void OPLResetChip(FM_OPL *OPL);
+int OPLWrite(FM_OPL *OPL, int a, int v);
+unsigned char OPLRead(FM_OPL *OPL, int a);
+int OPLTimerOver(FM_OPL *OPL, int c);
+void OPLWriteReg(FM_OPL *OPL, int r, int v);
+void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length);
+
+// Factory method
+FM_OPL *makeAdLibOPL(int rate);
+
+// OPL API implementation
+class OPL : public ::OPL::OPL {
+private:
+ FM_OPL *_opl;
+public:
+ OPL() : _opl(0) {}
+ ~OPL();
+
+ bool init(int rate);
+ void reset();
+
+ void write(int a, int v);
+ byte read(int a);
+
+ void writeReg(int r, int v);
+
+ void readBuffer(int16 *buffer, int length);
+ bool isStereo() const { return false; }
+};
+
+} // End of namespace MAME
+} // End of namespace OPL
+
+#endif
diff --git a/audio/softsynth/pcspk.cpp b/audio/softsynth/pcspk.cpp
new file mode 100644
index 0000000000..69ba113c8b
--- /dev/null
+++ b/audio/softsynth/pcspk.cpp
@@ -0,0 +1,187 @@
+/* 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 "audio/softsynth/pcspk.h"
+#include "audio/null.h"
+
+namespace Audio {
+
+const PCSpeaker::generatorFunc PCSpeaker::generateWave[] =
+ {&PCSpeaker::generateSquare, &PCSpeaker::generateSine,
+ &PCSpeaker::generateSaw, &PCSpeaker::generateTriangle};
+
+PCSpeaker::PCSpeaker(int rate) {
+ _rate = rate;
+ _wave = kWaveFormSquare;
+ _playForever = false;
+ _oscLength = 0;
+ _oscSamples = 0;
+ _remainingSamples = 0;
+ _mixedSamples = 0;
+ _volume = 255;
+}
+
+PCSpeaker::~PCSpeaker() {
+}
+
+void PCSpeaker::play(WaveForm wave, int freq, int32 length) {
+ Common::StackLock lock(_mutex);
+
+ assert((wave >= kWaveFormSquare) && (wave <= kWaveFormTriangle));
+
+ _wave = wave;
+ _oscLength = _rate / freq;
+ _oscSamples = 0;
+ if (length == -1) {
+ _remainingSamples = 1;
+ _playForever = true;
+ } else {
+ _remainingSamples = (_rate * length) / 1000;
+ _playForever = false;
+ }
+ _mixedSamples = 0;
+}
+
+void PCSpeaker::stop(int32 delay) {
+ Common::StackLock lock(_mutex);
+
+ _remainingSamples = (_rate * delay) / 1000;
+ _playForever = false;
+}
+
+void PCSpeaker::setVolume(byte volume) {
+ _volume = volume;
+}
+
+bool PCSpeaker::isPlaying() const {
+ return _remainingSamples != 0;
+}
+
+int PCSpeaker::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ int i;
+
+ for (i = 0; _remainingSamples && (i < numSamples); i++) {
+ buffer[i] = generateWave[_wave](_oscSamples, _oscLength) * _volume;
+ if (_oscSamples++ >= _oscLength)
+ _oscSamples = 0;
+ if (!_playForever)
+ _remainingSamples--;
+ _mixedSamples++;
+ }
+
+ // Clear the rest of the buffer
+ if (i < numSamples)
+ memset(buffer + i, 0, (numSamples - i) * sizeof(int16));
+
+ return numSamples;
+}
+
+int8 PCSpeaker::generateSquare(uint32 x, uint32 oscLength) {
+ return (x < (oscLength / 2)) ? 127 : -128;
+}
+
+int8 PCSpeaker::generateSine(uint32 x, uint32 oscLength) {
+ if (oscLength == 0)
+ return 0;
+
+ // TODO: Maybe using a look-up-table would be better?
+ return CLIP<int16>((int16) (128 * sin(2.0 * PI * x / oscLength)), -128, 127);
+}
+
+int8 PCSpeaker::generateSaw(uint32 x, uint32 oscLength) {
+ if (oscLength == 0)
+ return 0;
+
+ return ((x * (65536 / oscLength)) >> 8) - 128;
+}
+
+int8 PCSpeaker::generateTriangle(uint32 x, uint32 oscLength) {
+ if (oscLength == 0)
+ return 0;
+
+ int y = ((x * (65536 / (oscLength / 2))) >> 8) - 128;
+
+ return (x <= (oscLength / 2)) ? y : (256 - y);
+}
+
+} // End of namespace Audio
+
+
+// Plugin interface
+// (This can only create a null driver since pc speaker support is not part of the
+// midi driver architecture. But we need the plugin for the options menu in the launcher
+// and for MidiDriver::detectDevice() which is more or less used by all engines.)
+
+class PCSpeakerMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("PC Speaker Emulator");
+ }
+
+ const char *getId() const {
+ return "pcspk";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices PCSpeakerMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_PCSPK));
+ return devices;
+}
+
+class PCjrMusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("IBM PCjr Emulator");
+ }
+
+ const char *getId() const {
+ return "pcjr";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices PCjrMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_PCJR));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(PCSPK)
+ //REGISTER_PLUGIN_DYNAMIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(PCSPK, PLUGIN_TYPE_MUSIC, PCSpeakerMusicPlugin);
+//#endif
+
+//#if PLUGIN_ENABLED_DYNAMIC(PCJR)
+ //REGISTER_PLUGIN_DYNAMIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(PCJR, PLUGIN_TYPE_MUSIC, PCjrMusicPlugin);
+//#endif
diff --git a/audio/softsynth/pcspk.h b/audio/softsynth/pcspk.h
new file mode 100644
index 0000000000..c0d85bbceb
--- /dev/null
+++ b/audio/softsynth/pcspk.h
@@ -0,0 +1,88 @@
+/* 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$
+ */
+
+#ifndef SOUND_SOFTSYNTH_PCSPK_H
+#define SOUND_SOFTSYNTH_PCSPK_H
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/mutex.h"
+
+namespace Audio {
+
+class PCSpeaker : public AudioStream {
+public:
+ enum WaveForm {
+ kWaveFormSquare = 0,
+ kWaveFormSine,
+ kWaveFormSaw,
+ kWaveFormTriangle
+ };
+
+ PCSpeaker(int rate = 44100);
+ ~PCSpeaker();
+
+ /** Play a note for length ms.
+ *
+ * If length is negative, play until told to stop.
+ */
+ void play(WaveForm wave, int freq, int32 length);
+ /** Stop the currently playing note after delay ms. */
+ void stop(int32 delay = 0);
+ /** Adjust the volume. */
+ void setVolume(byte volume);
+
+ bool isPlaying() const;
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return false; }
+ bool endOfData() const { return false; }
+ bool endOfStream() const { return false; }
+ int getRate() const { return _rate; }
+
+protected:
+ Common::Mutex _mutex;
+
+ int _rate;
+ WaveForm _wave;
+ bool _playForever;
+ uint32 _oscLength;
+ uint32 _oscSamples;
+ uint32 _remainingSamples;
+ uint32 _mixedSamples;
+ byte _volume;
+
+ typedef int8 (*generatorFunc)(uint32, uint32);
+ static const generatorFunc generateWave[];
+
+ static int8 generateSquare(uint32 x, uint32 oscLength);
+ static int8 generateSine(uint32 x, uint32 oscLength);
+ static int8 generateSaw(uint32 x, uint32 oscLength);
+ static int8 generateTriangle(uint32 x, uint32 oscLength);
+};
+
+} // End of namespace Audio
+
+#endif // SOUND_SOFTSYNTH_PCSPEAKER_H
diff --git a/audio/softsynth/sid.cpp b/audio/softsynth/sid.cpp
new file mode 100644
index 0000000000..241766fa0a
--- /dev/null
+++ b/audio/softsynth/sid.cpp
@@ -0,0 +1,1456 @@
+/* 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$
+ *
+ */
+
+/*
+ * This file is based on reSID, a MOS6581 SID emulator engine.
+ * Copyright (C) 2004 Dag Lem <resid@nimrod.no>
+ */
+
+#ifndef DISABLE_SID
+
+#include "sid.h"
+#include "audio/null.h"
+#include <math.h>
+
+namespace Resid {
+
+// Fixpoint constants (16.16 bits).
+const int SID::FIXP_SHIFT = 16;
+const int SID::FIXP_MASK = 0xffff;
+
+/*
+ * WaveformGenerator
+ */
+
+WaveformGenerator::WaveformGenerator() {
+ sync_source = this;
+
+ reset();
+}
+
+void WaveformGenerator::set_sync_source(WaveformGenerator* source) {
+ sync_source = source;
+ source->sync_dest = this;
+}
+
+void WaveformGenerator::writeFREQ_LO(reg8 freq_lo) {
+ freq = (freq & 0xff00) | (freq_lo & 0x00ff);
+}
+
+void WaveformGenerator::writeFREQ_HI(reg8 freq_hi) {
+ freq = ((freq_hi << 8) & 0xff00) | (freq & 0x00ff);
+}
+
+void WaveformGenerator::writePW_LO(reg8 pw_lo) {
+ pw = (pw & 0xf00) | (pw_lo & 0x0ff);
+}
+
+void WaveformGenerator::writePW_HI(reg8 pw_hi) {
+ pw = ((pw_hi << 8) & 0xf00) | (pw & 0x0ff);
+}
+
+void WaveformGenerator::writeCONTROL_REG(reg8 control) {
+ waveform = (control >> 4) & 0x0f;
+ ring_mod = control & 0x04;
+ sync = control & 0x02;
+
+ reg8 test_next = control & 0x08;
+
+ // Test bit set.
+ if (test_next) {
+ accumulator = 0;
+ shift_register = 0;
+ }
+ // Test bit cleared.
+ else if (test) {
+ shift_register = 0x7ffff8;
+ }
+
+ test = test_next;
+
+ // The gate bit is handled by the EnvelopeGenerator.
+}
+
+reg8 WaveformGenerator::readOSC() {
+ return output() >> 4;
+}
+
+void WaveformGenerator::reset() {
+ accumulator = 0;
+ shift_register = 0x7ffff8;
+ freq = 0;
+ pw = 0;
+
+ test = 0;
+ ring_mod = 0;
+ sync = 0;
+
+ msb_rising = false;
+}
+
+RESID_INLINE void WaveformGenerator::clock(cycle_count delta_t) {
+ // No operation if test bit is set.
+ if (test) {
+ return;
+ }
+
+ reg24 accumulator_prev = accumulator;
+
+ // Calculate new accumulator value;
+ reg24 delta_accumulator = delta_t*freq;
+ accumulator += delta_accumulator;
+ accumulator &= 0xffffff;
+
+ // Check whether the MSB is set high. This is used for synchronization.
+ msb_rising = !(accumulator_prev & 0x800000) && (accumulator & 0x800000);
+
+ // Shift noise register once for each time accumulator bit 19 is set high.
+ // Bit 19 is set high each time 2^20 (0x100000) is added to the accumulator.
+ reg24 shift_period = 0x100000;
+
+ while (delta_accumulator) {
+ if (delta_accumulator < shift_period) {
+ shift_period = delta_accumulator;
+ // Determine whether bit 19 is set on the last period.
+ // NB! Requires two's complement integer.
+ if (shift_period <= 0x080000) {
+ // Check for flip from 0 to 1.
+ if (((accumulator - shift_period) & 0x080000) || !(accumulator & 0x080000))
+ {
+ break;
+ }
+ }
+ else {
+ // Check for flip from 0 (to 1 or via 1 to 0) or from 1 via 0 to 1.
+ if (((accumulator - shift_period) & 0x080000) && !(accumulator & 0x080000))
+ {
+ break;
+ }
+ }
+ }
+
+ // Shift the noise/random register.
+ // NB! The shift is actually delayed 2 cycles, this is not modeled.
+ reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1;
+ shift_register <<= 1;
+ shift_register &= 0x7fffff;
+ shift_register |= bit0;
+
+ delta_accumulator -= shift_period;
+ }
+}
+
+
+/**
+ * Synchronize oscillators.
+ * This must be done after all the oscillators have been clock()'ed since the
+ * oscillators operate in parallel.
+ * Note that the oscillators must be clocked exactly on the cycle when the
+ * MSB is set high for hard sync to operate correctly. See SID::clock().
+ */
+RESID_INLINE void WaveformGenerator::synchronize() {
+ // A special case occurs when a sync source is synced itself on the same
+ // cycle as when its MSB is set high. In this case the destination will
+ // not be synced. This has been verified by sampling OSC3.
+ if (msb_rising && sync_dest->sync && !(sync && sync_source->msb_rising)) {
+ sync_dest->accumulator = 0;
+ }
+}
+
+
+/*
+ * Output functions
+ */
+
+// No waveform: Zero output.
+RESID_INLINE reg12 WaveformGenerator::output____() {
+ return 0x000;
+}
+
+// Triangle:
+RESID_INLINE reg12 WaveformGenerator::output___T() {
+ reg24 msb = (ring_mod ? accumulator ^ sync_source->accumulator : accumulator)
+ & 0x800000;
+ return ((msb ? ~accumulator : accumulator) >> 11) & 0xfff;
+}
+
+// Sawtooth:
+RESID_INLINE reg12 WaveformGenerator::output__S_() {
+ return accumulator >> 12;
+}
+
+// Pulse:
+RESID_INLINE reg12 WaveformGenerator::output_P__() {
+ return (test || (accumulator >> 12) >= pw) ? 0xfff : 0x000;
+}
+
+// Noise:
+RESID_INLINE reg12 WaveformGenerator::outputN___() {
+ return
+ ((shift_register & 0x400000) >> 11) |
+ ((shift_register & 0x100000) >> 10) |
+ ((shift_register & 0x010000) >> 7) |
+ ((shift_register & 0x002000) >> 5) |
+ ((shift_register & 0x000800) >> 4) |
+ ((shift_register & 0x000080) >> 1) |
+ ((shift_register & 0x000010) << 1) |
+ ((shift_register & 0x000004) << 2);
+}
+
+// Combined waveforms:
+
+RESID_INLINE reg12 WaveformGenerator::output__ST() {
+ return wave6581__ST[output__S_()] << 4;
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_P_T() {
+ return (wave6581_P_T[output___T() >> 1] << 4) & output_P__();
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_PS_() {
+ return (wave6581_PS_[output__S_()] << 4) & output_P__();
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_PST() {
+ return (wave6581_PST[output__S_()] << 4) & output_P__();
+}
+
+// Combined waveforms including noise:
+
+RESID_INLINE reg12 WaveformGenerator::outputN__T() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputN_S_() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputN_ST() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNP__() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNP_T() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNPS_() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNPST() {
+ return 0;
+}
+
+/**
+ * Select one of 16 possible combinations of waveforms.
+ */
+RESID_INLINE reg12 WaveformGenerator::output() {
+ // It may seem cleaner to use an array of member functions to return
+ // waveform output; however a switch with inline functions is faster.
+
+ switch (waveform) {
+ default:
+ case 0x0:
+ return output____();
+ case 0x1:
+ return output___T();
+ case 0x2:
+ return output__S_();
+ case 0x3:
+ return output__ST();
+ case 0x4:
+ return output_P__();
+ case 0x5:
+ return output_P_T();
+ case 0x6:
+ return output_PS_();
+ case 0x7:
+ return output_PST();
+ case 0x8:
+ return outputN___();
+ case 0x9:
+ return outputN__T();
+ case 0xa:
+ return outputN_S_();
+ case 0xb:
+ return outputN_ST();
+ case 0xc:
+ return outputNP__();
+ case 0xd:
+ return outputNP_T();
+ case 0xe:
+ return outputNPS_();
+ case 0xf:
+ return outputNPST();
+ }
+}
+
+/*
+ * Our objective is to construct a smooth interpolating single-valued function
+ * y = f(x).
+ * Our approach is to approximate the properties of Catmull-Rom splines for
+ * piecewice cubic polynomials.
+ */
+
+/**
+ * Calculation of coefficients.
+ */
+inline void cubic_coefficients(double x1, double y1, double x2, double y2,
+ double k1, double k2,
+ double& a, double& b, double& c, double& d)
+{
+ double dx = x2 - x1, dy = y2 - y1;
+
+ a = ((k1 + k2) - 2*dy/dx)/(dx*dx);
+ b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2;
+ c = k1 - (3*x1*a + 2*b)*x1;
+ d = y1 - ((x1*a + b)*x1 + c)*x1;
+}
+
+/**
+ * Evaluation of cubic polynomial by forward differencing.
+ */
+template<class PointPlotter>
+inline void interpolate_segment(double x1, double y1, double x2, double y2,
+ double k1, double k2,
+ PointPlotter plot, double res)
+{
+ double a, b, c, d;
+ cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d);
+
+ double y = ((a*x1 + b)*x1 + c)*x1 + d;
+ double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res;
+ double d2y = (6*a*(x1 + res) + 2*b)*res*res;
+ double d3y = 6*a*res*res*res;
+
+ // Calculate each point.
+ for (double x = x1; x <= x2; x += res) {
+ plot(x, y);
+ y += dy; dy += d2y; d2y += d3y;
+ }
+}
+
+template<class PointIter>
+inline double x(PointIter p) {
+ return (*p)[0];
+}
+
+template<class PointIter>
+inline double y(PointIter p) {
+ return (*p)[1];
+}
+
+/**
+ * Evaluation of complete interpolating function.
+ * Note that since each curve segment is controlled by four points, the
+ * end points will not be interpolated. If extra control points are not
+ * desirable, the end points can simply be repeated to ensure interpolation.
+ * Note also that points of non-differentiability and discontinuity can be
+ * introduced by repeating points.
+ */
+template<class PointIter, class PointPlotter>
+inline void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res) {
+ double k1, k2;
+
+ // Set up points for first curve segment.
+ PointIter p1 = p0; ++p1;
+ PointIter p2 = p1; ++p2;
+ PointIter p3 = p2; ++p3;
+
+ // Draw each curve segment.
+ for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) {
+ // p1 and p2 equal; single point.
+ if (x(p1) == x(p2)) {
+ continue;
+ }
+ // Both end points repeated; straight line.
+ if (x(p0) == x(p1) && x(p2) == x(p3)) {
+ k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1));
+ }
+ // p0 and p1 equal; use f''(x1) = 0.
+ else if (x(p0) == x(p1)) {
+ k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
+ k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2;
+ }
+ // p2 and p3 equal; use f''(x2) = 0.
+ else if (x(p2) == x(p3)) {
+ k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
+ k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2;
+ }
+ // Normal curve.
+ else {
+ k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
+ k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
+ }
+
+ interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res);
+ }
+}
+
+/**
+ * Class for plotting integers into an array.
+ */
+template<class F>
+class PointPlotter {
+protected:
+ F* f;
+
+public:
+ PointPlotter(F* arr) : f(arr) {
+ }
+
+ void operator ()(double x, double y) {
+ // Clamp negative values to zero.
+ if (y < 0) {
+ y = 0;
+ }
+
+ f[F(x)] = F(y);
+ }
+};
+
+fc_point Filter::f0_points_6581[] = {
+ // FC f FCHI FCLO
+ // ----------------------------
+ { 0, 220 }, // 0x00 - repeated end point
+ { 0, 220 }, // 0x00
+ { 128, 230 }, // 0x10
+ { 256, 250 }, // 0x20
+ { 384, 300 }, // 0x30
+ { 512, 420 }, // 0x40
+ { 640, 780 }, // 0x50
+ { 768, 1600 }, // 0x60
+ { 832, 2300 }, // 0x68
+ { 896, 3200 }, // 0x70
+ { 960, 4300 }, // 0x78
+ { 992, 5000 }, // 0x7c
+ { 1008, 5400 }, // 0x7e
+ { 1016, 5700 }, // 0x7f
+ { 1023, 6000 }, // 0x7f 0x07
+ { 1023, 6000 }, // 0x7f 0x07 - discontinuity
+ { 1024, 4600 }, // 0x80 -
+ { 1024, 4600 }, // 0x80
+ { 1032, 4800 }, // 0x81
+ { 1056, 5300 }, // 0x84
+ { 1088, 6000 }, // 0x88
+ { 1120, 6600 }, // 0x8c
+ { 1152, 7200 }, // 0x90
+ { 1280, 9500 }, // 0xa0
+ { 1408, 12000 }, // 0xb0
+ { 1536, 14500 }, // 0xc0
+ { 1664, 16000 }, // 0xd0
+ { 1792, 17100 }, // 0xe0
+ { 1920, 17700 }, // 0xf0
+ { 2047, 18000 }, // 0xff 0x07
+ { 2047, 18000 } // 0xff 0x07 - repeated end point
+};
+
+
+/*
+ * Filter
+ */
+
+Filter::Filter() {
+ fc = 0;
+
+ res = 0;
+
+ filt = 0;
+
+ voice3off = 0;
+
+ hp_bp_lp = 0;
+
+ vol = 0;
+
+ // State of filter.
+ Vhp = 0;
+ Vbp = 0;
+ Vlp = 0;
+ Vnf = 0;
+
+ enable_filter(true);
+
+ // Create mappings from FC to cutoff frequency.
+ interpolate(f0_points_6581, f0_points_6581
+ + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1,
+ PointPlotter<sound_sample>(f0_6581), 1.0);
+
+ mixer_DC = (-0xfff*0xff/18) >> 7;
+
+ f0 = f0_6581;
+ f0_points = f0_points_6581;
+ f0_count = sizeof(f0_points_6581)/sizeof(*f0_points_6581);
+
+ set_w0();
+ set_Q();
+}
+
+void Filter::enable_filter(bool enable) {
+ enabled = enable;
+}
+
+void Filter::reset(){
+ fc = 0;
+
+ res = 0;
+
+ filt = 0;
+
+ voice3off = 0;
+
+ hp_bp_lp = 0;
+
+ vol = 0;
+
+ // State of filter.
+ Vhp = 0;
+ Vbp = 0;
+ Vlp = 0;
+ Vnf = 0;
+
+ set_w0();
+ set_Q();
+}
+
+void Filter::writeFC_LO(reg8 fc_lo) {
+ fc = (fc & 0x7f8) | (fc_lo & 0x007);
+ set_w0();
+}
+
+void Filter::writeFC_HI(reg8 fc_hi) {
+ fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007);
+ set_w0();
+}
+
+void Filter::writeRES_FILT(reg8 res_filt) {
+ res = (res_filt >> 4) & 0x0f;
+ set_Q();
+
+ filt = res_filt & 0x0f;
+}
+
+void Filter::writeMODE_VOL(reg8 mode_vol) {
+ voice3off = mode_vol & 0x80;
+
+ hp_bp_lp = (mode_vol >> 4) & 0x07;
+
+ vol = mode_vol & 0x0f;
+}
+
+// Set filter cutoff frequency.
+void Filter::set_w0() {
+ const double pi = 3.1415926535897932385;
+
+ // Multiply with 1.048576 to facilitate division by 1 000 000 by right-
+ // shifting 20 times (2 ^ 20 = 1048576).
+ w0 = static_cast<sound_sample>(2*pi*f0[fc]*1.048576);
+
+ // Limit f0 to 16kHz to keep 1 cycle filter stable.
+ const sound_sample w0_max_1 = static_cast<sound_sample>(2*pi*16000*1.048576);
+ w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1;
+
+ // Limit f0 to 4kHz to keep delta_t cycle filter stable.
+ const sound_sample w0_max_dt = static_cast<sound_sample>(2*pi*4000*1.048576);
+ w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt;
+}
+
+// Set filter resonance.
+void Filter::set_Q() {
+ // Q is controlled linearly by res. Q has approximate range [0.707, 1.7].
+ // As resonance is increased, the filter must be clocked more often to keep
+ // stable.
+
+ // The coefficient 1024 is dispensed of later by right-shifting 10 times
+ // (2 ^ 10 = 1024).
+ _1024_div_Q = static_cast<sound_sample>(1024.0/(0.707 + 1.0*res/0x0f));
+}
+
+RESID_INLINE void Filter::clock(cycle_count delta_t,
+ sound_sample voice1,
+ sound_sample voice2,
+ sound_sample voice3)
+{
+ // Scale each voice down from 20 to 13 bits.
+ voice1 >>= 7;
+ voice2 >>= 7;
+
+ // NB! Voice 3 is not silenced by voice3off if it is routed through
+ // the filter.
+ if (voice3off && !(filt & 0x04)) {
+ voice3 = 0;
+ }
+ else {
+ voice3 >>= 7;
+ }
+
+ // Enable filter on/off.
+ // This is not really part of SID, but is useful for testing.
+ // On slow CPUs it may be necessary to bypass the filter to lower the CPU
+ // load.
+ if (!enabled) {
+ Vnf = voice1 + voice2 + voice3;
+ Vhp = Vbp = Vlp = 0;
+ return;
+ }
+
+ // Route voices into or around filter.
+ // The code below is expanded to a switch for faster execution.
+ // (filt1 ? Vi : Vnf) += voice1;
+ // (filt2 ? Vi : Vnf) += voice2;
+ // (filt3 ? Vi : Vnf) += voice3;
+
+ sound_sample Vi;
+
+ switch (filt) {
+ default:
+ case 0x0:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x1:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0x2:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0x3:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0x4:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0x5:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0x6:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0x7:
+ Vi = voice1 + voice2 + voice3;
+ Vnf = 0;
+ break;
+ case 0x8:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x9:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0xa:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0xb:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0xc:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0xd:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0xe:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0xf:
+ Vi = voice1 + voice2 + voice3;
+ Vnf = 0;
+ break;
+ }
+
+ // Maximum delta cycles for the filter to work satisfactorily under current
+ // cutoff frequency and resonance constraints is approximately 8.
+ cycle_count delta_t_flt = 8;
+
+ while (delta_t) {
+ if (delta_t < delta_t_flt) {
+ delta_t_flt = delta_t;
+ }
+
+ // delta_t is converted to seconds given a 1MHz clock by dividing
+ // with 1 000 000. This is done in two operations to avoid integer
+ // multiplication overflow.
+
+ // Calculate filter outputs.
+ // Vhp = Vbp/Q - Vlp - Vi;
+ // dVbp = -w0*Vhp*dt;
+ // dVlp = -w0*Vbp*dt;
+ sound_sample w0_delta_t = w0_ceil_dt*delta_t_flt >> 6;
+
+ sound_sample dVbp = (w0_delta_t*Vhp >> 14);
+ sound_sample dVlp = (w0_delta_t*Vbp >> 14);
+ Vbp -= dVbp;
+ Vlp -= dVlp;
+ Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi;
+
+ delta_t -= delta_t_flt;
+ }
+}
+
+RESID_INLINE sound_sample Filter::output() {
+ // This is handy for testing.
+ if (!enabled) {
+ return (Vnf + mixer_DC)*static_cast<sound_sample>(vol);
+ }
+
+ // Mix highpass, bandpass, and lowpass outputs. The sum is not
+ // weighted, this can be confirmed by sampling sound output for
+ // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip.
+
+ // The code below is expanded to a switch for faster execution.
+ // if (hp) Vf += Vhp;
+ // if (bp) Vf += Vbp;
+ // if (lp) Vf += Vlp;
+
+ sound_sample Vf;
+
+ switch (hp_bp_lp) {
+ default:
+ case 0x0:
+ Vf = 0;
+ break;
+ case 0x1:
+ Vf = Vlp;
+ break;
+ case 0x2:
+ Vf = Vbp;
+ break;
+ case 0x3:
+ Vf = Vlp + Vbp;
+ break;
+ case 0x4:
+ Vf = Vhp;
+ break;
+ case 0x5:
+ Vf = Vlp + Vhp;
+ break;
+ case 0x6:
+ Vf = Vbp + Vhp;
+ break;
+ case 0x7:
+ Vf = Vlp + Vbp + Vhp;
+ break;
+ }
+
+ // Sum non-filtered and filtered output.
+ // Multiply the sum with volume.
+ return (Vnf + Vf + mixer_DC)*static_cast<sound_sample>(vol);
+}
+
+
+/*
+ * EnvelopeGenerator
+ */
+
+EnvelopeGenerator::EnvelopeGenerator() {
+ reset();
+}
+
+void EnvelopeGenerator::reset() {
+ envelope_counter = 0;
+
+ attack = 0;
+ decay = 0;
+ sustain = 0;
+ release = 0;
+
+ gate = 0;
+
+ rate_counter = 0;
+ exponential_counter = 0;
+ exponential_counter_period = 1;
+
+ state = RELEASE;
+ rate_period = rate_counter_period[release];
+ hold_zero = true;
+}
+
+reg16 EnvelopeGenerator::rate_counter_period[] = {
+ 9, // 2ms*1.0MHz/256 = 7.81
+ 32, // 8ms*1.0MHz/256 = 31.25
+ 63, // 16ms*1.0MHz/256 = 62.50
+ 95, // 24ms*1.0MHz/256 = 93.75
+ 149, // 38ms*1.0MHz/256 = 148.44
+ 220, // 56ms*1.0MHz/256 = 218.75
+ 267, // 68ms*1.0MHz/256 = 265.63
+ 313, // 80ms*1.0MHz/256 = 312.50
+ 392, // 100ms*1.0MHz/256 = 390.63
+ 977, // 250ms*1.0MHz/256 = 976.56
+ 1954, // 500ms*1.0MHz/256 = 1953.13
+ 3126, // 800ms*1.0MHz/256 = 3125.00
+ 3907, // 1 s*1.0MHz/256 = 3906.25
+ 11720, // 3 s*1.0MHz/256 = 11718.75
+ 19532, // 5 s*1.0MHz/256 = 19531.25
+ 31251 // 8 s*1.0MHz/256 = 31250.00
+};
+
+
+reg8 EnvelopeGenerator::sustain_level[] = {
+ 0x00,
+ 0x11,
+ 0x22,
+ 0x33,
+ 0x44,
+ 0x55,
+ 0x66,
+ 0x77,
+ 0x88,
+ 0x99,
+ 0xaa,
+ 0xbb,
+ 0xcc,
+ 0xdd,
+ 0xee,
+ 0xff,
+};
+
+void EnvelopeGenerator::writeCONTROL_REG(reg8 control) {
+ reg8 gate_next = control & 0x01;
+
+ // The rate counter is never reset, thus there will be a delay before the
+ // envelope counter starts counting up (attack) or down (release).
+
+ // Gate bit on: Start attack, decay, sustain.
+ if (!gate && gate_next) {
+ state = ATTACK;
+ rate_period = rate_counter_period[attack];
+
+ // Switching to attack state unlocks the zero freeze.
+ hold_zero = false;
+ }
+ // Gate bit off: Start release.
+ else if (gate && !gate_next) {
+ state = RELEASE;
+ rate_period = rate_counter_period[release];
+ }
+
+ gate = gate_next;
+}
+
+void EnvelopeGenerator::writeATTACK_DECAY(reg8 attack_decay) {
+ attack = (attack_decay >> 4) & 0x0f;
+ decay = attack_decay & 0x0f;
+ if (state == ATTACK) {
+ rate_period = rate_counter_period[attack];
+ }
+ else if (state == DECAY_SUSTAIN) {
+ rate_period = rate_counter_period[decay];
+ }
+}
+
+void EnvelopeGenerator::writeSUSTAIN_RELEASE(reg8 sustain_release) {
+ sustain = (sustain_release >> 4) & 0x0f;
+ release = sustain_release & 0x0f;
+ if (state == RELEASE) {
+ rate_period = rate_counter_period[release];
+ }
+}
+
+reg8 EnvelopeGenerator::readENV() {
+ return output();
+}
+
+RESID_INLINE void EnvelopeGenerator::clock(cycle_count delta_t) {
+ // Check for ADSR delay bug.
+ // If the rate counter comparison value is set below the current value of the
+ // rate counter, the counter will continue counting up until it wraps around
+ // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the
+ // envelope can finally be stepped.
+ // This has been verified by sampling ENV3.
+ //
+
+ // NB! This requires two's complement integer.
+ int rate_step = rate_period - rate_counter;
+ if (rate_step <= 0) {
+ rate_step += 0x7fff;
+ }
+
+ while (delta_t) {
+ if (delta_t < rate_step) {
+ rate_counter += delta_t;
+ if (rate_counter & 0x8000) {
+ ++rate_counter &= 0x7fff;
+ }
+ return;
+ }
+
+ rate_counter = 0;
+ delta_t -= rate_step;
+
+ // The first envelope step in the attack state also resets the exponential
+ // counter. This has been verified by sampling ENV3.
+ //
+ if (state == ATTACK || ++exponential_counter == exponential_counter_period)
+ {
+ exponential_counter = 0;
+
+ // Check whether the envelope counter is frozen at zero.
+ if (hold_zero) {
+ rate_step = rate_period;
+ continue;
+ }
+
+ switch (state) {
+ case ATTACK:
+ // The envelope counter can flip from 0xff to 0x00 by changing state to
+ // release, then to attack. The envelope counter is then frozen at
+ // zero; to unlock this situation the state must be changed to release,
+ // then to attack. This has been verified by sampling ENV3.
+ //
+ ++envelope_counter &= 0xff;
+ if (envelope_counter == 0xff) {
+ state = DECAY_SUSTAIN;
+ rate_period = rate_counter_period[decay];
+ }
+ break;
+ case DECAY_SUSTAIN:
+ if (envelope_counter != sustain_level[sustain]) {
+ --envelope_counter;
+ }
+ break;
+ case RELEASE:
+ // The envelope counter can flip from 0x00 to 0xff by changing state to
+ // attack, then to release. The envelope counter will then continue
+ // counting down in the release state.
+ // This has been verified by sampling ENV3.
+ // NB! The operation below requires two's complement integer.
+ //
+ --envelope_counter &= 0xff;
+ break;
+ }
+
+ // Check for change of exponential counter period.
+ switch (envelope_counter) {
+ case 0xff:
+ exponential_counter_period = 1;
+ break;
+ case 0x5d:
+ exponential_counter_period = 2;
+ break;
+ case 0x36:
+ exponential_counter_period = 4;
+ break;
+ case 0x1a:
+ exponential_counter_period = 8;
+ break;
+ case 0x0e:
+ exponential_counter_period = 16;
+ break;
+ case 0x06:
+ exponential_counter_period = 30;
+ break;
+ case 0x00:
+ exponential_counter_period = 1;
+
+ // When the envelope counter is changed to zero, it is frozen at zero.
+ // This has been verified by sampling ENV3.
+ hold_zero = true;
+ break;
+ }
+ }
+
+ rate_step = rate_period;
+ }
+}
+
+RESID_INLINE reg8 EnvelopeGenerator::output() {
+ return envelope_counter;
+}
+
+
+/*
+ * ExternalFilter
+ */
+
+ExternalFilter::ExternalFilter() {
+ reset();
+ enable_filter(true);
+ set_sampling_parameter(15915.6);
+ mixer_DC = ((((0x800 - 0x380) + 0x800)*0xff*3 - 0xfff*0xff/18) >> 7)*0x0f;
+}
+
+void ExternalFilter::enable_filter(bool enable) {
+ enabled = enable;
+}
+
+void ExternalFilter::set_sampling_parameter(double pass_freq) {
+ static const double pi = 3.1415926535897932385;
+
+ w0hp = 105;
+ w0lp = (sound_sample) (pass_freq * (2.0 * pi * 1.048576));
+ if (w0lp > 104858)
+ w0lp = 104858;
+}
+
+void ExternalFilter::reset() {
+ // State of filter.
+ Vlp = 0;
+ Vhp = 0;
+ Vo = 0;
+}
+
+RESID_INLINE void ExternalFilter::clock(cycle_count delta_t, sound_sample Vi) {
+ // This is handy for testing.
+ if (!enabled) {
+ // Remove maximum DC level since there is no filter to do it.
+ Vlp = Vhp = 0;
+ Vo = Vi - mixer_DC;
+ return;
+ }
+
+ // Maximum delta cycles for the external filter to work satisfactorily
+ // is approximately 8.
+ cycle_count delta_t_flt = 8;
+
+ while (delta_t) {
+ if (delta_t < delta_t_flt) {
+ delta_t_flt = delta_t;
+ }
+
+ // delta_t is converted to seconds given a 1MHz clock by dividing
+ // with 1 000 000.
+
+ // Calculate filter outputs.
+ // Vo = Vlp - Vhp;
+ // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
+ // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;
+
+ sound_sample dVlp = (w0lp*delta_t_flt >> 8)*(Vi - Vlp) >> 12;
+ sound_sample dVhp = w0hp*delta_t_flt*(Vlp - Vhp) >> 20;
+ Vo = Vlp - Vhp;
+ Vlp += dVlp;
+ Vhp += dVhp;
+
+ delta_t -= delta_t_flt;
+ }
+}
+
+RESID_INLINE sound_sample ExternalFilter::output() {
+ return Vo;
+}
+
+
+/*
+ * Voice
+ */
+
+Voice::Voice() {
+ wave_zero = 0x380;
+ voice_DC = 0x800*0xff;
+}
+
+void Voice::set_sync_source(Voice* source) {
+ wave.set_sync_source(&source->wave);
+}
+
+void Voice::writeCONTROL_REG(reg8 control) {
+ wave.writeCONTROL_REG(control);
+ envelope.writeCONTROL_REG(control);
+}
+
+void Voice::reset() {
+ wave.reset();
+ envelope.reset();
+}
+
+
+/*
+ * SID
+ */
+
+SID::SID() {
+ voice[0].set_sync_source(&voice[2]);
+ voice[1].set_sync_source(&voice[0]);
+ voice[2].set_sync_source(&voice[1]);
+
+ set_sampling_parameters(985248, 44100);
+
+ bus_value = 0;
+ bus_value_ttl = 0;
+}
+
+SID::~SID() {}
+
+void SID::reset() {
+ for (int i = 0; i < 3; i++) {
+ voice[i].reset();
+ }
+ filter.reset();
+ extfilt.reset();
+
+ bus_value = 0;
+ bus_value_ttl = 0;
+}
+
+int SID::output() {
+ const int range = 1 << 16;
+ const int half = range >> 1;
+ int sample = extfilt.output()/((4095*255 >> 7)*3*15*2/range);
+ if (sample >= half) {
+ return half - 1;
+ }
+ if (sample < -half) {
+ return -half;
+ }
+ return sample;
+}
+
+
+/**
+ * Read registers.
+ *
+ * Reading a write only register returns the last byte written to any SID
+ * register. The individual bits in this value start to fade down towards
+ * zero after a few cycles. All bits reach zero within approximately
+ * $2000 - $4000 cycles.
+ * It has been claimed that this fading happens in an orderly fashion, however
+ * sampling of write only registers reveals that this is not the case.
+ * NB! This is not correctly modeled.
+ * The actual use of write only registers has largely been made in the belief
+ * that all SID registers are readable. To support this belief the read
+ * would have to be done immediately after a write to the same register
+ * (remember that an intermediate write to another register would yield that
+ * value instead). With this in mind we return the last value written to
+ * any SID register for $2000 cycles without modeling the bit fading.
+ */
+reg8 SID::read(reg8 offset) {
+ switch (offset) {
+ case 0x19:
+ case 0x1a:
+ return 0; //readPOT();
+ case 0x1b:
+ return voice[2].wave.readOSC();
+ case 0x1c:
+ return voice[2].envelope.readENV();
+ default:
+ return bus_value;
+ }
+}
+
+void SID::write(reg8 offset, reg8 value) {
+ bus_value = value;
+ bus_value_ttl = 0x2000;
+
+ switch (offset) {
+ case 0x00:
+ voice[0].wave.writeFREQ_LO(value);
+ break;
+ case 0x01:
+ voice[0].wave.writeFREQ_HI(value);
+ break;
+ case 0x02:
+ voice[0].wave.writePW_LO(value);
+ break;
+ case 0x03:
+ voice[0].wave.writePW_HI(value);
+ break;
+ case 0x04:
+ voice[0].writeCONTROL_REG(value);
+ break;
+ case 0x05:
+ voice[0].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x06:
+ voice[0].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x07:
+ voice[1].wave.writeFREQ_LO(value);
+ break;
+ case 0x08:
+ voice[1].wave.writeFREQ_HI(value);
+ break;
+ case 0x09:
+ voice[1].wave.writePW_LO(value);
+ break;
+ case 0x0a:
+ voice[1].wave.writePW_HI(value);
+ break;
+ case 0x0b:
+ voice[1].writeCONTROL_REG(value);
+ break;
+ case 0x0c:
+ voice[1].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x0d:
+ voice[1].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x0e:
+ voice[2].wave.writeFREQ_LO(value);
+ break;
+ case 0x0f:
+ voice[2].wave.writeFREQ_HI(value);
+ break;
+ case 0x10:
+ voice[2].wave.writePW_LO(value);
+ break;
+ case 0x11:
+ voice[2].wave.writePW_HI(value);
+ break;
+ case 0x12:
+ voice[2].writeCONTROL_REG(value);
+ break;
+ case 0x13:
+ voice[2].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x14:
+ voice[2].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x15:
+ filter.writeFC_LO(value);
+ break;
+ case 0x16:
+ filter.writeFC_HI(value);
+ break;
+ case 0x17:
+ filter.writeRES_FILT(value);
+ break;
+ case 0x18:
+ filter.writeMODE_VOL(value);
+ break;
+ default:
+ break;
+ }
+}
+
+void SID::enable_filter(bool enable) {
+ filter.enable_filter(enable);
+}
+
+void SID::enable_external_filter(bool enable) {
+ extfilt.enable_filter(enable);
+}
+
+
+/**
+ * Setting of SID sampling parameters.
+ *
+ * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
+ * The default end of passband frequency is pass_freq = 0.9*sample_freq/2
+ * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample
+ * frequencies.
+ *
+ * For resampling, the ratio between the clock frequency and the sample
+ * frequency is limited as follows:
+ * 125*clock_freq/sample_freq < 16384
+ * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not
+ * be set lower than ~ 8kHz. A lower sample frequency would make the
+ * resampling code overfill its 16k sample ring buffer.
+ *
+ * The end of passband frequency is also limited:
+ * pass_freq <= 0.9*sample_freq/2
+ *
+ * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited
+ * to slightly below 20kHz. This constraint ensures that the FIR table is
+ * not overfilled.
+ */
+bool SID::set_sampling_parameters(double clock_freq,
+ double sample_freq, double pass_freq,
+ double filter_scale)
+{
+ // The default passband limit is 0.9*sample_freq/2 for sample
+ // frequencies below ~ 44.1kHz, and 20kHz for higher sample frequencies.
+ if (pass_freq < 0) {
+ pass_freq = 20000;
+ if (2*pass_freq/sample_freq >= 0.9) {
+ pass_freq = 0.9*sample_freq/2;
+ }
+ }
+ // Check whether the FIR table would overfill.
+ else if (pass_freq > 0.9*sample_freq/2) {
+ return false;
+ }
+
+ // The filter scaling is only included to avoid clipping, so keep
+ // it sane.
+ if (filter_scale < 0.9 || filter_scale > 1.0) {
+ return false;
+ }
+
+ // Set the external filter to the pass freq
+ extfilt.set_sampling_parameter (pass_freq);
+ clock_frequency = clock_freq;
+
+ cycles_per_sample =
+ cycle_count(clock_freq/sample_freq*(1 << FIXP_SHIFT) + 0.5);
+
+ sample_offset = 0;
+ sample_prev = 0;
+
+ return true;
+}
+
+void SID::clock(cycle_count delta_t) {
+ int i;
+
+ if (delta_t <= 0) {
+ return;
+ }
+
+ // Age bus value.
+ bus_value_ttl -= delta_t;
+ if (bus_value_ttl <= 0) {
+ bus_value = 0;
+ bus_value_ttl = 0;
+ }
+
+ // Clock amplitude modulators.
+ for (i = 0; i < 3; i++) {
+ voice[i].envelope.clock(delta_t);
+ }
+
+ // Clock and synchronize oscillators.
+ // Loop until we reach the current cycle.
+ cycle_count delta_t_osc = delta_t;
+ while (delta_t_osc) {
+ cycle_count delta_t_min = delta_t_osc;
+
+ // Find minimum number of cycles to an oscillator accumulator MSB toggle.
+ // We have to clock on each MSB on / MSB off for hard sync to operate
+ // correctly.
+ for (i = 0; i < 3; i++) {
+ WaveformGenerator& wave = voice[i].wave;
+
+ // It is only necessary to clock on the MSB of an oscillator that is
+ // a sync source and has freq != 0.
+ if (!(wave.sync_dest->sync && wave.freq)) {
+ continue;
+ }
+
+ reg16 freq = wave.freq;
+ reg24 accumulator = wave.accumulator;
+
+ // Clock on MSB off if MSB is on, clock on MSB on if MSB is off.
+ reg24 delta_accumulator =
+ (accumulator & 0x800000 ? 0x1000000 : 0x800000) - accumulator;
+
+ cycle_count delta_t_next = delta_accumulator/freq;
+ if (delta_accumulator%freq) {
+ ++delta_t_next;
+ }
+
+ if (delta_t_next < delta_t_min) {
+ delta_t_min = delta_t_next;
+ }
+ }
+
+ // Clock oscillators.
+ for (i = 0; i < 3; i++) {
+ voice[i].wave.clock(delta_t_min);
+ }
+
+ // Synchronize oscillators.
+ for (i = 0; i < 3; i++) {
+ voice[i].wave.synchronize();
+ }
+
+ delta_t_osc -= delta_t_min;
+ }
+
+ // Clock filter.
+ filter.clock(delta_t,
+ voice[0].output(), voice[1].output(), voice[2].output());
+
+ // Clock external filter.
+ extfilt.clock(delta_t, filter.output());
+}
+
+
+/**
+ * SID clocking with audio sampling.
+ * Fixpoint arithmetics is used.
+ */
+int SID::clock(cycle_count& delta_t, short* buf, int n, int interleave) {
+ int s = 0;
+
+ for (;;) {
+ cycle_count next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1));
+ cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT;
+ if (delta_t_sample > delta_t) {
+ break;
+ }
+ if (s >= n) {
+ return s;
+ }
+ clock(delta_t_sample);
+ delta_t -= delta_t_sample;
+ sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1));
+ buf[s++*interleave] = output();
+ }
+
+ clock(delta_t);
+ sample_offset -= delta_t << FIXP_SHIFT;
+ delta_t = 0;
+ return s;
+}
+
+}
+
+// Plugin interface
+// (This can only create a null driver since C64 audio support is not part of the
+// midi driver architecture. But we need the plugin for the options menu in the launcher
+// and for MidiDriver::detectDevice() which is more or less used by all engines.)
+
+class C64MusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("C64 Audio Emulator");
+ }
+
+ const char *getId() const {
+ return "C64";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices C64MusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_C64));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(C64)
+ //REGISTER_PLUGIN_DYNAMIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin);
+//#endif
+
+#endif
diff --git a/audio/softsynth/sid.h b/audio/softsynth/sid.h
new file mode 100644
index 0000000000..c78f538441
--- /dev/null
+++ b/audio/softsynth/sid.h
@@ -0,0 +1,348 @@
+/* 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$
+ *
+ */
+
+/*
+ * This file is based on reSID, a MOS6581 SID emulator engine.
+ * Copyright (C) 2004 Dag Lem <resid@nimrod.no>
+ */
+
+#ifndef __SID_H__
+#define __SID_H__
+
+// Inlining on/off.
+#define RESID_INLINE inline
+
+namespace Resid {
+
+// We could have used the smallest possible data type for each SID register,
+// however this would give a slower engine because of data type conversions.
+// An int is assumed to be at least 32 bits (necessary in the types reg24,
+// cycle_count, and sound_sample). GNU does not support 16-bit machines
+// (GNU Coding Standards: Portability between CPUs), so this should be
+// a valid assumption.
+
+typedef unsigned int reg4;
+typedef unsigned int reg8;
+typedef unsigned int reg12;
+typedef unsigned int reg16;
+typedef unsigned int reg24;
+
+typedef int cycle_count;
+typedef int sound_sample;
+typedef sound_sample fc_point[2];
+
+
+class WaveformGenerator {
+public:
+ WaveformGenerator();
+
+ void set_sync_source(WaveformGenerator*);
+
+ void clock(cycle_count delta_t);
+ void synchronize();
+ void reset();
+
+ void writeFREQ_LO(reg8);
+ void writeFREQ_HI(reg8);
+ void writePW_LO(reg8);
+ void writePW_HI(reg8);
+ void writeCONTROL_REG(reg8);
+ reg8 readOSC();
+
+ // 12-bit waveform output.
+ reg12 output();
+
+protected:
+ const WaveformGenerator* sync_source;
+ WaveformGenerator* sync_dest;
+
+ // Tell whether the accumulator MSB was set high on this cycle.
+ bool msb_rising;
+
+ reg24 accumulator;
+ reg24 shift_register;
+
+ // Fout = (Fn*Fclk/16777216)Hz
+ reg16 freq;
+ // PWout = (PWn/40.95)%
+ reg12 pw;
+
+ // The control register right-shifted 4 bits; used for output function
+ // table lookup.
+ reg8 waveform;
+
+ // The remaining control register bits.
+ reg8 test;
+ reg8 ring_mod;
+ reg8 sync;
+ // The gate bit is handled by the EnvelopeGenerator.
+
+ // 16 possible combinations of waveforms.
+ reg12 output____();
+ reg12 output___T();
+ reg12 output__S_();
+ reg12 output__ST();
+ reg12 output_P__();
+ reg12 output_P_T();
+ reg12 output_PS_();
+ reg12 output_PST();
+ reg12 outputN___();
+ reg12 outputN__T();
+ reg12 outputN_S_();
+ reg12 outputN_ST();
+ reg12 outputNP__();
+ reg12 outputNP_T();
+ reg12 outputNPS_();
+ reg12 outputNPST();
+
+ // Sample data for combinations of waveforms.
+ static const reg8 wave6581__ST[];
+ static const reg8 wave6581_P_T[];
+ static const reg8 wave6581_PS_[];
+ static const reg8 wave6581_PST[];
+
+ friend class Voice;
+ friend class SID;
+};
+
+class Filter {
+public:
+ Filter();
+
+ void enable_filter(bool enable);
+
+ void clock(cycle_count delta_t,
+ sound_sample voice1, sound_sample voice2, sound_sample voice3);
+ void reset();
+
+ // Write registers.
+ void writeFC_LO(reg8);
+ void writeFC_HI(reg8);
+ void writeRES_FILT(reg8);
+ void writeMODE_VOL(reg8);
+
+ // SID audio output (16 bits).
+ sound_sample output();
+
+protected:
+ void set_w0();
+ void set_Q();
+
+ // Filter enabled.
+ bool enabled;
+
+ // Filter cutoff frequency.
+ reg12 fc;
+
+ // Filter resonance.
+ reg8 res;
+
+ // Selects which inputs to route through filter.
+ reg8 filt;
+
+ // Switch voice 3 off.
+ reg8 voice3off;
+
+ // Highpass, bandpass, and lowpass filter modes.
+ reg8 hp_bp_lp;
+
+ // Output master volume.
+ reg4 vol;
+
+ // Mixer DC offset.
+ sound_sample mixer_DC;
+
+ // State of filter.
+ sound_sample Vhp; // highpass
+ sound_sample Vbp; // bandpass
+ sound_sample Vlp; // lowpass
+ sound_sample Vnf; // not filtered
+
+ // Cutoff frequency, resonance.
+ sound_sample w0, w0_ceil_1, w0_ceil_dt;
+ sound_sample _1024_div_Q;
+
+ // Cutoff frequency tables.
+ // FC is an 11 bit register.
+ sound_sample f0_6581[2048];
+ sound_sample* f0;
+ static fc_point f0_points_6581[];
+ fc_point* f0_points;
+ int f0_count;
+
+ friend class SID;
+};
+
+class EnvelopeGenerator {
+public:
+ EnvelopeGenerator();
+
+ enum State { ATTACK, DECAY_SUSTAIN, RELEASE };
+
+ void clock(cycle_count delta_t);
+ void reset();
+
+ void writeCONTROL_REG(reg8);
+ void writeATTACK_DECAY(reg8);
+ void writeSUSTAIN_RELEASE(reg8);
+ reg8 readENV();
+
+ // 8-bit envelope output.
+ reg8 output();
+
+protected:
+ reg16 rate_counter;
+ reg16 rate_period;
+ reg8 exponential_counter;
+ reg8 exponential_counter_period;
+ reg8 envelope_counter;
+ bool hold_zero;
+
+ reg4 attack;
+ reg4 decay;
+ reg4 sustain;
+ reg4 release;
+
+ reg8 gate;
+
+ State state;
+
+ // Lookup table to convert from attack, decay, or release value to rate
+ // counter period.
+ static reg16 rate_counter_period[];
+
+ // The 16 selectable sustain levels.
+ static reg8 sustain_level[];
+
+ friend class SID;
+};
+
+class ExternalFilter {
+public:
+ ExternalFilter();
+
+ void enable_filter(bool enable);
+ void set_sampling_parameter(double pass_freq);
+
+ void clock(cycle_count delta_t, sound_sample Vi);
+ void reset();
+
+ // Audio output (20 bits).
+ sound_sample output();
+
+protected:
+ // Filter enabled.
+ bool enabled;
+
+ // Maximum mixer DC offset.
+ sound_sample mixer_DC;
+
+ // State of filters.
+ sound_sample Vlp; // lowpass
+ sound_sample Vhp; // highpass
+ sound_sample Vo;
+
+ // Cutoff frequencies.
+ sound_sample w0lp;
+ sound_sample w0hp;
+
+ friend class SID;
+};
+
+class Voice {
+public:
+ Voice();
+
+ void set_sync_source(Voice*);
+ void reset();
+
+ void writeCONTROL_REG(reg8);
+
+ // Amplitude modulated waveform output.
+ // Range [-2048*255, 2047*255].
+ sound_sample output() {
+ // Multiply oscillator output with envelope output.
+ return (wave.output() - wave_zero)*envelope.output() + voice_DC;
+ }
+
+protected:
+ WaveformGenerator wave;
+ EnvelopeGenerator envelope;
+
+ // Waveform D/A zero level.
+ sound_sample wave_zero;
+
+ // Multiplying D/A DC offset.
+ sound_sample voice_DC;
+
+ friend class SID;
+};
+
+
+class SID {
+public:
+ SID();
+ ~SID();
+
+ void enable_filter(bool enable);
+ void enable_external_filter(bool enable);
+ bool set_sampling_parameters(double clock_freq,
+ double sample_freq, double pass_freq = -1,
+ double filter_scale = 0.97);
+
+ void clock(cycle_count delta_t);
+ int clock(cycle_count& delta_t, short* buf, int n, int interleave = 1);
+ void reset();
+
+ // Read/write registers.
+ reg8 read(reg8 offset);
+ void write(reg8 offset, reg8 value);
+
+ // 16-bit output (AUDIO OUT).
+ int output();
+
+protected:
+ Voice voice[3];
+ Filter filter;
+ ExternalFilter extfilt;
+
+ reg8 bus_value;
+ cycle_count bus_value_ttl;
+
+ double clock_frequency;
+
+ // Fixpoint constants.
+ static const int FIXP_SHIFT;
+ static const int FIXP_MASK;
+
+ // Sampling variables.
+ cycle_count cycles_per_sample;
+ cycle_count sample_offset;
+ short sample_prev;
+};
+
+}
+
+#endif // not __SID_H__
diff --git a/audio/softsynth/wave6581.cpp b/audio/softsynth/wave6581.cpp
new file mode 100644
index 0000000000..d1ddad1623
--- /dev/null
+++ b/audio/softsynth/wave6581.cpp
@@ -0,0 +1,2098 @@
+/* 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$
+ *
+ */
+
+/*
+ * This file is based on reSID, a MOS6581 SID emulator engine.
+ * Copyright (C) 2004 Dag Lem <resid@nimrod.no>
+ */
+
+#ifndef DISABLE_SID
+
+#include "sid.h"
+
+namespace Resid {
+
+const reg8 WaveformGenerator::wave6581__ST[] = {
+/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f,
+/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3f0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+/* 0x3f8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f,
+/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f,
+/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7e0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+/* 0x7e8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+/* 0x7f0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+/* 0x7f8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f,
+/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f,
+/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbf0: */ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+/* 0xbf8: */ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x3f, 0x3f,
+/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf8: */ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x1f,
+/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07,
+/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfe0: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+/* 0xfe8: */ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+/* 0xff0: */ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+/* 0xff8: */ 0x3e, 0x3e, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f,
+};
+
+const reg8 WaveformGenerator::wave6581_P_T[] = {
+/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x38, 0x3f,
+/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x5f,
+/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x378: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x6f,
+/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x3b8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x70, 0x77,
+/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x3d8: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7b,
+/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x70,
+/* 0x3e8: */ 0x00, 0x40, 0x40, 0x70, 0x60, 0x70, 0x78, 0x7d,
+/* 0x3f0: */ 0x00, 0x40, 0x60, 0x78, 0x60, 0x78, 0x78, 0x7e,
+/* 0x3f8: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f,
+/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x4f8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x9f,
+/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x578: */ 0x00, 0x80, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xaf,
+/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
+/* 0x5b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0,
+/* 0x5b8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xa0, 0xb0, 0xb7,
+/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x5c8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0,
+/* 0x5d0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xa0,
+/* 0x5d8: */ 0x00, 0x80, 0x80, 0xa0, 0x80, 0xb0, 0xb0, 0xbb,
+/* 0x5e0: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xb0,
+/* 0x5e8: */ 0x80, 0x80, 0x80, 0xb0, 0x80, 0xb0, 0xb8, 0xbd,
+/* 0x5f0: */ 0x80, 0x80, 0x80, 0xb8, 0xa0, 0xb8, 0xb8, 0xbe,
+/* 0x5f8: */ 0xa0, 0xb8, 0xbc, 0xbf, 0xbe, 0xbf, 0xbf, 0xbf,
+/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
+/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0,
+/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x668: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0,
+/* 0x670: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0,
+/* 0x678: */ 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xcf,
+/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x698: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0,
+/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x6a8: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0,
+/* 0x6b0: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0xc0, 0xc0,
+/* 0x6b8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd7,
+/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x6c8: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0xc0, 0xc0,
+/* 0x6d0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xc0,
+/* 0x6d8: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xd0, 0xd0, 0xdb,
+/* 0x6e0: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xd0,
+/* 0x6e8: */ 0x80, 0xc0, 0xc0, 0xd0, 0xc0, 0xd0, 0xd8, 0xdd,
+/* 0x6f0: */ 0xc0, 0xc0, 0xc0, 0xd0, 0xc0, 0xd8, 0xd8, 0xde,
+/* 0x6f8: */ 0xc0, 0xd8, 0xdc, 0xdf, 0xdc, 0xdf, 0xdf, 0xdf,
+/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x718: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xe0,
+/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+/* 0x728: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0,
+/* 0x730: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0,
+/* 0x738: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xe7,
+/* 0x740: */ 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0,
+/* 0x748: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0,
+/* 0x750: */ 0x00, 0x80, 0x80, 0xc0, 0x80, 0xc0, 0xc0, 0xe0,
+/* 0x758: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xeb,
+/* 0x760: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0,
+/* 0x768: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xed,
+/* 0x770: */ 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe8, 0xe8, 0xee,
+/* 0x778: */ 0xe0, 0xe8, 0xec, 0xef, 0xec, 0xef, 0xef, 0xef,
+/* 0x780: */ 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xc0,
+/* 0x788: */ 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xf0,
+/* 0x790: */ 0x80, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xf0,
+/* 0x798: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf3,
+/* 0x7a0: */ 0x80, 0xc0, 0xc0, 0xe0, 0xc0, 0xe0, 0xe0, 0xf0,
+/* 0x7a8: */ 0xc0, 0xe0, 0xe0, 0xf0, 0xe0, 0xf0, 0xf0, 0xf5,
+/* 0x7b0: */ 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf6,
+/* 0x7b8: */ 0xf0, 0xf0, 0xf4, 0xf7, 0xf4, 0xf7, 0xf7, 0xf7,
+/* 0x7c0: */ 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xf0,
+/* 0x7c8: */ 0xe0, 0xe0, 0xe0, 0xf8, 0xf0, 0xf8, 0xf8, 0xf9,
+/* 0x7d0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xf8, 0xfa,
+/* 0x7d8: */ 0xf0, 0xf8, 0xf8, 0xfb, 0xf8, 0xfb, 0xfb, 0xfb,
+/* 0x7e0: */ 0xe0, 0xf0, 0xf0, 0xf8, 0xf0, 0xf8, 0xfc, 0xfc,
+/* 0x7e8: */ 0xf8, 0xfc, 0xfc, 0xfd, 0xfc, 0xfd, 0xfd, 0xfd,
+/* 0x7f0: */ 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+/* 0x7f8: */ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+/* 0x800: */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+/* 0x808: */ 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8,
+/* 0x810: */ 0xfd, 0xfd, 0xfd, 0xfc, 0xfd, 0xfc, 0xfc, 0xf8,
+/* 0x818: */ 0xfc, 0xfc, 0xfc, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0,
+/* 0x820: */ 0xfb, 0xfb, 0xfb, 0xf8, 0xfb, 0xf8, 0xf8, 0xf0,
+/* 0x828: */ 0xfa, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xf0, 0xe0,
+/* 0x830: */ 0xf9, 0xf8, 0xf8, 0xf0, 0xf8, 0xf0, 0xe0, 0xe0,
+/* 0x838: */ 0xf0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0,
+/* 0x840: */ 0xf7, 0xf7, 0xf7, 0xf4, 0xf7, 0xf4, 0xf0, 0xf0,
+/* 0x848: */ 0xf6, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0,
+/* 0x850: */ 0xf5, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0,
+/* 0x858: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80,
+/* 0x860: */ 0xf3, 0xf0, 0xf0, 0xe0, 0xf0, 0xe0, 0xe0, 0xc0,
+/* 0x868: */ 0xf0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80,
+/* 0x870: */ 0xf0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80,
+/* 0x878: */ 0xc0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+/* 0x880: */ 0xef, 0xef, 0xef, 0xec, 0xef, 0xec, 0xe8, 0xe0,
+/* 0x888: */ 0xee, 0xe8, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0,
+/* 0x890: */ 0xed, 0xe8, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0,
+/* 0x898: */ 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80,
+/* 0x8a0: */ 0xeb, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0,
+/* 0x8a8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x8b0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x8b8: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x8c0: */ 0xe7, 0xe0, 0xe0, 0xc0, 0xe0, 0xc0, 0xc0, 0x80,
+/* 0x8c8: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x8d0: */ 0xe0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x8d8: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e0: */ 0xe0, 0xc0, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00,
+/* 0x8e8: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x900: */ 0xdf, 0xdf, 0xdf, 0xdc, 0xdf, 0xdc, 0xd8, 0xc0,
+/* 0x908: */ 0xde, 0xd8, 0xd8, 0xc0, 0xd8, 0xc0, 0xc0, 0xc0,
+/* 0x910: */ 0xdd, 0xd8, 0xd0, 0xc0, 0xd0, 0xc0, 0xc0, 0x80,
+/* 0x918: */ 0xd0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x920: */ 0xdb, 0xd0, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80,
+/* 0x928: */ 0xc0, 0xc0, 0xc0, 0x80, 0xc0, 0x80, 0x80, 0x00,
+/* 0x930: */ 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+/* 0x938: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x940: */ 0xd7, 0xd0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x80,
+/* 0x948: */ 0xc0, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x950: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x958: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x960: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x968: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x970: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x980: */ 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x00,
+/* 0x988: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x990: */ 0xc0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a0: */ 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c0: */ 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa00: */ 0xbf, 0xbf, 0xbf, 0xbe, 0xbf, 0xbc, 0xbc, 0xa0,
+/* 0xa08: */ 0xbe, 0xbc, 0xb8, 0xa0, 0xb8, 0xa0, 0x80, 0x80,
+/* 0xa10: */ 0xbd, 0xb8, 0xb0, 0x80, 0xb0, 0x80, 0x80, 0x80,
+/* 0xa18: */ 0xb0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+/* 0xa20: */ 0xbb, 0xb0, 0xb0, 0x80, 0xa0, 0x80, 0x80, 0x00,
+/* 0xa28: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0xa30: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0xa38: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa40: */ 0xb7, 0xb0, 0xa0, 0x80, 0xa0, 0x80, 0x80, 0x00,
+/* 0xa48: */ 0xa0, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00,
+/* 0xa50: */ 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa60: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa80: */ 0xaf, 0xa0, 0xa0, 0x80, 0x80, 0x80, 0x80, 0x00,
+/* 0xa88: */ 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa90: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac0: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb00: */ 0x9f, 0x90, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
+/* 0xb08: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb10: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb20: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb40: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb80: */ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc00: */ 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7c, 0x7c, 0x70,
+/* 0xc08: */ 0x7e, 0x7c, 0x78, 0x60, 0x78, 0x60, 0x60, 0x00,
+/* 0xc10: */ 0x7d, 0x78, 0x78, 0x60, 0x70, 0x40, 0x40, 0x00,
+/* 0xc18: */ 0x70, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc20: */ 0x7b, 0x78, 0x70, 0x40, 0x70, 0x40, 0x00, 0x00,
+/* 0xc28: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc30: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc40: */ 0x77, 0x70, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00,
+/* 0xc48: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc50: */ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc60: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc80: */ 0x6f, 0x60, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00,
+/* 0xc88: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc90: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd00: */ 0x5f, 0x58, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,
+/* 0xd08: */ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe00: */ 0x3f, 0x3c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xff8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const reg8 WaveformGenerator::wave6581_PS_[] = {
+/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
+/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f,
+/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37,
+/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b,
+/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d,
+/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
+/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f,
+/* 0x3f8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f,
+/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f,
+/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57,
+/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b,
+/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d,
+/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e,
+/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f,
+/* 0x5f8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f,
+/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67,
+/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b,
+/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x6d,
+/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x6e8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e,
+/* 0x6f0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f,
+/* 0x6f8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f,
+/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x738: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73,
+/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0x758: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75,
+/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x768: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76,
+/* 0x770: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77,
+/* 0x778: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77,
+/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x798: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79,
+/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0x7a8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a,
+/* 0x7b0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b,
+/* 0x7b8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b,
+/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+/* 0x7c8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c,
+/* 0x7d0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d,
+/* 0x7d8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d,
+/* 0x7e0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e,
+/* 0x7e8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e,
+/* 0x7f0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f,
+/* 0x7f8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
+/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1f,
+/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37,
+/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b,
+/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d,
+/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
+/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x3f,
+/* 0xbf8: */ 0x00, 0x30, 0x38, 0x3f, 0x3e, 0x3f, 0x3f, 0x3f,
+/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f,
+/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57,
+/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b,
+/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d,
+/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e,
+/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5f,
+/* 0xdf8: */ 0x00, 0x40, 0x40, 0x5f, 0x5c, 0x5f, 0x5f, 0x5f,
+/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67,
+/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6b,
+/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6d,
+/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xee8: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x6e,
+/* 0xef0: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x6f,
+/* 0xef8: */ 0x00, 0x60, 0x60, 0x6f, 0x60, 0x6f, 0x6f, 0x6f,
+/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xf38: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x60, 0x73,
+/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+/* 0xf58: */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x60, 0x60, 0x75,
+/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0xf68: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x76,
+/* 0xf70: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x77,
+/* 0xf78: */ 0x00, 0x70, 0x70, 0x77, 0x70, 0x77, 0x77, 0x77,
+/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0xf98: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x60, 0x79,
+/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
+/* 0xfa8: */ 0x00, 0x00, 0x00, 0x60, 0x00, 0x70, 0x70, 0x7a,
+/* 0xfb0: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7b,
+/* 0xfb8: */ 0x40, 0x70, 0x70, 0x7b, 0x78, 0x7b, 0x7b, 0x7b,
+/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
+/* 0xfc8: */ 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x70, 0x7c,
+/* 0xfd0: */ 0x00, 0x00, 0x00, 0x70, 0x40, 0x70, 0x70, 0x7d,
+/* 0xfd8: */ 0x40, 0x70, 0x78, 0x7d, 0x78, 0x7d, 0x7d, 0x7d,
+/* 0xfe0: */ 0x00, 0x40, 0x40, 0x78, 0x60, 0x78, 0x78, 0x7e,
+/* 0xfe8: */ 0x60, 0x78, 0x78, 0x7e, 0x7c, 0x7e, 0x7e, 0x7e,
+/* 0xff0: */ 0x70, 0x7c, 0x7c, 0x7f, 0x7c, 0x7f, 0x7f, 0x7f,
+/* 0xff8: */ 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
+};
+
+const reg8 WaveformGenerator::wave6581_PST[] = {
+/* 0x000: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x008: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x010: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x018: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x020: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x028: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x030: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x038: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x040: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x048: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x050: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x058: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x060: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x068: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x070: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x078: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x080: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x088: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x090: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x098: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x0f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x100: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x108: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x110: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x118: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x120: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x128: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x130: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x138: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x140: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x148: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x150: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x158: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x160: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x168: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x170: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x178: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x180: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x188: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x190: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x198: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x1f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x200: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x208: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x210: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x218: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x220: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x228: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x230: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x238: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x240: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x248: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x250: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x258: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x260: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x268: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x270: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x278: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x280: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x288: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x290: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x298: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x2f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x300: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x308: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x310: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x318: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x320: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x328: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x330: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x338: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x340: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x348: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x350: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x358: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x360: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x368: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x370: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x378: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x380: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x388: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x390: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x398: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x3f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
+/* 0x400: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x408: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x410: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x418: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x420: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x428: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x430: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x438: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x440: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x448: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x450: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x458: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x460: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x468: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x470: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x478: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x480: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x488: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x490: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x498: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x4f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x500: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x508: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x510: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x518: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x520: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x528: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x530: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x538: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x540: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x548: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x550: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x558: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x560: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x568: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x570: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x578: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x580: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x588: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x590: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x598: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x5f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x600: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x608: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x610: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x618: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x620: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x628: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x630: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x638: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x640: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x648: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x650: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x658: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x660: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x668: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x670: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x678: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x680: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x688: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x690: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x698: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x6f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x700: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x708: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x710: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x718: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x720: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x728: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x730: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x738: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x740: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x748: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x750: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x758: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x760: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x768: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x770: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x778: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x780: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x788: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x790: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x798: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x7e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+/* 0x7f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
+/* 0x7f8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f,
+/* 0x800: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x808: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x810: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x818: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x820: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x828: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x830: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x838: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x840: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x848: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x850: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x858: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x860: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x868: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x870: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x878: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x880: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x888: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x890: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x898: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x8f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x900: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x908: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x910: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x918: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x920: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x928: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x930: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x938: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x940: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x948: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x950: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x958: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x960: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x968: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x970: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x978: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x980: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x988: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x990: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x998: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9a8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9b8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9c8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9d8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9e8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0x9f8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xa98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xab8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xac8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xad8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xae8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xaf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xb98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xba8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xbf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
+/* 0xc00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xc98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xca8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xce8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xcf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xd98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xda8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xde8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xdf8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xe98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xea8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xeb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xec8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xed8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xee8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xef8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf00: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf08: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf10: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf18: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf20: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf28: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf30: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf38: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf40: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf48: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf50: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf58: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf60: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf68: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf70: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf78: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf80: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf88: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf90: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xf98: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfa8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfb8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfc0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfc8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfd8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfe0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+/* 0xfe8: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
+/* 0xff0: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
+/* 0xff8: */ 0x00, 0x00, 0x00, 0x78, 0x78, 0x7e, 0x7f, 0x7f,
+};
+
+}
+#endif
diff --git a/audio/softsynth/ym2612.cpp b/audio/softsynth/ym2612.cpp
new file mode 100644
index 0000000000..94fcfb97db
--- /dev/null
+++ b/audio/softsynth/ym2612.cpp
@@ -0,0 +1,789 @@
+/* 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 <math.h>
+
+#include "audio/softsynth/ym2612.h"
+#include "common/util.h"
+#include "audio/musicplugin.h"
+#include "common/translation.h"
+
+////////////////////////////////////////
+//
+// Miscellaneous
+//
+////////////////////////////////////////
+
+static int *sintbl = 0;
+static int *powtbl = 0;
+static int *frequencyTable = 0;
+static int *keycodeTable = 0;
+static int *keyscaleTable = 0;
+static int *attackOut = 0;
+
+
+////////////////////////////////////////
+//
+// Operator2612 implementation
+//
+////////////////////////////////////////
+
+Operator2612::Operator2612 (Voice2612 *owner) :
+ _owner (owner),
+ _state (_s_ready),
+ _currentLevel ((int32)0x7f << 15),
+ _phase (0),
+ _lastOutput (0),
+ _feedbackLevel (0),
+ _detune (0),
+ _multiple (1),
+ _keyScale (0),
+ _specifiedTotalLevel (127),
+ _specifiedAttackRate (0),
+ _specifiedDecayRate (0),
+ _specifiedSustainRate (0),
+ _specifiedReleaseRate (15) {
+ velocity(0);
+}
+
+Operator2612::~Operator2612()
+{ }
+
+void Operator2612::velocity(int velo) {
+ _velocity = velo;
+ _totalLevel = ((int32)_specifiedTotalLevel << 15) +
+ ((int32)(127-_velocity) << 13);
+ _sustainLevel = ((int32)_specifiedSustainLevel << 17);
+}
+
+void Operator2612::feedbackLevel(int level) {
+ _feedbackLevel = level;
+}
+
+void Operator2612::setInstrument(byte const *instrument) {
+ _detune = (instrument[8] >> 4) & 7;
+ _multiple = instrument[8] & 15;
+ _specifiedTotalLevel = instrument[12] & 127;
+ _keyScale = (instrument[16] >> 6) & 3;
+ _specifiedAttackRate = instrument[16] & 31;
+ _specifiedDecayRate = instrument[20] & 31;
+ _specifiedSustainRate = instrument[24] & 31;
+ _specifiedSustainLevel = (instrument[28] >> 4) & 15;
+ _specifiedReleaseRate = instrument[28] & 15;
+ _state = _s_ready;
+ velocity(_velocity);
+}
+
+void Operator2612::keyOn() {
+ _state = _s_attacking;
+ _tickCount = 0;
+ _phase = 0;
+ _currentLevel = ((int32)0x7f << 15);
+}
+
+void Operator2612::keyOff() {
+ if (_state != _s_ready)
+ _state = _s_releasing;
+}
+
+void Operator2612::frequency(int freq) {
+ double value; // Use for intermediate computations to avoid int64 arithmetic
+ int r;
+
+ _frequency = freq / _owner->_rate;
+
+ r = _specifiedAttackRate;
+ if (r != 0) {
+ r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale));
+ if (r >= 64)
+ r = 63;
+ }
+
+ r = 63 - r;
+ if (_specifiedTotalLevel >= 128)
+ value = 0;
+ else {
+ value = powtbl[(r&3) << 7];
+ value *= 1 << (r >> 2);
+ value *= 41;
+ value /= 1 << (15 + 5);
+ value *= 127 - _specifiedTotalLevel;
+ value /= 127;
+ }
+ _attackTime = (int32) value; // 1 ?? == (1 << 12)
+ if (_attackTime > 0)
+ _attackTime = (1 << (12+10)) / (_owner->_rate * _attackTime);
+
+ r = _specifiedDecayRate;
+ if (r != 0) {
+ r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale));
+ if (r >= 64)
+ r = 63;
+ }
+ value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31;
+ _decayRate = (int32) value / _owner->_rate;
+
+ r = _specifiedSustainRate;
+ if (r != 0) {
+ r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale));
+ if (r >= 64)
+ r = 63;
+ }
+ value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31;
+ _sustainRate = (int32) value / _owner->_rate;
+
+ r = _specifiedReleaseRate;
+ if (r != 0) {
+ r = r * 2 + 1; // (Translated) I cannot know whether the timing is a good choice or not
+ r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale));
+ // KS
+ if (r >= 64)
+ r = 63;
+ }
+ value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31;
+ _releaseRate = (int32) value / _owner->_rate;
+}
+
+void Operator2612::nextTick(const int *phasebuf, int *outbuf, int buflen) {
+ if (_state == _s_ready)
+ return;
+ if (_state == _s_attacking && _attackTime <= 0) {
+ _currentLevel = 0;
+ _state = _s_decaying;
+ }
+
+ int32 levelIncrement = 0;
+ int32 target = 0;
+ State next_state = _s_ready;
+ const int32 zero_level = ((int32)0x7f << 15);
+ const int phaseIncrement = (_multiple > 0) ? (_frequency * _multiple) : (_frequency / 2);
+
+ int32 output = _lastOutput;
+ int32 level = _currentLevel + _totalLevel;
+
+ while (buflen) {
+ switch (_state) {
+ case _s_ready:
+ return;
+ case _s_attacking:
+ next_state = _s_attacking;
+ break;
+ case _s_decaying:
+ levelIncrement = _decayRate;
+ target = _sustainLevel + _totalLevel;
+ next_state = _s_sustaining;
+ break;
+ case _s_sustaining:
+ levelIncrement = _sustainRate;
+ target = zero_level + _totalLevel;
+ next_state = _s_ready;
+ break;
+ case _s_releasing:
+ levelIncrement = _releaseRate;
+ target = zero_level + _totalLevel;
+ next_state = _s_ready;
+ break;
+ }
+
+ bool switching = false;
+ do {
+ if (next_state == _s_attacking) {
+ // Attack phase
+ ++_tickCount;
+ int i = (int) (_tickCount * _attackTime);
+ if (i >= 1024) {
+ level = _totalLevel;
+ _state = _s_decaying;
+ switching = true;
+ } else {
+ level = (attackOut[i] << (31 - 8 - 16)) + _totalLevel;
+ }
+ } else {
+ // Decay, Sustain and Release phases
+ level += levelIncrement;
+ if (level >= target) {
+ level = target;
+ _state = next_state;
+ switching = true;
+ }
+ }
+
+ if (level < zero_level) {
+ int phaseShift = *phasebuf >> 2;
+ if (_feedbackLevel)
+ phaseShift += (output << (_feedbackLevel - 1)) / 1024;
+ output = sintbl[((_phase >> 7) + phaseShift) & 0x7ff];
+ output >>= (level >> 18);
+ // Here is the original code, which requires 64-bit ints
+// output *= powtbl[511 - ((level>>25)&511)];
+// output >>= 16;
+// output >>= 1;
+ // And here's our 32-bit trick for doing it. (Props to Fingolfin!)
+ // Result varies from original code by max of 1.
+// int powVal = powtbl[511 - ((level>>9)&511)];
+// int outputHI = output / 256;
+// int powHI = powVal / 256;
+// output = (outputHI * powHI) / 2 + (outputHI * (powVal % 256) + powHI * (output % 256)) / 512;
+ // And here's the even faster code.
+ // Result varies from original code by max of 8.
+ output = ((output >> 4) * (powtbl[511-((level>>9)&511)] >> 3)) / 1024;
+
+ _phase += phaseIncrement;
+ _phase &= 0x3ffff;
+ } else
+ output = 0;
+
+ *outbuf += output;
+ --buflen;
+ ++phasebuf;
+ ++outbuf;
+ } while (buflen && !switching);
+ }
+ _lastOutput = output;
+ _currentLevel = level - _totalLevel;
+}
+
+////////////////////////////////////////
+//
+// Voice2612 implementation
+//
+////////////////////////////////////////
+
+Voice2612::Voice2612() {
+ next = 0;
+ _control7 = 127;
+ _note = 40;
+ _frequency = 440;
+ _frequencyOffs = 0x2000;
+ _algorithm = 7;
+
+ _buffer = 0;
+ _buflen = 0;
+
+ int i;
+ for (i = 0; i < ARRAYSIZE(_opr); ++i)
+ _opr[i] = new Operator2612 (this);
+ velocity(0);
+}
+
+Voice2612::~Voice2612() {
+ int i;
+ for (i = 0; i < ARRAYSIZE(_opr); ++i)
+ delete _opr[i];
+ free(_buffer);
+}
+
+void Voice2612::velocity(int velo) {
+ _velocity = velo;
+#if 0
+ int v = (velo * _control7) >> 7;
+#else
+ int v = velo + (_control7 - 127) * 4;
+#endif
+ bool iscarrier[8][4] = {
+ { false, false, false, true, }, //0
+ { false, false, false, true, }, //1
+ { false, false, false, true, }, //2
+ { false, false, false, true, }, //3
+ { false, true, false, true, }, //4
+ { false, true, true, true, }, //5
+ { false, true, true, true, }, //6
+ { true, true, true, true, }, //7
+ };
+ int opr;
+ for (opr = 0; opr < 4; opr++)
+ if (iscarrier[_algorithm][opr])
+ _opr[opr]->velocity(v);
+ else
+ _opr[opr]->velocity(127);
+}
+
+void Voice2612::setControlParameter(int control, int value) {
+ switch (control) {
+ case 7:
+ _control7 = value;
+ velocity(_velocity);
+ break;
+ case 123:
+ // All notes off
+ noteOff(_note);
+ };
+}
+
+void Voice2612::setInstrument(byte const *instrument) {
+ if (instrument == NULL)
+ return;
+
+ _algorithm = instrument[32] & 7;
+ _opr[0]->feedbackLevel((instrument[32] >> 3) & 7);
+ _opr[1]->feedbackLevel(0);
+ _opr[2]->feedbackLevel(0);
+ _opr[3]->feedbackLevel(0);
+ _opr[0]->setInstrument(instrument + 0);
+ _opr[1]->setInstrument(instrument + 2);
+ _opr[2]->setInstrument(instrument + 1);
+ _opr[3]->setInstrument(instrument + 3);
+}
+
+void Voice2612::nextTick(int *outbuf, int buflen) {
+ if (_velocity == 0)
+ return;
+
+ if (_buflen < buflen) {
+ free(_buffer);
+ _buflen = buflen;
+ _buffer = (int *) malloc(sizeof(int) * buflen * 2);
+ }
+
+ int *buf1 = _buffer;
+ int *buf2 = _buffer + buflen;
+ memset(_buffer, 0, sizeof(int) * buflen * 2);
+
+ switch (_algorithm) {
+ case 0:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf2, buf1, buflen);
+ memset (buf2, 0, sizeof (int) * buflen);
+ _opr[2]->nextTick(buf1, buf2, buflen);
+ _opr[3]->nextTick(buf2, outbuf, buflen);
+ break;
+ case 1:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf1, buf2, buflen);
+ _opr[2]->nextTick(buf2, buf1, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ case 2:
+ _opr[1]->nextTick(buf1, buf2, buflen);
+ _opr[2]->nextTick(buf2, buf1, buflen);
+ memset(buf2, 0, sizeof(int) * buflen);
+ _opr[0]->nextTick(buf2, buf1, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ case 3:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf2, buf1, buflen);
+ memset(buf2, 0, sizeof(int) * buflen);
+ _opr[2]->nextTick(buf2, buf1, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ case 4:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf2, outbuf, buflen);
+ _opr[2]->nextTick(buf1, buf1, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ case 5:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf2, outbuf, buflen);
+ _opr[2]->nextTick(buf2, outbuf, buflen);
+ _opr[3]->nextTick(buf2, outbuf, buflen);
+ break;
+ case 6:
+ _opr[0]->nextTick(buf1, buf2, buflen);
+ _opr[1]->nextTick(buf2, outbuf, buflen);
+ _opr[2]->nextTick(buf1, outbuf, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ case 7:
+ _opr[0]->nextTick(buf1, outbuf, buflen);
+ _opr[1]->nextTick(buf1, outbuf, buflen);
+ _opr[2]->nextTick(buf1, outbuf, buflen);
+ _opr[3]->nextTick(buf1, outbuf, buflen);
+ break;
+ };
+}
+
+void Voice2612::noteOn(int n, int onVelo) {
+ _note = n;
+ velocity(onVelo);
+ recalculateFrequency();
+ int i;
+ for (i = 0; i < ARRAYSIZE(_opr); i++)
+ _opr[i]->keyOn();
+}
+
+bool Voice2612::noteOff(int note) {
+ if (_note != note)
+ return false;
+ int i;
+ for (i = 0; i < ARRAYSIZE(_opr); i++)
+ _opr[i]->keyOff();
+ return true;
+}
+
+void Voice2612::pitchBend(int value) {
+ _frequencyOffs = value;
+ recalculateFrequency();
+}
+
+void Voice2612::recalculateFrequency() {
+ //
+ //
+ //
+ int32 basefreq = frequencyTable[_note];
+ int cfreq = frequencyTable[_note - (_note % 12)];
+ int oct = _note / 12;
+ int fnum = (int) (((double)basefreq * (1 << 13)) / cfreq);
+ fnum += _frequencyOffs - 0x2000;
+ if (fnum < 0x2000) {
+ fnum += 0x2000;
+ oct--;
+ }
+ if (fnum >= 0x4000) {
+ fnum -= 0x2000;
+ oct++;
+ }
+
+ //
+ _frequency = (int) ((frequencyTable[oct*12] * (double)fnum) / 8);
+
+ int i;
+ for (i = 0; i < ARRAYSIZE(_opr); i++)
+ _opr[i]->frequency(_frequency);
+}
+
+////////////////////////////////////////
+//
+// MidiChannel_YM2612
+//
+////////////////////////////////////////
+
+MidiChannel_YM2612::MidiChannel_YM2612() {
+ _voices = 0;
+ _next_voice = 0;
+}
+
+MidiChannel_YM2612::~MidiChannel_YM2612() {
+ removeAllVoices();
+}
+
+void MidiChannel_YM2612::removeAllVoices() {
+ if (!_voices)
+ return;
+ Voice2612 *last, *voice = _voices;
+ for (; voice; voice = last) {
+ last = voice->next;
+ delete voice;
+ }
+ _voices = _next_voice = 0;
+}
+
+void MidiChannel_YM2612::noteOn(byte note, byte onVelo) {
+ if (!_voices)
+ return;
+ _next_voice = _next_voice ? _next_voice : _voices;
+ _next_voice->noteOn(note, onVelo);
+ _next_voice = _next_voice->next;
+}
+
+void MidiChannel_YM2612::noteOff(byte note) {
+ if (!_voices)
+ return;
+ if (_next_voice == _voices)
+ _next_voice = 0;
+ Voice2612 *voice = _next_voice;
+ do {
+ if (!voice)
+ voice = _voices;
+ if (voice->noteOff(note)) {
+ _next_voice = voice;
+ break;
+ }
+ voice = voice->next;
+ } while (voice != _next_voice);
+}
+
+void MidiChannel_YM2612::controlChange(byte control, byte value) {
+ //
+ if (control == 121) {
+ // Reset controller
+ removeAllVoices();
+ } else {
+ Voice2612 *voice = _voices;
+ for (; voice; voice = voice->next)
+ voice->setControlParameter(control, value);
+ }
+}
+
+void MidiChannel_YM2612::sysEx_customInstrument(uint32 type, const byte *fmInst) {
+ if (type != 'EUP ')
+ return;
+ Voice2612 *voice = new Voice2612;
+ voice->next = _voices;
+ _voices = voice;
+ voice->_rate = _rate;
+ voice->setInstrument(fmInst);
+}
+
+void MidiChannel_YM2612::pitchBend(int16 value) {
+ //
+ Voice2612 *voice = _voices;
+ for (; voice; voice = voice->next)
+ voice->pitchBend(value);
+}
+
+void MidiChannel_YM2612::nextTick(int *outbuf, int buflen) {
+ Voice2612 *voice = _voices;
+ for (; voice; voice = voice->next)
+ voice->nextTick(outbuf, buflen);
+}
+
+void MidiChannel_YM2612::rate(uint16 r) {
+ _rate = r;
+ Voice2612 *voice = _voices;
+ for (; voice; voice = voice->next)
+ voice->_rate = r;
+}
+
+////////////////////////////////////////
+//
+// MidiDriver_YM2612
+//
+////////////////////////////////////////
+
+MidiDriver_YM2612::MidiDriver_YM2612(Audio::Mixer *mixer)
+ : MidiDriver_Emulated(mixer) {
+ _next_voice = 0;
+
+ createLookupTables();
+ _volume = 256;
+ int i;
+ for (i = 0; i < ARRAYSIZE(_channel); i++)
+ _channel[i] = new MidiChannel_YM2612;
+ rate(getRate());
+}
+
+MidiDriver_YM2612::~MidiDriver_YM2612() {
+ int i;
+ for (i = 0; i < ARRAYSIZE(_channel); i++)
+ delete _channel[i];
+ removeLookupTables();
+}
+
+int MidiDriver_YM2612::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ return 0;
+}
+
+void MidiDriver_YM2612::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_mixerSoundHandle);
+}
+
+void MidiDriver_YM2612::send(uint32 b) {
+ send(b & 0xF, b & 0xFFFFFFF0);
+}
+
+void MidiDriver_YM2612::send(byte chan, uint32 b) {
+ //byte param3 = (byte) ((b >> 24) & 0xFF);
+ byte param2 = (byte) ((b >> 16) & 0xFF);
+ byte param1 = (byte) ((b >> 8) & 0xFF);
+ byte cmd = (byte) (b & 0xF0);
+ if (chan > ARRAYSIZE(_channel))
+ return;
+
+ switch (cmd) {
+ case 0x80:// Note Off
+ _channel[chan]->noteOff(param1);
+ break;
+ case 0x90: // Note On
+ _channel[chan]->noteOn(param1, param2);
+ break;
+ case 0xA0: // Aftertouch
+ break; // Not supported.
+ case 0xB0: // Control Change
+ _channel[chan]->controlChange(param1, param2);
+ break;
+ case 0xC0: // Program Change
+ _channel[chan]->programChange(param1);
+ break;
+ case 0xD0: // Channel Pressure
+ break; // Not supported.
+ case 0xE0: // Pitch Bend
+ _channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000);
+ break;
+ case 0xF0: // SysEx
+ // We should never get here! SysEx information has to be
+ // sent via high-level semantic methods.
+ warning("MidiDriver_YM2612: Receiving SysEx command on a send() call");
+ break;
+
+ default:
+ warning("MidiDriver_YM2612: Unknown send() command 0x%02X", cmd);
+ }
+}
+
+void MidiDriver_YM2612::sysEx(const byte *msg, uint16 length) {
+ if (msg[0] != 0x7C || msg[1] >= ARRAYSIZE(_channel))
+ return;
+ _channel[msg[1]]->sysEx_customInstrument('EUP ', &msg[2]);
+}
+
+void MidiDriver_YM2612::generateSamples(int16 *data, int len) {
+ memset(data, 0, 2 * sizeof(int16) * len);
+ nextTick(data, len);
+}
+
+void MidiDriver_YM2612::nextTick(int16 *buf1, int buflen) {
+ int *buf0 = (int *)buf1;
+
+ int i;
+ for (i = 0; i < ARRAYSIZE(_channel); i++)
+ _channel[i]->nextTick(buf0, buflen);
+
+ for (i = 0; i < buflen; ++i)
+ buf1[i*2+1] = buf1[i*2] = ((buf0[i] * volume()) >> 10) & 0xffff;
+}
+
+void MidiDriver_YM2612::rate(uint16 r)
+{
+ int i;
+ for (i = 0; i < ARRAYSIZE(_channel); i++)
+ _channel[i]->rate(r);
+}
+
+void MidiDriver_YM2612::createLookupTables() {
+ {
+ int i;
+ sintbl = new int [2048];
+ for (i = 0; i < 2048; i++)
+ sintbl[i] = (int)(0xffff * sin(i/2048.0*2.0*PI));
+ }
+
+ {
+ int i;
+ powtbl = new int [1025];
+ for (i = 0; i <= 1024; i++)
+ powtbl[i] = (int)(0x10000 * pow(2.0, (i-512)/512.0));
+ }
+
+ {
+ int i;
+ int block;
+
+ static int fnum[] = {
+ 0x026a, 0x028f, 0x02b6, 0x02df,
+ 0x030b, 0x0339, 0x036a, 0x039e,
+ 0x03d5, 0x0410, 0x044e, 0x048f,
+ };
+
+ // (int)(880.0 * 256.0 * pow(2.0, (note-0x51)/12.0))
+ //
+ frequencyTable = new int [120];
+ for (block = -1; block < 9; block++) {
+ for (i = 0; i < 12; i++) {
+ double freq = fnum[i] * (166400.0 / 3) * pow(2.0, block-21);
+ frequencyTable[(block+1)*12+i] = (int)(256.0 * freq);
+ }
+ }
+
+ keycodeTable = new int [120];
+ // detune
+ for (block = -1; block < 9; block++) {
+ for (i = 0; i < 12; i++) {
+ // see p.204
+ int f8 = (fnum[i] >> 7) & 1;
+ int f9 = (fnum[i] >> 8) & 1;
+ int f10 = (fnum[i] >> 9) & 1;
+ int f11 = (fnum[i] >> 10) & 1;
+ int n4 = f11;
+ int n3 = (f11&(f10|f9|f8)) | (~f11&f10&f9&f8);
+ int note = n4*2 + n3;
+ // see p.207
+ keycodeTable[(block+1)*12+i] = block*4 + note;
+ }
+ }
+ }
+
+ {
+ int freq;
+ keyscaleTable = new int [8192];
+ keyscaleTable[0] = 0;
+ for (freq = 1; freq < 8192; freq++) {
+ keyscaleTable[freq] = (int)(log((double)freq) / 9.03 * 32.0) - 1;
+ // 8368[Hz] (o9c)
+ }
+ }
+
+ {
+ int i;
+ attackOut = new int [1024];
+ for (i = 0; i < 1024; i++)
+ attackOut[i] = (int)(((0x7fff+0x03a5)*30.0) / (30.0+i)) - 0x03a5;
+ }
+}
+
+void MidiDriver_YM2612::removeLookupTables() {
+ delete[] sintbl;
+ delete[] powtbl;
+ delete[] frequencyTable;
+ delete[] keycodeTable;
+ delete[] keyscaleTable;
+ delete[] attackOut;
+ sintbl = powtbl = frequencyTable = keycodeTable = keyscaleTable = attackOut = 0;
+}
+
+
+// Plugin interface
+
+class TownsEmuMusicPlugin : public MusicPluginObject {
+public:
+ const char *getName() const {
+ return _s("FM Towns Emulator");
+ }
+
+ const char *getId() const {
+ return "towns";
+ }
+
+ MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+MusicDevices TownsEmuMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_TOWNS));
+ return devices;
+}
+
+Common::Error TownsEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
+ *mididriver = new MidiDriver_YM2612(g_system->getMixer());
+
+ return Common::kNoError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(TOWNS)
+ //REGISTER_PLUGIN_DYNAMIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(TOWNS, PLUGIN_TYPE_MUSIC, TownsEmuMusicPlugin);
+//#endif
diff --git a/audio/softsynth/ym2612.h b/audio/softsynth/ym2612.h
new file mode 100644
index 0000000000..f652b2d9e4
--- /dev/null
+++ b/audio/softsynth/ym2612.h
@@ -0,0 +1,179 @@
+/* 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$
+ */
+
+#ifndef SOUND_SOFTSYNTH_Y2612_H
+#define SOUND_SOFTSYNTH_Y2612_H
+
+#include "common/scummsys.h"
+
+#include "audio/softsynth/emumidi.h"
+
+////////////////////////////////////////
+//
+// Class declarations
+//
+////////////////////////////////////////
+
+class Voice2612;
+class Operator2612 {
+protected:
+ Voice2612 *_owner;
+ enum State { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing };
+ State _state;
+ int32 _currentLevel;
+ int _frequency;
+ uint32 _phase;
+ int _lastOutput;
+ int _feedbackLevel;
+ int _detune;
+ int _multiple;
+ int32 _totalLevel;
+ int _keyScale;
+ int _velocity;
+ int _specifiedTotalLevel;
+ int _specifiedAttackRate;
+ int _specifiedDecayRate;
+ int _specifiedSustainLevel;
+ int _specifiedSustainRate;
+ int _specifiedReleaseRate;
+ int _tickCount;
+ int _attackTime;
+ int32 _decayRate;
+ int32 _sustainLevel;
+ int32 _sustainRate;
+ int32 _releaseRate;
+
+public:
+ Operator2612 (Voice2612 *owner);
+ ~Operator2612();
+ void feedbackLevel(int level);
+ void setInstrument(byte const *instrument);
+ void velocity(int velo);
+ void keyOn();
+ void keyOff();
+ void frequency(int freq);
+ void nextTick(const int *phaseShift, int *outbuf, int buflen);
+ bool inUse() { return (_state != _s_ready); }
+};
+
+class Voice2612 {
+public:
+ Voice2612 *next;
+ uint16 _rate;
+
+protected:
+ Operator2612 *_opr[4];
+ int _velocity;
+ int _control7;
+ int _note;
+ int _frequencyOffs;
+ int _frequency;
+ int _algorithm;
+
+ int *_buffer;
+ int _buflen;
+
+public:
+ Voice2612();
+ ~Voice2612();
+ void setControlParameter(int control, int value);
+ void setInstrument(byte const *instrument);
+ void velocity(int velo);
+ void nextTick(int *outbuf, int buflen);
+ void noteOn(int n, int onVelo);
+ bool noteOff(int note);
+ void pitchBend(int value);
+ void recalculateFrequency();
+};
+
+class MidiChannel_YM2612 : public MidiChannel {
+protected:
+ uint16 _rate;
+ Voice2612 *_voices;
+ Voice2612 *_next_voice;
+
+public:
+ void removeAllVoices();
+ void nextTick(int *outbuf, int buflen);
+ void rate(uint16 r);
+
+public:
+ MidiChannel_YM2612();
+ virtual ~MidiChannel_YM2612();
+
+ // MidiChannel interface
+ MidiDriver *device() { return 0; }
+ byte getNumber() { return 0; }
+ void release() { }
+ void send(uint32 b) { }
+ void noteOff(byte note);
+ void noteOn(byte note, byte onVelo);
+ void programChange(byte program) { }
+ void pitchBend(int16 value);
+ void controlChange(byte control, byte value);
+ void pitchBendFactor(byte value) { }
+ void sysEx_customInstrument(uint32 type, const byte *instr);
+};
+
+class MidiDriver_YM2612 : public MidiDriver_Emulated {
+protected:
+ MidiChannel_YM2612 *_channel[16];
+
+ int _next_voice;
+ int _volume;
+
+protected:
+ void nextTick(int16 *buf1, int buflen);
+ int volume(int val = -1) { if (val >= 0) _volume = val; return _volume; }
+ void rate(uint16 r);
+
+ void generateSamples(int16 *buf, int len);
+
+public:
+ MidiDriver_YM2612(Audio::Mixer *mixer);
+ virtual ~MidiDriver_YM2612();
+
+ static void createLookupTables();
+ static void removeLookupTables();
+
+ int open();
+ void close();
+ void send(uint32 b);
+ void send(byte channel, uint32 b); // Supports higher than channel 15
+ uint32 property(int prop, uint32 param) { return 0; }
+
+ void setPitchBendRange(byte channel, uint range) { }
+ void sysEx(const byte *msg, uint16 length);
+
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+
+
+ // AudioStream API
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+};
+
+#endif
+
diff --git a/audio/timestamp.cpp b/audio/timestamp.cpp
new file mode 100644
index 0000000000..cdc5bc3a02
--- /dev/null
+++ b/audio/timestamp.cpp
@@ -0,0 +1,212 @@
+/* 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 "audio/timestamp.h"
+#include "common/algorithm.h"
+
+namespace Audio {
+
+Timestamp::Timestamp(uint ms, uint fr) {
+ assert(fr > 0);
+
+ _secs = ms / 1000;
+ _framerateFactor = 1000 / Common::gcd<uint>(1000, fr);
+ _framerate = fr * _framerateFactor;
+
+ // Note that _framerate is always divisible by 1000.
+ _numFrames = (ms % 1000) * (_framerate / 1000);
+}
+
+Timestamp::Timestamp(uint s, uint frames, uint fr) {
+ assert(fr > 0);
+
+ _secs = s;
+ _framerateFactor = 1000 / Common::gcd<uint>(1000, fr);
+ _framerate = fr * _framerateFactor;
+ _numFrames = frames * _framerateFactor;
+
+ normalize();
+}
+
+Timestamp Timestamp::convertToFramerate(uint newFramerate) const {
+ Timestamp ts(*this);
+
+ if (ts.framerate() != newFramerate) {
+ ts._framerateFactor = 1000 / Common::gcd<uint>(1000, newFramerate);
+ ts._framerate = newFramerate * ts._framerateFactor;
+
+ const uint g = Common::gcd(_framerate, ts._framerate);
+ const uint p = _framerate / g;
+ const uint q = ts._framerate / g;
+
+ // Convert the frame offset to the new framerate.
+ // We round to the nearest (as opposed to always
+ // rounding down), to minimize rounding errors during
+ // round trip conversions.
+ ts._numFrames = (ts._numFrames * q + p/2) / p;
+
+ ts.normalize();
+ }
+
+ return ts;
+}
+
+void Timestamp::normalize() {
+ // Convert negative _numFrames values to positive ones by adjusting _secs
+ if (_numFrames < 0) {
+ int secsub = 1 + (-_numFrames / _framerate);
+
+ _numFrames += _framerate * secsub;
+ _secs -= secsub;
+ }
+
+ // Wrap around if necessary
+ _secs += (_numFrames / _framerate);
+ _numFrames %= _framerate;
+}
+
+bool Timestamp::operator==(const Timestamp &ts) const {
+ return cmp(ts) == 0;
+}
+
+bool Timestamp::operator!=(const Timestamp &ts) const {
+ return cmp(ts) != 0;
+}
+
+bool Timestamp::operator<(const Timestamp &ts) const {
+ return cmp(ts) < 0;
+}
+
+bool Timestamp::operator<=(const Timestamp &ts) const {
+ return cmp(ts) <= 0;
+}
+
+bool Timestamp::operator>(const Timestamp &ts) const {
+ return cmp(ts) > 0;
+}
+
+bool Timestamp::operator>=(const Timestamp &ts) const {
+ return cmp(ts) >= 0;
+}
+
+int Timestamp::cmp(const Timestamp &ts) const {
+ int delta = _secs - ts._secs;
+ if (!delta) {
+ const uint g = Common::gcd(_framerate, ts._framerate);
+ const uint p = _framerate / g;
+ const uint q = ts._framerate / g;
+
+ delta = (_numFrames * q - ts._numFrames * p);
+ }
+
+ return delta;
+}
+
+
+Timestamp Timestamp::addFrames(int frames) const {
+ Timestamp ts(*this);
+
+ // The frames are given in the original framerate, so we have to
+ // adjust by _framerateFactor accordingly.
+ ts._numFrames += frames * _framerateFactor;
+ ts.normalize();
+
+ return ts;
+}
+
+Timestamp Timestamp::addMsecs(int ms) const {
+ Timestamp ts(*this);
+ ts._secs += ms / 1000;
+ // Add the remaining frames. Note that _framerate is always divisible by 1000.
+ ts._numFrames += (ms % 1000) * (ts._framerate / 1000);
+
+ ts.normalize();
+
+ return ts;
+}
+
+void Timestamp::addIntern(const Timestamp &ts) {
+ assert(_framerate == ts._framerate);
+ _secs += ts._secs;
+ _numFrames += ts._numFrames;
+
+ normalize();
+}
+
+Timestamp Timestamp::operator-() const {
+ Timestamp result(*this);
+ result._secs = -_secs;
+ result._numFrames = -_numFrames;
+ result.normalize();
+ return result;
+}
+
+Timestamp Timestamp::operator+(const Timestamp &ts) const {
+ Timestamp result(*this);
+ result.addIntern(ts);
+ return result;
+}
+
+Timestamp Timestamp::operator-(const Timestamp &ts) const {
+ Timestamp result(*this);
+ result.addIntern(-ts);
+ return result;
+}
+
+int Timestamp::frameDiff(const Timestamp &ts) const {
+
+ int delta = 0;
+ if (_secs != ts._secs)
+ delta = (_secs - ts._secs) * _framerate;
+
+ delta += _numFrames;
+
+ if (_framerate == ts._framerate) {
+ delta -= ts._numFrames;
+ } else {
+ // We need to multiply by the quotient of the two framerates.
+ // We cancel the GCD in this fraction to reduce the risk of
+ // overflows.
+ const uint g = Common::gcd(_framerate, ts._framerate);
+ const uint p = _framerate / g;
+ const uint q = ts._framerate / g;
+
+ delta -= ((long)ts._numFrames * p + q/2) / (long)q;
+ }
+
+ return delta / (int)_framerateFactor;
+}
+
+int Timestamp::msecsDiff(const Timestamp &ts) const {
+ return msecs() - ts.msecs();
+}
+
+int Timestamp::msecs() const {
+ // Note that _framerate is always divisible by 1000.
+ return _secs * 1000 + _numFrames / (_framerate / 1000);
+}
+
+
+} // End of namespace Audio
diff --git a/audio/timestamp.h b/audio/timestamp.h
new file mode 100644
index 0000000000..4130793fc8
--- /dev/null
+++ b/audio/timestamp.h
@@ -0,0 +1,251 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_TIMESTAMP_H
+#define SOUND_TIMESTAMP_H
+
+#include "common/scummsys.h"
+
+namespace Audio {
+
+/**
+ * Timestamps allow specifying points in time and measuring time intervals
+ * with a sub-millisecond granularity.
+ *
+ * When dealing with audio and video decoding, it is often necessary to
+ * measure time (intervals) in terms of frames, relative to a fixed
+ * frame rate (that is, a fixed number of frames per seconds). For
+ * example, in a typical video there are 24 frames per second, and in a
+ * typical sound there are 44100 frames (i.e. samples for mono sound
+ * and pairs of samples for stereo) per second.
+ *
+ * At the same time, the system clock provided by ScummVM measures time
+ * in milliseconds. For syncing purposes and other reasons, it is often
+ * necessary to convert between and compare time measures given on the
+ * one hand as a frame count, and on the other hand as a number of
+ * milliseconds.
+ *
+ * If handled carelessly, this can introduce rounding errors that
+ * quickly accumulate, resulting in user noticeable disturbance, such as
+ * audio and video running out of sync. E.g. a typical approach is to
+ * measure all time in milliseconds. But with a frame rate of 24 frames
+ * per second, one frame is 41.66666... milliseconds long. On the other
+ * hand, if measuring in frames, then similar rounding issue occur when
+ * converting from milliseconds to frames.
+ *
+ * One solution is to use floating point arithmetic to compute with
+ * fractional frames resp. (milli)seconds. This has other undesirable
+ * side effects; foremost, some platforms ScummVM runs on still have
+ * only limited (and slow) floating point support.
+ *
+ * This class provides an alternate solution: It stores time in terms of
+ * frames, but with a twist: Client code can specify arbitrary
+ * (integral) framerates; but internally, Timestamp modifies the
+ * framerate to be a multiple of 1000. This way, both numbers of frames
+ * (relative to the original framerate) as well as milliseconds can be
+ * represented as integers. This change is completely hidden from the
+ * user, however.
+ *
+ * A Timestamp can be converted to a frame count or milliseconds at
+ * virtually no cost. Likewise, it is posible to compute the difference
+ * between two Timestamps in milliseconds or number of frames.
+ * Timestamps can be easily compared using regular comparison operators,
+ * resulting in nicely readable code; this is even possible for
+ * timestamps that are specified using different framerates.
+ * Client code can modify Timestamps by adding a number of frames
+ * to it, or adding a number of milliseconds. Adding negative amounts is
+ * also allowed, and a Timestamp can even represent a "negative time"
+ * (mainly useful when using the Timestamp to store a time interval).
+ */
+class Timestamp {
+public:
+ /**
+ * Set up a timestamp with a given time and framerate.
+ * @param msecs starting time in milliseconds
+ * @param framerate number of frames per second (must be > 0)
+ */
+ Timestamp(uint msecs = 0, uint framerate = 1);
+
+ /**
+ * Set up a timestamp with a given time, frames and framerate.
+ * @param secs starting time in seconds
+ * @param frames starting frames
+ * @param framerate number of frames per second (must be > 0)
+ */
+ Timestamp(uint secs, uint frames, uint framerate);
+
+ /**
+ * Return a timestamp which represents as closely as possible
+ * the point in time describes by this timestamp, but with
+ * a different framerate.
+ */
+ Timestamp convertToFramerate(uint newFramerate) const;
+
+ /**
+ * Check whether to timestamps describe the exact same moment
+ * in time. This means that two timestamps can compare
+ * as equal even if they use different framerates.
+ */
+ bool operator==(const Timestamp &ts) const;
+ bool operator!=(const Timestamp &ts) const;
+ bool operator<(const Timestamp &ts) const;
+ bool operator<=(const Timestamp &ts) const;
+ bool operator>(const Timestamp &ts) const;
+ bool operator>=(const Timestamp &ts) const;
+
+ /**
+ * Returns a new timestamp, which corresponds to the time encoded
+ * by this timestamp with the given number of frames added.
+ * @param frames number of frames to add
+ */
+ Timestamp addFrames(int frames) const;
+
+ /**
+ * Returns a new timestamp, which corresponds to the time encoded
+ * by this timestamp with the given number of milliseconds added.
+ * @param msecs number of milliseconds to add
+ */
+ Timestamp addMsecs(int msecs) const;
+
+
+ // unary minus
+ Timestamp operator-() const;
+
+ /**
+ * Compute the sum of two timestamps. This is only
+ * allowed if they use the same framerate.
+ */
+ Timestamp operator+(const Timestamp &ts) const;
+
+ /**
+ * Compute the difference between two timestamps. This is only
+ * allowed if they use the same framerate.
+ */
+ Timestamp operator-(const Timestamp &ts) const;
+
+ /**
+ * Computes the number of frames between this timestamp and ts.
+ * The frames are with respect to the framerate used by this
+ * Timestamp (which may differ from the framerate used by ts).
+ */
+ int frameDiff(const Timestamp &ts) const;
+
+ /** Computes the number off milliseconds between this timestamp and ts. */
+ int msecsDiff(const Timestamp &ts) const;
+
+ /**
+ * Return the time in milliseconds described by this timestamp,
+ * rounded down.
+ */
+ int msecs() const;
+
+ /**
+ * Return the time in seconds described by this timestamp,
+ * rounded down.
+ */
+ inline int secs() const {
+ return _secs;
+ }
+
+ /**
+ * Return the time in frames described by this timestamp.
+ */
+ inline int totalNumberOfFrames() const {
+ return _numFrames / (int)_framerateFactor + _secs * (int)(_framerate / _framerateFactor);
+ }
+
+ /**
+ * A timestamp consists of a number of seconds, plus a number
+ * of frames, the latter describing a fraction of a second.
+ * This method returns the latter number.
+ */
+ inline int numberOfFrames() const {
+ return _numFrames / (int)_framerateFactor;
+ }
+
+ /** Return the framerate used by this timestamp. */
+ inline uint framerate() const { return _framerate / _framerateFactor; }
+
+protected:
+ /**
+ * Compare this timestamp to another one and return
+ * a value similar to strcmp.
+ */
+ int cmp(const Timestamp &ts) const;
+
+ /**
+ * Normalize this timestamp by making _numFrames non-negative
+ * and reducing it modulo _framerate.
+ */
+ void normalize();
+
+ /**
+ * Add another timestamp to this one and normalize the result.
+ */
+ void addIntern(const Timestamp &ts);
+
+protected:
+ /**
+ * The seconds part of this timestamp.
+ * The total time in seconds represented by this timestamp can be
+ * computed as follows:
+ * _secs + (double)_numFrames / _framerate
+ */
+ int _secs;
+
+ /**
+ * The number of frames which together with _secs encodes the
+ * timestamp. The total number of *internal* frames represented
+ * by this timestamp can be computed as follows:
+ * _numFrames + _secs * _framerate
+ * To obtain the number of frames with respect to the original
+ * framerate, this value has to be divided by _framerateFactor.
+ *
+ * This is always a value greater or equal to zero.
+ * The only reason this is an int and not an uint is to
+ * allow intermediate negative values.
+ */
+ int _numFrames;
+
+ /**
+ * The internal framerate, i.e. the number of frames per second.
+ * This is computed as the least common multiple of the framerate
+ * specified by the client code, and 1000.
+ * This way, we ensure that we can store both frames and
+ * milliseconds without any rounding losses.
+ */
+ uint _framerate;
+
+ /**
+ * Factor by which the original framerate specified by the client
+ * code was multipled to obtain the internal _framerate value.
+ */
+ uint _framerateFactor;
+};
+
+
+} // End of namespace Audio
+
+#endif