Logo Search packages:      
Sourcecode: scummvm version File versions

quicktime.cpp

/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-13-1/backends/midi/quicktime.cpp $
 * $Id: quicktime.cpp 34906 2008-11-05 17:24:56Z fingolfin $
 */

#if defined(MACOSX) || defined(macintosh)

// HACK to disable deprecated warnings under Mac OS X 10.5.
// Apple depracted the complete QuickTime Music/MIDI API.
// Apps are supposed to use CoreAudio & CoreMIDI. We do support
// those, but while QT Midi support is still around, there is no
// reason to disable this driver. If they really ditch the API in 10.6,
// we can still release binaries with this driver disabled/removed.
#include <AvailabilityMacros.h>
#undef DEPRECATED_ATTRIBUTE
#define DEPRECATED_ATTRIBUTE



#include "common/endian.h"
#include "common/util.h"
#include "sound/musicplugin.h"
#include "sound/mpu401.h"

#if defined(MACOSX)
#include <QuickTime/QuickTimeComponents.h>
#include <QuickTime/QuickTimeMusic.h>
#else
#include <QuickTimeComponents.h>
#include <QuickTimeMusic.h>
#endif


// FIXME: the following disables reverb support in the QuickTime / CoreAudio
// midi backends. For some reasons, reverb will suck away a *lot* of CPU time.
// Until we know for sure what is causing this and if there is a better way to
// fix the problem, we just disable all reverb for these backends.
#define COREAUDIO_REVERB_HACK


/* QuickTime MIDI driver
 * Original QuickTime support by Florent Boudet <flobo@ifrance.com>
 * Modified by Max Horn
 */
class MidiDriver_QT : public MidiDriver_MPU401 {
public:
      MidiDriver_QT();

      int open();
      void close();
      void send(uint32 b);
      void setPitchBendRange (byte channel, uint range);

private:
      NoteAllocator qtNoteAllocator;
      NoteChannel qtNoteChannel[16];
      NoteRequest simpleNoteRequest;

      // Pitch bend tracking. Necessary since QTMA handles
      // pitch bending so differently from MPU401.
      uint16 _pitchbend [16];
      byte _pitchbend_range [16];

      void dispose();
};

MidiDriver_QT::MidiDriver_QT() {
      qtNoteAllocator = 0;
      for (int i = 0; i < 16; i++)
            qtNoteChannel[i] = 0;
}

int MidiDriver_QT::open() {
      ComponentResult qtErr = noErr;

      if (qtNoteAllocator != 0)
            return MERR_ALREADY_OPEN;

      qtNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType, 0);
      if (qtNoteAllocator == 0)
            goto bail;

      simpleNoteRequest.info.flags = 0;
      WRITE_BE_UINT16(& simpleNoteRequest.info.polyphony, 11);        // simultaneous tones
      WRITE_BE_UINT16(& simpleNoteRequest.info.typicalPolyphony, 0x00010000);

      qtErr = NAStuffToneDescription(qtNoteAllocator, 1, &simpleNoteRequest.tone);
      if (qtErr != noErr)
            goto bail;

      for (int i = 0; i < 16; i++) {
            qtErr = NANewNoteChannel(qtNoteAllocator, &simpleNoteRequest, &(qtNoteChannel[i]));
            if ((qtErr != noErr) || (qtNoteChannel[i] == 0))
                  goto bail;

            qtErr = NAResetNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
            if (qtErr != noErr)
                  goto bail;

            // Channel 10 (i.e. index 9) is the drum channel. Set it up as such.
            // All other channels default to piano.
            qtErr = NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[i], (i == 9 ? kFirstDrumkit : kFirstGMInstrument) + 1);
            if (qtErr != noErr)
                  goto bail;
      }
      return 0;

bail:
      error("Init QT failed %x %x %d\n", (int)qtNoteAllocator, (int)qtNoteChannel, (int)qtErr);

      dispose();

      return MERR_DEVICE_NOT_AVAILABLE;
}

void MidiDriver_QT::close() {
      MidiDriver_MPU401::close();
      dispose();
}

void MidiDriver_QT::send(uint32 b) {
      MusicMIDIPacket midPacket;
      unsigned char *midiCmd = midPacket.data;
      midPacket.length = 3;
      midiCmd[3] = (b & 0xFF000000) >> 24;
      midiCmd[2] = (b & 0x00FF0000) >> 16;
      midiCmd[1] = (b & 0x0000FF00) >> 8;
      midiCmd[0] = (b & 0x000000FF);

      unsigned char chanID = midiCmd[0] & 0x0F;
      switch (midiCmd[0] & 0xF0) {
      case 0x80:                                                        // Note off
            NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], 0);
            break;

      case 0x90:                                                        // Note on
            NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1], midiCmd[2]);
            break;

      case 0xB0:                                                        // Effect
            switch (midiCmd[1]) {
            case 0x01:                                                  // Modulation
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerModulationWheel, midiCmd[2] << 8);
                  break;

            case 0x07:                                                  // Volume
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerVolume, midiCmd[2] << 8);
                  break;

            case 0x0A:                                                  // Pan
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPan, (midiCmd[2] << 1) + 256);
                  break;

            case 0x40:                                                  // Sustain on/off
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerSustain, midiCmd[2]);
                  break;

            case 0x5b:                                                  // ext effect depth
