You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5475 lines
165 KiB
5475 lines
165 KiB
/*
|
|
Rosegarden
|
|
A sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <bownie@bownie.com>
|
|
|
|
The moral right of the authors to claim authorship of this work
|
|
has been asserted.
|
|
|
|
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. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include "misc/Debug.h"
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <algorithm>
|
|
|
|
#ifdef HAVE_ALSA
|
|
|
|
// ALSA
|
|
#include <alsa/asoundlib.h>
|
|
#include <alsa/seq_event.h>
|
|
#include <alsa/version.h>
|
|
#include <alsa/seq.h>
|
|
|
|
#include "AlsaDriver.h"
|
|
#include "AlsaPort.h"
|
|
#include "ExternalTransport.h"
|
|
#include "MappedInstrument.h"
|
|
#include "Midi.h"
|
|
#include "MappedStudio.h"
|
|
#include "misc/Strings.h"
|
|
#include "MappedCommon.h"
|
|
#include "MappedEvent.h"
|
|
#include "Audit.h"
|
|
#include "AudioPlayQueue.h"
|
|
#include "ExternalTransport.h"
|
|
|
|
#include <tqregexp.h>
|
|
#include <pthread.h>
|
|
|
|
|
|
//#define DEBUG_ALSA 1
|
|
//#define DEBUG_PROCESS_MIDI_OUT 1
|
|
//#define DEBUG_PROCESS_SOFT_SYNTH_OUT 1
|
|
//#define MTC_DEBUG 1
|
|
|
|
// This driver implements MIDI in and out via the ALSA (www.alsa-project.org)
|
|
// sequencer interface.
|
|
|
|
using std::cerr;
|
|
using std::endl;
|
|
|
|
static size_t _debug_jack_frame_count = 0;
|
|
|
|
#define AUTO_TIMER_NAME "(auto)"
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
#define FAILURE_REPORT_COUNT 256
|
|
static MappedEvent::FailureCode _failureReports[FAILURE_REPORT_COUNT];
|
|
static int _failureReportWriteIndex = 0;
|
|
static int _failureReportReadIndex = 0;
|
|
|
|
AlsaDriver::AlsaDriver(MappedStudio *studio):
|
|
SoundDriver(studio,
|
|
std::string("[ALSA library version ") +
|
|
std::string(SND_LIB_VERSION_STR) +
|
|
std::string(", module version ") +
|
|
getAlsaModuleVersionString() +
|
|
std::string(", kernel version ") +
|
|
getKernelVersionString() +
|
|
"]"),
|
|
m_client( -1),
|
|
m_inputPort( -1),
|
|
m_syncOutputPort( -1),
|
|
m_controllerPort( -1),
|
|
m_queue( -1),
|
|
m_maxClients( -1),
|
|
m_maxPorts( -1),
|
|
m_maxQueues( -1),
|
|
m_midiInputPortConnected(false),
|
|
m_midiSyncAutoConnect(false),
|
|
m_alsaPlayStartTime(0, 0),
|
|
m_alsaRecordStartTime(0, 0),
|
|
m_loopStartTime(0, 0),
|
|
m_loopEndTime(0, 0),
|
|
m_eat_mtc(0),
|
|
m_looping(false),
|
|
m_haveShutdown(false)
|
|
#ifdef HAVE_LIBJACK
|
|
, m_jackDriver(0)
|
|
#endif
|
|
, m_queueRunning(false)
|
|
, m_portCheckNeeded(false),
|
|
m_needJackStart(NeedNoJackStart),
|
|
m_doTimerChecks(false),
|
|
m_firstTimerCheck(true),
|
|
m_timerRatio(0),
|
|
m_timerRatioCalculated(false)
|
|
|
|
{
|
|
Audit audit;
|
|
audit << "Rosegarden " << VERSION << " - AlsaDriver "
|
|
<< m_name << std::endl;
|
|
}
|
|
|
|
AlsaDriver::~AlsaDriver()
|
|
{
|
|
if (!m_haveShutdown) {
|
|
std::cerr << "WARNING: AlsaDriver::shutdown() was not called before destructor, calling now" << std::endl;
|
|
shutdown();
|
|
}
|
|
}
|
|
|
|
int
|
|
AlsaDriver::checkAlsaError(int rc, const char *
|
|
#ifdef DEBUG_ALSA
|
|
message
|
|
#endif
|
|
)
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
if (rc < 0) {
|
|
std::cerr << "AlsaDriver::"
|
|
<< message
|
|
<< ": " << rc
|
|
<< " (" << snd_strerror(rc) << ")"
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::shutdown()
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::~AlsaDriver - shutting down" << std::endl;
|
|
#endif
|
|
|
|
processNotesOff(getAlsaTime(), true, true);
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
delete m_jackDriver;
|
|
m_jackDriver = 0;
|
|
#endif
|
|
|
|
if (m_midiHandle) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::shutdown - closing MIDI client" << std::endl;
|
|
#endif
|
|
|
|
checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, 0), "shutdown(): stopping queue");
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "shutdown(): drain output");
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::shutdown - stopped queue" << std::endl;
|
|
#endif
|
|
|
|
snd_seq_close(m_midiHandle);
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::shutdown - closed MIDI handle" << std::endl;
|
|
#endif
|
|
|
|
m_midiHandle = 0;
|
|
}
|
|
|
|
DataBlockRepository::clear();
|
|
|
|
m_haveShutdown = true;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setLoop(const RealTime &loopStart, const RealTime &loopEnd)
|
|
{
|
|
m_loopStartTime = loopStart;
|
|
m_loopEndTime = loopEnd;
|
|
|
|
// currently we use this simple test for looping - it might need
|
|
// to get more sophisticated in the future.
|
|
//
|
|
if (m_loopStartTime != m_loopEndTime)
|
|
m_looping = true;
|
|
else
|
|
m_looping = false;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::getSystemInfo()
|
|
{
|
|
int err;
|
|
snd_seq_system_info_t *sysinfo;
|
|
|
|
snd_seq_system_info_alloca(&sysinfo);
|
|
|
|
if ((err = snd_seq_system_info(m_midiHandle, sysinfo)) < 0) {
|
|
std::cerr << "System info error: " << snd_strerror(err)
|
|
<< std::endl;
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
m_maxQueues = 0;
|
|
m_maxClients = 0;
|
|
m_maxPorts = 0;
|
|
return ;
|
|
}
|
|
|
|
m_maxQueues = snd_seq_system_info_get_queues(sysinfo);
|
|
m_maxClients = snd_seq_system_info_get_clients(sysinfo);
|
|
m_maxPorts = snd_seq_system_info_get_ports(sysinfo);
|
|
}
|
|
|
|
void
|
|
AlsaDriver::showQueueStatus(int queue)
|
|
{
|
|
int err, idx, min, max;
|
|
snd_seq_queue_status_t *status;
|
|
|
|
snd_seq_queue_status_alloca(&status);
|
|
min = queue < 0 ? 0 : queue;
|
|
max = queue < 0 ? m_maxQueues : queue + 1;
|
|
|
|
for (idx = min; idx < max; ++idx) {
|
|
if ((err = snd_seq_get_queue_status(m_midiHandle, idx, status)) < 0) {
|
|
|
|
if (err == -ENOENT)
|
|
continue;
|
|
|
|
std::cerr << "Client " << idx << " info error: "
|
|
<< snd_strerror(err) << std::endl;
|
|
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
return ;
|
|
}
|
|
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "Queue " << snd_seq_queue_status_get_queue(status)
|
|
<< std::endl;
|
|
|
|
std::cerr << "Tick = "
|
|
<< snd_seq_queue_status_get_tick_time(status)
|
|
<< std::endl;
|
|
|
|
std::cerr << "Realtime = "
|
|
<< snd_seq_queue_status_get_real_time(status)->tv_sec
|
|
<< "."
|
|
<< snd_seq_queue_status_get_real_time(status)->tv_nsec
|
|
<< std::endl;
|
|
|
|
std::cerr << "Flags = 0x"
|
|
<< snd_seq_queue_status_get_status(status)
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::generateTimerList()
|
|
{
|
|
// Enumerate the available timers
|
|
|
|
snd_timer_t *timerHandle;
|
|
|
|
snd_timer_id_t *timerId;
|
|
snd_timer_info_t *timerInfo;
|
|
|
|
snd_timer_id_alloca(&timerId);
|
|
snd_timer_info_alloca(&timerInfo);
|
|
|
|
snd_timer_query_t *timerQuery;
|
|
char timerName[64];
|
|
|
|
m_timers.clear();
|
|
|
|
if (snd_timer_query_open(&timerQuery, "hw", 0) >= 0) {
|
|
|
|
snd_timer_id_set_class(timerId, SND_TIMER_CLASS_NONE);
|
|
|
|
while (1) {
|
|
|
|
if (snd_timer_query_next_device(timerQuery, timerId) < 0)
|
|
break;
|
|
if (snd_timer_id_get_class(timerId) < 0)
|
|
break;
|
|
|
|
AlsaTimerInfo info = {
|
|
snd_timer_id_get_class(timerId),
|
|
snd_timer_id_get_sclass(timerId),
|
|
snd_timer_id_get_card(timerId),
|
|
snd_timer_id_get_device(timerId),
|
|
snd_timer_id_get_subdevice(timerId),
|
|
"",
|
|
0
|
|
};
|
|
|
|
if (info.card < 0)
|
|
info.card = 0;
|
|
if (info.device < 0)
|
|
info.device = 0;
|
|
if (info.subdevice < 0)
|
|
info.subdevice = 0;
|
|
|
|
// std::cerr << "got timer: class " << info.clas << std::endl;
|
|
|
|
sprintf(timerName, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
|
|
info.clas, info.sclas, info.card, info.device, info.subdevice);
|
|
|
|
if (snd_timer_open(&timerHandle, timerName, SND_TIMER_OPEN_NONBLOCK) < 0) {
|
|
std::cerr << "Failed to open timer: " << timerName << std::endl;
|
|
continue;
|
|
}
|
|
|
|
if (snd_timer_info(timerHandle, timerInfo) < 0)
|
|
continue;
|
|
|
|
info.name = snd_timer_info_get_name(timerInfo);
|
|
info.resolution = snd_timer_info_get_resolution(timerInfo);
|
|
snd_timer_close(timerHandle);
|
|
|
|
// std::cerr << "adding timer: " << info.name << std::endl;
|
|
|
|
m_timers.push_back(info);
|
|
}
|
|
|
|
snd_timer_query_close(timerQuery);
|
|
}
|
|
}
|
|
|
|
|
|
std::string
|
|
AlsaDriver::getAutoTimer(bool &wantTimerChecks)
|
|
{
|
|
Audit audit;
|
|
|
|
// Look for the apparent best-choice timer.
|
|
|
|
if (m_timers.empty())
|
|
return "";
|
|
|
|
// The system RTC timer ought to be good, but it doesn't look like
|
|
// a very safe choice -- we've seen some system lockups apparently
|
|
// connected with use of this timer on 2.6 kernels. So we avoid
|
|
// using that as an auto option.
|
|
|
|
// Looks like our most reliable options for timers are, in order:
|
|
//
|
|
// 1. System timer if at 1000Hz, with timer checks (i.e. automatic
|
|
// drift correction against PCM frame count). Only available
|
|
// when JACK is running.
|
|
//
|
|
// 2. PCM playback timer currently in use by JACK (no drift, but
|
|
// suffers from jitter).
|
|
//
|
|
// 3. System timer if at 1000Hz.
|
|
//
|
|
// 4. System RTC timer.
|
|
//
|
|
// 5. System timer.
|
|
|
|
// As of Linux kernel 2.6.13 (?) the default system timer
|
|
// resolution has been reduced from 1000Hz to 250Hz, giving us
|
|
// only 4ms accuracy instead of 1ms. This may be better than the
|
|
// 10ms available from the stock 2.4 kernel, but it's not enough
|
|
// for really solid MIDI timing. If JACK is running at 44.1 or
|
|
// 48KHz with a buffer size less than 256 frames, then the PCM
|
|
// timer will give us less jitter. Even at 256 frames, it may be
|
|
// preferable in practice just because it's simpler.
|
|
|
|
// However, we can't safely choose the PCM timer over the system
|
|
// timer unless the latter has really awful resolution, because we
|
|
// don't know for certain which PCM JACK is using. We guess at
|
|
// hw:0 for the moment, which gives us a stuck timer problem if
|
|
// it's actually using something else. So if the system timer
|
|
// runs at 250Hz, we really have to choose it anyway and just give
|
|
// a warning.
|
|
|
|
bool pcmTimerAccepted = false;
|
|
wantTimerChecks = false; // for most options
|
|
|
|
bool rtcCouldBeOK = false;
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
wantTimerChecks = true;
|
|
pcmTimerAccepted = true;
|
|
}
|
|
#endif
|
|
|
|
// look for a high frequency system timer
|
|
|
|
for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
|
|
i != m_timers.end(); ++i) {
|
|
if (i->sclas != SND_TIMER_SCLASS_NONE)
|
|
continue;
|
|
if (i->clas == SND_TIMER_CLASS_GLOBAL) {
|
|
if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
|
|
long hz = 1000000000 / i->resolution;
|
|
if (hz >= 750) {
|
|
return i->name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for the system RTC timer if available. This has been
|
|
// known to hang some real-time kernels, but reports suggest that
|
|
// recent kernels are OK. Avoid if the kernel is older than
|
|
// 2.6.20 or the ALSA driver is older than 1.0.14.
|
|
|
|
if (versionIsAtLeast(getAlsaModuleVersionString(),
|
|
1, 0, 14) &&
|
|
versionIsAtLeast(getKernelVersionString(),
|
|
2, 6, 20)) {
|
|
|
|
rtcCouldBeOK = true;
|
|
|
|
for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
|
|
i != m_timers.end(); ++i) {
|
|
if (i->sclas != SND_TIMER_SCLASS_NONE) continue;
|
|
if (i->clas == SND_TIMER_CLASS_GLOBAL) {
|
|
if (i->device == SND_TIMER_GLOBAL_RTC) {
|
|
return i->name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// look for the first PCM playback timer; that's all we know about
|
|
// for now (until JACK becomes able to tell us which PCM it's on)
|
|
|
|
if (pcmTimerAccepted) {
|
|
|
|
for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
|
|
i != m_timers.end(); ++i) {
|
|
if (i->sclas != SND_TIMER_SCLASS_NONE)
|
|
continue;
|
|
if (i->clas == SND_TIMER_CLASS_PCM) {
|
|
if (i->resolution != 0) {
|
|
long hz = 1000000000 / i->resolution;
|
|
if (hz >= 750) {
|
|
wantTimerChecks = false; // pointless with PCM timer
|
|
return i->name;
|
|
} else {
|
|
audit << "PCM timer: inadequate resolution " << i->resolution << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// next look for slow, unpopular 100Hz (2.4) or 250Hz (2.6) system timer
|
|
|
|
for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
|
|
i != m_timers.end(); ++i) {
|
|
if (i->sclas != SND_TIMER_SCLASS_NONE)
|
|
continue;
|
|
if (i->clas == SND_TIMER_CLASS_GLOBAL) {
|
|
if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
|
|
audit << "Using low-resolution system timer, sending a warning" << std::endl;
|
|
if (rtcCouldBeOK) {
|
|
reportFailure(MappedEvent::WarningImpreciseTimerTryRTC);
|
|
} else {
|
|
reportFailure(MappedEvent::WarningImpreciseTimer);
|
|
}
|
|
return i->name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// falling back to something that almost certainly won't work,
|
|
// if for any reason all of the above failed
|
|
|
|
return m_timers.begin()->name;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
AlsaDriver::generatePortList(AlsaPortList *newPorts)
|
|
{
|
|
Audit audit;
|
|
AlsaPortList alsaPorts;
|
|
|
|
snd_seq_client_info_t *cinfo;
|
|
snd_seq_port_info_t *pinfo;
|
|
int client;
|
|
unsigned int writeCap = SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE;
|
|
unsigned int readCap = SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_READ;
|
|
|
|
snd_seq_client_info_alloca(&cinfo);
|
|
snd_seq_client_info_set_client(cinfo, -1);
|
|
|
|
audit << std::endl << " ALSA Client information:"
|
|
<< std::endl << std::endl;
|
|
|
|
// Get only the client ports we're interested in and store them
|
|
// for sorting and then device creation.
|
|
//
|
|
while (snd_seq_query_next_client(m_midiHandle, cinfo) >= 0) {
|
|
client = snd_seq_client_info_get_client(cinfo);
|
|
snd_seq_port_info_alloca(&pinfo);
|
|
snd_seq_port_info_set_client(pinfo, client);
|
|
snd_seq_port_info_set_port(pinfo, -1);
|
|
|
|
// Ignore ourselves and the system client
|
|
//
|
|
if (client == m_client || client == 0)
|
|
continue;
|
|
|
|
while (snd_seq_query_next_port(m_midiHandle, pinfo) >= 0) {
|
|
int client = snd_seq_port_info_get_client(pinfo);
|
|
int port = snd_seq_port_info_get_port(pinfo);
|
|
unsigned int clientType = snd_seq_client_info_get_type(cinfo);
|
|
unsigned int portType = snd_seq_port_info_get_type(pinfo);
|
|
unsigned int capability = snd_seq_port_info_get_capability(pinfo);
|
|
|
|
|
|
if ((((capability & writeCap) == writeCap) ||
|
|
((capability & readCap) == readCap)) &&
|
|
((capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0)) {
|
|
audit << " "
|
|
<< client << ","
|
|
<< port << " - ("
|
|
<< snd_seq_client_info_get_name(cinfo) << ", "
|
|
<< snd_seq_port_info_get_name(pinfo) << ")";
|
|
|
|
PortDirection direction;
|
|
|
|
if ((capability & SND_SEQ_PORT_CAP_DUPLEX) ||
|
|
((capability & SND_SEQ_PORT_CAP_WRITE) &&
|
|
(capability & SND_SEQ_PORT_CAP_READ))) {
|
|
direction = Duplex;
|
|
audit << "\t\t\t(DUPLEX)";
|
|
} else if (capability & SND_SEQ_PORT_CAP_WRITE) {
|
|
direction = WriteOnly;
|
|
audit << "\t\t(WRITE ONLY)";
|
|
} else {
|
|
direction = ReadOnly;
|
|
audit << "\t\t(READ ONLY)";
|
|
}
|
|
|
|
audit << " [ctype " << clientType << ", ptype " << portType << ", cap " << capability << "]";
|
|
|
|
// Generate a unique name using the client id
|
|
//
|
|
char portId[40];
|
|
sprintf(portId, "%d:%d ", client, port);
|
|
|
|
std::string fullClientName =
|
|
std::string(snd_seq_client_info_get_name(cinfo));
|
|
|
|
std::string fullPortName =
|
|
std::string(snd_seq_port_info_get_name(pinfo));
|
|
|
|
std::string name;
|
|
|
|
// If the first part of the client name is the same as the
|
|
// start of the port name, just use the port name. otherwise
|
|
// concatenate.
|
|
//
|
|
int firstSpace = fullClientName.find(" ");
|
|
|
|
// If no space is found then we try to match the whole string
|
|
//
|
|
if (firstSpace < 0)
|
|
firstSpace = fullClientName.length();
|
|
|
|
if (firstSpace > 0 &&
|
|
int(fullPortName.length()) >= firstSpace &&
|
|
fullPortName.substr(0, firstSpace) ==
|
|
fullClientName.substr(0, firstSpace)) {
|
|
name = portId + fullPortName;
|
|
} else {
|
|
name = portId + fullClientName + ": " + fullPortName;
|
|
}
|
|
|
|
// Sanity check for length
|
|
//
|
|
if (name.length() > 35)
|
|
name = portId + fullPortName;
|
|
|
|
if (direction == WriteOnly) {
|
|
name += " (write)";
|
|
} else if (direction == ReadOnly) {
|
|
name += " (read)";
|
|
} else if (direction == Duplex) {
|
|
name += " (duplex)";
|
|
}
|
|
|
|
AlsaPortDescription *portDescription =
|
|
new AlsaPortDescription(
|
|
Instrument::Midi,
|
|
name,
|
|
client,
|
|
port,
|
|
clientType,
|
|
portType,
|
|
capability,
|
|
direction);
|
|
|
|
if (newPorts &&
|
|
(getPortName(ClientPortPair(client, port)) == "")) {
|
|
newPorts->push_back(portDescription);
|
|
}
|
|
|
|
alsaPorts.push_back(portDescription);
|
|
|
|
audit << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
audit << std::endl;
|
|
|
|
// Ok now sort by duplexicity
|
|
//
|
|
std::sort(alsaPorts.begin(), alsaPorts.end(), AlsaPortCmp());
|
|
m_alsaPorts = alsaPorts;
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::generateInstruments()
|
|
{
|
|
// Reset these before each Instrument hunt
|
|
//
|
|
int audioCount = 0;
|
|
getAudioInstrumentNumbers(m_audioRunningId, audioCount);
|
|
m_midiRunningId = MidiInstrumentBase;
|
|
|
|
// Clear these
|
|
//
|
|
m_instruments.clear();
|
|
m_devices.clear();
|
|
m_devicePortMap.clear();
|
|
m_suspendedPortMap.clear();
|
|
|
|
AlsaPortList::iterator it = m_alsaPorts.begin();
|
|
for (; it != m_alsaPorts.end(); it++) {
|
|
if ((*it)->m_client == m_client) {
|
|
std::cerr << "(Ignoring own port " << (*it)->m_client
|
|
<< ":" << (*it)->m_port << ")" << std::endl;
|
|
continue;
|
|
} else if ((*it)->m_client == 0) {
|
|
std::cerr << "(Ignoring system port " << (*it)->m_client
|
|
<< ":" << (*it)->m_port << ")" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
if ((*it)->isWriteable()) {
|
|
MappedDevice *device = createMidiDevice(*it, MidiDevice::Play);
|
|
if (!device) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "WARNING: Failed to create play device" << std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
} else {
|
|
addInstrumentsForDevice(device);
|
|
m_devices.push_back(device);
|
|
}
|
|
}
|
|
if ((*it)->isReadable()) {
|
|
MappedDevice *device = createMidiDevice(*it, MidiDevice::Record);
|
|
if (!device) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "WARNING: Failed to create record device" << std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
} else {
|
|
m_devices.push_back(device);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_DSSI
|
|
// Create a number of soft synth Instruments
|
|
//
|
|
{
|
|
MappedInstrument *instr;
|
|
char number[100];
|
|
InstrumentId first;
|
|
int count;
|
|
getSoftSynthInstrumentNumbers(first, count);
|
|
|
|
DeviceId ssiDeviceId = getSpareDeviceId();
|
|
|
|
if (m_driverStatus & AUDIO_OK) {
|
|
for (int i = 0; i < count; ++i) {
|
|
sprintf(number, " #%d", i + 1);
|
|
std::string name = "Synth plugin" + std::string(number);
|
|
instr = new MappedInstrument(Instrument::SoftSynth,
|
|
i,
|
|
first + i,
|
|
name,
|
|
ssiDeviceId);
|
|
m_instruments.push_back(instr);
|
|
|
|
m_studio->createObject(MappedObject::AudioFader,
|
|
first + i);
|
|
}
|
|
|
|
MappedDevice *device =
|
|
new MappedDevice(ssiDeviceId,
|
|
Device::SoftSynth,
|
|
"Synth plugin",
|
|
"Soft synth connection");
|
|
m_devices.push_back(device);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
// Create a number of audio Instruments - these are just
|
|
// logical Instruments anyway and so we can create as
|
|
// many as we like and then use them as Tracks.
|
|
//
|
|
{
|
|
MappedInstrument *instr;
|
|
char number[100];
|
|
std::string audioName;
|
|
|
|
DeviceId audioDeviceId = getSpareDeviceId();
|
|
|
|
if (m_driverStatus & AUDIO_OK)
|
|
{
|
|
for (int channel = 0; channel < audioCount; ++channel) {
|
|
sprintf(number, " #%d", channel + 1);
|
|
audioName = "Audio" + std::string(number);
|
|
instr = new MappedInstrument(Instrument::Audio,
|
|
channel,
|
|
m_audioRunningId,
|
|
audioName,
|
|
audioDeviceId);
|
|
m_instruments.push_back(instr);
|
|
|
|
// Create a fader with a matching id - this is the starting
|
|
// point for all audio faders.
|
|
//
|
|
m_studio->createObject(MappedObject::AudioFader,
|
|
m_audioRunningId);
|
|
|
|
/*
|
|
std::cerr << "AlsaDriver::generateInstruments - "
|
|
<< "added audio fader (id=" << m_audioRunningId
|
|
<< ")" << std::endl;
|
|
*/
|
|
|
|
m_audioRunningId++;
|
|
}
|
|
|
|
// Create audio device
|
|
//
|
|
MappedDevice *device =
|
|
new MappedDevice(audioDeviceId,
|
|
Device::Audio,
|
|
"Audio",
|
|
"Audio connection");
|
|
m_devices.push_back(device);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
MappedDevice *
|
|
AlsaDriver::createMidiDevice(AlsaPortDescription *port,
|
|
MidiDevice::DeviceDirection reqDirection)
|
|
{
|
|
char deviceName[100];
|
|
std::string connectionName("");
|
|
Audit audit;
|
|
|
|
static int unknownCounter;
|
|
|
|
static int counters[3][2]; // [system/hardware/software][out/in]
|
|
const int UNKNOWN = -1, SYSTEM = 0, HARDWARE = 1, SOFTWARE = 2;
|
|
static const char *firstNames[4][2] = {
|
|
{ "MIDI output system device", "MIDI input system device"
|
|
},
|
|
{ "MIDI external device", "MIDI hardware input device" },
|
|
{ "MIDI software device", "MIDI software input" }
|
|
};
|
|
static const char *countedNames[4][2] = {
|
|
{ "MIDI output system device %d", "MIDI input system device %d"
|
|
},
|
|
{ "MIDI external device %d", "MIDI hardware input device %d" },
|
|
{ "MIDI software device %d", "MIDI software input %d" }
|
|
};
|
|
|
|
static int specificCounters[2];
|
|
static const char *specificNames[2] = {
|
|
"MIDI soundcard synth", "MIDI soft synth",
|
|
};
|
|
static const char *specificCountedNames[2] = {
|
|
"MIDI soundcard synth %d", "MIDI soft synth %d",
|
|
};
|
|
|
|
DeviceId deviceId = getSpareDeviceId();
|
|
|
|
if (port) {
|
|
|
|
if (reqDirection == MidiDevice::Record && !port->isReadable())
|
|
return 0;
|
|
if (reqDirection == MidiDevice::Play && !port->isWriteable())
|
|
return 0;
|
|
|
|
int category = UNKNOWN;
|
|
bool noConnect = false;
|
|
bool isSynth = false;
|
|
bool synthKnown = false;
|
|
|
|
if (port->m_client < 16) {
|
|
|
|
category = SYSTEM;
|
|
noConnect = true;
|
|
isSynth = false;
|
|
synthKnown = true;
|
|
|
|
} else {
|
|
|
|
#ifdef SND_SEQ_PORT_TYPE_HARDWARE
|
|
if (port->m_portType & SND_SEQ_PORT_TYPE_HARDWARE) {
|
|
category = HARDWARE;
|
|
}
|
|
#endif
|
|
#ifdef SND_SEQ_PORT_TYPE_SOFTWARE
|
|
if (port->m_portType & SND_SEQ_PORT_TYPE_SOFTWARE) {
|
|
category = SOFTWARE;
|
|
}
|
|
#endif
|
|
#ifdef SND_SEQ_PORT_TYPE_SYNTHESIZER
|
|
if (port->m_portType & SND_SEQ_PORT_TYPE_SYNTHESIZER) {
|
|
isSynth = true;
|
|
synthKnown = true;
|
|
}
|
|
#endif
|
|
#ifdef SND_SEQ_PORT_TYPE_APPLICATION
|
|
if (port->m_portType & SND_SEQ_PORT_TYPE_APPLICATION) {
|
|
category = SOFTWARE;
|
|
isSynth = false;
|
|
synthKnown = true;
|
|
}
|
|
#endif
|
|
|
|
if (category == UNKNOWN) {
|
|
|
|
if (port->m_client < 64) {
|
|
|
|
if (versionIsAtLeast(getAlsaModuleVersionString(),
|
|
1, 0, 11)) {
|
|
|
|
category = HARDWARE;
|
|
|
|
} else {
|
|
|
|
category = SYSTEM;
|
|
noConnect = true;
|
|
}
|
|
|
|
} else if (port->m_client < 128) {
|
|
|
|
category = HARDWARE;
|
|
|
|
} else {
|
|
|
|
category = SOFTWARE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool haveName = false;
|
|
|
|
if (!synthKnown) {
|
|
|
|
if (category != SYSTEM && reqDirection == MidiDevice::Play) {
|
|
|
|
// We assume GM/GS/XG/MT32 devices are synths.
|
|
|
|
bool isSynth = (port->m_portType &
|
|
(SND_SEQ_PORT_TYPE_MIDI_GM |
|
|
SND_SEQ_PORT_TYPE_MIDI_GS |
|
|
SND_SEQ_PORT_TYPE_MIDI_XG |
|
|
SND_SEQ_PORT_TYPE_MIDI_MT32));
|
|
|
|
if (!isSynth &&
|
|
(port->m_name.find("ynth") < port->m_name.length()))
|
|
isSynth = true;
|
|
if (!isSynth &&
|
|
(port->m_name.find("nstrument") < port->m_name.length()))
|
|
isSynth = true;
|
|
if (!isSynth &&
|
|
(port->m_name.find("VSTi") < port->m_name.length()))
|
|
isSynth = true;
|
|
|
|
} else {
|
|
isSynth = false;
|
|
}
|
|
}
|
|
|
|
if (isSynth) {
|
|
int clientType = (category == SOFTWARE) ? 1 : 0;
|
|
if (specificCounters[clientType] == 0) {
|
|
sprintf(deviceName, specificNames[clientType]);
|
|
++specificCounters[clientType];
|
|
} else {
|
|
sprintf(deviceName,
|
|
specificCountedNames[clientType],
|
|
++specificCounters[clientType]);
|
|
}
|
|
haveName = true;
|
|
}
|
|
|
|
if (!haveName) {
|
|
if (counters[category][reqDirection] == 0) {
|
|
sprintf(deviceName, firstNames[category][reqDirection]);
|
|
++counters[category][reqDirection];
|
|
} else {
|
|
sprintf(deviceName,
|
|
countedNames[category][reqDirection],
|
|
++counters[category][reqDirection]);
|
|
}
|
|
}
|
|
|
|
if (!noConnect) {
|
|
m_devicePortMap[deviceId] = ClientPortPair(port->m_client,
|
|
port->m_port);
|
|
connectionName = port->m_name;
|
|
}
|
|
|
|
audit << "Creating device " << deviceId << " in "
|
|
<< (reqDirection == MidiDevice::Play ? "Play" : "Record")
|
|
<< " mode for connection " << port->m_name
|
|
<< (noConnect ? " (not connecting)" : "")
|
|
<< "\nDefault device name for this device is "
|
|
<< deviceName << std::endl;
|
|
|
|
} else { // !port
|
|
|
|
sprintf(deviceName, "Anonymous MIDI device %d", ++unknownCounter);
|
|
|
|
audit << "Creating device " << deviceId << " in "
|
|
<< (reqDirection == MidiDevice::Play ? "Play" : "Record")
|
|
<< " mode -- no connection available "
|
|
<< "\nDefault device name for this device is "
|
|
<< deviceName << std::endl;
|
|
}
|
|
|
|
if (reqDirection == MidiDevice::Play) {
|
|
|
|
TQString portName;
|
|
|
|
if (TQString(deviceName).startsWith("Anonymous MIDI device ")) {
|
|
portName = TQString("out %1")
|
|
.arg(m_outputPorts.size() + 1);
|
|
} else {
|
|
portName = TQString("out %1 - %2")
|
|
.arg(m_outputPorts.size() + 1)
|
|
.arg(deviceName);
|
|
}
|
|
|
|
int outputPort = checkAlsaError(snd_seq_create_simple_port
|
|
(m_midiHandle,
|
|
portName.ascii(),
|
|
SND_SEQ_PORT_CAP_READ |
|
|
SND_SEQ_PORT_CAP_SUBS_READ,
|
|
SND_SEQ_PORT_TYPE_APPLICATION),
|
|
"createMidiDevice - can't create output port");
|
|
|
|
if (outputPort >= 0) {
|
|
|
|
std::cerr << "CREATED OUTPUT PORT " << outputPort << ":" << portName.ascii() << " for device " << deviceId << std::endl;
|
|
|
|
m_outputPorts[deviceId] = outputPort;
|
|
|
|
if (port) {
|
|
if (connectionName != "") {
|
|
std::cerr << "Connecting my port " << outputPort << " to " << port->m_client << ":" << port->m_port << " on initialisation" << std::endl;
|
|
snd_seq_connect_to(m_midiHandle,
|
|
outputPort,
|
|
port->m_client,
|
|
port->m_port);
|
|
if (m_midiSyncAutoConnect) {
|
|
snd_seq_connect_to(m_midiHandle,
|
|
m_syncOutputPort,
|
|
port->m_client,
|
|
port->m_port);
|
|
}
|
|
}
|
|
std::cerr << "done" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
MappedDevice *device = new MappedDevice(deviceId,
|
|
Device::Midi,
|
|
deviceName,
|
|
connectionName);
|
|
device->setDirection(reqDirection);
|
|
return device;
|
|
}
|
|
|
|
DeviceId
|
|
AlsaDriver::getSpareDeviceId()
|
|
{
|
|
std::set
|
|
<DeviceId> ids;
|
|
for (unsigned int i = 0; i < m_devices.size(); ++i) {
|
|
ids.insert(m_devices[i]->getId());
|
|
}
|
|
|
|
DeviceId id = 0;
|
|
while (ids.find(id) != ids.end())
|
|
++id;
|
|
return id;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::addInstrumentsForDevice(MappedDevice *device)
|
|
{
|
|
std::string channelName;
|
|
char number[100];
|
|
|
|
for (int channel = 0; channel < 16; ++channel) {
|
|
// Create MappedInstrument for export to GUI
|
|
//
|
|
// name is just number, derive rest from device at gui
|
|
sprintf(number, "#%d", channel + 1);
|
|
channelName = std::string(number);
|
|
|
|
if (channel == 9)
|
|
channelName = std::string("#10[D]");
|
|
MappedInstrument *instr = new MappedInstrument(Instrument::Midi,
|
|
channel,
|
|
m_midiRunningId++,
|
|
channelName,
|
|
device->getId());
|
|
m_instruments.push_back(instr);
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
AlsaDriver::canReconnect(Device::DeviceType type)
|
|
{
|
|
return (type == Device::Midi);
|
|
}
|
|
|
|
DeviceId
|
|
AlsaDriver::addDevice(Device::DeviceType type,
|
|
MidiDevice::DeviceDirection direction)
|
|
{
|
|
if (type == Device::Midi) {
|
|
|
|
MappedDevice *device = createMidiDevice(0, direction);
|
|
if (!device) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "WARNING: Device creation failed" << std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
} else {
|
|
addInstrumentsForDevice(device);
|
|
m_devices.push_back(device);
|
|
|
|
MappedEvent *mE =
|
|
new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
|
|
0, 0);
|
|
insertMappedEventForReturn(mE);
|
|
|
|
return device->getId();
|
|
}
|
|
}
|
|
|
|
return Device::NO_DEVICE;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::removeDevice(DeviceId id)
|
|
{
|
|
DeviceIntMap::iterator i1 = m_outputPorts.find(id);
|
|
if (i1 == m_outputPorts.end()) {
|
|
std::cerr << "WARNING: AlsaDriver::removeDevice: Cannot find device "
|
|
<< id << " in port map" << std::endl;
|
|
return ;
|
|
}
|
|
checkAlsaError( snd_seq_delete_port(m_midiHandle, i1->second),
|
|
"removeDevice");
|
|
m_outputPorts.erase(i1);
|
|
|
|
for (MappedDeviceList::iterator i = m_devices.end();
|
|
i != m_devices.begin(); ) {
|
|
|
|
--i;
|
|
|
|
if ((*i)->getId() == id) {
|
|
delete *i;
|
|
m_devices.erase(i);
|
|
}
|
|
}
|
|
|
|
for (MappedInstrumentList::iterator i = m_instruments.end();
|
|
i != m_instruments.begin(); ) {
|
|
|
|
--i;
|
|
|
|
if ((*i)->getDevice() == id) {
|
|
delete *i;
|
|
m_instruments.erase(i);
|
|
}
|
|
}
|
|
|
|
MappedEvent *mE =
|
|
new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
|
|
0, 0);
|
|
insertMappedEventForReturn(mE);
|
|
}
|
|
|
|
void
|
|
AlsaDriver::renameDevice(DeviceId id, TQString name)
|
|
{
|
|
DeviceIntMap::iterator i = m_outputPorts.find(id);
|
|
if (i == m_outputPorts.end()) {
|
|
std::cerr << "WARNING: AlsaDriver::renameDevice: Cannot find device "
|
|
<< id << " in port map" << std::endl;
|
|
return ;
|
|
}
|
|
|
|
snd_seq_port_info_t *pinfo;
|
|
snd_seq_port_info_alloca(&pinfo);
|
|
snd_seq_get_port_info(m_midiHandle, i->second, pinfo);
|
|
|
|
TQString oldName = snd_seq_port_info_get_name(pinfo);
|
|
int sep = oldName.find(" - ");
|
|
|
|
TQString newName;
|
|
|
|
if (name.startsWith("Anonymous MIDI device ")) {
|
|
if (sep < 0)
|
|
sep = 0;
|
|
newName = oldName.left(sep);
|
|
} else if (sep < 0) {
|
|
newName = oldName + " - " + name;
|
|
} else {
|
|
newName = oldName.left(sep + 3) + name;
|
|
}
|
|
|
|
snd_seq_port_info_set_name(pinfo, newName.ascii());
|
|
checkAlsaError(snd_seq_set_port_info(m_midiHandle, i->second, pinfo),
|
|
"renameDevice");
|
|
|
|
for (unsigned int i = 0; i < m_devices.size(); ++i) {
|
|
if (m_devices[i]->getId() == id) {
|
|
m_devices[i]->setName(newName.ascii());
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name.ascii() << std::endl;
|
|
}
|
|
|
|
ClientPortPair
|
|
AlsaDriver::getPortByName(std::string name)
|
|
{
|
|
for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
|
|
if (m_alsaPorts[i]->m_name == name) {
|
|
return ClientPortPair(m_alsaPorts[i]->m_client,
|
|
m_alsaPorts[i]->m_port);
|
|
}
|
|
}
|
|
return ClientPortPair( -1, -1);
|
|
}
|
|
|
|
std::string
|
|
AlsaDriver::getPortName(ClientPortPair port)
|
|
{
|
|
for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
|
|
if (m_alsaPorts[i]->m_client == port.first &&
|
|
m_alsaPorts[i]->m_port == port.second) {
|
|
return m_alsaPorts[i]->m_name;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
unsigned int
|
|
AlsaDriver::getConnections(Device::DeviceType type,
|
|
MidiDevice::DeviceDirection direction)
|
|
{
|
|
if (type != Device::Midi)
|
|
return 0;
|
|
|
|
int count = 0;
|
|
for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
|
|
if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
|
|
(direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
|
|
++count;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
TQString
|
|
AlsaDriver::getConnection(Device::DeviceType type,
|
|
MidiDevice::DeviceDirection direction,
|
|
unsigned int connectionNo)
|
|
{
|
|
if (type != Device::Midi)
|
|
return "";
|
|
|
|
AlsaPortList tempList;
|
|
for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
|
|
if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
|
|
(direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
|
|
tempList.push_back(m_alsaPorts[j]);
|
|
}
|
|
}
|
|
|
|
if (connectionNo < tempList.size()) {
|
|
return tempList[connectionNo]->m_name.c_str();
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setConnectionToDevice(MappedDevice &device, TQString connection)
|
|
{
|
|
ClientPortPair pair( -1, -1);
|
|
if (!connection.isNull() && connection != "") {
|
|
pair = getPortByName(connection.ascii());
|
|
}
|
|
setConnectionToDevice(device, connection, pair);
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setConnectionToDevice(MappedDevice &device, TQString connection,
|
|
const ClientPortPair &pair)
|
|
{
|
|
TQString prevConnection = device.getConnection().c_str();
|
|
device.setConnection(connection.ascii());
|
|
|
|
if (device.getDirection() == MidiDevice::Play) {
|
|
|
|
DeviceIntMap::iterator j = m_outputPorts.find(device.getId());
|
|
|
|
if (j != m_outputPorts.end()) {
|
|
|
|
if (prevConnection != "") {
|
|
ClientPortPair prevPair = getPortByName(prevConnection.ascii());
|
|
if (prevPair.first >= 0 && prevPair.second >= 0) {
|
|
|
|
std::cerr << "Disconnecting my port " << j->second << " from " << prevPair.first << ":" << prevPair.second << " on reconnection" << std::endl;
|
|
snd_seq_disconnect_to(m_midiHandle,
|
|
j->second,
|
|
prevPair.first,
|
|
prevPair.second);
|
|
|
|
if (m_midiSyncAutoConnect) {
|
|
bool foundElsewhere = false;
|
|
for (MappedDeviceList::iterator k = m_devices.begin();
|
|
k != m_devices.end(); ++k) {
|
|
if ((*k)->getId() != device.getId()) {
|
|
if ((*k)->getConnection() == prevConnection.ascii()) {
|
|
foundElsewhere = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!foundElsewhere) {
|
|
snd_seq_disconnect_to(m_midiHandle,
|
|
m_syncOutputPort,
|
|
pair.first,
|
|
pair.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pair.first >= 0 && pair.second >= 0) {
|
|
std::cerr << "Connecting my port " << j->second << " to " << pair.first << ":" << pair.second << " on reconnection" << std::endl;
|
|
snd_seq_connect_to(m_midiHandle,
|
|
j->second,
|
|
pair.first,
|
|
pair.second);
|
|
if (m_midiSyncAutoConnect) {
|
|
snd_seq_connect_to(m_midiHandle,
|
|
m_syncOutputPort,
|
|
pair.first,
|
|
pair.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setConnection(DeviceId id, TQString connection)
|
|
{
|
|
Audit audit;
|
|
ClientPortPair port(getPortByName(connection.ascii()));
|
|
|
|
if (port.first != -1 && port.second != -1) {
|
|
|
|
m_devicePortMap[id] = port;
|
|
|
|
for (unsigned int i = 0; i < m_devices.size(); ++i) {
|
|
|
|
if (m_devices[i]->getId() == id) {
|
|
setConnectionToDevice(*m_devices[i], connection, port);
|
|
|
|
MappedEvent *mE =
|
|
new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
|
|
0, 0);
|
|
insertMappedEventForReturn(mE);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setPlausibleConnection(DeviceId id, TQString idealConnection)
|
|
{
|
|
Audit audit;
|
|
ClientPortPair port(getPortByName(idealConnection.ascii()));
|
|
|
|
audit << "AlsaDriver::setPlausibleConnection: connection like "
|
|
<< idealConnection.ascii() << " requested for device " << id << std::endl;
|
|
|
|
if (port.first != -1 && port.second != -1) {
|
|
|
|
m_devicePortMap[id] = port;
|
|
|
|
for (unsigned int i = 0; i < m_devices.size(); ++i) {
|
|
|
|
if (m_devices[i]->getId() == id) {
|
|
setConnectionToDevice(*m_devices[i], idealConnection, port);
|
|
break;
|
|
}
|
|
}
|
|
|
|
audit << "AlsaDriver::setPlausibleConnection: exact match available"
|
|
<< std::endl;
|
|
return ;
|
|
}
|
|
|
|
// What we want is a connection that:
|
|
//
|
|
// * is in the right "class" (the 0-63/64-127/128+ range of client id)
|
|
// * has at least some text in common
|
|
// * is not yet in use for any device.
|
|
//
|
|
// To do this, we exploit our privileged position as part of AlsaDriver
|
|
// and use our knowledge of how connection strings are made (see
|
|
// AlsaDriver::generatePortList above) to pick out the relevant parts
|
|
// of the requested string.
|
|
|
|
int client = -1;
|
|
int colon = idealConnection.find(":");
|
|
if (colon >= 0)
|
|
client = idealConnection.left(colon).toInt();
|
|
|
|
int portNo = -1;
|
|
if (client > 0) {
|
|
TQString remainder = idealConnection.mid(colon + 1);
|
|
int space = remainder.find(" ");
|
|
if (space >= 0)
|
|
portNo = remainder.left(space).toInt();
|
|
}
|
|
|
|
int firstSpace = idealConnection.find(" ");
|
|
int endOfText = idealConnection.find(TQRegExp("[^\\w ]"), firstSpace);
|
|
|
|
TQString text;
|
|
if (endOfText < 2) {
|
|
text = idealConnection.mid(firstSpace + 1);
|
|
} else {
|
|
text = idealConnection.mid(firstSpace + 1, endOfText - firstSpace - 2);
|
|
}
|
|
|
|
for (int testUsed = 1; testUsed >= 0; --testUsed) {
|
|
|
|
for (int testNumbers = 1; testNumbers >= 0; --testNumbers) {
|
|
|
|
for (int testName = 1; testName >= 0; --testName) {
|
|
|
|
int fitness =
|
|
(testName << 3) +
|
|
(testNumbers << 2) +
|
|
(testUsed << 1) + 1;
|
|
|
|
for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
|
|
|
|
AlsaPortDescription *port = m_alsaPorts[i];
|
|
|
|
if (client > 0) {
|
|
|
|
if (port->m_client / 64 != client / 64)
|
|
continue;
|
|
|
|
if (testNumbers) {
|
|
// We always check the client class (above).
|
|
// But we also prefer to have something in
|
|
// common with client or port number, at least
|
|
// for ports that aren't used elsewhere
|
|
// already. We don't check both because the
|
|
// chances are the entire string would already
|
|
// have matched if both figures did; instead
|
|
// we check the port if it's > 0 (handy for
|
|
// e.g. matching the MIDI synth port on a
|
|
// multi-port soundcard) and the client
|
|
// otherwise.
|
|
if (portNo > 0) {
|
|
if (port->m_port != portNo)
|
|
continue;
|
|
} else {
|
|
if (port->m_client != client)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (testName && text != "" &&
|
|
!TQString(port->m_name.c_str()).contains(text))
|
|
continue;
|
|
|
|
if (testUsed) {
|
|
bool used = false;
|
|
for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
|
|
dpmi != m_devicePortMap.end(); ++dpmi) {
|
|
if (dpmi->second.first == port->m_client &&
|
|
dpmi->second.second == port->m_port) {
|
|
used = true;
|
|
break;
|
|
}
|
|
}
|
|
if (used)
|
|
continue;
|
|
}
|
|
|
|
// OK, this one will do
|
|
|
|
audit << "AlsaDriver::setPlausibleConnection: fuzzy match "
|
|
<< port->m_name << " available with fitness "
|
|
<< fitness << std::endl;
|
|
|
|
m_devicePortMap[id] = ClientPortPair(port->m_client, port->m_port);
|
|
|
|
for (unsigned int i = 0; i < m_devices.size(); ++i) {
|
|
|
|
if (m_devices[i]->getId() == id) {
|
|
setConnectionToDevice(*m_devices[i],
|
|
port->m_name.c_str(),
|
|
m_devicePortMap[id]);
|
|
|
|
// in this case we don't request a device resync,
|
|
// because this is only invoked at times such as
|
|
// file load when the GUI is well aware that the
|
|
// whole situation is in upheaval anyway
|
|
|
|
return ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
audit << "AlsaDriver::setPlausibleConnection: nothing suitable available"
|
|
<< std::endl;
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::checkTimerSync(size_t frames)
|
|
{
|
|
if (!m_doTimerChecks)
|
|
return ;
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (!m_jackDriver || !m_queueRunning || frames == 0 ||
|
|
(getMTCStatus() == TRANSPORT_SLAVE)) {
|
|
m_firstTimerCheck = true;
|
|
return ;
|
|
}
|
|
|
|
static RealTime startAlsaTime;
|
|
static size_t startJackFrames = 0;
|
|
static size_t lastJackFrames = 0;
|
|
|
|
size_t nowJackFrames = m_jackDriver->getFramesProcessed();
|
|
RealTime nowAlsaTime = getAlsaTime();
|
|
|
|
if (m_firstTimerCheck ||
|
|
(nowJackFrames <= lastJackFrames) ||
|
|
(nowAlsaTime <= startAlsaTime)) {
|
|
|
|
startAlsaTime = nowAlsaTime;
|
|
startJackFrames = nowJackFrames;
|
|
lastJackFrames = nowJackFrames;
|
|
|
|
m_firstTimerCheck = false;
|
|
return ;
|
|
}
|
|
|
|
RealTime jackDiff = RealTime::frame2RealTime
|
|
(nowJackFrames - startJackFrames,
|
|
m_jackDriver->getSampleRate());
|
|
|
|
RealTime alsaDiff = nowAlsaTime - startAlsaTime;
|
|
|
|
if (alsaDiff > RealTime(10, 0)) {
|
|
|
|
#ifdef DEBUG_ALSA
|
|
if (!m_playing) {
|
|
std::cout << "\nALSA:" << startAlsaTime << "\t->" << nowAlsaTime << "\nJACK: " << startJackFrames << "\t\t-> " << nowJackFrames << std::endl;
|
|
std::cout << "ALSA diff: " << alsaDiff << "\nJACK diff: " << jackDiff << std::endl;
|
|
}
|
|
#endif
|
|
|
|
double ratio = (jackDiff - alsaDiff) / alsaDiff;
|
|
|
|
if (fabs(ratio) > 0.1) {
|
|
#ifdef DEBUG_ALSA
|
|
if (!m_playing) {
|
|
std::cout << "Ignoring excessive ratio " << ratio
|
|
<< ", hoping for a more likely result next time"
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
|
|
} else if (fabs(ratio) > 0.000001) {
|
|
|
|
#ifdef DEBUG_ALSA
|
|
if (alsaDiff > RealTime::zeroTime && jackDiff > RealTime::zeroTime) {
|
|
if (!m_playing) {
|
|
if (jackDiff < alsaDiff) {
|
|
std::cout << "<<<< ALSA timer is faster by " << 100.0 * ((alsaDiff - jackDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
|
|
} else {
|
|
std::cout << ">>>> JACK timer is faster by " << 100.0 * ((jackDiff - alsaDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_timerRatio = ratio;
|
|
m_timerRatioCalculated = true;
|
|
}
|
|
|
|
m_firstTimerCheck = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
unsigned int
|
|
AlsaDriver::getTimers()
|
|
{
|
|
return m_timers.size() + 1; // one extra for auto
|
|
}
|
|
|
|
TQString
|
|
AlsaDriver::getTimer(unsigned int n)
|
|
{
|
|
if (n == 0)
|
|
return AUTO_TIMER_NAME;
|
|
else
|
|
return m_timers[n -1].name.c_str();
|
|
}
|
|
|
|
TQString
|
|
AlsaDriver::getCurrentTimer()
|
|
{
|
|
return m_currentTimer.c_str();
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setCurrentTimer(TQString timer)
|
|
{
|
|
Audit audit;
|
|
|
|
if (timer == getCurrentTimer())
|
|
return ;
|
|
|
|
std::cerr << "AlsaDriver::setCurrentTimer(" << timer.ascii() << ")" << std::endl;
|
|
|
|
std::string name(timer.ascii());
|
|
|
|
if (name == AUTO_TIMER_NAME) {
|
|
name = getAutoTimer(m_doTimerChecks);
|
|
} else {
|
|
m_doTimerChecks = false;
|
|
}
|
|
m_timerRatioCalculated = false;
|
|
|
|
// Stop and restart the queue around the timer change. We don't
|
|
// call stopClocks/startClocks here because they do the wrong
|
|
// thing if we're currently playing and on the JACK transport.
|
|
|
|
m_queueRunning = false;
|
|
checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "setCurrentTimer(): stopping queue");
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to stop queue");
|
|
|
|
snd_seq_event_t event;
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_real_time_t z = { 0, 0 };
|
|
snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
|
|
snd_seq_ev_set_direct(&event);
|
|
checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
|
|
0, &event), "setCurrentTimer(): control queue");
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to control queue");
|
|
m_alsaPlayStartTime = RealTime::zeroTime;
|
|
|
|
for (unsigned int i = 0; i < m_timers.size(); ++i) {
|
|
if (m_timers[i].name == name) {
|
|
|
|
snd_seq_queue_timer_t *timer;
|
|
snd_timer_id_t *timerid;
|
|
|
|
snd_seq_queue_timer_alloca(&timer);
|
|
snd_seq_get_queue_timer(m_midiHandle, m_queue, timer);
|
|
|
|
snd_timer_id_alloca(&timerid);
|
|
snd_timer_id_set_class(timerid, m_timers[i].clas);
|
|
snd_timer_id_set_sclass(timerid, m_timers[i].sclas);
|
|
snd_timer_id_set_card(timerid, m_timers[i].card);
|
|
snd_timer_id_set_device(timerid, m_timers[i].device);
|
|
snd_timer_id_set_subdevice(timerid, m_timers[i].subdevice);
|
|
|
|
snd_seq_queue_timer_set_id(timer, timerid);
|
|
snd_seq_set_queue_timer(m_midiHandle, m_queue, timer);
|
|
|
|
if (m_doTimerChecks) {
|
|
audit << " Current timer set to \"" << name << "\" with timer checks"
|
|
<< std::endl;
|
|
} else {
|
|
audit << " Current timer set to \"" << name << "\""
|
|
<< std::endl;
|
|
}
|
|
|
|
if (m_timers[i].clas == SND_TIMER_CLASS_GLOBAL &&
|
|
m_timers[i].device == SND_TIMER_GLOBAL_SYSTEM) {
|
|
long hz = 1000000000 / m_timers[i].resolution;
|
|
if (hz < 900) {
|
|
audit << " WARNING: using system timer with only "
|
|
<< hz << "Hz resolution!" << std::endl;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver)
|
|
m_jackDriver->prebufferAudio();
|
|
#endif
|
|
|
|
checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "checkAlsaError(): continue queue");
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to continue queue");
|
|
m_queueRunning = true;
|
|
|
|
m_firstTimerCheck = true;
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::initialise()
|
|
{
|
|
bool result = true;
|
|
|
|
initialiseAudio();
|
|
result = initialiseMidi();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Set up queue, client and port
|
|
//
|
|
bool
|
|
AlsaDriver::initialiseMidi()
|
|
{
|
|
Audit audit;
|
|
|
|
// Create a non-blocking handle.
|
|
//
|
|
if (snd_seq_open(&m_midiHandle,
|
|
"default",
|
|
SND_SEQ_OPEN_DUPLEX,
|
|
SND_SEQ_NONBLOCK) < 0) {
|
|
audit << "AlsaDriver::initialiseMidi - "
|
|
<< "couldn't open sequencer - " << snd_strerror(errno)
|
|
<< " - perhaps you need to modprobe snd-seq-midi."
|
|
<< std::endl;
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
return false;
|
|
}
|
|
|
|
snd_seq_set_client_name(m_midiHandle, "rosegarden");
|
|
|
|
if ((m_client = snd_seq_client_id(m_midiHandle)) < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::initialiseMidi - can't create client"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create a queue
|
|
//
|
|
if ((m_queue = snd_seq_alloc_named_queue(m_midiHandle,
|
|
"Rosegarden queue")) < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::initialiseMidi - can't allocate queue"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create the input port
|
|
//
|
|
snd_seq_port_info_t *pinfo;
|
|
|
|
snd_seq_port_info_alloca(&pinfo);
|
|
snd_seq_port_info_set_capability(pinfo,
|
|
SND_SEQ_PORT_CAP_WRITE |
|
|
SND_SEQ_PORT_CAP_SUBS_WRITE );
|
|
snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
|
|
snd_seq_port_info_set_midi_channels(pinfo, 16);
|
|
/* we want to know when the events got delivered to us */
|
|
snd_seq_port_info_set_timestamping(pinfo, 1);
|
|
snd_seq_port_info_set_timestamp_real(pinfo, 1);
|
|
snd_seq_port_info_set_timestamp_queue(pinfo, m_queue);
|
|
snd_seq_port_info_set_name(pinfo, "record in");
|
|
|
|
if (checkAlsaError(snd_seq_create_port(m_midiHandle, pinfo),
|
|
"initialiseMidi - can't create input port") < 0)
|
|
return false;
|
|
m_inputPort = snd_seq_port_info_get_port(pinfo);
|
|
|
|
// Subscribe the input port to the ALSA Announce port
|
|
// to receive notifications when clients, ports and subscriptions change
|
|
snd_seq_connect_from( m_midiHandle, m_inputPort,
|
|
SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE );
|
|
|
|
m_midiInputPortConnected = true;
|
|
|
|
// Set the input queue size
|
|
//
|
|
if (snd_seq_set_client_pool_output(m_midiHandle, 2000) < 0 ||
|
|
snd_seq_set_client_pool_input(m_midiHandle, 2000) < 0 ||
|
|
snd_seq_set_client_pool_output_room(m_midiHandle, 2000) < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::initialiseMidi - "
|
|
<< "can't modify pool parameters"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create sync output now as well
|
|
m_syncOutputPort = checkAlsaError(snd_seq_create_simple_port
|
|
(m_midiHandle,
|
|
"sync out",
|
|
SND_SEQ_PORT_CAP_READ |
|
|
SND_SEQ_PORT_CAP_SUBS_READ,
|
|
SND_SEQ_PORT_TYPE_APPLICATION),
|
|
"initialiseMidi - can't create sync output port");
|
|
|
|
// and port for hardware controller
|
|
m_controllerPort = checkAlsaError(snd_seq_create_simple_port
|
|
(m_midiHandle,
|
|
"external controller",
|
|
SND_SEQ_PORT_CAP_READ |
|
|
SND_SEQ_PORT_CAP_WRITE |
|
|
SND_SEQ_PORT_CAP_SUBS_READ |
|
|
SND_SEQ_PORT_CAP_SUBS_WRITE,
|
|
SND_SEQ_PORT_TYPE_APPLICATION),
|
|
"initialiseMidi - can't create controller port");
|
|
|
|
getSystemInfo();
|
|
|
|
generatePortList();
|
|
generateInstruments();
|
|
|
|
// Modify status with MIDI success
|
|
//
|
|
m_driverStatus |= MIDI_OK;
|
|
|
|
generateTimerList();
|
|
setCurrentTimer(AUTO_TIMER_NAME);
|
|
|
|
// Start the timer
|
|
if (checkAlsaError(snd_seq_start_queue(m_midiHandle, m_queue, NULL),
|
|
"initialiseMidi(): couldn't start queue") < 0) {
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
return false;
|
|
}
|
|
|
|
m_queueRunning = true;
|
|
|
|
// process anything pending
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "initialiseMidi(): couldn't drain output");
|
|
|
|
audit << "AlsaDriver::initialiseMidi - initialised MIDI subsystem"
|
|
<< std::endl << std::endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
// We don't even attempt to use ALSA audio. We just use JACK instead.
|
|
// See comment at the top of this file and jackProcess() for further
|
|
// information on how we use this.
|
|
//
|
|
void
|
|
AlsaDriver::initialiseAudio()
|
|
{
|
|
#ifdef HAVE_LIBJACK
|
|
m_jackDriver = new JackDriver(this);
|
|
|
|
if (m_jackDriver->isOK()) {
|
|
m_driverStatus |= AUDIO_OK;
|
|
} else {
|
|
delete m_jackDriver;
|
|
m_jackDriver = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AlsaDriver::initialisePlayback(const RealTime &position)
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "\n\nAlsaDriver - initialisePlayback" << std::endl;
|
|
#endif
|
|
|
|
// now that we restart the queue at each play, the origin is always zero
|
|
m_alsaPlayStartTime = RealTime::zeroTime;
|
|
m_playStartPosition = position;
|
|
|
|
m_startPlayback = true;
|
|
|
|
m_mtcFirstTime = -1;
|
|
m_mtcSigmaE = 0;
|
|
m_mtcSigmaC = 0;
|
|
|
|
if (getMMCStatus() == TRANSPORT_MASTER) {
|
|
sendMMC(127, MIDI_MMC_PLAY, true, "");
|
|
m_eat_mtc = 0;
|
|
}
|
|
|
|
if (getMTCStatus() == TRANSPORT_MASTER) {
|
|
insertMTCFullFrame(position);
|
|
}
|
|
|
|
// If MIDI Sync is enabled then adjust for the MIDI Clock to
|
|
// synchronise the sequencer with the clock.
|
|
//
|
|
if (getMIDISyncStatus() == TRANSPORT_MASTER) {
|
|
// Send the Song Position Pointer for MIDI CLOCK positioning
|
|
//
|
|
// Get time from current alsa time to start of alsa timing -
|
|
// add the initial starting point and divide by the MIDI Beat
|
|
// length. The SPP is is the MIDI Beat upon which to start the song.
|
|
// Songs are always assumed to start on a MIDI Beat of 0. Each MIDI
|
|
// Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th
|
|
// note (since there are 24 MIDI Clocks in a quarter note).
|
|
//
|
|
long spp =
|
|
long(((getAlsaTime() - m_alsaPlayStartTime + m_playStartPosition) /
|
|
m_midiClockInterval) / 6.0 );
|
|
|
|
// Ok now we have the new SPP - stop the transport and restart with the
|
|
// new value.
|
|
//
|
|
sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
|
|
|
|
signed int args = spp;
|
|
sendSystemDirect(SND_SEQ_EVENT_SONGPOS, &args);
|
|
|
|
// Now send the START/CONTINUE
|
|
//
|
|
if (m_playStartPosition == RealTime::zeroTime)
|
|
sendSystemQueued(SND_SEQ_EVENT_START, "",
|
|
m_alsaPlayStartTime);
|
|
else
|
|
sendSystemQueued(SND_SEQ_EVENT_CONTINUE, "",
|
|
m_alsaPlayStartTime);
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
m_needJackStart = NeedJackStart;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::stopPlayback()
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "\n\nAlsaDriver - stopPlayback" << std::endl;
|
|
#endif
|
|
|
|
if (getMIDISyncStatus() == TRANSPORT_MASTER) {
|
|
sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
|
|
}
|
|
|
|
if (getMMCStatus() == TRANSPORT_MASTER) {
|
|
sendMMC(127, MIDI_MMC_STOP, true, "");
|
|
//<VN> need to throw away the next MTC event
|
|
m_eat_mtc = 3;
|
|
}
|
|
|
|
allNotesOff();
|
|
m_playing = false;
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
m_jackDriver->stopTransport();
|
|
m_needJackStart = NeedNoJackStart;
|
|
}
|
|
#endif
|
|
|
|
// Flush the output and input queues
|
|
//
|
|
snd_seq_remove_events_t *info;
|
|
snd_seq_remove_events_alloca(&info);
|
|
snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_INPUT |
|
|
SND_SEQ_REMOVE_OUTPUT);
|
|
snd_seq_remove_events(m_midiHandle, info);
|
|
|
|
// send sounds-off to all play devices
|
|
//
|
|
for (MappedDeviceList::iterator i = m_devices.begin(); i != m_devices.end(); ++i) {
|
|
if ((*i)->getDirection() == MidiDevice::Play) {
|
|
sendDeviceController((*i)->getId(),
|
|
MIDI_CONTROLLER_SUSTAIN, 0);
|
|
sendDeviceController((*i)->getId(),
|
|
MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
|
}
|
|
}
|
|
|
|
punchOut();
|
|
|
|
stopClocks(); // Resets ALSA timer to zero
|
|
|
|
clearAudioQueue();
|
|
|
|
startClocksApproved(); // restarts ALSA timer without starting JACK transport
|
|
}
|
|
|
|
void
|
|
AlsaDriver::punchOut()
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::punchOut" << std::endl;
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
// Close any recording file
|
|
if (m_recordStatus == RECORD_ON) {
|
|
for (InstrumentSet::const_iterator i = m_recordingInstruments.begin();
|
|
i != m_recordingInstruments.end(); ++i) {
|
|
|
|
InstrumentId id = *i;
|
|
|
|
if (id >= AudioInstrumentBase &&
|
|
id < MidiInstrumentBase) {
|
|
|
|
AudioFileId auid = 0;
|
|
if (m_jackDriver && m_jackDriver->closeRecordFile(id, auid)) {
|
|
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::stopPlayback: sending back to GUI for instrument " << id << std::endl;
|
|
#endif
|
|
|
|
// Create event to return to gui to say that we've
|
|
// completed an audio file and we can generate a
|
|
// preview for it now.
|
|
//
|
|
// nasty hack -- don't have right audio id here, and
|
|
// the sequencer will wipe out the instrument id and
|
|
// replace it with currently-selected one in gui --
|
|
// so use audio id slot to pass back instrument id
|
|
// and handle accordingly in gui
|
|
try {
|
|
MappedEvent *mE =
|
|
new MappedEvent(id,
|
|
MappedEvent::AudioGeneratePreview,
|
|
id % 256,
|
|
id / 256);
|
|
|
|
// send completion event
|
|
insertMappedEventForReturn(mE);
|
|
} catch (...) {
|
|
;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Change recorded state if any set
|
|
//
|
|
if (m_recordStatus == RECORD_ON)
|
|
m_recordStatus = RECORD_OFF;
|
|
|
|
m_recordingInstruments.clear();
|
|
}
|
|
|
|
void
|
|
AlsaDriver::resetPlayback(const RealTime &oldPosition, const RealTime &position)
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "\n\nAlsaDriver - resetPlayback(" << oldPosition << "," << position << ")" << std::endl;
|
|
#endif
|
|
|
|
if (getMMCStatus() == TRANSPORT_MASTER) {
|
|
unsigned char t_sec = (unsigned char) position.sec % 60;
|
|
unsigned char t_min = (unsigned char) (position.sec / 60) % 60;
|
|
unsigned char t_hrs = (unsigned char) (position.sec / 3600);
|
|
#define STUPID_BROKEN_EQUIPMENT
|
|
#ifdef STUPID_BROKEN_EQUIPMENT
|
|
// Some recorders assume you are talking in 30fps...
|
|
unsigned char t_frm = (unsigned char) (position.nsec / 33333333U);
|
|
unsigned char t_sbf = (unsigned char) ((position.nsec / 333333U) % 100U);
|
|
#else
|
|
// We always send at 25fps, it's the easiest to avoid rounding problems
|
|
unsigned char t_frm = (unsigned char) (position.nsec / 40000000U);
|
|
unsigned char t_sbf = (unsigned char) ((position.nsec / 400000U) % 100U);
|
|
#endif
|
|
|
|
std::cerr << "\n Jump using MMC LOCATE to" << position << std::endl;
|
|
std::cerr << "\t which is " << int(t_hrs) << ":" << int(t_min) << ":" << int(t_sec) << "." << int(t_frm) << "." << int(t_sbf) << std::endl;
|
|
unsigned char locateDataArr[7] = {
|
|
0x06,
|
|
0x01,
|
|
(unsigned char)(0x60 + t_hrs), // (30fps flag) + hh
|
|
t_min, // mm
|
|
t_sec, // ss
|
|
t_frm, // frames
|
|
t_sbf // subframes
|
|
};
|
|
|
|
sendMMC(127, MIDI_MMC_LOCATE, true, std::string((const char *) locateDataArr, 7));
|
|
}
|
|
|
|
RealTime formerStartPosition = m_playStartPosition;
|
|
|
|
m_playStartPosition = position;
|
|
m_alsaPlayStartTime = getAlsaTime();
|
|
|
|
// Reset note offs to correct positions
|
|
//
|
|
RealTime jump = position - oldPosition;
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "Currently " << m_noteOffQueue.size() << " in note off queue" << std::endl;
|
|
#endif
|
|
|
|
// modify the note offs that exist as they're relative to the
|
|
// playStartPosition terms.
|
|
//
|
|
for (NoteOffQueue::iterator i = m_noteOffQueue.begin();
|
|
i != m_noteOffQueue.end(); ++i) {
|
|
|
|
// if we're fast forwarding then we bring the note off closer
|
|
if (jump >= RealTime::zeroTime) {
|
|
|
|
RealTime endTime = formerStartPosition + (*i)->getRealTime();
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "Forward jump of " << jump << ": adjusting note off from "
|
|
<< (*i)->getRealTime() << " (absolute " << endTime
|
|
<< ") to ";
|
|
#endif
|
|
(*i)->setRealTime(endTime - position);
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << (*i)->getRealTime() << std::endl;
|
|
#endif
|
|
} else // we're rewinding - kill the note immediately
|
|
{
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "Rewind by " << jump << ": setting note off to zero" << std::endl;
|
|
#endif
|
|
(*i)->setRealTime(RealTime::zeroTime);
|
|
}
|
|
}
|
|
|
|
pushRecentNoteOffs();
|
|
processNotesOff(getAlsaTime(), true);
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "resetPlayback(): draining");
|
|
|
|
// Ensure we clear down output queue on reset - in the case of
|
|
// MIDI clock where we might have a long queue of events already
|
|
// posted.
|
|
//
|
|
snd_seq_remove_events_t *info;
|
|
snd_seq_remove_events_alloca(&info);
|
|
snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
|
|
snd_seq_remove_events(m_midiHandle, info);
|
|
|
|
if (getMTCStatus() == TRANSPORT_MASTER) {
|
|
m_mtcFirstTime = -1;
|
|
m_mtcSigmaE = 0;
|
|
m_mtcSigmaC = 0;
|
|
insertMTCFullFrame(position);
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
m_jackDriver->clearSynthPluginEvents();
|
|
m_needJackStart = NeedJackReposition;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AlsaDriver::setMIDIClockInterval(RealTime interval)
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::setMIDIClockInterval(" << interval << ")" << endl;
|
|
#endif
|
|
|
|
// Reset the value
|
|
//
|
|
SoundDriver::setMIDIClockInterval(interval);
|
|
|
|
// Return if the clock isn't enabled
|
|
//
|
|
if (!m_midiClockEnabled)
|
|
return ;
|
|
|
|
if (false) // don't remove any events quite yet
|
|
{
|
|
|
|
// Remove all queued events (although we should filter this
|
|
// down to just the clock events.
|
|
//
|
|
snd_seq_remove_events_t *info;
|
|
snd_seq_remove_events_alloca(&info);
|
|
|
|
//if (snd_seq_type_check(SND_SEQ_EVENT_CLOCK, SND_SEQ_EVFLG_CONTROL))
|
|
//snd_seq_remove_events_set_event_type(info,
|
|
snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
|
|
snd_seq_remove_events_set_event_type(info, SND_SEQ_EVFLG_CONTROL);
|
|
std::cout << "AlsaDriver::setMIDIClockInterval - "
|
|
<< "MIDI CLOCK TYPE IS CONTROL" << std::endl;
|
|
snd_seq_remove_events(m_midiHandle, info);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::pushRecentNoteOffs()
|
|
{
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::pushRecentNoteOffs: have " << m_recentNoteOffs.size() << " in queue" << std::endl;
|
|
#endif
|
|
|
|
for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
|
|
i != m_recentNoteOffs.end(); ++i) {
|
|
(*i)->setRealTime(RealTime::zeroTime);
|
|
m_noteOffQueue.insert(*i);
|
|
}
|
|
|
|
m_recentNoteOffs.clear();
|
|
}
|
|
|
|
void
|
|
AlsaDriver::cropRecentNoteOffs(const RealTime &t)
|
|
{
|
|
while (!m_recentNoteOffs.empty()) {
|
|
NoteOffEvent *ev = *m_recentNoteOffs.begin();
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::cropRecentNoteOffs: " << ev->getRealTime() << " vs " << t << std::endl;
|
|
#endif
|
|
if (ev->getRealTime() >= t) break;
|
|
delete ev;
|
|
m_recentNoteOffs.erase(m_recentNoteOffs.begin());
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
|
|
InstrumentId instrument)
|
|
{
|
|
for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
|
|
i != m_recentNoteOffs.end(); ++i) {
|
|
if ((*i)->getPitch() == pitch &&
|
|
(*i)->getChannel() == channel &&
|
|
(*i)->getInstrument() == instrument) {
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::weedRecentNoteOffs: deleting one" << std::endl;
|
|
#endif
|
|
delete *i;
|
|
m_recentNoteOffs.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::allNotesOff()
|
|
{
|
|
snd_seq_event_t event;
|
|
ClientPortPair outputDevice;
|
|
RealTime offTime;
|
|
|
|
// drop any pending notes
|
|
snd_seq_drop_output_buffer(m_midiHandle);
|
|
snd_seq_drop_output(m_midiHandle);
|
|
|
|
// prepare the event
|
|
snd_seq_ev_clear(&event);
|
|
offTime = getAlsaTime();
|
|
|
|
for (NoteOffQueue::iterator it = m_noteOffQueue.begin();
|
|
it != m_noteOffQueue.end(); ++it) {
|
|
// Set destination according to connection for instrument
|
|
//
|
|
outputDevice = getPairForMappedInstrument((*it)->getInstrument());
|
|
if (outputDevice.first < 0 || outputDevice.second < 0)
|
|
continue;
|
|
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
// Set source according to port for device
|
|
//
|
|
int src = getOutputPortForMappedInstrument((*it)->getInstrument());
|
|
if (src < 0)
|
|
continue;
|
|
snd_seq_ev_set_source(&event, src);
|
|
|
|
snd_seq_ev_set_noteoff(&event,
|
|
(*it)->getChannel(),
|
|
(*it)->getPitch(),
|
|
127);
|
|
|
|
//snd_seq_event_output(m_midiHandle, &event);
|
|
int error = snd_seq_event_output_direct(m_midiHandle, &event);
|
|
|
|
if (error < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::allNotesOff - "
|
|
<< "can't send event" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
delete(*it);
|
|
}
|
|
|
|
m_noteOffQueue.erase(m_noteOffQueue.begin(), m_noteOffQueue.end());
|
|
|
|
/*
|
|
std::cerr << "AlsaDriver::allNotesOff - "
|
|
<< " queue size = " << m_noteOffQueue.size() << std::endl;
|
|
*/
|
|
|
|
// flush
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "allNotesOff(): draining");
|
|
}
|
|
|
|
void
|
|
AlsaDriver::processNotesOff(const RealTime &time, bool now, bool everything)
|
|
{
|
|
if (m_noteOffQueue.empty()) {
|
|
return;
|
|
}
|
|
|
|
snd_seq_event_t event;
|
|
|
|
ClientPortPair outputDevice;
|
|
RealTime offTime;
|
|
|
|
// prepare the event
|
|
snd_seq_ev_clear(&event);
|
|
|
|
RealTime alsaTime = getAlsaTime();
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::processNotesOff(" << time << "): alsaTime = " << alsaTime << ", now = " << now << std::endl;
|
|
#endif
|
|
|
|
while (m_noteOffQueue.begin() != m_noteOffQueue.end()) {
|
|
|
|
NoteOffEvent *ev = *m_noteOffQueue.begin();
|
|
|
|
if (ev->getRealTime() > time) {
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "Note off time " << ev->getRealTime() << " is beyond current time " << time << std::endl;
|
|
#endif
|
|
if (!everything) break;
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::processNotesOff(" << time << "): found event at " << ev->getRealTime() << ", instr " << ev->getInstrument() << ", channel " << int(ev->getChannel()) << ", pitch " << int(ev->getPitch()) << std::endl;
|
|
#endif
|
|
|
|
bool isSoftSynth = (ev->getInstrument() >= SoftSynthInstrumentBase);
|
|
|
|
offTime = ev->getRealTime();
|
|
if (offTime < RealTime::zeroTime) offTime = RealTime::zeroTime;
|
|
bool scheduled = (offTime > alsaTime) && !now;
|
|
if (!scheduled) offTime = RealTime::zeroTime;
|
|
|
|
snd_seq_real_time_t alsaOffTime = { (unsigned int)offTime.sec,
|
|
(unsigned int)offTime.nsec };
|
|
|
|
snd_seq_ev_set_noteoff(&event,
|
|
ev->getChannel(),
|
|
ev->getPitch(),
|
|
127);
|
|
|
|
if (!isSoftSynth) {
|
|
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
// Set source according to instrument
|
|
//
|
|
int src = getOutputPortForMappedInstrument(ev->getInstrument());
|
|
if (src < 0) {
|
|
std::cerr << "note off has no output port (instr = " << ev->getInstrument() << ")" << std::endl;
|
|
delete ev;
|
|
m_noteOffQueue.erase(m_noteOffQueue.begin());
|
|
continue;
|
|
}
|
|
|
|
snd_seq_ev_set_source(&event, src);
|
|
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime);
|
|
|
|
if (scheduled) {
|
|
snd_seq_event_output(m_midiHandle, &event);
|
|
} else {
|
|
snd_seq_event_output_direct(m_midiHandle, &event);
|
|
}
|
|
|
|
} else {
|
|
|
|
event.time.time = alsaOffTime;
|
|
|
|
processSoftSynthEventOut(ev->getInstrument(), &event, now);
|
|
}
|
|
|
|
if (!now) {
|
|
m_recentNoteOffs.insert(ev);
|
|
} else {
|
|
delete ev;
|
|
}
|
|
m_noteOffQueue.erase(m_noteOffQueue.begin());
|
|
}
|
|
|
|
// We don't flush the queue here, as this is called nested from
|
|
// processMidiOut, which does the flushing
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::processNotesOff - "
|
|
<< " queue size now: " << m_noteOffQueue.size() << std::endl;
|
|
#endif
|
|
}
|
|
|
|
// Get the queue time and convert it to RealTime for the gui
|
|
// to use.
|
|
//
|
|
RealTime
|
|
AlsaDriver::getSequencerTime()
|
|
{
|
|
RealTime t(0, 0);
|
|
|
|
t = getAlsaTime() + m_playStartPosition - m_alsaPlayStartTime;
|
|
|
|
// std::cerr << "AlsaDriver::getSequencerTime: alsa time is "
|
|
// << getAlsaTime() << ", start time is " << m_alsaPlayStartTime << ", play start position is " << m_playStartPosition << endl;
|
|
|
|
return t;
|
|
}
|
|
|
|
// Gets the time of the ALSA queue
|
|
//
|
|
RealTime
|
|
AlsaDriver::getAlsaTime()
|
|
{
|
|
RealTime sequencerTime(0, 0);
|
|
|
|
snd_seq_queue_status_t *status;
|
|
snd_seq_queue_status_alloca(&status);
|
|
|
|
if (snd_seq_get_queue_status(m_midiHandle, m_queue, status) < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::getAlsaTime - can't get queue status"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return sequencerTime;
|
|
}
|
|
|
|
sequencerTime.sec = snd_seq_queue_status_get_real_time(status)->tv_sec;
|
|
sequencerTime.nsec = snd_seq_queue_status_get_real_time(status)->tv_nsec;
|
|
|
|
// std::cerr << "AlsaDriver::getAlsaTime: alsa time is " << sequencerTime << std::endl;
|
|
|
|
return sequencerTime;
|
|
}
|
|
|
|
|
|
// Get all pending input events and turn them into a MappedComposition.
|
|
//
|
|
//
|
|
MappedComposition*
|
|
AlsaDriver::getMappedComposition()
|
|
{
|
|
m_recordComposition.clear();
|
|
|
|
while (_failureReportReadIndex != _failureReportWriteIndex) {
|
|
MappedEvent::FailureCode code = _failureReports[_failureReportReadIndex];
|
|
// std::cerr << "AlsaDriver::reportFailure(" << code << ")" << std::endl;
|
|
MappedEvent *mE = new MappedEvent
|
|
(0, MappedEvent::SystemFailure, code, 0);
|
|
m_returnComposition.insert(mE);
|
|
_failureReportReadIndex =
|
|
(_failureReportReadIndex + 1) % FAILURE_REPORT_COUNT;
|
|
}
|
|
|
|
if (!m_returnComposition.empty()) {
|
|
for (MappedComposition::iterator i = m_returnComposition.begin();
|
|
i != m_returnComposition.end(); ++i) {
|
|
m_recordComposition.insert(new MappedEvent(**i));
|
|
}
|
|
m_returnComposition.clear();
|
|
}
|
|
|
|
// If the input port hasn't connected we shouldn't poll it
|
|
//
|
|
if (m_midiInputPortConnected == false) {
|
|
return &m_recordComposition;
|
|
}
|
|
|
|
RealTime eventTime(0, 0);
|
|
|
|
snd_seq_event_t *event;
|
|
|
|
while (snd_seq_event_input(m_midiHandle, &event) > 0) {
|
|
|
|
unsigned int channel = (unsigned int)event->data.note.channel;
|
|
unsigned int chanNoteKey = ( channel << 8 ) +
|
|
(unsigned int) event->data.note.note;
|
|
|
|
bool fromController = false;
|
|
|
|
if (event->dest.client == m_client &&
|
|
event->dest.port == m_controllerPort) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "Received an external controller event" << std::endl;
|
|
#endif
|
|
|
|
fromController = true;
|
|
}
|
|
|
|
unsigned int deviceId = Device::NO_DEVICE;
|
|
|
|
if (fromController) {
|
|
deviceId = Device::CONTROL_DEVICE;
|
|
} else {
|
|
for (MappedDeviceList::iterator i = m_devices.begin();
|
|
i != m_devices.end(); ++i) {
|
|
ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
|
|
if (((*i)->getDirection() == MidiDevice::Record) &&
|
|
( pair.first == event->source.client ) &&
|
|
( pair.second == event->source.port )) {
|
|
deviceId = (*i)->getId();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
eventTime.sec = event->time.time.tv_sec;
|
|
eventTime.nsec = event->time.time.tv_nsec;
|
|
eventTime = eventTime - m_alsaRecordStartTime + m_playStartPosition;
|
|
|
|
#ifdef DEBUG_ALSA
|
|
if (!fromController) {
|
|
std::cerr << "Received normal event: type " << int(event->type) << ", chan " << channel << ", note " << int(event->data.note.note) << ", time " << eventTime << std::endl;
|
|
}
|
|
#endif
|
|
|
|
switch (event->type) {
|
|
case SND_SEQ_EVENT_NOTE:
|
|
case SND_SEQ_EVENT_NOTEON:
|
|
if (fromController)
|
|
continue;
|
|
if (event->data.note.velocity > 0) {
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setPitch(event->data.note.note);
|
|
mE->setVelocity(event->data.note.velocity);
|
|
mE->setEventTime(eventTime);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
|
|
// Negative duration - we need to hear the NOTE ON
|
|
// so we must insert it now with a negative duration
|
|
// and pick and mix against the following NOTE OFF
|
|
// when we create the recorded segment.
|
|
//
|
|
mE->setDuration(RealTime( -1, 0));
|
|
|
|
// Create a copy of this when we insert the NOTE ON -
|
|
// keeping a copy alive on the m_noteOnMap.
|
|
//
|
|
// We shake out the two NOTE Ons after we've recorded
|
|
// them.
|
|
//
|
|
m_recordComposition.insert(new MappedEvent(mE));
|
|
m_noteOnMap[deviceId][chanNoteKey] = mE;
|
|
|
|
break;
|
|
}
|
|
|
|
case SND_SEQ_EVENT_NOTEOFF:
|
|
if (fromController)
|
|
continue;
|
|
|
|
if (m_noteOnMap[deviceId][chanNoteKey] != 0) {
|
|
|
|
// Set duration correctly on the NOTE OFF
|
|
//
|
|
MappedEvent *mE = m_noteOnMap[deviceId][chanNoteKey];
|
|
RealTime duration = eventTime - mE->getEventTime();
|
|
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "NOTE OFF: found NOTE ON at " << mE->getEventTime() << std::endl;
|
|
#endif
|
|
|
|
if (duration < RealTime::zeroTime) {
|
|
duration = RealTime::zeroTime;
|
|
mE->setEventTime(eventTime);
|
|
}
|
|
|
|
// Velocity 0 - NOTE OFF. Set duration correctly
|
|
// for recovery later.
|
|
//
|
|
mE->setVelocity(0);
|
|
mE->setDuration(duration);
|
|
|
|
// force shut off of note
|
|
m_recordComposition.insert(mE);
|
|
|
|
// reset the reference
|
|
//
|
|
m_noteOnMap[deviceId][chanNoteKey] = 0;
|
|
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_KEYPRESS: {
|
|
if (fromController)
|
|
continue;
|
|
|
|
// Fix for 632964 by Pedro Lopez-Cabanillas (20030523)
|
|
//
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiKeyPressure);
|
|
mE->setEventTime(eventTime);
|
|
mE->setData1(event->data.note.note);
|
|
mE->setData2(event->data.note.velocity);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
m_recordComposition.insert(mE);
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_CONTROLLER: {
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiController);
|
|
mE->setEventTime(eventTime);
|
|
mE->setData1(event->data.control.param);
|
|
mE->setData2(event->data.control.value);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
m_recordComposition.insert(mE);
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_PGMCHANGE: {
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiProgramChange);
|
|
mE->setEventTime(eventTime);
|
|
mE->setData1(event->data.control.value);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
m_recordComposition.insert(mE);
|
|
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_PITCHBEND: {
|
|
if (fromController)
|
|
continue;
|
|
|
|
// Fix for 711889 by Pedro Lopez-Cabanillas (20030523)
|
|
//
|
|
int s = event->data.control.value + 8192;
|
|
int d1 = (s >> 7) & 0x7f; // data1 = MSB
|
|
int d2 = s & 0x7f; // data2 = LSB
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiPitchBend);
|
|
mE->setEventTime(eventTime);
|
|
mE->setData1(d1);
|
|
mE->setData2(d2);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
m_recordComposition.insert(mE);
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_CHANPRESS: {
|
|
if (fromController)
|
|
continue;
|
|
|
|
// Fixed by Pedro Lopez-Cabanillas (20030523)
|
|
//
|
|
int s = event->data.control.value & 0x7f;
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiChannelPressure);
|
|
mE->setEventTime(eventTime);
|
|
mE->setData1(s);
|
|
mE->setRecordedChannel(channel);
|
|
mE->setRecordedDevice(deviceId);
|
|
m_recordComposition.insert(mE);
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_SYSEX:
|
|
|
|
if (fromController)
|
|
continue;
|
|
|
|
if (!testForMTCSysex(event) &&
|
|
!testForMMCSysex(event)) {
|
|
|
|
// Bundle up the data into a block on the MappedEvent
|
|
//
|
|
std::string data;
|
|
char *ptr = (char*)(event->data.ext.ptr);
|
|
for (unsigned int i = 0; i < event->data.ext.len; ++i)
|
|
data += *(ptr++);
|
|
|
|
#ifdef DEBUG_ALSA
|
|
|
|
if ((MidiByte)(data[1]) == MIDI_SYSEX_RT) {
|
|
std::cerr << "REALTIME SYSEX" << endl;
|
|
for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
|
|
printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
|
|
}
|
|
} else {
|
|
std::cerr << "NON-REALTIME SYSEX" << endl;
|
|
for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
|
|
printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
MappedEvent *mE = new MappedEvent();
|
|
mE->setType(MappedEvent::MidiSystemMessage);
|
|
mE->setData1(MIDI_SYSTEM_EXCLUSIVE);
|
|
mE->setRecordedDevice(deviceId);
|
|
// chop off SYX and EOX bytes from data block
|
|
// Fix for 674731 by Pedro Lopez-Cabanillas (20030601)
|
|
DataBlockRepository::setDataBlockForEvent(mE, data.substr(1, data.length() - 2));
|
|
mE->setEventTime(eventTime);
|
|
m_recordComposition.insert(mE);
|
|
}
|
|
break;
|
|
|
|
|
|
case SND_SEQ_EVENT_SENSING: // MIDI device is still there
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_QFRAME:
|
|
if (fromController)
|
|
continue;
|
|
if (getMTCStatus() == TRANSPORT_SLAVE) {
|
|
handleMTCTQFrame(event->data.control.value, eventTime);
|
|
}
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_CLOCK:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "got realtime MIDI clock" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_START:
|
|
if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportJump(ExternalTransport::TransportStopAtTime,
|
|
RealTime::zeroTime);
|
|
transport->transportChange(ExternalTransport::TransportStart);
|
|
}
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "START" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_CONTINUE:
|
|
if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportChange(ExternalTransport::TransportPlay);
|
|
}
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "CONTINUE" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_STOP:
|
|
if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && isPlaying()) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportChange(ExternalTransport::TransportStop);
|
|
}
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "STOP" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case SND_SEQ_EVENT_SONGPOS:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "SONG POSITION" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
// these cases are handled by checkForNewClients
|
|
//
|
|
case SND_SEQ_EVENT_CLIENT_START:
|
|
case SND_SEQ_EVENT_CLIENT_EXIT:
|
|
case SND_SEQ_EVENT_CLIENT_CHANGE:
|
|
case SND_SEQ_EVENT_PORT_START:
|
|
case SND_SEQ_EVENT_PORT_EXIT:
|
|
case SND_SEQ_EVENT_PORT_CHANGE:
|
|
case SND_SEQ_EVENT_PORT_SUBSCRIBED:
|
|
case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
|
|
m_portCheckNeeded = true;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "got announce event ("
|
|
<< int(event->type) << ")" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
case SND_SEQ_EVENT_TICK:
|
|
default:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::getMappedComposition - "
|
|
<< "got unhandled MIDI event type from ALSA sequencer"
|
|
<< "(" << int(event->type) << ")" << std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
if (getMTCStatus() == TRANSPORT_SLAVE && isPlaying()) {
|
|
#ifdef MTC_DEBUG
|
|
std::cerr << "seq time is " << getSequencerTime() << ", last MTC receive "
|
|
<< m_mtcLastReceive << ", first time " << m_mtcFirstTime << std::endl;
|
|
#endif
|
|
|
|
if (m_mtcFirstTime == 0) { // have received _some_ MTC quarter-frame info
|
|
RealTime seqTime = getSequencerTime();
|
|
if (m_mtcLastReceive < seqTime &&
|
|
seqTime - m_mtcLastReceive > RealTime(0, 500000000L)) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportJump(ExternalTransport::TransportStopAtTime,
|
|
m_mtcLastEncoded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return &m_recordComposition;
|
|
}
|
|
|
|
static int lock_count = 0;
|
|
|
|
void
|
|
AlsaDriver::handleMTCTQFrame(unsigned int data_byte, RealTime the_time)
|
|
{
|
|
if (getMTCStatus() != TRANSPORT_SLAVE)
|
|
return ;
|
|
|
|
switch (data_byte & 0xF0) {
|
|
/* Frame */
|
|
case 0x00:
|
|
/*
|
|
* Reset everything
|
|
*/
|
|
m_mtcReceiveTime = the_time;
|
|
m_mtcFrames = data_byte & 0x0f;
|
|
m_mtcSeconds = 0;
|
|
m_mtcMinutes = 0;
|
|
m_mtcHours = 0;
|
|
m_mtcSMPTEType = 0;
|
|
|
|
break;
|
|
|
|
case 0x10:
|
|
m_mtcFrames |= (data_byte & 0x0f) << 4;
|
|
break;
|
|
|
|
/* Seconds */
|
|
case 0x20:
|
|
m_mtcSeconds = data_byte & 0x0f;
|
|
break;
|
|
case 0x30:
|
|
m_mtcSeconds |= (data_byte & 0x0f) << 4;
|
|
break;
|
|
|
|
/* Minutes */
|
|
case 0x40:
|
|
m_mtcMinutes = data_byte & 0x0f;
|
|
break;
|
|
case 0x50:
|
|
m_mtcMinutes |= (data_byte & 0x0f) << 4;
|
|
break;
|
|
|
|
/* Hours and SMPTE type */
|
|
case 0x60:
|
|
m_mtcHours = data_byte & 0x0f;
|
|
break;
|
|
|
|
case 0x70: {
|
|
m_mtcHours |= (data_byte & 0x01) << 4;
|
|
m_mtcSMPTEType = (data_byte & 0x06) >> 1;
|
|
|
|
int fps = 30;
|
|
if (m_mtcSMPTEType == 0)
|
|
fps = 24;
|
|
else if (m_mtcSMPTEType == 1)
|
|
fps = 25;
|
|
|
|
/*
|
|
* Ok, got all the bits now
|
|
* (Assuming time is rolling forward)
|
|
*/
|
|
|
|
/* correct for 2-frame lag */
|
|
m_mtcFrames += 2;
|
|
if (m_mtcFrames >= fps) {
|
|
m_mtcFrames -= fps;
|
|
if (++m_mtcSeconds == 60) {
|
|
m_mtcSeconds = 0;
|
|
if (++m_mtcMinutes == 60) {
|
|
m_mtcMinutes = 0;
|
|
++m_mtcHours;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MTC_DEBUG
|
|
printf("RG MTC: Got a complete sequence: %02d:%02d:%02d.%02d (type %d)\n",
|
|
m_mtcHours,
|
|
m_mtcMinutes,
|
|
m_mtcSeconds,
|
|
m_mtcFrames,
|
|
m_mtcSMPTEType);
|
|
#endif
|
|
|
|
/* compute encoded time */
|
|
m_mtcEncodedTime.sec = m_mtcSeconds +
|
|
m_mtcMinutes * 60 +
|
|
m_mtcHours * 60 * 60;
|
|
|
|
switch (fps) {
|
|
case 24:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
|
|
break;
|
|
case 25:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
(40000000UL * (unsigned)m_mtcFrames);
|
|
break;
|
|
case 30:
|
|
default:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We only mess with the clock if we are playing
|
|
*/
|
|
if (m_playing) {
|
|
#ifdef MTC_DEBUG
|
|
std::cerr << "RG MTC: Tstamp " << m_mtcEncodedTime;
|
|
std::cerr << " Received @ " << m_mtcReceiveTime << endl;
|
|
#endif
|
|
|
|
calibrateMTC();
|
|
|
|
RealTime t_diff = m_mtcEncodedTime - m_mtcReceiveTime;
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cerr << "Diff: " << t_diff << endl;
|
|
#endif
|
|
|
|
/* -ve diff means ALSA time ahead of MTC time */
|
|
|
|
if (t_diff.sec > 0) {
|
|
tweakSkewForMTC(60000);
|
|
} else if (t_diff.sec < 0) {
|
|
tweakSkewForMTC( -60000);
|
|
} else {
|
|
/* "small" diff - use adaptive technique */
|
|
tweakSkewForMTC(t_diff.nsec / 1400);
|
|
if ((t_diff.nsec / 1000000) == 0) {
|
|
if (++lock_count == 3) {
|
|
printf("Got a lock @ %02d:%02d:%02d.%02d (type %d)\n",
|
|
m_mtcHours,
|
|
m_mtcMinutes,
|
|
m_mtcSeconds,
|
|
m_mtcFrames,
|
|
m_mtcSMPTEType);
|
|
}
|
|
} else {
|
|
lock_count = 0;
|
|
}
|
|
}
|
|
|
|
} else if (m_eat_mtc > 0) {
|
|
#ifdef MTC_DEBUG
|
|
std::cerr << "MTC: Received quarter frame just after issuing MMC stop - ignore it" << std::endl;
|
|
#endif
|
|
|
|
--m_eat_mtc;
|
|
} else {
|
|
/* If we're not playing, we should be. */
|
|
#ifdef MTC_DEBUG
|
|
std::cerr << "MTC: Received quarter frame while not playing - starting now" << std::endl;
|
|
#endif
|
|
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportJump
|
|
(ExternalTransport::TransportStartAtTime,
|
|
m_mtcEncodedTime);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Oh dear, demented device! */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::insertMTCFullFrame(RealTime time)
|
|
{
|
|
snd_seq_event_t event;
|
|
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_ev_set_source(&event, m_syncOutputPort);
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
m_mtcEncodedTime = time;
|
|
m_mtcSeconds = m_mtcEncodedTime.sec % 60;
|
|
m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
|
|
m_mtcHours = (m_mtcEncodedTime.sec / 3600);
|
|
|
|
// We always send at 25fps, it's the easiest to avoid rounding problems
|
|
m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U;
|
|
|
|
time = time + m_alsaPlayStartTime - m_playStartPosition;
|
|
snd_seq_real_time_t atime = { (unsigned int)time.sec, (unsigned int)time.nsec };
|
|
|
|
unsigned char data[10] =
|
|
{ MIDI_SYSTEM_EXCLUSIVE,
|
|
MIDI_SYSEX_RT, 127, 1, 1,
|
|
0, 0, 0, 0,
|
|
MIDI_END_OF_EXCLUSIVE };
|
|
|
|
data[5] = ((unsigned char)m_mtcHours & 0x1f) + (1 << 5); // 1 indicates 25fps
|
|
data[6] = (unsigned char)m_mtcMinutes;
|
|
data[7] = (unsigned char)m_mtcSeconds;
|
|
data[8] = (unsigned char)m_mtcFrames;
|
|
|
|
snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
|
|
snd_seq_ev_set_sysex(&event, 10, data);
|
|
|
|
checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
|
|
"insertMTCFullFrame event send");
|
|
|
|
if (m_queueRunning) {
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "insertMTCFullFrame drain");
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::insertMTCTQFrames(RealTime sliceStart, RealTime sliceEnd)
|
|
{
|
|
if (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime) {
|
|
// not a real slice
|
|
return ;
|
|
}
|
|
|
|
// We send at 25fps, it's the easiest to avoid rounding problems
|
|
RealTime twoFrames(0, 80000000U);
|
|
RealTime quarterFrame(0, 10000000U);
|
|
int fps = 25;
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cout << "AlsaDriver::insertMTCTQFrames(" << sliceStart << ","
|
|
<< sliceEnd << "): first time " << m_mtcFirstTime << std::endl;
|
|
#endif
|
|
|
|
RealTime t;
|
|
|
|
if (m_mtcFirstTime != 0) { // first time through, reset location
|
|
m_mtcEncodedTime = sliceStart;
|
|
t = sliceStart;
|
|
m_mtcFirstTime = 0;
|
|
} else {
|
|
t = m_mtcEncodedTime + quarterFrame;
|
|
}
|
|
|
|
m_mtcSeconds = m_mtcEncodedTime.sec % 60;
|
|
m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
|
|
m_mtcHours = (m_mtcEncodedTime.sec / 3600);
|
|
m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; // 25fps
|
|
|
|
std::string bytes = " ";
|
|
|
|
int type = 0;
|
|
|
|
while (m_mtcEncodedTime < sliceEnd) {
|
|
|
|
snd_seq_event_t event;
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_ev_set_source(&event, m_syncOutputPort);
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cout << "Sending MTC quarter frame at " << t << std::endl;
|
|
#endif
|
|
|
|
unsigned char c = (type << 4);
|
|
|
|
switch (type) {
|
|
case 0:
|
|
c += ((unsigned char)m_mtcFrames & 0x0f);
|
|
break;
|
|
case 1:
|
|
c += (((unsigned char)m_mtcFrames & 0xf0) >> 4);
|
|
break;
|
|
case 2:
|
|
c += ((unsigned char)m_mtcSeconds & 0x0f);
|
|
break;
|
|
case 3:
|
|
c += (((unsigned char)m_mtcSeconds & 0xf0) >> 4);
|
|
break;
|
|
case 4:
|
|
c += ((unsigned char)m_mtcMinutes & 0x0f);
|
|
break;
|
|
case 5:
|
|
c += (((unsigned char)m_mtcMinutes & 0xf0) >> 4);
|
|
break;
|
|
case 6:
|
|
c += ((unsigned char)m_mtcHours & 0x0f);
|
|
break;
|
|
case 7: // hours high nibble + smpte type
|
|
c += (m_mtcHours >> 4) & 0x01;
|
|
c += (1 << 1); // type 1 indicates 25fps
|
|
break;
|
|
}
|
|
|
|
RealTime scheduleTime = t + m_alsaPlayStartTime - m_playStartPosition;
|
|
snd_seq_real_time_t atime = { (unsigned int)scheduleTime.sec, (unsigned int)scheduleTime.nsec };
|
|
|
|
event.type = SND_SEQ_EVENT_QFRAME;
|
|
event.data.control.value = c;
|
|
|
|
snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
|
|
|
|
checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
|
|
"insertMTCTQFrames sending qframe event");
|
|
|
|
if (++type == 8) {
|
|
m_mtcFrames += 2;
|
|
if (m_mtcFrames >= fps) {
|
|
m_mtcFrames -= fps;
|
|
if (++m_mtcSeconds == 60) {
|
|
m_mtcSeconds = 0;
|
|
if (++m_mtcMinutes == 60) {
|
|
m_mtcMinutes = 0;
|
|
++m_mtcHours;
|
|
}
|
|
}
|
|
}
|
|
m_mtcEncodedTime = t;
|
|
type = 0;
|
|
}
|
|
|
|
t = t + quarterFrame;
|
|
}
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::testForMTCSysex(const snd_seq_event_t *event)
|
|
{
|
|
if (getMTCStatus() != TRANSPORT_SLAVE)
|
|
return false;
|
|
|
|
// At this point, and possibly for the foreseeable future, the only
|
|
// sysex we're interested in is full-frame transport location
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cerr << "MTC: testing sysex of length " << event->data.ext.len << ":" << std::endl;
|
|
for (int i = 0; i < event->data.ext.len; ++i) {
|
|
std::cerr << (int)*((unsigned char *)event->data.ext.ptr + i) << " ";
|
|
}
|
|
std::cerr << endl;
|
|
#endif
|
|
|
|
if (event->data.ext.len != 10)
|
|
return false;
|
|
|
|
unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
|
|
|
|
if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
|
|
return false;
|
|
if (*ptr++ != MIDI_SYSEX_RT)
|
|
return false;
|
|
if (*ptr++ > 127)
|
|
return false;
|
|
|
|
// 01 01 for MTC full frame
|
|
|
|
if (*ptr++ != 1)
|
|
return false;
|
|
if (*ptr++ != 1)
|
|
return false;
|
|
|
|
int htype = *ptr++;
|
|
int min = *ptr++;
|
|
int sec = *ptr++;
|
|
int frame = *ptr++;
|
|
|
|
if (*ptr != MIDI_END_OF_EXCLUSIVE)
|
|
return false;
|
|
|
|
int hour = (htype & 0x1f);
|
|
int type = (htype & 0xe0) >> 5;
|
|
|
|
m_mtcFrames = frame;
|
|
m_mtcSeconds = sec;
|
|
m_mtcMinutes = min;
|
|
m_mtcHours = hour;
|
|
m_mtcSMPTEType = type;
|
|
|
|
int fps = 30;
|
|
if (m_mtcSMPTEType == 0)
|
|
fps = 24;
|
|
else if (m_mtcSMPTEType == 1)
|
|
fps = 25;
|
|
|
|
m_mtcEncodedTime.sec = sec + min * 60 + hour * 60 * 60;
|
|
|
|
switch (fps) {
|
|
case 24:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
|
|
break;
|
|
case 25:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
(40000000UL * (unsigned)m_mtcFrames);
|
|
break;
|
|
case 30:
|
|
default:
|
|
m_mtcEncodedTime.nsec = (int)
|
|
((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
|
|
break;
|
|
}
|
|
|
|
#ifdef MTC_DEBUG
|
|
std::cerr << "MTC: MTC sysex found (frame type " << type
|
|
<< "), jumping to " << m_mtcEncodedTime << std::endl;
|
|
#endif
|
|
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportJump
|
|
(ExternalTransport::TransportJumpToTime,
|
|
m_mtcEncodedTime);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int last_factor = 0;
|
|
static int bias_factor = 0;
|
|
|
|
void
|
|
AlsaDriver::calibrateMTC()
|
|
{
|
|
if (m_mtcFirstTime < 0)
|
|
return ;
|
|
else if (m_mtcFirstTime > 0) {
|
|
--m_mtcFirstTime;
|
|
m_mtcSigmaC = 0;
|
|
m_mtcSigmaE = 0;
|
|
} else {
|
|
RealTime diff_e = m_mtcEncodedTime - m_mtcLastEncoded;
|
|
RealTime diff_c = m_mtcReceiveTime - m_mtcLastReceive;
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
printf("RG MTC: diffs %d %d %d\n", diff_c.nsec, diff_e.nsec, m_mtcSkew);
|
|
#endif
|
|
|
|
m_mtcSigmaE += ((long long int) diff_e.nsec) * m_mtcSkew;
|
|
m_mtcSigmaC += diff_c.nsec;
|
|
|
|
|
|
int t_bias = (m_mtcSigmaE / m_mtcSigmaC) - 0x10000;
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
printf("RG MTC: sigmas %lld %lld %d\n", m_mtcSigmaE, m_mtcSigmaC, t_bias);
|
|
#endif
|
|
|
|
bias_factor = t_bias;
|
|
}
|
|
|
|
m_mtcLastReceive = m_mtcReceiveTime;
|
|
m_mtcLastEncoded = m_mtcEncodedTime;
|
|
|
|
}
|
|
|
|
void
|
|
AlsaDriver::tweakSkewForMTC(int factor)
|
|
{
|
|
if (factor > 50000) {
|
|
factor = 50000;
|
|
} else if (factor < -50000) {
|
|
factor = -50000;
|
|
} else if (factor == last_factor) {
|
|
return ;
|
|
} else {
|
|
if (m_mtcFirstTime == -1)
|
|
m_mtcFirstTime = 5;
|
|
}
|
|
last_factor = factor;
|
|
|
|
snd_seq_queue_tempo_t *q_ptr;
|
|
snd_seq_queue_tempo_alloca(&q_ptr);
|
|
|
|
snd_seq_get_queue_tempo( m_midiHandle, m_queue, q_ptr);
|
|
|
|
unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cerr << "RG MTC: skew: " << t_skew;
|
|
#endif
|
|
|
|
t_skew = 0x10000 + factor + bias_factor;
|
|
|
|
#ifdef MTC_DEBUG
|
|
|
|
std::cerr << " changed to " << factor << "+" << bias_factor << endl;
|
|
#endif
|
|
|
|
snd_seq_queue_tempo_set_skew(q_ptr, t_skew);
|
|
snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
|
|
|
|
m_mtcSkew = t_skew;
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::testForMMCSysex(const snd_seq_event_t *event)
|
|
{
|
|
if (getMMCStatus() != TRANSPORT_SLAVE)
|
|
return false;
|
|
|
|
if (event->data.ext.len != 6)
|
|
return false;
|
|
|
|
unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
|
|
|
|
if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
|
|
return false;
|
|
if (*ptr++ != MIDI_SYSEX_RT)
|
|
return false;
|
|
if (*ptr++ > 127)
|
|
return false;
|
|
if (*ptr++ != MIDI_SYSEX_RT_COMMAND)
|
|
return false;
|
|
|
|
int instruction = *ptr++;
|
|
|
|
if (*ptr != MIDI_END_OF_EXCLUSIVE)
|
|
return false;
|
|
|
|
if (instruction == MIDI_MMC_PLAY ||
|
|
instruction == MIDI_MMC_DEFERRED_PLAY) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportChange(ExternalTransport::TransportPlay);
|
|
}
|
|
} else if (instruction == MIDI_MMC_STOP) {
|
|
ExternalTransport *transport = getExternalTransportControl();
|
|
if (transport) {
|
|
transport->transportChange(ExternalTransport::TransportStop);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
AlsaDriver::processMidiOut(const MappedComposition &mC,
|
|
const RealTime &sliceStart,
|
|
const RealTime &sliceEnd)
|
|
{
|
|
RealTime outputTime;
|
|
RealTime outputStopTime;
|
|
MappedInstrument *instrument;
|
|
ClientPortPair outputDevice;
|
|
MidiByte channel;
|
|
snd_seq_event_t event;
|
|
|
|
// special case for unqueued events
|
|
bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
|
|
|
|
if (!now) {
|
|
// This 0.5 sec is arbitrary, but it must be larger than the
|
|
// sequencer's read-ahead
|
|
RealTime diff = RealTime::fromSeconds(0.5);
|
|
RealTime cutoff = sliceStart - diff;
|
|
cropRecentNoteOffs(cutoff - m_playStartPosition + m_alsaPlayStartTime);
|
|
}
|
|
|
|
// These won't change in this slice
|
|
//
|
|
snd_seq_ev_clear(&event);
|
|
|
|
if ((mC.begin() != mC.end()) && getSequencerDataBlock()) {
|
|
getSequencerDataBlock()->setVisual(*mC.begin());
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "AlsaDriver::processMidiOut(" << sliceStart << "," << sliceEnd
|
|
<< "), " << mC.size() << " events, now is " << now << std::endl;
|
|
#endif
|
|
|
|
// NB the MappedComposition is implicitly ordered by time (std::multiset)
|
|
|
|
for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
|
|
if ((*i)->getType() >= MappedEvent::Audio)
|
|
continue;
|
|
|
|
bool isControllerOut = ((*i)->getRecordedDevice() ==
|
|
Device::CONTROL_DEVICE);
|
|
|
|
bool isSoftSynth = (!isControllerOut &&
|
|
((*i)->getInstrument() >= SoftSynthInstrumentBase));
|
|
|
|
outputTime = (*i)->getEventTime() - m_playStartPosition +
|
|
m_alsaPlayStartTime;
|
|
|
|
if (now && !m_playing && m_queueRunning) {
|
|
// stop queue to ensure exact timing and make sure the
|
|
// event gets through right now
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut: stopping queue for now-event" << std::endl;
|
|
#endif
|
|
|
|
checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): stop queue");
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
|
|
}
|
|
|
|
RealTime alsaTimeNow = getAlsaTime();
|
|
|
|
if (now) {
|
|
if (!m_playing) {
|
|
outputTime = alsaTimeNow;
|
|
} else if (outputTime < alsaTimeNow) {
|
|
outputTime = alsaTimeNow + RealTime(0, 10000000);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut[" << now << "]: event is at " << outputTime << " (" << outputTime - alsaTimeNow << " ahead of queue time), type " << int((*i)->getType()) << ", duration " << (*i)->getDuration() << std::endl;
|
|
#endif
|
|
|
|
if (!m_queueRunning && outputTime < alsaTimeNow) {
|
|
RealTime adjust = alsaTimeNow - outputTime;
|
|
if ((*i)->getDuration() > RealTime::zeroTime) {
|
|
if ((*i)->getDuration() <= adjust) {
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut[" << now << "]: too late for this event, abandoning it" << std::endl;
|
|
#endif
|
|
|
|
continue;
|
|
} else {
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut[" << now << "]: pushing event forward and reducing duration by " << adjust << std::endl;
|
|
#endif
|
|
|
|
(*i)->setDuration((*i)->getDuration() - adjust);
|
|
}
|
|
} else {
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut[" << now << "]: pushing zero-duration event forward by " << adjust << std::endl;
|
|
#endif
|
|
|
|
}
|
|
outputTime = alsaTimeNow;
|
|
}
|
|
|
|
processNotesOff(outputTime, now);
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (m_jackDriver) {
|
|
size_t frameCount = m_jackDriver->getFramesProcessed();
|
|
size_t elapsed = frameCount - _debug_jack_frame_count;
|
|
RealTime rt = RealTime::frame2RealTime(elapsed, m_jackDriver->getSampleRate());
|
|
rt = rt - getAlsaTime();
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
|
|
std::cerr << "processMidiOut[" << now << "]: JACK time is " << rt << " ahead of ALSA time" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
// Second and nanoseconds for ALSA
|
|
//
|
|
snd_seq_real_time_t time = { (unsigned int)outputTime.sec, (unsigned int)outputTime.nsec };
|
|
|
|
if (!isSoftSynth) {
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cout << "processMidiOut[" << now << "]: instrument " << (*i)->getInstrument() << std::endl;
|
|
std::cout << "pitch: " << (int)(*i)->getPitch() << ", velocity " << (int)(*i)->getVelocity() << ", duration " << (*i)->getDuration() << std::endl;
|
|
#endif
|
|
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
// Set source according to port for device
|
|
//
|
|
int src;
|
|
|
|
if (isControllerOut) {
|
|
src = m_controllerPort;
|
|
} else {
|
|
src = getOutputPortForMappedInstrument((*i)->getInstrument());
|
|
}
|
|
|
|
if (src < 0) continue;
|
|
snd_seq_ev_set_source(&event, src);
|
|
|
|
snd_seq_ev_schedule_real(&event, m_queue, 0, &time);
|
|
|
|
} else {
|
|
event.time.time = time;
|
|
}
|
|
|
|
instrument = getMappedInstrument((*i)->getInstrument());
|
|
|
|
// set the stop time for Note Off
|
|
//
|
|
outputStopTime = outputTime + (*i)->getDuration()
|
|
- RealTime(0, 1); // notch it back 1nsec just to ensure
|
|
// correct ordering against any other
|
|
// note-ons at the same nominal time
|
|
bool needNoteOff = false;
|
|
|
|
if (isControllerOut) {
|
|
channel = (*i)->getRecordedChannel();
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "processMidiOut() - Event of type " << (int)((*i)->getType()) << " (data1 " << (int)(*i)->getData1() << ", data2 " << (int)(*i)->getData2() << ") for external controller channel " << (int)channel << std::endl;
|
|
#endif
|
|
|
|
} else if (instrument != 0) {
|
|
channel = instrument->getChannel();
|
|
} else {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "processMidiOut() - No instrument for event of type "
|
|
<< (int)(*i)->getType() << " at " << (*i)->getEventTime()
|
|
<< std::endl;
|
|
#endif
|
|
|
|
channel = 0;
|
|
}
|
|
|
|
switch ((*i)->getType()) {
|
|
|
|
case MappedEvent::MidiNoteOneShot:
|
|
{
|
|
snd_seq_ev_set_noteon(&event,
|
|
channel,
|
|
(*i)->getPitch(),
|
|
(*i)->getVelocity());
|
|
needNoteOff = true;
|
|
|
|
if (!isSoftSynth && getSequencerDataBlock()) {
|
|
LevelInfo info;
|
|
info.level = (*i)->getVelocity();
|
|
info.levelRight = 0;
|
|
getSequencerDataBlock()->setInstrumentLevel
|
|
((*i)->getInstrument(), info);
|
|
}
|
|
|
|
weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
|
|
}
|
|
break;
|
|
|
|
case MappedEvent::MidiNote:
|
|
// We always use plain NOTE ON here, not ALSA
|
|
// time+duration notes, because we have our own NOTE
|
|
// OFF stack (which will be augmented at the bottom of
|
|
// this function) and we want to ensure it gets used
|
|
// for the purposes of e.g. soft synths
|
|
//
|
|
if ((*i)->getVelocity() > 0) {
|
|
snd_seq_ev_set_noteon(&event,
|
|
channel,
|
|
(*i)->getPitch(),
|
|
(*i)->getVelocity());
|
|
|
|
if (!isSoftSynth && getSequencerDataBlock()) {
|
|
LevelInfo info;
|
|
info.level = (*i)->getVelocity();
|
|
info.levelRight = 0;
|
|
getSequencerDataBlock()->setInstrumentLevel
|
|
((*i)->getInstrument(), info);
|
|
}
|
|
|
|
weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
|
|
} else {
|
|
snd_seq_ev_set_noteoff(&event,
|
|
channel,
|
|
(*i)->getPitch(),
|
|
(*i)->getVelocity());
|
|
}
|
|
|
|
break;
|
|
|
|
case MappedEvent::MidiProgramChange:
|
|
snd_seq_ev_set_pgmchange(&event,
|
|
channel,
|
|
(*i)->getData1());
|
|
break;
|
|
|
|
case MappedEvent::MidiKeyPressure:
|
|
snd_seq_ev_set_keypress(&event,
|
|
channel,
|
|
(*i)->getData1(),
|
|
(*i)->getData2());
|
|
break;
|
|
|
|
case MappedEvent::MidiChannelPressure:
|
|
snd_seq_ev_set_chanpress(&event,
|
|
channel,
|
|
(*i)->getData1());
|
|
break;
|
|
|
|
case MappedEvent::MidiPitchBend: {
|
|
int d1 = (int)((*i)->getData1());
|
|
int d2 = (int)((*i)->getData2());
|
|
int value = ((d1 << 7) | d2) - 8192;
|
|
|
|
// keep within -8192 to +8192
|
|
//
|
|
// if (value & 0x4000)
|
|
// value -= 0x8000;
|
|
|
|
snd_seq_ev_set_pitchbend(&event,
|
|
channel,
|
|
value);
|
|
}
|
|
break;
|
|
|
|
case MappedEvent::MidiSystemMessage: {
|
|
switch ((*i)->getData1()) {
|
|
case MIDI_SYSTEM_EXCLUSIVE: {
|
|
char out[2];
|
|
sprintf(out, "%c", MIDI_SYSTEM_EXCLUSIVE);
|
|
std::string data = out;
|
|
|
|
data += DataBlockRepository::getDataBlockForEvent((*i));
|
|
|
|
sprintf(out, "%c", MIDI_END_OF_EXCLUSIVE);
|
|
data += out;
|
|
|
|
snd_seq_ev_set_sysex(&event,
|
|
data.length(),
|
|
(char*)(data.c_str()));
|
|
}
|
|
break;
|
|
|
|
case MIDI_TIMING_CLOCK: {
|
|
RealTime rt =
|
|
RealTime(time.tv_sec, time.tv_nsec);
|
|
|
|
/*
|
|
std::cerr << "AlsaDriver::processMidiOut - "
|
|
<< "send clock @ " << rt << std::endl;
|
|
*/
|
|
|
|
sendSystemQueued(SND_SEQ_EVENT_CLOCK, "", rt);
|
|
|
|
continue;
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
std::cerr << "AlsaDriver::processMidiOut - "
|
|
<< "unrecognised system message"
|
|
<< std::endl;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MappedEvent::MidiController:
|
|
snd_seq_ev_set_controller(&event,
|
|
channel,
|
|
(*i)->getData1(),
|
|
(*i)->getData2());
|
|
break;
|
|
|
|
case MappedEvent::Audio:
|
|
case MappedEvent::AudioCancel:
|
|
case MappedEvent::AudioLevel:
|
|
case MappedEvent::AudioStopped:
|
|
case MappedEvent::SystemUpdateInstruments:
|
|
case MappedEvent::SystemJackTransport: //???
|
|
case MappedEvent::SystemMMCTransport:
|
|
case MappedEvent::SystemMIDIClock:
|
|
case MappedEvent::SystemMIDISyncAuto:
|
|
break;
|
|
|
|
default:
|
|
case MappedEvent::InvalidMappedEvent:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processMidiOut - "
|
|
<< "skipping unrecognised or invalid MappedEvent type"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
continue;
|
|
}
|
|
|
|
if (isSoftSynth) {
|
|
|
|
processSoftSynthEventOut((*i)->getInstrument(), &event, now);
|
|
|
|
} else {
|
|
checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
|
|
"processMidiOut(): output queued");
|
|
|
|
if (now) {
|
|
if (m_queueRunning && !m_playing) {
|
|
// restart queue
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut: restarting queue after now-event" << std::endl;
|
|
#endif
|
|
|
|
checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
|
|
}
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
|
|
}
|
|
}
|
|
|
|
// Add note to note off stack
|
|
//
|
|
if (needNoteOff) {
|
|
NoteOffEvent *noteOffEvent =
|
|
new NoteOffEvent(outputStopTime, // already calculated
|
|
(*i)->getPitch(),
|
|
channel,
|
|
(*i)->getInstrument());
|
|
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "Adding NOTE OFF at " << outputStopTime
|
|
<< std::endl;
|
|
#endif
|
|
|
|
m_noteOffQueue.insert(noteOffEvent);
|
|
}
|
|
}
|
|
|
|
processNotesOff(sliceEnd - m_playStartPosition + m_alsaPlayStartTime, now);
|
|
|
|
if (getMTCStatus() == TRANSPORT_MASTER) {
|
|
insertMTCTQFrames(sliceStart, sliceEnd);
|
|
}
|
|
|
|
if (m_queueRunning) {
|
|
|
|
if (now && !m_playing) {
|
|
// just to be sure
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
std::cerr << "processMidiOut: restarting queue after all now-events" << std::endl;
|
|
#endif
|
|
|
|
checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS_MIDI_OUT
|
|
// std::cerr << "processMidiOut: m_queueRunning " << m_queueRunning
|
|
// << ", now " << now << std::endl;
|
|
#endif
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::processSoftSynthEventOut(InstrumentId id, const snd_seq_event_t *ev, bool now)
|
|
{
|
|
#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
|
|
std::cerr << "AlsaDriver::processSoftSynthEventOut: instrument " << id << ", now " << now << std::endl;
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (!m_jackDriver)
|
|
return ;
|
|
RunnablePluginInstance *synthPlugin = m_jackDriver->getSynthPlugin(id);
|
|
|
|
if (synthPlugin) {
|
|
|
|
RealTime t(ev->time.time.tv_sec, ev->time.time.tv_nsec);
|
|
|
|
if (now)
|
|
t = RealTime::zeroTime;
|
|
else
|
|
t = t + m_playStartPosition - m_alsaPlayStartTime;
|
|
|
|
#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
|
|
|
|
std::cerr << "AlsaDriver::processSoftSynthEventOut: event time " << t << std::endl;
|
|
#endif
|
|
|
|
synthPlugin->sendEvent(t, ev);
|
|
|
|
if (now) {
|
|
#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
|
|
std::cerr << "AlsaDriver::processSoftSynthEventOut: setting haveAsyncAudioEvent" << std::endl;
|
|
#endif
|
|
|
|
m_jackDriver->setHaveAsyncAudioEvent();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AlsaDriver::startClocks()
|
|
{
|
|
int result;
|
|
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::startClocks" << std::endl;
|
|
#endif
|
|
|
|
if (m_needJackStart) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: Need JACK start (m_playing = " << m_playing << ")" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
// New JACK transport scheme: The initialisePlayback,
|
|
// resetPlayback and stopPlayback methods set m_needJackStart, and
|
|
// then this method checks it and calls the appropriate JACK
|
|
// transport start or relocate method, which calls back on
|
|
// startClocksApproved when ready. (Previously this method always
|
|
// called the JACK transport start method, so we couldn't handle
|
|
// moving the pointer when not playing, and we had to stop the
|
|
// transport explicitly from resetPlayback when repositioning
|
|
// during playback.)
|
|
|
|
if (m_jackDriver) {
|
|
|
|
// Don't need any locks on this, except for those that the
|
|
// driver methods take and hold for themselves
|
|
|
|
if (m_needJackStart != NeedNoJackStart) {
|
|
if (m_needJackStart == NeedJackStart ||
|
|
m_playing) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: playing, prebuffer audio" << std::endl;
|
|
#endif
|
|
|
|
m_jackDriver->prebufferAudio();
|
|
} else {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: prepare audio only" << std::endl;
|
|
#endif
|
|
|
|
m_jackDriver->prepareAudio();
|
|
}
|
|
bool rv;
|
|
if (m_needJackStart == NeedJackReposition) {
|
|
rv = m_jackDriver->relocateTransport();
|
|
} else {
|
|
rv = m_jackDriver->startTransport();
|
|
if (!rv) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: Waiting for startClocksApproved" << std::endl;
|
|
#endif
|
|
// need to wait for transport sync
|
|
_debug_jack_frame_count = m_jackDriver->getFramesProcessed();
|
|
return ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Restart the timer
|
|
if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
|
|
std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
|
|
<< snd_strerror(result)
|
|
<< std::endl;
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
}
|
|
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: started clocks" << std::endl;
|
|
#endif
|
|
|
|
m_queueRunning = true;
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (m_jackDriver) {
|
|
_debug_jack_frame_count = m_jackDriver->getFramesProcessed();
|
|
}
|
|
#endif
|
|
|
|
// process pending MIDI events
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocks(): draining");
|
|
}
|
|
|
|
void
|
|
AlsaDriver::startClocksApproved()
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::startClocks: startClocksApproved" << std::endl;
|
|
#endif
|
|
|
|
//!!!
|
|
m_needJackStart = NeedNoJackStart;
|
|
startClocks();
|
|
return ;
|
|
|
|
int result;
|
|
|
|
// Restart the timer
|
|
if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
|
|
std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
|
|
<< snd_strerror(result)
|
|
<< std::endl;
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
}
|
|
|
|
m_queueRunning = true;
|
|
|
|
// process pending MIDI events
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocksApproved(): draining");
|
|
}
|
|
|
|
void
|
|
AlsaDriver::stopClocks()
|
|
{
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::stopClocks" << std::endl;
|
|
#endif
|
|
|
|
if (checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "stopClocks(): stopping queue") < 0) {
|
|
reportFailure(MappedEvent::FailureALSACallFailed);
|
|
}
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to stop queue");
|
|
|
|
m_queueRunning = false;
|
|
|
|
// We used to call m_jackDriver->stop() from here, but we no
|
|
// longer do -- it's now called from stopPlayback() so as to
|
|
// handle repositioning during playback (when stopClocks is
|
|
// necessary but stopPlayback and m_jackDriver->stop() are not).
|
|
|
|
snd_seq_event_t event;
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_real_time_t z = { 0, 0 };
|
|
snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
|
|
snd_seq_ev_set_direct(&event);
|
|
checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
|
|
0, &event), "stopClocks(): setting zpos to queue");
|
|
// process that
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to zpos queue");
|
|
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::stopClocks: ALSA time now is " << getAlsaTime() << std::endl;
|
|
#endif
|
|
|
|
m_alsaPlayStartTime = RealTime::zeroTime;
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::processEventsOut(const MappedComposition &mC)
|
|
{
|
|
processEventsOut(mC, RealTime::zeroTime, RealTime::zeroTime);
|
|
}
|
|
|
|
void
|
|
AlsaDriver::processEventsOut(const MappedComposition &mC,
|
|
const RealTime &sliceStart,
|
|
const RealTime &sliceEnd)
|
|
{
|
|
// special case for unqueued events
|
|
bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
|
|
|
|
if (m_startPlayback) {
|
|
m_startPlayback = false;
|
|
// This only records whether we're playing in principle,
|
|
// not whether the clocks are actually ticking. Contrariwise,
|
|
// areClocksRunning tells us whether the clocks are ticking
|
|
// but not whether we're actually playing (the clocks go even
|
|
// when we're not). Check both if you want to know whether
|
|
// we're really rolling.
|
|
m_playing = true;
|
|
|
|
if (getMTCStatus() == TRANSPORT_SLAVE) {
|
|
tweakSkewForMTC(0);
|
|
}
|
|
}
|
|
|
|
AudioFile *audioFile = 0;
|
|
bool haveNewAudio = false;
|
|
|
|
// insert audio events if we find them
|
|
for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
// Play an audio file
|
|
//
|
|
if ((*i)->getType() == MappedEvent::Audio) {
|
|
if (!m_jackDriver)
|
|
continue;
|
|
|
|
// This is used for handling asynchronous
|
|
// (i.e. unexpected) audio events only
|
|
|
|
if ((*i)->getEventTime() > RealTime( -120, 0)) {
|
|
// Not an asynchronous event
|
|
continue;
|
|
}
|
|
|
|
// Check for existence of file - if the sequencer has died
|
|
// and been restarted then we're not always loaded up with
|
|
// the audio file references we should have. In the future
|
|
// we could make this just get the gui to reload our files
|
|
// when (or before) this fails.
|
|
//
|
|
audioFile = getAudioFile((*i)->getAudioID());
|
|
|
|
if (audioFile) {
|
|
MappedAudioFader *fader =
|
|
dynamic_cast<MappedAudioFader*>
|
|
(getMappedStudio()->getAudioFader((*i)->getInstrument()));
|
|
|
|
if (!fader) {
|
|
std::cerr << "WARNING: AlsaDriver::processEventsOut: no fader for audio instrument " << (*i)->getInstrument() << std::endl;
|
|
continue;
|
|
}
|
|
|
|
unsigned int channels = fader->getPropertyList(
|
|
MappedAudioFader::Channels)[0].toInt();
|
|
|
|
RealTime bufferLength = getAudioReadBufferLength();
|
|
int bufferFrames = RealTime::realTime2Frame
|
|
(bufferLength, m_jackDriver->getSampleRate());
|
|
if (bufferFrames % m_jackDriver->getBufferSize()) {
|
|
bufferFrames /= m_jackDriver->getBufferSize();
|
|
bufferFrames ++;
|
|
bufferFrames *= m_jackDriver->getBufferSize();
|
|
}
|
|
|
|
//#define DEBUG_PLAYING_AUDIO
|
|
#ifdef DEBUG_PLAYING_AUDIO
|
|
std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << (*i)->getEventTime() << ", time now " << getAlsaTime() << ", start marker " << (*i)->getAudioStartMarker() << ", duration " << (*i)->getDuration() << ", instrument " << (*i)->getInstrument() << " channels " << channels << std::endl;
|
|
|
|
std::cout << "Read buffer length is " << bufferLength << " (" << bufferFrames << " frames)" << std::endl;
|
|
#endif
|
|
|
|
PlayableAudioFile *paf = 0;
|
|
|
|
try {
|
|
paf = new PlayableAudioFile((*i)->getInstrument(),
|
|
audioFile,
|
|
getSequencerTime() +
|
|
(RealTime(1, 0) / 4),
|
|
(*i)->getAudioStartMarker(),
|
|
(*i)->getDuration(),
|
|
bufferFrames,
|
|
getSmallFileSize() * 1024,
|
|
channels,
|
|
m_jackDriver->getSampleRate());
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
|
|
if ((*i)->isAutoFading()) {
|
|
paf->setAutoFade(true);
|
|
paf->setFadeInTime((*i)->getFadeInTime());
|
|
paf->setFadeOutTime((*i)->getFadeInTime());
|
|
|
|
//#define DEBUG_AUTOFADING
|
|
#ifdef DEBUG_AUTOFADING
|
|
|
|
std::cout << "PlayableAudioFile is AUTOFADING - "
|
|
<< "in = " << (*i)->getFadeInTime()
|
|
<< ", out = " << (*i)->getFadeOutTime()
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
#ifdef DEBUG_AUTOFADING
|
|
else {
|
|
std::cout << "PlayableAudioFile has no AUTOFADE"
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
|
|
|
|
// segment runtime id
|
|
paf->setRuntimeSegmentId((*i)->getRuntimeSegmentId());
|
|
|
|
m_audioQueue->addUnscheduled(paf);
|
|
|
|
haveNewAudio = true;
|
|
} else {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "can't find audio file reference"
|
|
<< std::endl;
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "try reloading the current Rosegarden file"
|
|
<< std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
// Cancel a playing audio file preview (this is predicated on
|
|
// runtime segment ID and optionally start time)
|
|
//
|
|
if ((*i)->getType() == MappedEvent::AudioCancel) {
|
|
cancelAudioFile(*i);
|
|
}
|
|
|
|
#endif // HAVE_LIBJACK
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemMIDIClock) {
|
|
switch ((int)(*i)->getData1()) {
|
|
case 0:
|
|
m_midiClockEnabled = false;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden MIDI CLOCK, START and STOP DISABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMIDISyncStatus(TRANSPORT_OFF);
|
|
break;
|
|
|
|
case 1:
|
|
m_midiClockEnabled = true;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden send MIDI CLOCK, START and STOP ENABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMIDISyncStatus(TRANSPORT_MASTER);
|
|
break;
|
|
|
|
case 2:
|
|
m_midiClockEnabled = false;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden accept START and STOP ENABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMIDISyncStatus(TRANSPORT_SLAVE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemMIDISyncAuto) {
|
|
if ((*i)->getData1()) {
|
|
m_midiSyncAutoConnect = true;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden MIDI SYNC AUTO ENABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
|
|
dpmi != m_devicePortMap.end(); ++dpmi) {
|
|
snd_seq_connect_to(m_midiHandle,
|
|
m_syncOutputPort,
|
|
dpmi->second.first,
|
|
dpmi->second.second);
|
|
}
|
|
} else {
|
|
m_midiSyncAutoConnect = false;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden MIDI SYNC AUTO DISABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
// Set the JACK transport
|
|
if ((*i)->getType() == MappedEvent::SystemJackTransport) {
|
|
bool enabled = false;
|
|
bool master = false;
|
|
|
|
switch ((int)(*i)->getData1()) {
|
|
case 2:
|
|
master = true;
|
|
enabled = true;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden to follow JACK transport and request JACK timebase master role (not yet implemented)"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case 1:
|
|
enabled = true;
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden to follow JACK transport"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden to ignore JACK transport"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
if (m_jackDriver) {
|
|
m_jackDriver->setTransportEnabled(enabled);
|
|
m_jackDriver->setTransportMaster(master);
|
|
}
|
|
}
|
|
#endif // HAVE_LIBJACK
|
|
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemMMCTransport) {
|
|
switch ((int)(*i)->getData1()) {
|
|
case 1:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden is MMC MASTER"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMMCStatus(TRANSPORT_MASTER);
|
|
break;
|
|
|
|
case 2:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden is MMC SLAVE"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMMCStatus(TRANSPORT_SLAVE);
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden MMC Transport DISABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMMCStatus(TRANSPORT_OFF);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemMTCTransport) {
|
|
switch ((int)(*i)->getData1()) {
|
|
case 1:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden is MTC MASTER"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMTCStatus(TRANSPORT_MASTER);
|
|
tweakSkewForMTC(0);
|
|
m_mtcFirstTime = -1;
|
|
break;
|
|
|
|
case 2:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden is MTC SLAVE"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMTCStatus(TRANSPORT_SLAVE);
|
|
m_mtcFirstTime = -1;
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "Rosegarden MTC Transport DISABLED"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
setMTCStatus(TRANSPORT_OFF);
|
|
m_mtcFirstTime = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemRecordDevice) {
|
|
DeviceId recordDevice =
|
|
(DeviceId)((*i)->getData1());
|
|
bool conn = (bool) ((*i)->getData2());
|
|
|
|
// Unset connections
|
|
//
|
|
// unsetRecordDevices();
|
|
|
|
// Special case to set for all record ports
|
|
//
|
|
if (recordDevice == Device::ALL_DEVICES) {
|
|
/* set all record devices */
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "set all record devices - not implemented"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
/*
|
|
MappedDeviceList::iterator it = m_devices.begin();
|
|
std::vector<int> ports;
|
|
std::vector<int>::iterator pIt;
|
|
|
|
for (; it != m_devices.end(); ++it)
|
|
{
|
|
std::cout << "DEVICE = " << (*it)->getName() << " - DIR = "
|
|
<< (*it)->getDirection() << endl;
|
|
// ignore ports we can't connect to
|
|
if ((*it)->getDirection() == MidiDevice::WriteOnly) continue;
|
|
|
|
std::cout << "PORTS = " << ports.size() << endl;
|
|
ports = (*it)->getPorts();
|
|
for (pIt = ports.begin(); pIt != ports.end(); ++pIt)
|
|
{
|
|
setRecordDevice((*it)->getClient(), *pIt);
|
|
}
|
|
}
|
|
*/
|
|
} else {
|
|
// Otherwise just for the one device and port
|
|
//
|
|
setRecordDevice(recordDevice, conn);
|
|
}
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemAudioPortCounts) {
|
|
// never actually used, I think?
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemAudioPorts) {
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
int data = (*i)->getData1();
|
|
m_jackDriver->setAudioPorts(data & MappedEvent::FaderOuts,
|
|
data & MappedEvent::SubmasterOuts);
|
|
}
|
|
#else
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "MappedEvent::SystemAudioPorts - no audio subsystem"
|
|
<< std::endl;
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::SystemAudioFileFormat) {
|
|
#ifdef HAVE_LIBJACK
|
|
int format = (*i)->getData1();
|
|
switch (format) {
|
|
case 0:
|
|
m_audioRecFileFormat = RIFFAudioFile::PCM;
|
|
break;
|
|
case 1:
|
|
m_audioRecFileFormat = RIFFAudioFile::FLOAT;
|
|
break;
|
|
default:
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "MappedEvent::SystemAudioFileFormat - unexpected format number " << format
|
|
<< std::endl;
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
#else
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::processEventsOut - "
|
|
<< "MappedEvent::SystemAudioFileFormat - no audio subsystem"
|
|
<< std::endl;
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
if ((*i)->getType() == MappedEvent::Panic) {
|
|
for (MappedDeviceList::iterator i = m_devices.begin();
|
|
i != m_devices.end(); ++i) {
|
|
if ((*i)->getDirection() == MidiDevice::Play) {
|
|
sendDeviceController((*i)->getId(),
|
|
MIDI_CONTROLLER_SUSTAIN, 0);
|
|
sendDeviceController((*i)->getId(),
|
|
MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
|
sendDeviceController((*i)->getId(),
|
|
MIDI_CONTROLLER_RESET, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process Midi and Audio
|
|
//
|
|
processMidiOut(mC, sliceStart, sliceEnd);
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (m_jackDriver) {
|
|
if (haveNewAudio) {
|
|
if (now) {
|
|
m_jackDriver->prebufferAudio();
|
|
m_jackDriver->setHaveAsyncAudioEvent();
|
|
}
|
|
if (m_queueRunning) {
|
|
m_jackDriver->kickAudio();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::record(RecordStatus recordStatus,
|
|
const std::vector<InstrumentId> *armedInstruments,
|
|
const std::vector<TQString> *audioFileNames)
|
|
{
|
|
m_recordingInstruments.clear();
|
|
|
|
if (recordStatus == RECORD_ON) {
|
|
// start recording
|
|
m_recordStatus = RECORD_ON;
|
|
m_alsaRecordStartTime = RealTime::zeroTime;
|
|
|
|
unsigned int audioCount = 0;
|
|
|
|
if (armedInstruments) {
|
|
|
|
for (unsigned int i = 0; i < armedInstruments->size(); ++i) {
|
|
|
|
InstrumentId id = (*armedInstruments)[i];
|
|
|
|
m_recordingInstruments.insert(id);
|
|
if (!audioFileNames || (audioCount >= audioFileNames->size())) {
|
|
continue;
|
|
}
|
|
|
|
TQString fileName = (*audioFileNames)[audioCount];
|
|
|
|
if (id >= AudioInstrumentBase &&
|
|
id < MidiInstrumentBase) {
|
|
|
|
bool good = false;
|
|
|
|
#ifdef DEBUG_ALSA
|
|
|
|
std::cerr << "AlsaDriver::record: Requesting new record file \"" << fileName << "\" for instrument " << id << std::endl;
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
|
|
if (m_jackDriver &&
|
|
m_jackDriver->openRecordFile(id, fileName.ascii())) {
|
|
good = true;
|
|
}
|
|
#endif
|
|
|
|
if (!good) {
|
|
m_recordStatus = RECORD_OFF;
|
|
std::cerr << "AlsaDriver::record: No JACK driver, or JACK driver failed to prepare for recording audio" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
++audioCount;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
if (recordStatus == RECORD_OFF) {
|
|
m_recordStatus = RECORD_OFF;
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
else {
|
|
std::cerr << "AlsaDriver::record - unsupported recording mode"
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
ClientPortPair
|
|
AlsaDriver::getFirstDestination(bool duplex)
|
|
{
|
|
ClientPortPair destPair( -1, -1);
|
|
AlsaPortList::iterator it;
|
|
|
|
for (it = m_alsaPorts.begin(); it != m_alsaPorts.end(); ++it) {
|
|
destPair.first = (*it)->m_client;
|
|
destPair.second = (*it)->m_port;
|
|
|
|
// If duplex port is required then choose first one
|
|
//
|
|
if (duplex) {
|
|
if ((*it)->m_direction == Duplex)
|
|
return destPair;
|
|
} else {
|
|
// If duplex port isn't required then choose first
|
|
// specifically non-duplex port (should be a synth)
|
|
//
|
|
if ((*it)->m_direction != Duplex)
|
|
return destPair;
|
|
}
|
|
}
|
|
|
|
return destPair;
|
|
}
|
|
|
|
|
|
// Sort through the ALSA client/port pairs for the range that
|
|
// matches the one we're querying. If none matches then send
|
|
// back -1 for each.
|
|
//
|
|
ClientPortPair
|
|
AlsaDriver::getPairForMappedInstrument(InstrumentId id)
|
|
{
|
|
MappedInstrument *instrument = getMappedInstrument(id);
|
|
if (instrument) {
|
|
DeviceId device = instrument->getDevice();
|
|
DevicePortMap::iterator i = m_devicePortMap.find(device);
|
|
if (i != m_devicePortMap.end()) {
|
|
return i->second;
|
|
}
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
/*
|
|
else
|
|
{
|
|
cerr << "WARNING: AlsaDriver::getPairForMappedInstrument: couldn't find instrument for id " << id << ", falling through" << endl;
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
return ClientPortPair( -1, -1);
|
|
}
|
|
|
|
int
|
|
AlsaDriver::getOutputPortForMappedInstrument(InstrumentId id)
|
|
{
|
|
MappedInstrument *instrument = getMappedInstrument(id);
|
|
if (instrument) {
|
|
DeviceId device = instrument->getDevice();
|
|
DeviceIntMap::iterator i = m_outputPorts.find(device);
|
|
if (i != m_outputPorts.end()) {
|
|
return i->second;
|
|
}
|
|
#ifdef DEBUG_ALSA
|
|
else {
|
|
cerr << "WARNING: AlsaDriver::getOutputPortForMappedInstrument: couldn't find output port for device for instrument " << id << ", falling through" << endl;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Send a direct controller to the specified port/client
|
|
//
|
|
void
|
|
AlsaDriver::sendDeviceController(DeviceId device,
|
|
MidiByte controller,
|
|
MidiByte value)
|
|
{
|
|
snd_seq_event_t event;
|
|
|
|
snd_seq_ev_clear(&event);
|
|
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
DeviceIntMap::iterator dimi = m_outputPorts.find(device);
|
|
if (dimi == m_outputPorts.end())
|
|
return ;
|
|
|
|
snd_seq_ev_set_source(&event, dimi->second);
|
|
snd_seq_ev_set_direct(&event);
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
snd_seq_ev_set_controller(&event,
|
|
i,
|
|
controller,
|
|
value);
|
|
snd_seq_event_output_direct(m_midiHandle, &event);
|
|
}
|
|
|
|
// we probably don't need this:
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendDeviceController(): draining");
|
|
}
|
|
|
|
void
|
|
AlsaDriver::processPending()
|
|
{
|
|
if (!m_playing) {
|
|
processNotesOff(getAlsaTime(), true);
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "processPending(): draining");
|
|
}
|
|
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
m_jackDriver->updateAudioData();
|
|
}
|
|
#endif
|
|
|
|
scavengePlugins();
|
|
m_audioQueueScavenger.scavenge();
|
|
}
|
|
|
|
void
|
|
AlsaDriver::insertMappedEventForReturn(MappedEvent *mE)
|
|
{
|
|
// Insert the event ready for return at the next opportunity.
|
|
//
|
|
m_returnComposition.insert(mE);
|
|
}
|
|
|
|
// check for recording status on any ALSA Port
|
|
//
|
|
bool
|
|
AlsaDriver::isRecording(AlsaPortDescription *port)
|
|
{
|
|
if (port->isReadable()) {
|
|
|
|
snd_seq_query_subscribe_t *qSubs;
|
|
snd_seq_addr_t rg_addr, sender_addr;
|
|
snd_seq_query_subscribe_alloca(&qSubs);
|
|
|
|
rg_addr.client = m_client;
|
|
rg_addr.port = m_inputPort;
|
|
|
|
snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
|
|
snd_seq_query_subscribe_set_index(qSubs, 0);
|
|
snd_seq_query_subscribe_set_root(qSubs, &rg_addr);
|
|
|
|
while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
|
|
sender_addr = *snd_seq_query_subscribe_get_addr(qSubs);
|
|
if (sender_addr.client == port->m_client &&
|
|
sender_addr.port == port->m_port)
|
|
return true;
|
|
|
|
snd_seq_query_subscribe_set_index(qSubs,
|
|
snd_seq_query_subscribe_get_index(qSubs) + 1);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::checkForNewClients()
|
|
{
|
|
Audit audit;
|
|
bool madeChange = false;
|
|
|
|
if (!m_portCheckNeeded)
|
|
return false;
|
|
|
|
AlsaPortList newPorts;
|
|
generatePortList(&newPorts); // updates m_alsaPorts, returns new ports as well
|
|
|
|
// If any devices have connections that no longer exist,
|
|
// clear those connections and stick them in the suspended
|
|
// port map in case they come back online later.
|
|
|
|
for (MappedDeviceList::iterator i = m_devices.begin();
|
|
i != m_devices.end(); ++i) {
|
|
|
|
ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
|
|
|
|
bool found = false;
|
|
for (AlsaPortList::iterator j = m_alsaPorts.begin();
|
|
j != m_alsaPorts.end(); ++j) {
|
|
if ((*j)->m_client == pair.first &&
|
|
(*j)->m_port == pair.second) {
|
|
if ((*i)->getDirection() == MidiDevice::Record) {
|
|
bool recState = isRecording(*j);
|
|
if (recState != (*i)->isRecording()) {
|
|
madeChange = true;
|
|
(*i)->setRecording(recState);
|
|
}
|
|
} else {
|
|
(*i)->setRecording(false);
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
m_suspendedPortMap[pair] = (*i)->getId();
|
|
m_devicePortMap[(*i)->getId()] = ClientPortPair( -1, -1);
|
|
setConnectionToDevice(**i, "");
|
|
(*i)->setRecording(false);
|
|
madeChange = true;
|
|
}
|
|
}
|
|
|
|
// If we've increased the number of connections, we need
|
|
// to assign the new connections to existing devices that
|
|
// have none, where possible, and create new devices for
|
|
// any left over.
|
|
|
|
if (newPorts.size() > 0) {
|
|
|
|
audit << "New ports:" << std::endl;
|
|
|
|
for (AlsaPortList::iterator i = newPorts.begin();
|
|
i != newPorts.end(); ++i) {
|
|
|
|
if ((*i)->m_client == m_client) {
|
|
audit << "(Ignoring own port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
|
|
continue;
|
|
} else if ((*i)->m_client == 0) {
|
|
audit << "(Ignoring system port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
audit << (*i)->m_name << std::endl;
|
|
|
|
TQString portName = (*i)->m_name.c_str();
|
|
ClientPortPair portPair = ClientPortPair((*i)->m_client,
|
|
(*i)->m_port);
|
|
|
|
if (m_suspendedPortMap.find(portPair) != m_suspendedPortMap.end()) {
|
|
|
|
DeviceId id = m_suspendedPortMap[portPair];
|
|
|
|
audit << "(Reusing suspended device " << id << ")" << std::endl;
|
|
|
|
for (MappedDeviceList::iterator j = m_devices.begin();
|
|
j != m_devices.end(); ++j) {
|
|
if ((*j)->getId() == id) {
|
|
setConnectionToDevice(**j, portName);
|
|
}
|
|
}
|
|
|
|
m_suspendedPortMap.erase(m_suspendedPortMap.find(portPair));
|
|
m_devicePortMap[id] = portPair;
|
|
madeChange = true;
|
|
continue;
|
|
}
|
|
|
|
bool needPlayDevice = true, needRecordDevice = true;
|
|
|
|
if ((*i)->isReadable()) {
|
|
for (MappedDeviceList::iterator j = m_devices.begin();
|
|
j != m_devices.end(); ++j) {
|
|
if ((*j)->getType() == Device::Midi &&
|
|
(*j)->getConnection() == "" &&
|
|
(*j)->getDirection() == MidiDevice::Record) {
|
|
audit << "(Reusing record device " << (*j)->getId()
|
|
<< ")" << std::endl;
|
|
m_devicePortMap[(*j)->getId()] = portPair;
|
|
setConnectionToDevice(**j, portName);
|
|
needRecordDevice = false;
|
|
madeChange = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
needRecordDevice = false;
|
|
}
|
|
|
|
if ((*i)->isWriteable()) {
|
|
for (MappedDeviceList::iterator j = m_devices.begin();
|
|
j != m_devices.end(); ++j) {
|
|
if ((*j)->getType() == Device::Midi &&
|
|
(*j)->getConnection() == "" &&
|
|
(*j)->getDirection() == MidiDevice::Play) {
|
|
audit << "(Reusing play device " << (*j)->getId()
|
|
<< ")" << std::endl;
|
|
m_devicePortMap[(*j)->getId()] = portPair;
|
|
setConnectionToDevice(**j, portName);
|
|
needPlayDevice = false;
|
|
madeChange = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
needPlayDevice = false;
|
|
}
|
|
|
|
if (needRecordDevice) {
|
|
MappedDevice *device = createMidiDevice(*i, MidiDevice::Record);
|
|
if (!device) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "WARNING: Failed to create record device" << std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
} else {
|
|
audit << "(Created new record device " << device->getId() << ")" << std::endl;
|
|
addInstrumentsForDevice(device);
|
|
m_devices.push_back(device);
|
|
madeChange = true;
|
|
}
|
|
}
|
|
|
|
if (needPlayDevice) {
|
|
MappedDevice *device = createMidiDevice(*i, MidiDevice::Play);
|
|
if (!device) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "WARNING: Failed to create play device" << std::endl;
|
|
#else
|
|
|
|
;
|
|
#endif
|
|
|
|
} else {
|
|
audit << "(Created new play device " << device->getId() << ")" << std::endl;
|
|
addInstrumentsForDevice(device);
|
|
m_devices.push_back(device);
|
|
madeChange = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If one of our ports is connected to a single other port and
|
|
// it isn't the one we thought, we should update our connection
|
|
|
|
for (MappedDeviceList::iterator i = m_devices.begin();
|
|
i != m_devices.end(); ++i) {
|
|
|
|
DevicePortMap::iterator j = m_devicePortMap.find((*i)->getId());
|
|
|
|
snd_seq_addr_t addr;
|
|
addr.client = m_client;
|
|
|
|
DeviceIntMap::iterator ii = m_outputPorts.find((*i)->getId());
|
|
if (ii == m_outputPorts.end())
|
|
continue;
|
|
addr.port = ii->second;
|
|
|
|
snd_seq_query_subscribe_t *subs;
|
|
snd_seq_query_subscribe_alloca(&subs);
|
|
snd_seq_query_subscribe_set_root(subs, &addr);
|
|
snd_seq_query_subscribe_set_index(subs, 0);
|
|
|
|
bool haveOurs = false;
|
|
int others = 0;
|
|
ClientPortPair firstOther;
|
|
|
|
while (!snd_seq_query_port_subscribers(m_midiHandle, subs)) {
|
|
|
|
const snd_seq_addr_t *otherEnd =
|
|
snd_seq_query_subscribe_get_addr(subs);
|
|
|
|
if (!otherEnd)
|
|
continue;
|
|
|
|
if (j != m_devicePortMap.end() &&
|
|
otherEnd->client == j->second.first &&
|
|
otherEnd->port == j->second.second) {
|
|
haveOurs = true;
|
|
} else {
|
|
++others;
|
|
firstOther = ClientPortPair(otherEnd->client, otherEnd->port);
|
|
}
|
|
|
|
snd_seq_query_subscribe_set_index
|
|
(subs, snd_seq_query_subscribe_get_index(subs) + 1);
|
|
}
|
|
|
|
if (haveOurs) { // leave our own connection alone, and stop worrying
|
|
continue;
|
|
|
|
} else {
|
|
if (others == 0) {
|
|
if (j != m_devicePortMap.end()) {
|
|
j->second = ClientPortPair( -1, -1);
|
|
setConnectionToDevice(**i, "");
|
|
madeChange = true;
|
|
}
|
|
} else {
|
|
for (AlsaPortList::iterator k = m_alsaPorts.begin();
|
|
k != m_alsaPorts.end(); ++k) {
|
|
if ((*k)->m_client == firstOther.first &&
|
|
(*k)->m_port == firstOther.second) {
|
|
m_devicePortMap[(*i)->getId()] = firstOther;
|
|
setConnectionToDevice(**i, (*k)->m_name.c_str(),
|
|
firstOther);
|
|
madeChange = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (madeChange) {
|
|
MappedEvent *mE =
|
|
new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
|
|
0, 0);
|
|
// send completion event
|
|
insertMappedEventForReturn(mE);
|
|
}
|
|
|
|
m_portCheckNeeded = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// From a DeviceId get a client/port pair for connecting as the
|
|
// MIDI record device.
|
|
//
|
|
void
|
|
AlsaDriver::setRecordDevice(DeviceId id, bool connectAction)
|
|
{
|
|
Audit audit;
|
|
|
|
// Locate a suitable port
|
|
//
|
|
if (m_devicePortMap.find(id) == m_devicePortMap.end()) {
|
|
#ifdef DEBUG_ALSA
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "couldn't match device id (" << id << ") to ALSA port"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
|
|
ClientPortPair pair = m_devicePortMap[id];
|
|
|
|
snd_seq_addr_t sender, dest;
|
|
sender.client = pair.first;
|
|
sender.port = pair.second;
|
|
|
|
for (MappedDeviceList::iterator i = m_devices.begin();
|
|
i != m_devices.end(); ++i) {
|
|
if ((*i)->getId() == id) {
|
|
if ((*i)->getDirection() == MidiDevice::Record) {
|
|
if ((*i)->isRecording() && connectAction) {
|
|
#ifdef DEBUG_ALSA
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "attempting to subscribe (" << id
|
|
<< ") already subscribed" << std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
if (!(*i)->isRecording() && !connectAction) {
|
|
#ifdef DEBUG_ALSA
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "attempting to unsubscribe (" << id
|
|
<< ") already unsubscribed" << std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
} else {
|
|
#ifdef DEBUG_ALSA
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "attempting to set play device (" << id
|
|
<< ") to record device" << std::endl;
|
|
#endif
|
|
|
|
return ;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
snd_seq_port_subscribe_t *subs;
|
|
snd_seq_port_subscribe_alloca(&subs);
|
|
|
|
dest.client = m_client;
|
|
dest.port = m_inputPort;
|
|
|
|
// Set destinations and senders
|
|
//
|
|
snd_seq_port_subscribe_set_sender(subs, &sender);
|
|
snd_seq_port_subscribe_set_dest(subs, &dest);
|
|
|
|
// subscribe or unsubscribe the port
|
|
//
|
|
if (connectAction) {
|
|
if (checkAlsaError(snd_seq_subscribe_port(m_midiHandle, subs),
|
|
"setRecordDevice - failed subscription of input port") < 0) {
|
|
// Not the end of the world if this fails but we
|
|
// have to flag it internally.
|
|
//
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< int(sender.client) << ":" << int(sender.port)
|
|
<< " failed to subscribe device "
|
|
<< id << " as record port" << std::endl;
|
|
} else {
|
|
m_midiInputPortConnected = true;
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "successfully subscribed device "
|
|
<< id << " as record port" << std::endl;
|
|
}
|
|
} else {
|
|
if (checkAlsaError(snd_seq_unsubscribe_port(m_midiHandle, subs),
|
|
"setRecordDevice - failed to unsubscribe a device") == 0)
|
|
audit << "AlsaDriver::setRecordDevice - "
|
|
<< "successfully unsubscribed device "
|
|
<< id << " as record port" << std::endl;
|
|
|
|
}
|
|
}
|
|
|
|
// Clear any record device connections
|
|
//
|
|
void
|
|
AlsaDriver::unsetRecordDevices()
|
|
{
|
|
snd_seq_addr_t dest;
|
|
dest.client = m_client;
|
|
dest.port = m_inputPort;
|
|
|
|
snd_seq_query_subscribe_t *qSubs;
|
|
snd_seq_addr_t tmp_addr;
|
|
snd_seq_query_subscribe_alloca(&qSubs);
|
|
|
|
tmp_addr.client = m_client;
|
|
tmp_addr.port = m_inputPort;
|
|
|
|
// Unsubsribe any existing connections
|
|
//
|
|
snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
|
|
snd_seq_query_subscribe_set_index(qSubs, 0);
|
|
snd_seq_query_subscribe_set_root(qSubs, &tmp_addr);
|
|
|
|
while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
|
|
tmp_addr = *snd_seq_query_subscribe_get_addr(qSubs);
|
|
|
|
snd_seq_port_subscribe_t *dSubs;
|
|
snd_seq_port_subscribe_alloca(&dSubs);
|
|
|
|
snd_seq_addr_t dSender;
|
|
dSender.client = tmp_addr.client;
|
|
dSender.port = tmp_addr.port;
|
|
|
|
snd_seq_port_subscribe_set_sender(dSubs, &dSender);
|
|
snd_seq_port_subscribe_set_dest(dSubs, &dest);
|
|
|
|
int error = snd_seq_unsubscribe_port(m_midiHandle, dSubs);
|
|
|
|
if (error < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::unsetRecordDevices - "
|
|
<< "can't unsubscribe record port" << std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
snd_seq_query_subscribe_set_index(qSubs,
|
|
snd_seq_query_subscribe_get_index(qSubs) + 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
AlsaDriver::sendMMC(MidiByte deviceArg,
|
|
MidiByte instruction,
|
|
bool isCommand,
|
|
const std::string &data)
|
|
{
|
|
snd_seq_event_t event;
|
|
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_ev_set_source(&event, m_syncOutputPort);
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
unsigned char dataArr[10] =
|
|
{ MIDI_SYSTEM_EXCLUSIVE,
|
|
MIDI_SYSEX_RT, deviceArg,
|
|
(isCommand ? MIDI_SYSEX_RT_COMMAND : MIDI_SYSEX_RT_RESPONSE),
|
|
instruction };
|
|
|
|
std::string dataString = std::string((const char *)dataArr) +
|
|
data + (char)MIDI_END_OF_EXCLUSIVE;
|
|
|
|
snd_seq_ev_set_sysex(&event, dataString.length(),
|
|
(char *)dataString.c_str());
|
|
|
|
event.queue = SND_SEQ_QUEUE_DIRECT;
|
|
|
|
checkAlsaError(snd_seq_event_output_direct(m_midiHandle, &event),
|
|
"sendMMC event send");
|
|
|
|
if (m_queueRunning) {
|
|
checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendMMC drain");
|
|
}
|
|
}
|
|
|
|
// Send a system real-time message from the sync output port
|
|
//
|
|
void
|
|
AlsaDriver::sendSystemDirect(MidiByte command, int *args)
|
|
{
|
|
snd_seq_event_t event;
|
|
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_ev_set_source(&event, m_syncOutputPort);
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
event.queue = SND_SEQ_QUEUE_DIRECT;
|
|
|
|
// set the command
|
|
event.type = command;
|
|
|
|
// set args if we have them
|
|
if (args) {
|
|
event.data.control.value = *args;
|
|
}
|
|
|
|
int error = snd_seq_event_output_direct(m_midiHandle, &event);
|
|
|
|
if (error < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::sendSystemDirect - "
|
|
<< "can't send event (" << int(command) << ")"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
// checkAlsaError(snd_seq_drain_output(m_midiHandle),
|
|
// "sendSystemDirect(): draining");
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::sendSystemQueued(MidiByte command,
|
|
const std::string &args,
|
|
const RealTime &time)
|
|
{
|
|
snd_seq_event_t event;
|
|
|
|
snd_seq_ev_clear(&event);
|
|
snd_seq_ev_set_source(&event, m_syncOutputPort);
|
|
snd_seq_ev_set_subs(&event);
|
|
|
|
snd_seq_real_time_t sendTime = { (unsigned int)time.sec, (unsigned int)time.nsec };
|
|
|
|
// Schedule the command
|
|
//
|
|
event.type = command;
|
|
|
|
snd_seq_ev_schedule_real(&event, m_queue, 0, &sendTime);
|
|
|
|
// set args if we have them
|
|
switch (args.length()) {
|
|
case 1:
|
|
event.data.control.value = args[0];
|
|
break;
|
|
|
|
case 2:
|
|
event.data.control.value = int(args[0]) | (int(args[1]) << 7);
|
|
break;
|
|
|
|
default: // do nothing
|
|
break;
|
|
}
|
|
|
|
int error = snd_seq_event_output(m_midiHandle, &event);
|
|
|
|
if (error < 0) {
|
|
#ifdef DEBUG_ALSA
|
|
std::cerr << "AlsaDriver::sendSystemQueued - "
|
|
<< "can't send event (" << int(command) << ")"
|
|
<< " - error = (" << error << ")"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
}
|
|
|
|
// if (m_queueRunning) {
|
|
// checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendSystemQueued(): draining");
|
|
// }
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::claimUnwantedPlugin(void *plugin)
|
|
{
|
|
m_pluginScavenger.claim((RunnablePluginInstance *)plugin);
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::scavengePlugins()
|
|
{
|
|
m_pluginScavenger.scavenge();
|
|
}
|
|
|
|
|
|
TQString
|
|
AlsaDriver::getStatusLog()
|
|
{
|
|
return TQString::fromUtf8(Audit::getAudit().c_str());
|
|
}
|
|
|
|
|
|
void
|
|
AlsaDriver::sleep(const RealTime &rt)
|
|
{
|
|
int npfd = snd_seq_poll_descriptors_count(m_midiHandle, POLLIN);
|
|
struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
|
|
snd_seq_poll_descriptors(m_midiHandle, pfd, npfd, POLLIN);
|
|
poll(pfd, npfd, rt.sec * 1000 + rt.msec());
|
|
}
|
|
|
|
void
|
|
AlsaDriver::runTasks()
|
|
{
|
|
#ifdef HAVE_LIBJACK
|
|
if (m_jackDriver) {
|
|
if (!m_jackDriver->isOK()) {
|
|
m_jackDriver->restoreIfRestorable();
|
|
}
|
|
}
|
|
|
|
if (m_doTimerChecks && m_timerRatioCalculated) {
|
|
|
|
double ratio = m_timerRatio;
|
|
m_timerRatioCalculated = false;
|
|
|
|
snd_seq_queue_tempo_t *q_ptr;
|
|
snd_seq_queue_tempo_alloca(&q_ptr);
|
|
|
|
snd_seq_get_queue_tempo(m_midiHandle, m_queue, q_ptr);
|
|
|
|
unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
|
|
#ifdef DEBUG_ALSA
|
|
|
|
unsigned int t_base = snd_seq_queue_tempo_get_skew_base(q_ptr);
|
|
if (!m_playing) {
|
|
std::cerr << "Skew: " << t_skew << "/" << t_base;
|
|
}
|
|
#endif
|
|
|
|
unsigned int newSkew = t_skew + (unsigned int)(t_skew * ratio);
|
|
|
|
if (newSkew != t_skew) {
|
|
#ifdef DEBUG_ALSA
|
|
if (!m_playing) {
|
|
std::cerr << " changed to " << newSkew << endl;
|
|
}
|
|
#endif
|
|
snd_seq_queue_tempo_set_skew(q_ptr, newSkew);
|
|
snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
|
|
} else {
|
|
#ifdef DEBUG_ALSA
|
|
if (!m_playing) {
|
|
std::cerr << endl;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
m_firstTimerCheck = true;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AlsaDriver::reportFailure(MappedEvent::FailureCode code)
|
|
{
|
|
//#define REPORT_XRUNS 1
|
|
#ifndef REPORT_XRUNS
|
|
if (code == MappedEvent::FailureXRuns ||
|
|
code == MappedEvent::FailureDiscUnderrun ||
|
|
code == MappedEvent::FailureBussMixUnderrun ||
|
|
code == MappedEvent::FailureMixUnderrun) {
|
|
return ;
|
|
}
|
|
#endif
|
|
|
|
// Ignore consecutive duplicates
|
|
if (_failureReportWriteIndex > 0 &&
|
|
_failureReportWriteIndex != _failureReportReadIndex) {
|
|
if (code == _failureReports[_failureReportWriteIndex - 1])
|
|
return ;
|
|
}
|
|
|
|
_failureReports[_failureReportWriteIndex] = code;
|
|
_failureReportWriteIndex =
|
|
(_failureReportWriteIndex + 1) % FAILURE_REPORT_COUNT;
|
|
}
|
|
|
|
std::string
|
|
AlsaDriver::getAlsaModuleVersionString()
|
|
{
|
|
FILE *v = fopen("/proc/asound/version", "r");
|
|
|
|
// Examples:
|
|
// Advanced Linux Sound Architecture Driver Version 1.0.14rc3.
|
|
// Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2008 UTC).
|
|
|
|
if (v) {
|
|
char buf[256];
|
|
fgets(buf, 256, v);
|
|
fclose(v);
|
|
|
|
std::string vs(buf);
|
|
std::string::size_type sp = vs.find_first_of('.');
|
|
if (sp > 0 && sp != std::string::npos) {
|
|
while (sp > 0 && isdigit(vs[sp-1])) --sp;
|
|
vs = vs.substr(sp);
|
|
if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
|
|
vs = vs.substr(0, vs.length()-1);
|
|
}
|
|
if (vs.length() > 0 && vs[vs.length()-1] == '.') {
|
|
vs = vs.substr(0, vs.length()-1);
|
|
}
|
|
return vs;
|
|
}
|
|
}
|
|
|
|
return "(unknown)";
|
|
}
|
|
|
|
std::string
|
|
AlsaDriver::getKernelVersionString()
|
|
{
|
|
FILE *v = fopen("/proc/version", "r");
|
|
|
|
if (v) {
|
|
char buf[256];
|
|
fgets(buf, 256, v);
|
|
fclose(v);
|
|
|
|
std::string vs(buf);
|
|
std::string key(" version ");
|
|
std::string::size_type sp = vs.find(key);
|
|
if (sp != std::string::npos) {
|
|
vs = vs.substr(sp + key.length());
|
|
sp = vs.find(' ');
|
|
if (sp != std::string::npos) {
|
|
vs = vs.substr(0, sp);
|
|
}
|
|
if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
|
|
vs = vs.substr(0, vs.length()-1);
|
|
}
|
|
return vs;
|
|
}
|
|
}
|
|
|
|
return "(unknown)";
|
|
}
|
|
|
|
void
|
|
AlsaDriver::extractVersion(std::string v, int &major, int &minor, int &subminor, std::string &suffix)
|
|
{
|
|
major = minor = subminor = 0;
|
|
suffix = "";
|
|
if (v == "(unknown)") return;
|
|
|
|
std::string::size_type sp, pp;
|
|
|
|
sp = v.find('.');
|
|
if (sp == std::string::npos) goto done;
|
|
major = atoi(v.substr(0, sp).c_str());
|
|
pp = sp + 1;
|
|
|
|
sp = v.find('.', pp);
|
|
if (sp == std::string::npos) goto done;
|
|
minor = atoi(v.substr(pp, sp - pp).c_str());
|
|
pp = sp + 1;
|
|
|
|
while (++sp < v.length() && (::isdigit(v[sp]) || v[sp] == '-'));
|
|
subminor = atoi(v.substr(pp, sp - pp).c_str());
|
|
|
|
if (sp >= v.length()) goto done;
|
|
suffix = v.substr(sp);
|
|
|
|
done:
|
|
std::cerr << "extractVersion: major = " << major << ", minor = " << minor << ", subminor = " << subminor << ", suffix = \"" << suffix << "\"" << std::endl;
|
|
}
|
|
|
|
bool
|
|
AlsaDriver::versionIsAtLeast(std::string v, int major, int minor, int subminor)
|
|
{
|
|
int actualMajor, actualMinor, actualSubminor;
|
|
std::string actualSuffix;
|
|
|
|
extractVersion(v, actualMajor, actualMinor, actualSubminor, actualSuffix);
|
|
|
|
bool ok = false;
|
|
|
|
if (actualMajor > major) {
|
|
ok = true;
|
|
} else if (actualMajor == major) {
|
|
if (actualMinor > minor) {
|
|
ok = true;
|
|
} else if (actualMinor == minor) {
|
|
if (actualSubminor > subminor) {
|
|
ok = true;
|
|
} else if (actualSubminor == subminor) {
|
|
if (strncmp(actualSuffix.c_str(), "rc", 2) &&
|
|
strncmp(actualSuffix.c_str(), "pre", 3)) {
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::cerr << "AlsaDriver::versionIsAtLeast: is version " << v << " at least " << major << "." << minor << "." << subminor << "? " << (ok ? "yes" : "no") << std::endl;
|
|
return ok;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endif // HAVE_ALSA
|