aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sound/softseq/amiga.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/sound/softseq/amiga.cpp')
-rw-r--r--engines/sci/sound/softseq/amiga.cpp676
1 files changed, 676 insertions, 0 deletions
diff --git a/engines/sci/sound/softseq/amiga.cpp b/engines/sci/sound/softseq/amiga.cpp
new file mode 100644
index 0000000000..452768901a
--- /dev/null
+++ b/engines/sci/sound/softseq/amiga.cpp
@@ -0,0 +1,676 @@
+/* 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 "sound/softsynth/emumidi.h"
+#include "sci/sound/softseq/mididriver.h"
+
+#include "common/file.h"
+#include "common/frac.h"
+#include "common/util.h"
+
+namespace Sci {
+
+/* #define DEBUG */
+
+// Frequencies for every note
+// FIXME Store only one octave
+static const int freq_table[] = {
+ 58, 62, 65, 69, 73, 78, 82, 87,
+ 92, 98, 104, 110, 117, 124, 131, 139,
+ 147, 156, 165, 175, 185, 196, 208, 220,
+ 234, 248, 262, 278, 294, 312, 331, 350,
+ 371, 393, 417, 441, 468, 496, 525, 556,
+ 589, 625, 662, 701, 743, 787, 834, 883,
+ 936, 992, 1051, 1113, 1179, 1250, 1324, 1403,
+ 1486, 1574, 1668, 1767, 1872, 1984, 2102, 2227,
+ 2359, 2500, 2648, 2806, 2973, 3149, 3337, 3535,
+ 3745, 3968, 4204, 4454, 4719, 5000, 5297, 5612,
+ 5946, 6299, 6674, 7071, 7491, 7937, 8408, 8908,
+ 9438, 10000, 10594, 11224, 11892, 12599, 13348, 14142,
+ 14983, 15874, 16817, 17817, 18877, 20000, 21189, 22449,
+ 23784, 25198, 26696, 28284, 29966, 31748, 33635, 35635,
+ 37754, 40000, 42378, 44898, 47568, 50396, 53393, 56568,
+ 59932, 63496, 67271, 71271, 75509, 80000, 84757, 89796
+};
+
+class MidiDriver_Amiga : public MidiDriver_Emulated {
+public:
+ enum {
+ kVoices = 4
+ };
+
+ MidiDriver_Amiga(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15) { }
+ virtual ~MidiDriver_Amiga() { }
+
+ // MidiDriver
+ int open();
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+
+ // AudioStream
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+ // MidiDriver_Emulated
+ void generateSamples(int16 *buf, int len);
+
+ void setVolume(byte volume);
+ void playSwitch(bool play);
+ virtual uint32 property(int prop, uint32 param);
+
+private:
+ enum {
+ kModeLoop = 1 << 0, // Instrument looping flag
+ kModePitch = 1 << 1 // Instrument pitch changes flag
+ };
+
+ enum {
+ kChannels = 10,
+ kBaseFreq = 20000, // Samplerate of the instrument bank
+ kPanLeft = 91,
+ kPanRight = 164
+ };
+
+ struct Channel {
+ int instrument;
+ int volume;
+ int pan;
+ };
+
+ struct Envelope {
+ int length; // Phase period length in samples
+ int delta; // Velocity delta per period
+ int target; // Target velocity
+ };
+
+ struct Voice {
+ int instrument;
+ int note;
+ int note_velocity;
+ int velocity;
+ int envelope;
+ int envelope_samples; // Number of samples till next envelope event
+ int decay;
+ int looping;
+ int hw_channel;
+ frac_t offset;
+ frac_t rate;
+ };
+
+ struct Instrument {
+ char name[30];
+ int mode;
+ int size; // Size of non-looping part in bytes
+ int loop_size; // Starting offset and size of loop in bytes
+ int transpose; // Transpose value in semitones
+ Envelope envelope[4]; // Envelope
+ int8 *samples;
+ int8 *loop;
+ };
+
+ struct Bank {
+ char name[30];
+ uint size;
+ Instrument *instruments[256];
+ };
+
+ bool _playSwitch;
+ int _masterVolume;
+ int _frequency;
+ Envelope _envDecay;
+ Bank _bank; // Instrument bank
+
+ Channel _channels[MIDI_CHANNELS];
+ /* Internal channels */
+ Voice _voices[kChannels];
+
+ void setEnvelope(Voice *channel, Envelope *envelope, int phase);
+ int interpolate(int8 *samples, frac_t offset);
+ void playInstrument(int16 *dest, Voice *channel, int count);
+ void changeInstrument(int channel, int instrument);
+ void stopChannel(int ch);
+ void stopNote(int ch, int note);
+ void startNote(int ch, int note, int velocity);
+ Instrument *readInstrument(Common::File &file, int *id);
+};
+
+void MidiDriver_Amiga::setEnvelope(Voice *channel, Envelope *envelope, int phase) {
+ channel->envelope = phase;
+ channel->envelope_samples = envelope[phase].length;
+
+ if (phase == 0)
+ channel->velocity = channel->note_velocity / 2;
+ else
+ channel->velocity = envelope[phase - 1].target;
+}
+
+int MidiDriver_Amiga::interpolate(int8 *samples, frac_t offset) {
+ int x = fracToInt(offset);
+ int diff = (samples[x + 1] - samples[x]) << 8;
+
+ return (samples[x] << 8) + fracToInt(diff * (offset & FRAC_LO_MASK));
+}
+
+void MidiDriver_Amiga::playInstrument(int16 *dest, Voice *channel, int count) {
+ int index = 0;
+ int vol = _channels[channel->hw_channel].volume;
+ Instrument *instrument = _bank.instruments[channel->instrument];
+
+ while (1) {
+ /* Available source samples until end of segment */
+ frac_t lin_avail;
+ int seg_end, rem, i, amount;
+ int8 *samples;
+
+ if (channel->looping) {
+ samples = instrument->loop;
+ seg_end = instrument->loop_size;
+ } else {
+ samples = instrument->samples;
+ seg_end = instrument->size;
+ }
+
+ lin_avail = intToFrac(seg_end) - channel->offset;
+
+ rem = count - index;
+
+ /* Amount of destination samples that we will compute this iteration */
+ amount = lin_avail / channel->rate;
+
+ if (lin_avail % channel->rate)
+ amount++;
+
+ if (amount > rem)
+ amount = rem;
+
+ /* Stop at next envelope event */
+ if ((channel->envelope_samples != -1) && (amount > channel->envelope_samples))
+ amount = channel->envelope_samples;
+
+ for (i = 0; i < amount; i++) {
+ dest[index++] = interpolate(samples, channel->offset) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127);
+ channel->offset += channel->rate;
+ }
+
+ if (channel->envelope_samples != -1)
+ channel->envelope_samples -= amount;
+
+ if (channel->envelope_samples == 0) {
+ Envelope *envelope;
+ int delta, target, velocity;
+
+ if (channel->decay)
+ envelope = &_envDecay;
+ else
+ envelope = &instrument->envelope[channel->envelope];
+
+ delta = envelope->delta;
+ target = envelope->target;
+ velocity = channel->velocity - envelope->delta;
+
+ /* Check whether we have reached the velocity target for the current phase */
+ if ((delta >= 0 && velocity <= target) || (delta < 0 && velocity >= target)) {
+ channel->velocity = target;
+
+ /* Stop note after velocity has dropped to 0 */
+ if (target == 0) {
+ channel->note = -1;
+ break;
+ } else
+ switch (channel->envelope) {
+ case 0:
+ case 2:
+ /* Go to next phase */
+ setEnvelope(channel, instrument->envelope, channel->envelope + 1);
+ break;
+ case 1:
+ case 3:
+ /* Stop envelope */
+ channel->envelope_samples = -1;
+ break;
+ }
+ } else {
+ /* We haven't reached the target yet */
+ channel->envelope_samples = envelope->length;
+ channel->velocity = velocity;
+ }
+ }
+
+ if (index == count)
+ break;
+
+ if (fracToInt(channel->offset) >= seg_end) {
+ if (instrument->mode & kModeLoop) {
+ /* Loop the samples */
+ channel->offset -= intToFrac(seg_end);
+ channel->looping = 1;
+ } else {
+ /* All samples have been played */
+ channel->note = -1;
+ break;
+ }
+ }
+ }
+}
+
+void MidiDriver_Amiga::changeInstrument(int channel, int instrument) {
+#ifdef DEBUG
+ if (_bank.instruments[instrument])
+ printf("[sfx:seq:amiga] Setting channel %i to \"%s\" (%i)\n", channel, _bank.instruments[instrument]->name, instrument);
+ else
+ warning("[sfx:seq:amiga] instrument %i does not exist (channel %i)", instrument, channel);
+#endif
+ _channels[channel].instrument = instrument;
+}
+
+void MidiDriver_Amiga::stopChannel(int ch) {
+ int i;
+
+ /* Start decay phase for note on this hw channel, if any */
+ for (i = 0; i < kChannels; i++)
+ if (_voices[i].note != -1 && _voices[i].hw_channel == ch && !_voices[i].decay) {
+ /* Trigger fast decay envelope */
+ _voices[i].decay = 1;
+ _voices[i].envelope_samples = _envDecay.length;
+ break;
+ }
+}
+
+void MidiDriver_Amiga::stopNote(int ch, int note) {
+ int channel;
+ Instrument *instrument;
+
+ for (channel = 0; channel < kChannels; channel++)
+ if (_voices[channel].note == note && _voices[channel].hw_channel == ch && !_voices[channel].decay)
+ break;
+
+ if (channel == kChannels) {
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] cannot stop note %i on channel %i", note, ch);
+#endif
+ return;
+ }
+
+ instrument = _bank.instruments[_voices[channel].instrument];
+
+ /* Start the envelope phases for note-off if looping is on and envelope is enabled */
+ if ((instrument->mode & kModeLoop) && (instrument->envelope[0].length != 0))
+ setEnvelope(&_voices[channel], instrument->envelope, 2);
+}
+
+void MidiDriver_Amiga::startNote(int ch, int note, int velocity) {
+ Instrument *instrument;
+ int channel;
+
+ if (_channels[ch].instrument < 0 || _channels[ch].instrument > 255) {
+ warning("[sfx:seq:amiga] invalid instrument %i on channel %i", _channels[ch].instrument, ch);
+ return;
+ }
+
+ instrument = _bank.instruments[_channels[ch].instrument];
+
+ if (!instrument) {
+ warning("[sfx:seq:amiga] instrument %i does not exist", _channels[ch].instrument);
+ return;
+ }
+
+ for (channel = 0; channel < kChannels; channel++)
+ if (_voices[channel].note == -1)
+ break;
+
+ if (channel == kChannels) {
+ warning("[sfx:seq:amiga] could not find a free channel");
+ return;
+ }
+
+ stopChannel(ch);
+
+ if (instrument->mode & kModePitch) {
+ int fnote = note + instrument->transpose;
+
+ if (fnote < 0 || fnote > 127) {
+ warning("[sfx:seq:amiga] illegal note %i\n", fnote);
+ return;
+ }
+
+ /* Compute rate for note */
+ _voices[channel].rate = doubleToFrac(freq_table[fnote] / (double) _frequency);
+ } else
+ _voices[channel].rate = doubleToFrac(kBaseFreq / (double) _frequency);
+
+ _voices[channel].instrument = _channels[ch].instrument;
+ _voices[channel].note = note;
+ _voices[channel].note_velocity = velocity;
+
+ if ((instrument->mode & kModeLoop) && (instrument->envelope[0].length != 0))
+ setEnvelope(&_voices[channel], instrument->envelope, 0);
+ else {
+ _voices[channel].velocity = 64;
+ _voices[channel].envelope_samples = -1;
+ }
+
+ _voices[channel].offset = 0;
+ _voices[channel].hw_channel = ch;
+ _voices[channel].decay = 0;
+ _voices[channel].looping = 0;
+}
+
+MidiDriver_Amiga::Instrument *MidiDriver_Amiga::readInstrument(Common::File &file, int *id) {
+ Instrument *instrument;
+ byte header[61];
+ int size;
+ int seg_size[3];
+ int loop_offset;
+ int i;
+
+ if (file.read(header, 61) < 61) {
+ warning("[sfx:seq:amiga] failed to read instrument header");
+ return NULL;
+ }
+
+ instrument = new Instrument;
+
+ seg_size[0] = READ_BE_UINT16(header + 35) * 2;
+ seg_size[1] = READ_BE_UINT16(header + 41) * 2;
+ seg_size[2] = READ_BE_UINT16(header + 47) * 2;
+
+ instrument->mode = header[33];
+ instrument->transpose = (int8) header[34];
+ for (i = 0; i < 4; i++) {
+ int length = (int8) header[49 + i];
+
+ if (length == 0 && i > 0)
+ length = 256;
+
+ instrument->envelope[i].length = length * _frequency / 60;
+ instrument->envelope[i].delta = (int8)header[53 + i];
+ instrument->envelope[i].target = header[57 + i];
+ }
+ /* Final target must be 0 */
+ instrument->envelope[3].target = 0;
+
+ loop_offset = READ_BE_UINT32(header + 37) & ~1;
+ size = seg_size[0] + seg_size[1] + seg_size[2];
+
+ *id = READ_BE_UINT16(header);
+
+ strncpy(instrument->name, (char *) header + 2, 29);
+ instrument->name[29] = 0;
+#ifdef DEBUG
+ printf("[sfx:seq:amiga] Reading instrument %i: \"%s\" (%i bytes)\n",
+ *id, instrument->name, size);
+ printf(" Mode: %02x\n", instrument->mode);
+ printf(" Looping: %s\n", instrument->mode & kModeLoop ? "on" : "off");
+ printf(" Pitch changes: %s\n", instrument->mode & kModePitch ? "on" : "off");
+ printf(" Segment sizes: %i %i %i\n", seg_size[0], seg_size[1], seg_size[2]);
+ printf(" Segment offsets: 0 %i %i\n", loop_offset, read_int32(header + 43));
+#endif
+ instrument->samples = (int8 *) malloc(size + 1);
+ if (file.read(instrument->samples, size) < (unsigned int)size) {
+ warning("[sfx:seq:amiga] failed to read instrument samples");
+ free(instrument->samples);
+ delete instrument;
+ return NULL;
+ }
+
+ if (instrument->mode & kModeLoop) {
+ if (loop_offset + seg_size[1] > size) {
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] looping samples extend %i bytes past end of sample block",
+ loop_offset + seg_size[1] - size);
+#endif
+ seg_size[1] = size - loop_offset;
+ }
+
+ if (seg_size[1] < 0) {
+ warning("[sfx:seq:amiga] invalid looping point");
+ free(instrument->samples);
+ delete instrument;
+ return NULL;
+ }
+
+ instrument->size = seg_size[0];
+ instrument->loop_size = seg_size[1];
+
+ instrument->loop = (int8*)malloc(instrument->loop_size + 1);
+ memcpy(instrument->loop, instrument->samples + loop_offset, instrument->loop_size);
+
+ instrument->samples[instrument->size] = instrument->loop[0];
+ instrument->loop[instrument->loop_size] = instrument->loop[0];
+ } else {
+ instrument->loop = NULL;
+ instrument->size = size;
+ instrument->samples[instrument->size] = 0;
+ }
+
+ return instrument;
+}
+
+uint32 MidiDriver_Amiga::property(int prop, uint32 param) {
+ switch(prop) {
+ case MIDI_PROP_MASTER_VOLUME:
+ if (param != 0xffff)
+ _masterVolume = param;
+ return _masterVolume;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int MidiDriver_Amiga::open() {
+ _frequency = _mixer->getOutputRate();
+ _envDecay.length = _frequency / (32 * 64);
+ _envDecay.delta = 1;
+ _envDecay.target = 0;
+
+ Common::File file;
+ byte header[40];
+
+ if (!file.open("bank.001")) {
+ warning("[sfx:seq:amiga] file bank.001 not found");
+ return Common::kUnknownError;
+ }
+
+ if (file.read(header, 40) < 40) {
+ warning("[sfx:seq:amiga] failed to read header of file bank.001");
+ return Common::kUnknownError;
+ }
+
+ for (uint i = 0; i < 256; i++)
+ _bank.instruments[i] = NULL;
+
+ for (uint i = 0; i < kChannels; i++) {
+ _voices[i].note = -1;
+ _voices[i].hw_channel = 0;
+ }
+
+ for (uint i = 0; i < MIDI_CHANNELS; i++) {
+ _channels[i].instrument = -1;
+ _channels[i].volume = 127;
+ _channels[i].pan = (i % 4 == 0 || i % 4 == 3 ? kPanLeft : kPanRight);
+ }
+
+ _bank.size = READ_BE_UINT16(header + 38);
+ strncpy(_bank.name, (char *) header + 8, 29);
+ _bank.name[29] = 0;
+#ifdef DEBUG
+ printf("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", _bank.size, _bank.name);
+#endif
+
+ for (uint i = 0; i < _bank.size; i++) {
+ int id;
+ Instrument *instrument = readInstrument(file, &id);
+
+ if (!instrument) {
+ warning("[sfx:seq:amiga] failed to read bank.001");
+ return Common::kUnknownError;
+ }
+
+ if (id < 0 || id > 255) {
+ warning("[sfx:seq:amiga] Error: instrument ID out of bounds");
+ return Common::kUnknownError;
+ }
+
+ _bank.instruments[id] = instrument;
+ }
+
+ MidiDriver_Emulated::open();
+
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, false);
+
+ return Common::kNoError;
+}
+
+void MidiDriver_Amiga::close() {
+ _mixer->stopHandle(_mixerSoundHandle);
+
+ for (uint i = 0; i < _bank.size; i++) {
+ if (_bank.instruments[i]) {
+ if (_bank.instruments[i]->loop)
+ free(_bank.instruments[i]->loop);
+ free(_bank.instruments[i]->samples);
+ delete _bank.instruments[i];
+ }
+ }
+}
+
+void MidiDriver_Amiga::playSwitch(bool play) {
+ _playSwitch = play;
+}
+
+void MidiDriver_Amiga::setVolume(byte volume_) {
+ _masterVolume = volume_;
+}
+
+void MidiDriver_Amiga::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80:
+ stopNote(channel, op1);
+ break;
+ case 0x90:
+ if (op2 > 0)
+ startNote(channel, op1, op2);
+ else
+ stopNote(channel, op1);
+ break;
+ case 0xb0:
+ switch (op1) {
+ case 0x07:
+ _channels[channel].volume = op2;
+ break;
+ case 0x0a:
+#ifdef DEBUG
+ warning("[sfx:seq:amiga] ignoring pan 0x%02x event for channel %i", op2, channel);
+#endif
+ break;
+ case 0x7b:
+ stopChannel(channel);
+ break;
+ default:
+ warning("[sfx:seq:amiga] unknown control event 0x%02x", op1);
+ }
+ break;
+ case 0xc0:
+ changeInstrument(channel, op1);
+ break;
+ default:
+ warning("[sfx:seq:amiga] unknown event %02x", command);
+ }
+}
+
+void MidiDriver_Amiga::generateSamples(int16 *data, int len) {
+ if (len == 0)
+ return;
+
+ int16 *buffers = (int16*)malloc(len * 2 * kChannels);
+
+ memset(buffers, 0, len * 2 * kChannels);
+
+ /* Generate samples for all notes */
+ for (int i = 0; i < kChannels; i++)
+ if (_voices[i].note >= 0)
+ playInstrument(buffers + i * len, &_voices[i], len);
+
+ if (isStereo()) {
+ for (int j = 0; j < len; j++) {
+ int mixedl = 0, mixedr = 0;
+
+ /* Mix and pan */
+ for (int i = 0; i < kChannels; i++) {
+ mixedl += buffers[i * len + j] * (256 - _channels[_voices[i].hw_channel].pan);
+ mixedr += buffers[i * len + j] * _channels[_voices[i].hw_channel].pan;
+ }
+
+ /* Adjust volume */
+ data[2 * j] = mixedl * _masterVolume >> 13;
+ data[2 * j + 1] = mixedr * _masterVolume >> 13;
+ }
+ } else {
+ for (int j = 0; j < len; j++) {
+ int mixed = 0;
+
+ /* Mix */
+ for (int i = 0; i < kChannels; i++)
+ mixed += buffers[i * len + j];
+
+ /* Adjust volume */
+ data[j] = mixed * _masterVolume >> 6;
+ }
+ }
+
+ free(buffers);
+}
+
+class MidiPlayer_Amiga : public MidiPlayer {
+public:
+ MidiPlayer_Amiga() { _driver = new MidiDriver_Amiga(g_system->getMixer()); }
+ int getPlayMask(SciVersion soundVersion);
+ int getPolyphony() const { return MidiDriver_Amiga::kVoices; }
+ bool hasRhythmChannel() const { return false; }
+ void setVolume(byte volume) { static_cast<MidiDriver_Amiga *>(_driver)->setVolume(volume); }
+ void playSwitch(bool play) { static_cast<MidiDriver_Amiga *>(_driver)->playSwitch(play); }
+ void loadInstrument(int idx, byte *data);
+};
+
+MidiPlayer *MidiPlayer_Amiga_create() {
+ return new MidiPlayer_Amiga();
+}
+
+int MidiPlayer_Amiga::getPlayMask(SciVersion soundVersion) {
+ if (soundVersion == SCI_VERSION_0_EARLY)
+ error("No amiga support for sci0early");
+
+ return 0x40;
+}
+
+} // End of namespace Sci