/* 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 3 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, see .
*
*/
/*
* Raw output support by Michael Pearce
* Alsa support by Nicolas Noble copied from
* both the QuickTime support and (vkeybd https://web.archive.org/web/20070629122111/http://www.alsa-project.org/~iwai/alsa.html)
*/
// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/scummsys.h"
#if defined(USE_SEQ_MIDI)
#include "common/error.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/musicplugin.h"
#include "audio/mpu401.h"
#include
#include
#include
#include
#include
////////////////////////////////////////
//
// Unix dev/sequencer driver
//
////////////////////////////////////////
#define SEQ_MIDIPUTC 5
class MidiDriver_SEQ : public MidiDriver_MPU401 {
public:
MidiDriver_SEQ(int port, bool isSynth);
int open() override;
bool isOpen() const override { return _isOpen; }
void close() override;
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length) override;
static const char *getDeviceName();
private:
bool _isSynth;
bool _isOpen;
int _device;
int _port;
};
MidiDriver_SEQ::MidiDriver_SEQ(int port, bool isSynth) {
_isOpen = false;
_isSynth = isSynth;
_device = -1;
_port = port;
}
int MidiDriver_SEQ::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
const char *deviceName = getDeviceName();
_isOpen = true;
_device = ::open(deviceName, O_RDWR, 0);
if (_device < 0) {
warning("Cannot open rawmidi device %s - using /dev/null (no music will be heard)", deviceName);
_device = (::open(("/dev/null"), O_RDWR, 0));
if (_device < 0)
error("Cannot open /dev/null to dump midi output");
}
return 0;
}
void MidiDriver_SEQ::close() {
MidiDriver_MPU401::close();
if (_isOpen)
::close(_device);
_isOpen = false;
}
void MidiDriver_SEQ::send(uint32 b) {
midiDriverCommonSend(b);
unsigned char buf[256];
int position = 0;
if (_isSynth) {
switch (b & 0xf0) {
case 0x80:
case 0x90:
case 0xa0:
buf[position++] = EV_CHN_VOICE;
buf[position++] = _port;
buf[position++] = (unsigned char)(b & 0xf0);
buf[position++] = (unsigned char)(b & 0x0f);
buf[position++] = (unsigned char)((b >> 8) & 0xff);
buf[position++] = (unsigned char)((b >> 16) & 0xff);
buf[position++] = 0;
buf[position++] = 0;
break;
case 0xc0: // Program change
case 0xd0: // Channel pressure
buf[position++] = EV_CHN_COMMON;
buf[position++] = _port;
buf[position++] = (unsigned char)(b & 0xf0);
buf[position++] = (unsigned char)(b & 0x0f);
buf[position++] = (unsigned char)((b >> 8) & 0xff);
buf[position++] = 0;
buf[position++] = 0;
buf[position++] = 0;
break;
case 0xb0: // Control change
buf[position++] = EV_CHN_COMMON;
buf[position++] = _port;
buf[position++] = (unsigned char)(b & 0xf0);
buf[position++] = (unsigned char)(b & 0x0f);
buf[position++] = (unsigned char)((b >> 8) & 0xff);
buf[position++] = 0;
// TODO/FIXME?: Main volume control in the soundcard.h macros is value*16383/100, expression is value*128, and pan is (value+128)/2
// Not sure how those translate to the scales we're using here.
*reinterpret_cast(buf + position) = static_cast((b >> 16) & 0xffff);
position += 2;
break;
case 0xe0: // Pitch bend
buf[position++] = EV_CHN_COMMON;
buf[position++] = _port;
buf[position++] = (unsigned char)(b & 0xf0);
buf[position++] = (unsigned char)(b & 0x0f);
buf[position++] = 0;
buf[position++] = 0;
*reinterpret_cast(buf + position) = static_cast((b >> 8) & 0xffff);
position += 2;
break;
default:
warning("MidiDriver_SEQ::send: unknown: %08x", (int)b);
break;
}
} else {
switch (b & 0xF0) {
case 0x80:
case 0x90:
case 0xA0:
case 0xB0:
case 0xE0:
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)b;
buf[position++] = _port;
buf[position++] = 0;
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)((b >> 8) & 0x7F);
buf[position++] = _port;
buf[position++] = 0;
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)((b >> 16) & 0x7F);
buf[position++] = _port;
buf[position++] = 0;
break;
case 0xC0:
case 0xD0:
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)b;
buf[position++] = _port;
buf[position++] = 0;
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)((b >> 8) & 0x7F);
buf[position++] = _port;
buf[position++] = 0;
break;
default:
warning("MidiDriver_SEQ::send: unknown: %08x", (int)b);
break;
}
}
if (write(_device, buf, position) == -1)
warning("MidiDriver_SEQ::send: write failed (%s)", strerror(errno));
}
void MidiDriver_SEQ::sysEx(const byte *msg, uint16 length) {
unsigned char buf [266*4];
int position = 0;
const byte *chr = msg;
assert(length + 2 <= 266);
midiDriverCommonSysEx(msg, length);
if (_isSynth) {
int chunksRequired = (length + 7) / 6;
assert(chunksRequired * 8 <= static_cast(sizeof(buf)));
for (int i = 0; i < chunksRequired; i++) {
int chunkStart = i * 6;
buf[position++] = EV_SYSEX;
buf[position++] = _port;
for (int j = 0; j < 6; j++) {
int pos = chunkStart + j - 1;
if (pos < 0)
buf[position++] = 0xf0;
else if (pos < length)
buf[position++] = msg[pos];
else if (pos == length)
buf[position++] = 0x7f;
else
buf[position++] = 0xff;
}
}
} else {
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = 0xF0;
buf[position++] = _port;
buf[position++] = 0;
for (; length; --length, ++chr) {
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = (unsigned char)*chr & 0x7F;
buf[position++] = _port;
buf[position++] = 0;
}
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = 0xF7;
buf[position++] = _port;
buf[position++] = 0;
}
if (write(_device, buf, position) == -1)
warning("MidiDriver_SEQ::send: write failed (%s)", strerror(errno));
}
const char *MidiDriver_SEQ::getDeviceName() {
const char *devName = getenv("SCUMMVM_MIDI");
if (devName)
return devName;
else
return "/dev/sequencer";
}
// Plugin interface
class SeqMusicPlugin : public MusicPluginObject {
public:
const char *getName() const {
return "SEQ";
}
const char *getId() const {
return "seq";
}
MusicDevices getDevices() const;
Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
bool checkDevice(MidiDriver::DeviceHandle hdl, int checkFlags, bool quiet) const;
private:
void addMidiDevices(int deviceFD, MusicDevices &devices, Common::Array *portIDs) const;
void addSynthDevices(int deviceFD, MusicDevices &devices, Common::Array *portIDs) const;
};
MusicDevices SeqMusicPlugin::getDevices() const {
MusicDevices devices;
int deviceFD = ::open(MidiDriver_SEQ::getDeviceName(), O_RDWR, 0);
if (deviceFD >= 0) {
addMidiDevices(deviceFD, devices, nullptr);
addSynthDevices(deviceFD, devices, nullptr);
::close(deviceFD);
}
return devices;
}
void SeqMusicPlugin::addMidiDevices(int deviceFD, MusicDevices &devices, Common::Array *portIDs) const {
int midiDeviceCount = 0;
if (ioctl(deviceFD, SNDCTL_SEQ_NRMIDIS, &midiDeviceCount) == 0) {
for (int i = 0; i < midiDeviceCount; i++) {
midi_info midiInfo;
midiInfo.device = i;
if (ioctl(deviceFD, SNDCTL_MIDI_INFO, &midiInfo) == 0) {
devices.push_back(MusicDevice(this, midiInfo.name, MT_GM)); // dev_type is unimplemented so we just assume GM
if (portIDs)
portIDs->push_back(i);
}
}
}
}
void SeqMusicPlugin::addSynthDevices(int deviceFD, MusicDevices &devices, Common::Array *portIDs) const {
int synthDeviceCount = 0;
if (ioctl(deviceFD, SNDCTL_SEQ_NRSYNTHS, &synthDeviceCount) == 0) {
for (int i = 0; i < synthDeviceCount; i++) {
synth_info synthInfo;
synthInfo.device = i;
if (ioctl(deviceFD, SNDCTL_SYNTH_ID, &synthInfo) == 0) {
MusicType musicType = MT_GM;
if (synthInfo.synth_type == SYNTH_TYPE_FM)
musicType = MT_ADLIB;
devices.push_back(MusicDevice(this, synthInfo.name, musicType)); // dev_type is unimplemented so we just assume GM
if (portIDs)
portIDs->push_back(i);
}
}
}
}
Common::Error SeqMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle dev) const {
int port = 0;
bool isSynth = false;
bool found = false;
if (dev) {
Common::String deviceIDString = MidiDriver::getDeviceString(dev, MidiDriver::kDeviceId);
MusicDevices devices;
Common::Array ports;
int firstSynthIndex = 0;
int deviceFD = ::open(MidiDriver_SEQ::getDeviceName(), O_RDONLY, 0);
if (deviceFD >= 0) {
addMidiDevices(deviceFD, devices, &ports);
firstSynthIndex = static_cast(ports.size());
addSynthDevices(deviceFD, devices, &ports);
::close(deviceFD);
} else {
warning("Device enumeration failed when creating device");
}
int devIndex = 0;
for (auto &d : devices) {
if (d.getCompleteId().equals(deviceIDString)) {
found = true;
isSynth = (devIndex >= firstSynthIndex);
port = ports[devIndex];
break;
}
devIndex++;
}
}
if (found) {
*mididriver = new MidiDriver_SEQ(port, isSynth);
return Common::kNoError;
}
return Common::kAudioDeviceInitFailed;
}
bool SeqMusicPlugin::checkDevice(MidiDriver::DeviceHandle hdl, int checkFlags, bool quiet) const {
return true;
}
//#if PLUGIN_ENABLED_DYNAMIC(SEQ)
//REGISTER_PLUGIN_DYNAMIC(SEQ, PLUGIN_TYPE_MUSIC, SeqMusicPlugin);
//#else
REGISTER_PLUGIN_STATIC(SEQ, PLUGIN_TYPE_MUSIC, SeqMusicPlugin);
//#endif
#endif