aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/sound_midi.cpp
diff options
context:
space:
mode:
authorEugene Sandulenko2010-06-15 10:34:45 +0000
committerEugene Sandulenko2010-06-15 10:34:45 +0000
commit0a1a9194064a96673b45d856f06dbb689838d746 (patch)
tree6f191ae868140ca41de56cf3b2e55bcd132d44cf /engines/agi/sound_midi.cpp
parent024f49213781a34c9d18a9580589e088062cec33 (diff)
downloadscummvm-rg350-0a1a9194064a96673b45d856f06dbb689838d746.tar.gz
scummvm-rg350-0a1a9194064a96673b45d856f06dbb689838d746.tar.bz2
scummvm-rg350-0a1a9194064a96673b45d856f06dbb689838d746.zip
AGI: Implement FR #1913900.
FR #1913900: "AGI: MIDI support". Currently it gets selected when Adlib is chosen. Finding a better way to do it is a todo. Also default instrument mapping is plain. Based on original code by Jens. Christian Restemeier. svn-id: r49751
Diffstat (limited to 'engines/agi/sound_midi.cpp')
-rwxr-xr-xengines/agi/sound_midi.cpp340
1 files changed, 340 insertions, 0 deletions
diff --git a/engines/agi/sound_midi.cpp b/engines/agi/sound_midi.cpp
new file mode 100755
index 0000000000..b31a4228fb
--- /dev/null
+++ b/engines/agi/sound_midi.cpp
@@ -0,0 +1,340 @@
+/* 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$
+ *
+ */
+
+// Code is based on:
+//
+// A very simple program, that converts an AGI-song into a MIDI-song.
+// Feel free to use it for anything.
+//
+// The default instrument is "piano" for all the channels, what gives
+// good results on most games. But I found, that some songs are interesting
+// with other instruments. If you want to experiment, modify the "instr"
+// array.
+//
+// Timing is not perfect, yet. It plays correct, when I use the
+// Gravis-Midiplayer, but the songs are too fast when I use playmidi on
+// Linux.
+//
+// Original program developed by Jens. Christian Restemeier
+//
+
+// MIDI and digital music class
+
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/stream.h"
+
+#include "agi/agi.h"
+
+#include "agi/sound.h"
+#include "agi/sound_midi.h"
+
+#define SPEED_FACTOR 6
+
+namespace Agi {
+
+static uint32 convertSND2MIDI(byte *snddata, byte **data);
+
+MIDISound::MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) {
+ _data = data; // Save the resource pointer
+ _len = len; // Save the resource's length
+ _type = READ_LE_UINT16(data); // Read sound resource's type
+ _isValid = (_type == AGI_SOUND_4CHN) && (_data != NULL) && (_len >= 2);
+
+ if (!_isValid) // Check for errors
+ warning("Error creating MIDI sound from resource %d (Type %d, length %d)", resnum, _type, len);
+}
+
+MusicPlayer::MusicPlayer(MidiDriver *driver, SoundMgr *manager) : _parser(0), _driver(driver), _isPlaying(false), _passThrough(false), _isGM(false), _manager(manager) {
+ memset(_channel, 0, sizeof(_channel));
+ memset(_channelVolume, 255, sizeof(_channelVolume));
+ _masterVolume = 0;
+ this->open();
+ _smfParser = MidiParser::createParser_SMF();
+ _midiMusicData = NULL;
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stop();
+ this->close();
+ _smfParser->setMidiDriver(NULL);
+ delete _smfParser;
+ delete[] _midiMusicData;
+}
+
+void MusicPlayer::setChannelVolume(int channel) {
+ int newVolume = _channelVolume[channel] * _masterVolume / 255;
+ _channel[channel]->volume(newVolume);
+}
+
+void MusicPlayer::setVolume(int volume) {
+ Common::StackLock lock(_mutex);
+
+ volume = CLIP(volume, 0, 255);
+ if (_masterVolume == volume)
+ return;
+ _masterVolume = volume;
+
+ for (int i = 0; i < 16; ++i) {
+ if (_channel[i]) {
+ setChannelVolume(i);
+ }
+ }
+}
+
+int MusicPlayer::open() {
+ // Don't ever call open without first setting the output driver!
+ if (!_driver)
+ return 255;
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ _driver->setTimerCallback(this, &onTimer);
+ return 0;
+}
+
+void MusicPlayer::close() {
+ stop();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::send(uint32 b) {
+ if (_passThrough) {
+ _driver->send(b);
+ return;
+ }
+
+ byte channel = (byte)(b & 0x0F);
+ if ((b & 0xFFF0) == 0x07B0) {
+ // Adjust volume changes by master volume
+ byte volume = (byte)((b >> 16) & 0x7F);
+ _channelVolume[channel] = volume;
+ volume = volume * _masterVolume / 255;
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) {
+ b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8;
+ }
+ else if ((b & 0xFFF0) == 0x007BB0) {
+ //Only respond to All Notes Off if this channel
+ //has currently been allocated
+ if (_channel[b & 0x0F])
+ return;
+ }
+
+ if (!_channel[channel]) {
+ _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+ // If a new channel is allocated during the playback, make sure
+ // its volume is correctly initialized.
+ if (_channel[channel])
+ setChannelVolume(channel);
+ }
+
+ if (_channel[channel])
+ _channel[channel]->send(b);
+}
+
+void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+
+ switch (type) {
+ case 0x2F: // End of Track
+ stop();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ Common::StackLock lock(music->_mutex);
+
+ if (music->_parser)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playMIDI(MIDISound *track) {
+ stop();
+
+ _isGM = true;
+
+ // Convert AGI Sound data to MIDI
+ int midiMusicSize = convertSND2MIDI(track->_data, &_midiMusicData);
+
+ if (_smfParser->loadMusic(_midiMusicData, midiMusicSize)) {
+ MidiParser *parser = _smfParser;
+ parser->setTrack(0);
+ parser->setMidiDriver(this);
+ parser->setTimerRate(getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _parser = parser;
+
+ syncVolume();
+
+ _isPlaying = true;
+ }
+}
+
+void MusicPlayer::stop() {
+ Common::StackLock lock(_mutex);
+
+ if (!_isPlaying)
+ return;
+
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+
+ if (_manager->_endflag != -1)
+ _manager->_vm->setflag(_manager->_endflag, true);
+}
+
+void MusicPlayer::pause() {
+ setVolume(-1);
+ _isPlaying = false;
+}
+
+void MusicPlayer::resume() {
+ syncVolume();
+ _isPlaying = true;
+}
+
+void MusicPlayer::syncVolume() {
+ int volume = ConfMan.getInt("music_volume");
+ if (ConfMan.getBool("mute")) {
+ volume = -1;
+ }
+ setVolume(volume);
+}
+
+/* channel / intrument setup: */
+
+/* most songs are good with this: */
+unsigned char instr[] = {0, 0, 0};
+
+/* cool for sq2:
+unsigned char instr[] = {50, 51, 19};
+*/
+
+static void writeDelta(Common::MemoryWriteStreamDynamic *st, int32 delta) {
+ int32 i;
+
+ i = delta >> 21; if (i > 0) st->writeByte((i & 127) | 128);
+ i = delta >> 14; if (i > 0) st->writeByte((i & 127) | 128);
+ i = delta >> 7; if (i > 0) st->writeByte((i & 127) | 128);
+ st->writeByte(delta & 127);
+}
+
+static uint32 convertSND2MIDI(byte *snddata, byte **data) {
+ int32 lp, ep;
+ int n;
+ double ll;
+
+ Common::MemoryWriteStreamDynamic st;
+
+ ll = log10(pow(2.0, 1.0 / 12.0));
+
+ /* Header */
+ st.write("MThd", 4);
+ st.writeUint32BE(6);
+ st.writeUint16BE(1); /* mode */
+ st.writeUint16BE(3); /* number of tracks */
+ st.writeUint16BE(192); /* ticks / quarter */
+
+ for (n = 0; n < 3; n++) {
+ uint16 start, end, pos;
+
+ st.write("MTrk", 4);
+ lp = st.pos();
+ st.writeUint32BE(0); /* chunklength */
+ writeDelta(&st, 0); /* set instrument */
+ st.writeByte(0xc0 + n);
+ st.writeByte(instr[n]);
+ start = snddata[n * 2 + 0] | (snddata[n * 2 + 1] << 8);
+ end = ((snddata[n * 2 + 2] | (snddata[n * 2 + 3] << 8))) - 5;
+
+ for (pos = start; pos < end; pos += 5) {
+ uint16 freq, dur;
+ dur = (snddata[pos + 0] | (snddata[pos + 1] << 8)) * SPEED_FACTOR;
+ freq = ((snddata[pos + 2] & 0x3F) << 4) + (snddata[pos + 3] & 0x0F);
+ if (snddata[pos + 2] > 0) {
+ double fr;
+ int note;
+ /* I don't know, what frequency equals midi note 0 ... */
+ /* This moves the song 4 octaves down: */
+ fr = (log10(111860.0 / (double)freq) / ll) - 48;
+ note = (int)floor(fr + 0.5);
+ if (note < 0) note = 0;
+ if (note > 127) note = 127;
+ /* note on */
+ writeDelta(&st, 0);
+ st.writeByte(144 + n);
+ st.writeByte(note);
+ st.writeByte(100);
+ /* note off */
+ writeDelta(&st, dur);
+ st.writeByte(128 + n);
+ st.writeByte(note);
+ st.writeByte(0);
+ } else {
+ /* note on */
+ writeDelta(&st, 0);
+ st.writeByte(144 + n);
+ st.writeByte(0);
+ st.writeByte(0);
+ /* note off */
+ writeDelta(&st, dur);
+ st.writeByte(128 + n);
+ st.writeByte(0);
+ st.writeByte(0);
+ }
+ }
+ writeDelta(&st, 0);
+ st.writeByte(0xff);
+ st.writeByte(0x2f);
+ st.writeByte(0x0);
+ ep = st.pos();
+ st.seek(lp, SEEK_SET);
+ st.writeUint32BE((ep - lp) - 4);
+ st.seek(ep, SEEK_SET);
+ }
+
+ *data = st.getData();
+
+ return st.pos();
+}
+
+} // End of namespace Agi