#if !defined(COREAUDIO_REVERB_HACK)
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerReverb, midiCmd[2] << 8);
#endif
                  break;

            case 0x5d:                                                  // chorus depth
                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerChorus, midiCmd[2] << 8);
                  break;

            case 0x7b:                                                  // mode message all notes off
                  // FIXME: For unknown reasons, the following code causes weird
                  // troubles. In particular, in the Sam&Max intro, all channel are
                  // sent this event. As a result, not only does the music stop - it
                  // also never resumes!!! This is very odd.
/*                for (int i = 0; i < 128; i++)
                        NAPlayNote(qtNoteAllocator, qtNoteChannel[chanID], i, 0);
*/
                  break;
            case 0x64:
            case 0x65:
            case 0x06:
            case 0x26:
                  // pitch bend changes - ignore those for now
                  break;

            case 0x12:  // Occurs in Scumm games
            case 0x77:  // Occurs in Simon2
            case 0x79:  // Occurs in Simon1
                  // What are these ?!? Ignore it for now
                  break;

            default:
                  warning("Unknown MIDI effect: %08x", (int)b);
                  break;
            }
            break;

      case 0xC0:                                                        // Program change
            NASetInstrumentNumber(qtNoteAllocator, qtNoteChannel[chanID], midiCmd[1] + (chanID == 9 ? kFirstDrumkit : kFirstGMInstrument));
            break;

      case 0xE0:{                                                 // Pitch bend
                  // QuickTime specifies pitchbend in semitones, using 8.8 fixed point values;
                  // but iMuse sends us the pitch bend data as 0-16383. which has to be mapped
                  // to +/- 12 semitones. Based on this, we first center the input data, then
                  // multiply it by a factor. If all was right, the factor would be 3/8, but for
                  // mysterious reasons the actual factor we have to use is more like 1/32 or 3/64.
                  // Maybe the QT docs are right, and
                  _pitchbend[chanID] = ((uint16) midiCmd[1] | (uint16) (midiCmd[2] << 7));
                  long theBend = ((long) _pitchbend[chanID] - 0x2000) * _pitchbend_range[chanID] / 32;

                  NASetController(qtNoteAllocator, qtNoteChannel[chanID], kControllerPitchBend, theBend);
            }
            break;

      default:
            error("Unknown Command: %08x", (int)b);
            NASendMIDI(qtNoteAllocator, qtNoteChannel[chanID], &midPacket);
            break;
      }
}

void MidiDriver_QT::setPitchBendRange (byte channel, uint range) {
      if (_pitchbend_range[channel] == range)
            return;
      _pitchbend_range[channel] = range;

      long theBend = _pitchbend[channel];
      theBend = (theBend - 0x2000) * range / 32;
      NASetController(qtNoteAllocator, qtNoteChannel[channel], kControllerPitchBend, theBend);
}

void MidiDriver_QT::dispose() {
      for (int i = 0; i < 16; i++) {
            if (qtNoteChannel[i] != 0)
                  NADisposeNoteChannel(qtNoteAllocator, qtNoteChannel[i]);
            qtNoteChannel[i] = 0;
      }

      if (qtNoteAllocator != 0) {
            CloseComponent(qtNoteAllocator);
            qtNoteAllocator = 0;
      }
}


// Plugin interface

class QuickTimeMusicPlugin : public MusicPluginObject {
public:
      const char *getName() const {
            return "QuickTime";
      }

      const char *getId() const {
            return "qt";
      }

      MusicDevices getDevices() const;
      Common::Error createInstance(Audio::Mixer *mixer, MidiDriver **mididriver) const;
};

MusicDevices QuickTimeMusicPlugin::getDevices() const {
      MusicDevices devices;
      // TODO: Return a different music type depending on the configuration
      // TODO: List the available devices
      devices.push_back(MusicDevice(this, "", MT_GM));
      return devices;
}

Common::Error QuickTimeMusicPlugin::createInstance(Audio::Mixer *mixer, MidiDriver **mididriver) const {
      *mididriver = new MidiDriver_QT();

      return Common::kNoError;
}

MidiDriver *MidiDriver_QT_create(Audio::Mixer *mixer) {
      MidiDriver *mididriver;

      QuickTimeMusicPlugin p;
      p.createInstance(mixer, &mididriver);

      return mididriver;
}

//#if PLUGIN_ENABLED_DYNAMIC(QUICKTIME)
      //REGISTER_PLUGIN_DYNAMIC(QUICKTIME, PLUGIN_TYPE_MUSIC, QuickTimeMusicPlugin);
//#else
      REGISTER_PLUGIN_STATIC(QUICKTIME, PLUGIN_TYPE_MUSIC, QuickTimeMusicPlugin);
//#endif

#endif // MACOSX || macintosh

Generated by  Doxygen 1.6.0   Back to index