mirror of
https://github.com/VARCem/munt.git
synced 2026-07-08 18:16:13 +00:00
Reworked routing of MIDI streams and audio rendering
- Audio drivers now use SynthRoute objects and not QSynth directly. - MIDI drivers now supply a reference to the relevant MidiSession when pushing / playing MIDI messages thus making the MIDI stream identifiable. - SynthRoute now implements merging of multiple MIDI streams immediately before audio rendering pass, so that the resulting stream contains MIDI messages ordered by their timestamp frame-wise. This improves the timing accuracy in case more than one MIDI stream is connected to a SynthRoute.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -120,15 +120,16 @@ void AudioFileWriter::close() {
|
||||
file.close();
|
||||
}
|
||||
|
||||
AudioFileRenderer::AudioFileRenderer() : synth(NULL), buffer(NULL), parsers(NULL) {
|
||||
AudioFileRenderer::AudioFileRenderer() : buffer(NULL), parsers(NULL) {
|
||||
audioRenderer.synth = NULL;
|
||||
connect(this, SIGNAL(parsingFailed(const QString &, const QString &)), Master::getInstance(), SLOT(showBalloon(const QString &, const QString &)));
|
||||
}
|
||||
|
||||
AudioFileRenderer::~AudioFileRenderer() {
|
||||
stop();
|
||||
if (!realtimeMode && synth != NULL) {
|
||||
synth->close();
|
||||
delete synth;
|
||||
if (!realtimeMode && audioRenderer.synth != NULL) {
|
||||
audioRenderer.synth->close();
|
||||
delete audioRenderer.synth;
|
||||
}
|
||||
delete[] parsers;
|
||||
delete[] buffer;
|
||||
@@ -153,23 +154,23 @@ bool AudioFileRenderer::convertMIDIFiles(QString useOutFileName, QStringList mid
|
||||
}
|
||||
}
|
||||
parsers[parsersCount - 1].addChannelsReset();
|
||||
if (synth != NULL) {
|
||||
synth->close();
|
||||
delete synth;
|
||||
if (audioRenderer.synth != NULL) {
|
||||
audioRenderer.synth->close();
|
||||
delete audioRenderer.synth;
|
||||
}
|
||||
synth = new QSynth(this);
|
||||
audioRenderer.synth = new QSynth(this);
|
||||
sampleRate = 0;
|
||||
if (!synth->open(sampleRate, MT32Emu::SamplerateConversionQuality_BEST, synthProfileName)) {
|
||||
synth->close();
|
||||
delete synth;
|
||||
synth = NULL;
|
||||
if (!audioRenderer.synth->open(sampleRate, MT32Emu::SamplerateConversionQuality_BEST, synthProfileName)) {
|
||||
audioRenderer.synth->close();
|
||||
delete audioRenderer.synth;
|
||||
audioRenderer.synth = NULL;
|
||||
delete[] parsers;
|
||||
parsers = NULL;
|
||||
qDebug() << "AudioFileRenderer: Can't open synth";
|
||||
QMessageBox::critical(NULL, "Error", "Failed to open synth");
|
||||
return false;
|
||||
}
|
||||
Master::getInstance()->setAudioFileWriterSynth(synth);
|
||||
Master::getInstance()->setAudioFileWriterSynth(audioRenderer.synth);
|
||||
bufferSize = useBufferSize;
|
||||
outFileName = useOutFileName;
|
||||
realtimeMode = false;
|
||||
@@ -180,9 +181,9 @@ bool AudioFileRenderer::convertMIDIFiles(QString useOutFileName, QStringList mid
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioFileRenderer::startRealtimeProcessing(QSynth *useSynth, unsigned int useSampleRate, QString useOutFileName, unsigned int useBufferSize) {
|
||||
void AudioFileRenderer::startRealtimeProcessing(SynthRoute *useSynthRoute, unsigned int useSampleRate, QString useOutFileName, unsigned int useBufferSize) {
|
||||
if (useOutFileName.isEmpty()) return;
|
||||
synth = useSynth;
|
||||
audioRenderer.synthRoute = useSynthRoute;
|
||||
sampleRate = useSampleRate;
|
||||
bufferSize = useBufferSize;
|
||||
outFileName = useOutFileName;
|
||||
@@ -198,10 +199,25 @@ void AudioFileRenderer::stop() {
|
||||
wait();
|
||||
}
|
||||
|
||||
inline void AudioFileRenderer::closeAudioRenderer() {
|
||||
if (realtimeMode) {
|
||||
audioRenderer.synthRoute->audioStreamFailed();
|
||||
} else {
|
||||
audioRenderer.synth->close();
|
||||
}
|
||||
}
|
||||
|
||||
inline void AudioFileRenderer::render(qint16 *buffer, uint length) {
|
||||
if (realtimeMode) {
|
||||
audioRenderer.synthRoute->render(buffer, length);
|
||||
} else {
|
||||
audioRenderer.synth->render(buffer, length);
|
||||
}
|
||||
}
|
||||
void AudioFileRenderer::run() {
|
||||
AudioFileWriter writer(sampleRate, outFileName);
|
||||
if (!writer.open(!realtimeMode)) {
|
||||
synth->close();
|
||||
closeAudioRenderer();
|
||||
if (!realtimeMode) Master::getInstance()->setAudioFileWriterSynth(NULL);
|
||||
emit conversionFinished();
|
||||
return;
|
||||
@@ -243,10 +259,10 @@ void AudioFileRenderer::run() {
|
||||
}
|
||||
switch (e.getType()) {
|
||||
case SHORT_MESSAGE:
|
||||
eventPushed = synth->playMIDIShortMessage(e.getShortMessage(), nextEventFrames);
|
||||
eventPushed = audioRenderer.synth->playMIDIShortMessage(e.getShortMessage(), nextEventFrames);
|
||||
break;
|
||||
case SYSEX:
|
||||
eventPushed = synth->playMIDISysex(e.getSysexData(), e.getSysexLen(), nextEventFrames);
|
||||
eventPushed = audioRenderer.synth->playMIDISysex(e.getSysexData(), e.getSysexLen(), nextEventFrames);
|
||||
break;
|
||||
case SET_TEMPO:
|
||||
midiTick = parsers[parserIx].getMidiTick(e.getShortMessage());
|
||||
@@ -270,16 +286,16 @@ void AudioFileRenderer::run() {
|
||||
midiTick = parsers[parserIx].getMidiTick();
|
||||
continue;
|
||||
}
|
||||
if (!synth->isActive() && frameCount == 0) break;
|
||||
if (!audioRenderer.synth->isActive() && frameCount == 0) break;
|
||||
frameCount += bufferSize;
|
||||
qDebug() << "AudioFileRenderer: Rendering after the end of MIDI file, time:" << (double)midiNanos / MasterClock::NANOS_PER_SECOND;
|
||||
}
|
||||
}
|
||||
while (frameCount > 0) {
|
||||
uint framesToRender = qMin(bufferSize, frameCount);
|
||||
synth->render(buffer, framesToRender);
|
||||
render(buffer, framesToRender);
|
||||
if (!writer.write(buffer, framesToRender)) {
|
||||
synth->close();
|
||||
closeAudioRenderer();
|
||||
if (!realtimeMode) Master::getInstance()->setAudioFileWriterSynth(NULL);
|
||||
emit conversionFinished();
|
||||
return;
|
||||
@@ -292,7 +308,7 @@ void AudioFileRenderer::run() {
|
||||
qDebug() << "AudioFileRenderer: Rendering finished";
|
||||
if (!realtimeMode) qDebug() << "AudioFileRenderer: Elapsed seconds: " << 1e-9 * (MasterClock::getClockNanos() - startNanos);
|
||||
writer.close();
|
||||
synth->close();
|
||||
closeAudioRenderer();
|
||||
if (!realtimeMode) Master::getInstance()->setAudioFileWriterSynth(NULL);
|
||||
if (!stopProcessing) emit conversionFinished();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ private:
|
||||
|
||||
class MidiParser;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
|
||||
class AudioFileRenderer : public QThread {
|
||||
Q_OBJECT
|
||||
@@ -33,14 +34,14 @@ public:
|
||||
~AudioFileRenderer();
|
||||
|
||||
bool convertMIDIFiles(QString useOutFileName, QStringList useMIDIFileNameList, QString synthProfileName, quint32 bufferSize = 65536);
|
||||
void startRealtimeProcessing(QSynth *useSynth, quint32 useSampleRate, QString useOutFileName, quint32 bufferSize);
|
||||
void startRealtimeProcessing(SynthRoute *synthRoute, quint32 useSampleRate, QString useOutFileName, quint32 bufferSize);
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
QSynth *synth;
|
||||
union {
|
||||
QSynth *synth;
|
||||
SynthRoute *synthRoute;
|
||||
} audioRenderer;
|
||||
uint sampleRate;
|
||||
QString outFileName;
|
||||
unsigned int bufferSize;
|
||||
@@ -50,6 +51,10 @@ private:
|
||||
bool realtimeMode;
|
||||
volatile bool stopProcessing;
|
||||
|
||||
inline void closeAudioRenderer();
|
||||
inline void render(qint16 *buffer, uint length);
|
||||
void run();
|
||||
|
||||
signals:
|
||||
void parsingFailed(const QString &, const QString &);
|
||||
void midiEventProcessed(int midiEventsProcessed, int midiEventsTotal);
|
||||
|
||||
@@ -20,16 +20,18 @@
|
||||
using namespace MT32Emu;
|
||||
|
||||
MidiSession::MidiSession(QObject *parent, MidiDriver *useMidiDriver, QString useName, SynthRoute *useSynthRoute) :
|
||||
QObject(parent), midiDriver(useMidiDriver), name(useName), synthRoute(useSynthRoute), qMidiStreamParser(NULL)
|
||||
QObject(parent), midiDriver(useMidiDriver), name(useName), synthRoute(useSynthRoute),
|
||||
qMidiStreamParser(), qMidiBuffer()
|
||||
{}
|
||||
|
||||
MidiSession::~MidiSession() {
|
||||
if (qMidiStreamParser != NULL) delete qMidiStreamParser;
|
||||
delete qMidiStreamParser;
|
||||
delete qMidiBuffer;
|
||||
}
|
||||
|
||||
QMidiStreamParser *MidiSession::getQMidiStreamParser() {
|
||||
if (qMidiStreamParser == NULL) {
|
||||
qMidiStreamParser = new QMidiStreamParser(*synthRoute);
|
||||
qMidiStreamParser = new QMidiStreamParser(*this);
|
||||
}
|
||||
return qMidiStreamParser;
|
||||
}
|
||||
@@ -41,7 +43,7 @@ QMidiBuffer *MidiSession::getQMidiBuffer() {
|
||||
return qMidiBuffer;
|
||||
}
|
||||
|
||||
SynthRoute *MidiSession::getSynthRoute() {
|
||||
SynthRoute *MidiSession::getSynthRoute() const {
|
||||
return synthRoute;
|
||||
}
|
||||
|
||||
@@ -53,18 +55,18 @@ void MidiSession::setName(const QString &newName) {
|
||||
name = newName;
|
||||
}
|
||||
|
||||
QMidiStreamParser::QMidiStreamParser(SynthRoute &useSynthRoute) : synthRoute(useSynthRoute) {}
|
||||
QMidiStreamParser::QMidiStreamParser(MidiSession &useMidiSession) : midiSession(useMidiSession) {}
|
||||
|
||||
void QMidiStreamParser::setTimestamp(MasterClockNanos newTimestamp) {
|
||||
timestamp = newTimestamp;
|
||||
}
|
||||
|
||||
void QMidiStreamParser::handleShortMessage(const Bit32u message) {
|
||||
synthRoute.pushMIDIShortMessage(message, timestamp);
|
||||
midiSession.getSynthRoute()->pushMIDIShortMessage(midiSession, message, timestamp);
|
||||
}
|
||||
|
||||
void QMidiStreamParser::handleSysex(const Bit8u stream[], const Bit32u length) {
|
||||
synthRoute.pushMIDISysex(stream, length, timestamp);
|
||||
midiSession.getSynthRoute()->pushMIDISysex(midiSession, stream, length, timestamp);
|
||||
}
|
||||
|
||||
void QMidiStreamParser::handleSystemRealtimeMessage(const Bit8u realtime) {
|
||||
|
||||
@@ -25,14 +25,14 @@ private:
|
||||
public:
|
||||
QString getName();
|
||||
void setName(const QString &newName);
|
||||
SynthRoute *getSynthRoute();
|
||||
SynthRoute *getSynthRoute() const;
|
||||
QMidiStreamParser *getQMidiStreamParser();
|
||||
QMidiBuffer *getQMidiBuffer();
|
||||
};
|
||||
|
||||
class QMidiStreamParser : public MT32Emu::MidiStreamParser {
|
||||
public:
|
||||
QMidiStreamParser(SynthRoute &useSynthRoute);
|
||||
QMidiStreamParser(MidiSession &midiSession);
|
||||
void setTimestamp(MasterClockNanos newTimestamp);
|
||||
|
||||
protected:
|
||||
@@ -42,7 +42,7 @@ protected:
|
||||
void printDebug(const char *debugMessage);
|
||||
|
||||
private:
|
||||
SynthRoute &synthRoute;
|
||||
MidiSession &midiSession;
|
||||
MasterClockNanos timestamp;
|
||||
};
|
||||
|
||||
|
||||
@@ -955,7 +955,7 @@ bool QSynth::isRealtime() const {
|
||||
return realtimeHelper != NULL;
|
||||
}
|
||||
|
||||
void QSynth::setRealtime() {
|
||||
void QSynth::enableRealtime() {
|
||||
QMutexLocker synthLocker(synthMutex);
|
||||
synth->preallocateReverbMemory(true);
|
||||
synth->configureMIDIEventQueueSysexStorage(MAX_STREAM_BUFFER_SIZE);
|
||||
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
void close();
|
||||
void reset() const;
|
||||
bool isRealtime() const;
|
||||
void setRealtime();
|
||||
void enableRealtime();
|
||||
|
||||
void flushMIDIQueue() const;
|
||||
void playMIDIShortMessageNow(MT32Emu::Bit32u msg) const;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "SynthRoute.h"
|
||||
#include "MidiSession.h"
|
||||
#include "QMidiBuffer.h"
|
||||
#include "audiodrv/AudioDriver.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
@@ -35,6 +36,7 @@ SynthRoute::SynthRoute(QObject *parent) :
|
||||
state(SynthRouteState_CLOSED),
|
||||
qSynth(this),
|
||||
exclusiveMidiMode(),
|
||||
multiMidiMode(),
|
||||
audioDevice(NULL),
|
||||
audioStream(NULL),
|
||||
debugLastEventTimestamp(0)
|
||||
@@ -80,9 +82,9 @@ bool SynthRoute::open(AudioStreamFactory audioStreamFactory) {
|
||||
qDebug() << "Using sample rate:" << sampleRate;
|
||||
|
||||
if (exclusiveMidiMode && audioStreamFactory != NULL) {
|
||||
audioStream = audioStreamFactory(audioDevice, qSynth, sampleRate, midiSessions.first());
|
||||
audioStream = audioStreamFactory(audioDevice, *this, sampleRate, midiSessions.first());
|
||||
} else {
|
||||
audioStream = audioDevice->startAudioStream(qSynth, sampleRate);
|
||||
audioStream = audioDevice->startAudioStream(*this, sampleRate);
|
||||
}
|
||||
if (audioStream != NULL) {
|
||||
setState(SynthRouteState_OPEN);
|
||||
@@ -123,6 +125,7 @@ bool SynthRoute::enableExclusiveMidiMode(MidiSession *midiSession) {
|
||||
if (exclusiveMidiMode || hasMIDISessions()) return false;
|
||||
addMidiSession(midiSession);
|
||||
exclusiveMidiMode = true;
|
||||
qDebug() << "SynthRoute: exclusiveMidiMode enabled";
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,6 +134,7 @@ void SynthRoute::disableExclusiveMidiMode() {
|
||||
MidiSession *midiSession = midiSessions.first();
|
||||
removeMidiSession(midiSession);
|
||||
exclusiveMidiMode = false;
|
||||
qDebug() << "SynthRoute: exclusiveMidiMode disabled";
|
||||
emit exclusiveMidiSessionRemoved(midiSession);
|
||||
}
|
||||
}
|
||||
@@ -139,19 +143,32 @@ bool SynthRoute::isExclusiveMidiModeEnabled() {
|
||||
return exclusiveMidiMode;
|
||||
}
|
||||
|
||||
void SynthRoute::enableMultiMidiMode() {
|
||||
if (exclusiveMidiMode || multiMidiMode) return;
|
||||
multiMidiMode = true;
|
||||
qDebug() << "SynthRoute: started merging MIDI stream buffers";
|
||||
}
|
||||
|
||||
SynthRouteState SynthRoute::getState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
void SynthRoute::addMidiSession(MidiSession *midiSession) {
|
||||
if (exclusiveMidiMode) return;
|
||||
if (hasMIDISessions() && !multiMidiMode) enableMultiMidiMode();
|
||||
QMutexLocker midiSessionsLocker(&midiSessionsMutex);
|
||||
midiSessions.append(midiSession);
|
||||
emit midiSessionAdded(midiSession);
|
||||
}
|
||||
|
||||
void SynthRoute::removeMidiSession(MidiSession *midiSession) {
|
||||
QMutexLocker midiSessionsLocker(&midiSessionsMutex);
|
||||
midiSessions.removeOne(midiSession);
|
||||
emit midiSessionRemoved(midiSession);
|
||||
if (!hasMIDISessions() && multiMidiMode) {
|
||||
multiMidiMode = false;
|
||||
qDebug() << "SynthRoute: stopped merging MIDI stream buffers";
|
||||
}
|
||||
}
|
||||
|
||||
void SynthRoute::setMidiSessionName(MidiSession *midiSession, QString name) {
|
||||
@@ -200,7 +217,7 @@ bool SynthRoute::connectReportHandler(const char *signal, const QObject *receive
|
||||
return QObject::connect(qSynth.getReportHandler(), signal, receiver, slot);
|
||||
}
|
||||
|
||||
bool SynthRoute::pushMIDIShortMessage(Bit32u msg, MasterClockNanos refNanos) {
|
||||
bool SynthRoute::pushMIDIShortMessage(MidiSession &midiSession, Bit32u msg, MasterClockNanos refNanos) {
|
||||
recorder.recordShortMessage(msg, refNanos);
|
||||
AudioStream *stream = audioStream;
|
||||
if (stream == NULL) return false;
|
||||
@@ -215,19 +232,102 @@ bool SynthRoute::pushMIDIShortMessage(Bit32u msg, MasterClockNanos refNanos) {
|
||||
debugLastEventTimestamp = timestamp;
|
||||
return false;
|
||||
}
|
||||
return qSynth.playMIDIShortMessage(msg, timestamp);
|
||||
return playMIDIShortMessage(midiSession, msg, timestamp);
|
||||
}
|
||||
|
||||
bool SynthRoute::pushMIDISysex(const Bit8u *sysexData, unsigned int sysexLen, MasterClockNanos refNanos) {
|
||||
bool SynthRoute::pushMIDISysex(MidiSession &midiSession, const Bit8u *sysexData, unsigned int sysexLen, MasterClockNanos refNanos) {
|
||||
recorder.recordSysex(sysexData, sysexLen, refNanos);
|
||||
AudioStream *stream = audioStream;
|
||||
if (stream == NULL) return false;
|
||||
quint64 timestamp = stream->estimateMIDITimestamp(refNanos);
|
||||
return qSynth.playMIDISysex(sysexData, sysexLen, timestamp);
|
||||
return playMIDISysex(midiSession, sysexData, sysexLen, timestamp);
|
||||
}
|
||||
|
||||
bool SynthRoute::playMIDIShortMessage(MidiSession &midiSession, Bit32u msg, quint64 timestamp) {
|
||||
if (multiMidiMode) {
|
||||
QMidiBuffer *qMidiBuffer = midiSession.getQMidiBuffer();
|
||||
if (qMidiBuffer->pushShortMessage(timestamp, msg)) {
|
||||
qMidiBuffer->flush();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return qSynth.playMIDIShortMessage(msg, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
bool SynthRoute::playMIDISysex(MidiSession &midiSession, const Bit8u *sysex, Bit32u sysexLen, quint64 timestamp) {
|
||||
if (multiMidiMode) {
|
||||
QMidiBuffer *qMidiBuffer = midiSession.getQMidiBuffer();
|
||||
if (qMidiBuffer->pushSysexMessage(timestamp, sysexLen, sysex)) {
|
||||
qMidiBuffer->flush();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return qSynth.playMIDISysex(sysex, sysexLen, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void SynthRoute::mergeMidiStreams(uint renderingPassFrameLength) {
|
||||
QMutexLocker midiSessionsLocker(&midiSessionsMutex);
|
||||
QVarLengthArray<QMidiBuffer *, 16> streamBuffers;
|
||||
const quint64 renderingPassEndTimestamp = audioStream->computeMIDITimestamp(renderingPassFrameLength);
|
||||
|
||||
for (int i = 0; i < midiSessions.size(); i++) {
|
||||
QMidiBuffer *midiBuffer = midiSessions.at(i)->getQMidiBuffer();
|
||||
if (midiBuffer->retieveEvents() && midiBuffer->getEventTimestamp() < renderingPassEndTimestamp) {
|
||||
streamBuffers.append(midiBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
while (!streamBuffers.isEmpty()) {
|
||||
int nextEventBufferIx = 0;
|
||||
quint64 nextEventTimestamp = streamBuffers.at(nextEventBufferIx)->getEventTimestamp();
|
||||
for (int i = 1; i < streamBuffers.size(); i++) {
|
||||
quint64 eventTimestamp = streamBuffers.at(i)->getEventTimestamp();
|
||||
if (eventTimestamp < nextEventTimestamp) {
|
||||
nextEventBufferIx = i;
|
||||
nextEventTimestamp = eventTimestamp;
|
||||
}
|
||||
}
|
||||
QMidiBuffer *midiBuffer = streamBuffers.at(nextEventBufferIx);
|
||||
do {
|
||||
const uchar *sysexData;
|
||||
quint32 eventData = midiBuffer->getEventData(sysexData);
|
||||
if (sysexData == NULL) {
|
||||
qSynth.playMIDIShortMessage(eventData, nextEventTimestamp);
|
||||
} else {
|
||||
qSynth.playMIDISysex(sysexData, eventData, nextEventTimestamp);
|
||||
}
|
||||
if (!(midiBuffer->nextEvent() && midiBuffer->getEventTimestamp() < renderingPassEndTimestamp)) {
|
||||
streamBuffers.remove(nextEventBufferIx);
|
||||
break;
|
||||
}
|
||||
} while (midiBuffer->getEventTimestamp() == nextEventTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void SynthRoute::render(MT32Emu::Bit16s *buffer, uint length) {
|
||||
if (multiMidiMode) mergeMidiStreams(length);
|
||||
qSynth.render(buffer, length);
|
||||
}
|
||||
|
||||
void SynthRoute::render(float *buffer, uint length) {
|
||||
if (multiMidiMode) mergeMidiStreams(length);
|
||||
qSynth.render(buffer, length);
|
||||
}
|
||||
|
||||
void SynthRoute::audioStreamFailed() {
|
||||
qSynth.close();
|
||||
}
|
||||
|
||||
// QSynth delegation
|
||||
|
||||
void SynthRoute::enableRealtimeMode() {
|
||||
qSynth.enableRealtime();
|
||||
}
|
||||
|
||||
void SynthRoute::flushMIDIQueue() {
|
||||
qSynth.flushMIDIQueue();
|
||||
}
|
||||
@@ -240,14 +340,6 @@ void SynthRoute::playMIDISysexNow(const Bit8u *sysex, Bit32u sysexLen) {
|
||||
qSynth.playMIDISysexNow(sysex, sysexLen);
|
||||
}
|
||||
|
||||
bool SynthRoute::playMIDIShortMessage(Bit32u msg, quint64 timestamp) {
|
||||
return qSynth.playMIDIShortMessage(msg, timestamp);
|
||||
}
|
||||
|
||||
bool SynthRoute::playMIDISysex(const Bit8u *sysex, Bit32u sysexLen, quint64 timestamp) {
|
||||
return qSynth.playMIDISysex(sysex, sysexLen, timestamp);
|
||||
}
|
||||
|
||||
void SynthRoute::reset() {
|
||||
qSynth.reset();
|
||||
}
|
||||
|
||||
@@ -23,13 +23,15 @@ enum SynthRouteState {
|
||||
class SynthRoute : public QObject {
|
||||
Q_OBJECT
|
||||
private:
|
||||
typedef AudioStream *(*AudioStreamFactory)(const AudioDevice *, QSynth &, const uint, MidiSession *midiSession);
|
||||
typedef AudioStream *(*AudioStreamFactory)(const AudioDevice *, SynthRoute &, const uint, MidiSession *midiSession);
|
||||
|
||||
SynthRouteState state;
|
||||
QSynth qSynth;
|
||||
QList<MidiSession *> midiSessions;
|
||||
QMutex midiSessionsMutex;
|
||||
MidiRecorder recorder;
|
||||
bool exclusiveMidiMode;
|
||||
volatile bool multiMidiMode;
|
||||
|
||||
const AudioDevice *audioDevice;
|
||||
AudioStream *audioStream; // NULL until a stream is created
|
||||
@@ -48,6 +50,7 @@ public:
|
||||
void reset();
|
||||
bool enableExclusiveMidiMode(MidiSession *midiSession);
|
||||
bool isExclusiveMidiModeEnabled();
|
||||
void enableMultiMidiMode();
|
||||
|
||||
const QString getPatchName(int partNum) const;
|
||||
void getPartStates(bool *partStates) const;
|
||||
@@ -58,10 +61,16 @@ public:
|
||||
void flushMIDIQueue();
|
||||
void playMIDIShortMessageNow(MT32Emu::Bit32u msg);
|
||||
void playMIDISysexNow(const MT32Emu::Bit8u *sysex, MT32Emu::Bit32u sysexLen);
|
||||
bool playMIDIShortMessage(MT32Emu::Bit32u msg, quint64 timestamp);
|
||||
bool playMIDISysex(const MT32Emu::Bit8u *sysex, MT32Emu::Bit32u sysexLen, quint64 timestamp);
|
||||
bool pushMIDIShortMessage(MT32Emu::Bit32u msg, MasterClockNanos midiNanos);
|
||||
bool pushMIDISysex(const MT32Emu::Bit8u *sysex, unsigned int sysexLen, MasterClockNanos midiNanos);
|
||||
bool playMIDIShortMessage(MidiSession &midiSession, MT32Emu::Bit32u msg, quint64 timestamp);
|
||||
bool playMIDISysex(MidiSession &midiSession, const MT32Emu::Bit8u *sysex, MT32Emu::Bit32u sysexLen, quint64 timestamp);
|
||||
bool pushMIDIShortMessage(MidiSession &midiSession, MT32Emu::Bit32u msg, MasterClockNanos midiNanos);
|
||||
bool pushMIDISysex(MidiSession &midiSession, const MT32Emu::Bit8u *sysex, unsigned int sysexLen, MasterClockNanos midiNanos);
|
||||
void mergeMidiStreams(uint renderingPassFrameLength);
|
||||
void render(MT32Emu::Bit16s *buffer, uint length);
|
||||
void render(float *buffer, uint length);
|
||||
void audioStreamFailed();
|
||||
|
||||
void enableRealtimeMode();
|
||||
void setMasterVolume(int masterVolume);
|
||||
void setOutputGain(float outputGain);
|
||||
void setReverbOutputGain(float reverbOutputGain);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -29,7 +29,7 @@ static const unsigned int DEFAULT_AUDIO_LATENCY = 64;
|
||||
static const unsigned int DEFAULT_MIDI_LATENCY = 32;
|
||||
|
||||
AlsaAudioStream::AlsaAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), stream(NULL), processingThreadID(0), stopProcessing(false)
|
||||
AudioStream(useSettings, useSynth, useSampleRate), stream(NULL), processingThreadID(0), stopProcessing(false)
|
||||
{
|
||||
bufferSize = settings.chunkLen * sampleRate / MasterClock::MILLIS_PER_SECOND;
|
||||
buffer = new Bit16s[/* channels */ 2 * bufferSize];
|
||||
@@ -80,7 +80,7 @@ void *AlsaAudioStream::processingThread(void *userData) {
|
||||
if (isErrorOccured) {
|
||||
snd_pcm_close(audioStream.stream);
|
||||
audioStream.stream = NULL;
|
||||
audioStream.synth.close();
|
||||
audioStream.synthRoute.audioStreamFailed();
|
||||
} else {
|
||||
audioStream.stopProcessing = false;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ bool AlsaAudioStream::start(const char *deviceID) {
|
||||
|
||||
// Set Sample format to use
|
||||
error = snd_pcm_set_params(stream, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, /* channels */ 2,
|
||||
sampleRate, /* allow resampling */ 1, settings.audioLatency * MasterClock::MICROS_PER_MILLISECOND);
|
||||
sampleRate, /* allow resampling */ 1, settings.audioLatency * MasterClock::MICROS_PER_MILLISECOND);
|
||||
if (error < 0) {
|
||||
qDebug() << "snd_pcm_set_params failed:" << snd_strerror(error);
|
||||
snd_pcm_close(stream);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -18,8 +18,8 @@
|
||||
#include <QSettings>
|
||||
#include "../Master.h"
|
||||
|
||||
AudioStream::AudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
synth(useSynth), sampleRate(useSampleRate), settings(useSettings), renderedFramesCount(0), lastEstimatedPlayedFramesCount(0), resetScheduled(true)
|
||||
AudioStream::AudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
synthRoute(useSynthRoute), sampleRate(useSampleRate), settings(useSettings), renderedFramesCount(0), lastEstimatedPlayedFramesCount(0), resetScheduled(true)
|
||||
{
|
||||
audioLatencyFrames = settings.audioLatency * sampleRate / MasterClock::MILLIS_PER_SECOND;
|
||||
midiLatencyFrames = settings.midiLatency * sampleRate / MasterClock::MILLIS_PER_SECOND;
|
||||
@@ -51,6 +51,10 @@ quint64 AudioStream::estimateMIDITimestamp(const MasterClockNanos refNanos) {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
quint64 AudioStream::computeMIDITimestamp(uint relativeFrameTime) const {
|
||||
return renderedFramesCount + relativeFrameTime;
|
||||
}
|
||||
|
||||
void AudioStream::updateTimeInfo(const MasterClockNanos measuredNanos, const quint32 framesInAudioBuffer) {
|
||||
#if 0
|
||||
qDebug() << "R" << renderedFramesCount - timeInfo[timeInfoIx].lastPlayedFramesCount
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
#include "../MasterClock.h"
|
||||
|
||||
class AudioDriver;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
struct AudioDriverSettings;
|
||||
|
||||
class AudioStream {
|
||||
protected:
|
||||
QSynth &synth;
|
||||
SynthRoute &synthRoute;
|
||||
const quint32 sampleRate;
|
||||
const AudioDriverSettings &settings;
|
||||
quint32 audioLatencyFrames;
|
||||
@@ -37,9 +37,10 @@ protected:
|
||||
bool isAutoLatencyMode() const;
|
||||
|
||||
public:
|
||||
AudioStream(const AudioDriverSettings &settings, QSynth &synth, const quint32 sampleRate);
|
||||
AudioStream(const AudioDriverSettings &settings, SynthRoute &synthRoute, const quint32 sampleRate);
|
||||
virtual ~AudioStream() {}
|
||||
virtual quint64 estimateMIDITimestamp(const MasterClockNanos refNanos = 0);
|
||||
quint64 computeMIDITimestamp(uint relativeFrameTime) const;
|
||||
};
|
||||
|
||||
class AudioDevice {
|
||||
@@ -49,7 +50,7 @@ public:
|
||||
|
||||
AudioDevice(AudioDriver &driver, const QString name);
|
||||
virtual ~AudioDevice() {}
|
||||
virtual AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const = 0;
|
||||
virtual AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(const AudioDevice *)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -23,8 +23,8 @@
|
||||
static const unsigned int DEFAULT_AUDIO_LATENCY = 150;
|
||||
static const unsigned int DEFAULT_MIDI_LATENCY = 200;
|
||||
|
||||
AudioFileWriterStream::AudioFileWriterStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate) {}
|
||||
AudioFileWriterStream::AudioFileWriterStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate) {}
|
||||
|
||||
bool AudioFileWriterStream::start() {
|
||||
static QString currentDir = NULL;
|
||||
@@ -32,7 +32,7 @@ bool AudioFileWriterStream::start() {
|
||||
if (fileName.isEmpty()) return false;
|
||||
currentDir = QDir(fileName).absolutePath();
|
||||
timeInfo[0].lastPlayedNanos = MasterClock::getClockNanos();
|
||||
writer.startRealtimeProcessing(&synth, sampleRate, fileName, audioLatencyFrames);
|
||||
writer.startRealtimeProcessing(&synthRoute, sampleRate, fileName, audioLatencyFrames);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ quint64 AudioFileWriterStream::estimateMIDITimestamp(const MasterClockNanos refN
|
||||
AudioFileWriterDevice::AudioFileWriterDevice(AudioFileWriterDriver &driver, QString useDeviceName) :
|
||||
AudioDevice(driver, useDeviceName) {}
|
||||
|
||||
AudioStream *AudioFileWriterDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
AudioFileWriterStream *stream = new AudioFileWriterStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *AudioFileWriterDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
AudioFileWriterStream *stream = new AudioFileWriterStream(driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
if (stream->start()) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "../AudioFileWriter.h"
|
||||
|
||||
class Master;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class AudioFileWriterDriver;
|
||||
class AudioFileWriterDevice;
|
||||
|
||||
@@ -16,7 +16,7 @@ private:
|
||||
AudioFileRenderer writer;
|
||||
|
||||
public:
|
||||
AudioFileWriterStream(const AudioDriverSettings &settings, QSynth &useSynth, const quint32 useSampleRate);
|
||||
AudioFileWriterStream(const AudioDriverSettings &settings, SynthRoute &useSynthRoute, const quint32 useSampleRate);
|
||||
quint64 estimateMIDITimestamp(const MasterClockNanos refNanos = 0);
|
||||
bool start();
|
||||
void close();
|
||||
@@ -27,7 +27,7 @@ friend class AudioFileWriterDriver;
|
||||
private:
|
||||
AudioFileWriterDevice(AudioFileWriterDriver &driver, QString useDeviceName);
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class AudioFileWriterDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -23,8 +23,8 @@ static const uint DEFAULT_CHUNK_MS = 20;
|
||||
static const uint DEFAULT_AUDIO_LATENCY = 60;
|
||||
static const uint DEFAULT_MIDI_LATENCY = 30;
|
||||
|
||||
CoreAudioStream::CoreAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), audioQueue(NULL)
|
||||
CoreAudioStream::CoreAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate), audioQueue(NULL)
|
||||
{
|
||||
const uint bufferSize = (settings.chunkLen * sampleRate) / MasterClock::MILLIS_PER_SECOND;
|
||||
bufferByteSize = bufferSize << 2;
|
||||
@@ -66,7 +66,7 @@ void CoreAudioStream::renderOutputBuffer(void *userData, AudioQueueRef queue, Au
|
||||
stream->updateTimeInfo(nanosNow, framesInAudioBuffer);
|
||||
|
||||
uint frameCount = buffer->mAudioDataByteSize >> 2;
|
||||
stream->synth.render((MT32Emu::Bit16s *)buffer->mAudioData, frameCount);
|
||||
stream->synthRoute.render((MT32Emu::Bit16s *)buffer->mAudioData, frameCount);
|
||||
stream->renderedFramesCount += frameCount;
|
||||
|
||||
OSStatus res = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
|
||||
@@ -123,7 +123,7 @@ void CoreAudioStream::close() {
|
||||
CoreAudioDevice::CoreAudioDevice(CoreAudioDriver &driver) :
|
||||
AudioDevice(driver, "Default output device") {}
|
||||
|
||||
AudioStream *CoreAudioDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
AudioStream *CoreAudioDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
CoreAudioStream *stream = new CoreAudioStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
if (stream->start()) {
|
||||
return (AudioStream *)stream;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "AudioDriver.h"
|
||||
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class Master;
|
||||
class CoreAudioDriver;
|
||||
|
||||
@@ -19,7 +19,7 @@ private:
|
||||
static void renderOutputBuffer(void *userData, AudioQueueRef queue, AudioQueueBufferRef buffer);
|
||||
|
||||
public:
|
||||
CoreAudioStream(const AudioDriverSettings &settings, QSynth &synth, const quint32 sampleRate);
|
||||
CoreAudioStream(const AudioDriverSettings &settings, SynthRoute &synthRoute, const quint32 sampleRate);
|
||||
~CoreAudioStream();
|
||||
bool start();
|
||||
void close();
|
||||
@@ -31,7 +31,7 @@ private:
|
||||
CoreAudioDevice(CoreAudioDriver &driver);
|
||||
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class CoreAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -26,7 +26,7 @@ static const uint MINIMUM_JACK_BUFFER_COUNT = 2;
|
||||
static const uint FRAME_BYTE_SIZE = sizeof(float[CHANNEL_COUNT]);
|
||||
|
||||
class JACKAudioProcessor : QThread {
|
||||
QSynth &qsynth;
|
||||
SynthRoute &synthRoute;
|
||||
JACKRingBuffer * const buffer;
|
||||
volatile bool stopProcessing;
|
||||
|
||||
@@ -48,15 +48,15 @@ class JACKAudioProcessor : QThread {
|
||||
if (framesToRender == 0) {
|
||||
bufferDataRetrievals.acquire(currentRetrievals + 1);
|
||||
} else {
|
||||
qsynth.render(writePointer, framesToRender);
|
||||
synthRoute.render(writePointer, framesToRender);
|
||||
buffer->advanceWritePointer(framesToRender * FRAME_BYTE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
JACKAudioProcessor(QSynth &useQSynth, quint32 bufferSizeFrames) :
|
||||
qsynth(useQSynth),
|
||||
JACKAudioProcessor(SynthRoute &useSynthRoute, quint32 bufferSizeFrames) :
|
||||
synthRoute(useSynthRoute),
|
||||
// JACKRingBuffer needs a bit of spare space to accommodate the entire requested size.
|
||||
// Adding 1 FRAME_BYTE_SIZE does the trick yet ensures proper alignment of pointers.
|
||||
buffer(new JACKRingBuffer((bufferSizeFrames + 1) * FRAME_BYTE_SIZE)),
|
||||
@@ -91,8 +91,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
JACKAudioStream::JACKAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), jackClient(new JACKClient), buffer(), processor()
|
||||
JACKAudioStream::JACKAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate), jackClient(new JACKClient), buffer(), processor()
|
||||
{}
|
||||
|
||||
JACKAudioStream::~JACKAudioStream() {
|
||||
@@ -116,7 +116,7 @@ bool JACKAudioStream::start(MidiSession *midiSession) {
|
||||
// Use prerendering to prevent the realtime thread from locking, yet to retain complete functionality.
|
||||
// Additional latency of at least the JACK buffer length is introduced.
|
||||
if (audioLatencyFrames < jackBufferSize) audioLatencyFrames = jackBufferSize;
|
||||
processor = new JACKAudioProcessor(synth, audioLatencyFrames);
|
||||
processor = new JACKAudioProcessor(synthRoute, audioLatencyFrames);
|
||||
processor->start();
|
||||
qDebug() << "JACKAudioDriver: Configured prerendering audio buffer size (s):" << double(audioLatencyFrames) / sampleRate;
|
||||
} else {
|
||||
@@ -133,7 +133,7 @@ bool JACKAudioStream::start(MidiSession *midiSession) {
|
||||
// MIDI processing is synchronous, zero latency introduced
|
||||
midiLatencyFrames = 0;
|
||||
qDebug() << "JACKAudioDriver: Configured synchronous MIDI processing";
|
||||
if (jackClient->isRealtimeProcessing()) synth.setRealtime();
|
||||
if (jackClient->isRealtimeProcessing()) synthRoute.enableRealtimeMode();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -150,7 +150,7 @@ void JACKAudioStream::stop() {
|
||||
|
||||
void JACKAudioStream::onJACKShutdown() {
|
||||
qDebug() << "JACKAudioDriver: JACK server is shutting down, closing synth";
|
||||
synth.close();
|
||||
synthRoute.audioStreamFailed();
|
||||
}
|
||||
|
||||
void JACKAudioStream::renderStreams(const quint32 totalFrameCount, JACKAudioSample *leftOutBuffer, JACKAudioSample *rightOutBuffer) {
|
||||
@@ -180,7 +180,7 @@ void JACKAudioStream::renderStreams(const quint32 totalFrameCount, JACKAudioSamp
|
||||
} else {
|
||||
bufferPtr = buffer;
|
||||
framesToRender = qMin(framesLeft, MT32Emu::MAX_SAMPLES_PER_RUN);
|
||||
synth.render(buffer, framesToRender);
|
||||
synthRoute.render(buffer, framesToRender);
|
||||
}
|
||||
for (JACKAudioSample *leftOutBufferEnd = leftOutBuffer + framesToRender; leftOutBuffer < leftOutBufferEnd;) {
|
||||
*(leftOutBuffer++) = JACKAudioSample(*(bufferPtr++));
|
||||
@@ -192,20 +192,16 @@ void JACKAudioStream::renderStreams(const quint32 totalFrameCount, JACKAudioSamp
|
||||
renderedFramesCount += totalFrameCount;
|
||||
}
|
||||
|
||||
quint64 JACKAudioStream::computeMIDITimestamp(const quint32 jackBufferFrameTime) const {
|
||||
return renderedFramesCount + jackBufferFrameTime;
|
||||
}
|
||||
|
||||
JACKAudioDefaultDevice::JACKAudioDefaultDevice(JACKAudioDriver &useDriver) :
|
||||
AudioDevice(useDriver, "Default")
|
||||
{}
|
||||
|
||||
AudioStream *JACKAudioDefaultDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
return startAudioStream(this, synth, sampleRate, NULL);
|
||||
AudioStream *JACKAudioDefaultDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
return startAudioStream(this, synthRoute, sampleRate, NULL);
|
||||
}
|
||||
|
||||
AudioStream *JACKAudioDefaultDevice::startAudioStream(const AudioDevice *audioDevice, QSynth &synth, const uint sampleRate, MidiSession *midiSession) {
|
||||
JACKAudioStream *stream = new JACKAudioStream(audioDevice->driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *JACKAudioDefaultDevice::startAudioStream(const AudioDevice *audioDevice, SynthRoute &synthRoute, const uint sampleRate, MidiSession *midiSession) {
|
||||
JACKAudioStream *stream = new JACKAudioStream(audioDevice->driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
if (stream->start(midiSession)) return stream;
|
||||
delete stream;
|
||||
return NULL;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
typedef float JACKAudioSample;
|
||||
|
||||
class Master;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class JACKClient;
|
||||
class JACKAudioDriver;
|
||||
class JACKAudioProcessor;
|
||||
@@ -16,13 +16,12 @@ class MidiSession;
|
||||
|
||||
class JACKAudioStream : public AudioStream {
|
||||
public:
|
||||
JACKAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate);
|
||||
JACKAudioStream(const AudioDriverSettings &useSettings, SynthRoute &synthRoute, const quint32 useSampleRate);
|
||||
~JACKAudioStream();
|
||||
bool start(MidiSession *midiSession);
|
||||
void stop();
|
||||
void onJACKShutdown();
|
||||
void renderStreams(const quint32 frameCount, JACKAudioSample *leftOutBuffer, JACKAudioSample *rightOutBuffer);
|
||||
quint64 computeMIDITimestamp(const quint32 jackBufferFrameTime) const;
|
||||
|
||||
private:
|
||||
JACKClient * const jackClient;
|
||||
@@ -33,9 +32,9 @@ private:
|
||||
class JACKAudioDefaultDevice : public AudioDevice {
|
||||
friend class JACKAudioDriver;
|
||||
public:
|
||||
static AudioStream *startAudioStream(const AudioDevice *audioDevice, QSynth &synth, const uint sampleRate, MidiSession *midiSession);
|
||||
static AudioStream *startAudioStream(const AudioDevice *audioDevice, SynthRoute &synthRoute, const uint sampleRate, MidiSession *midiSession);
|
||||
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
|
||||
private:
|
||||
JACKAudioDefaultDevice(JACKAudioDriver &driver);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -34,8 +34,8 @@ static const unsigned int DEFAULT_AUDIO_LATENCY = 32;
|
||||
static const unsigned int DEFAULT_MIDI_LATENCY = 16;
|
||||
static const char deviceName[] = "/dev/dsp";
|
||||
|
||||
OSSAudioStream::OSSAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), buffer(NULL), stream(0), processingThreadID(0), stopProcessing(false)
|
||||
OSSAudioStream::OSSAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate), buffer(NULL), stream(0), processingThreadID(0), stopProcessing(false)
|
||||
{
|
||||
bufferSize = settings.chunkLen * sampleRate / MasterClock::MILLIS_PER_SECOND;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ void *OSSAudioStream::processingThread(void *userData) {
|
||||
if (isErrorOccured) {
|
||||
close(audioStream.stream);
|
||||
audioStream.stream = 0;
|
||||
audioStream.synth.close();
|
||||
audioStream.synthRoute.audioStreamFailed();
|
||||
} else {
|
||||
audioStream.stopProcessing = false;
|
||||
}
|
||||
@@ -232,8 +232,8 @@ void OSSAudioStream::stop() {
|
||||
|
||||
OSSAudioDefaultDevice::OSSAudioDefaultDevice(OSSAudioDriver &driver) : AudioDevice(driver, "Default") {}
|
||||
|
||||
AudioStream *OSSAudioDefaultDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
OSSAudioStream *stream = new OSSAudioStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *OSSAudioDefaultDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
OSSAudioStream *stream = new OSSAudioStream(driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
if (stream->start()) return stream;
|
||||
delete stream;
|
||||
return NULL;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "AudioDriver.h"
|
||||
|
||||
class Master;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class OSSAudioDriver;
|
||||
|
||||
class OSSAudioStream : public AudioStream {
|
||||
@@ -22,7 +22,7 @@ private:
|
||||
static void *processingThread(void *);
|
||||
|
||||
public:
|
||||
OSSAudioStream(const AudioDriverSettings &settings, QSynth &synth, const quint32 sampleRate);
|
||||
OSSAudioStream(const AudioDriverSettings &settings, SynthRoute &synthRoute, const quint32 sampleRate);
|
||||
~OSSAudioStream();
|
||||
bool start();
|
||||
void stop();
|
||||
@@ -32,7 +32,7 @@ class OSSAudioDefaultDevice : public AudioDevice {
|
||||
friend class OSSAudioDriver;
|
||||
OSSAudioDefaultDevice(OSSAudioDriver &driver);
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class OSSAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "PortAudioDriver.h"
|
||||
|
||||
#include "../Master.h"
|
||||
#include "../QSynth.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
@@ -58,8 +57,8 @@ static void dumpPortAudioDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
PortAudioStream::PortAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), stream(NULL) {}
|
||||
PortAudioStream::PortAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate), stream(NULL) {}
|
||||
|
||||
PortAudioStream::~PortAudioStream() {
|
||||
close();
|
||||
@@ -137,7 +136,7 @@ int PortAudioStream::paCallback(const void *inputBuffer, void *outputBuffer, uns
|
||||
}
|
||||
stream->updateTimeInfo(nanosNow, framesInAudioBuffer);
|
||||
|
||||
stream->synth.render((Bit16s *)outputBuffer, frameCount);
|
||||
stream->synthRoute.render((Bit16s *)outputBuffer, frameCount);
|
||||
stream->renderedFramesCount += frameCount;
|
||||
return paContinue;
|
||||
}
|
||||
@@ -152,10 +151,10 @@ void PortAudioStream::close() {
|
||||
}
|
||||
|
||||
PortAudioDevice::PortAudioDevice(PortAudioDriver &driver, int useDeviceIndex, QString useDeviceName) :
|
||||
AudioDevice(driver, useDeviceName), deviceIndex(useDeviceIndex) {}
|
||||
AudioDevice(driver, useDeviceName), deviceIndex(useDeviceIndex) {}
|
||||
|
||||
AudioStream *PortAudioDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
PortAudioStream *stream = new PortAudioStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *PortAudioDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
PortAudioStream *stream = new PortAudioStream(driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
if (stream->start(deviceIndex)) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "AudioDriver.h"
|
||||
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class Master;
|
||||
class PortAudioDriver;
|
||||
class PortAudioDevice;
|
||||
@@ -21,7 +21,7 @@ private:
|
||||
static int paCallback(const void *inputBuffer, void *outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData);
|
||||
|
||||
public:
|
||||
PortAudioStream(const AudioDriverSettings &settings, QSynth &synth, const quint32 sampleRate);
|
||||
PortAudioStream(const AudioDriverSettings &settings, SynthRoute &synthRoute, const quint32 sampleRate);
|
||||
~PortAudioStream();
|
||||
bool start(PaDeviceIndex deviceIndex);
|
||||
void close();
|
||||
@@ -34,7 +34,7 @@ private:
|
||||
PortAudioDevice(PortAudioDriver &driver, int useDeviceIndex, QString useDeviceName);
|
||||
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class PortAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -127,8 +127,8 @@ static bool loadLibrary(bool loadNeeded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PulseAudioStream::PulseAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate), stream(NULL), processingThreadID(0), stopProcessing(false)
|
||||
PulseAudioStream::PulseAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate), stream(NULL), processingThreadID(0), stopProcessing(false)
|
||||
{
|
||||
bufferSize = settings.chunkLen * sampleRate / MasterClock::MILLIS_PER_SECOND;
|
||||
buffer = new Bit16s[/* channels */ 2 * bufferSize];
|
||||
@@ -158,7 +158,7 @@ void *PulseAudioStream::processingThread(void *userData) {
|
||||
_pa_simple_free(audioStream.stream);
|
||||
audioStream.stream = NULL;
|
||||
qDebug() << "PulseAudio: Processing thread stopped";
|
||||
audioStream.synth.close();
|
||||
audioStream.synthRoute.audioStreamFailed();
|
||||
audioStream.processingThreadID = 0;
|
||||
return NULL;
|
||||
}
|
||||
@@ -248,8 +248,8 @@ void PulseAudioStream::close() {
|
||||
|
||||
PulseAudioDefaultDevice::PulseAudioDefaultDevice(PulseAudioDriver &driver) : AudioDevice(driver, "Default") {}
|
||||
|
||||
AudioStream *PulseAudioDefaultDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
PulseAudioStream *stream = new PulseAudioStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *PulseAudioDefaultDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
PulseAudioStream *stream = new PulseAudioStream(driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
if (stream->start()) return stream;
|
||||
delete stream;
|
||||
return NULL;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "AudioDriver.h"
|
||||
|
||||
class Master;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class PulseAudioDriver;
|
||||
|
||||
class PulseAudioStream : public AudioStream {
|
||||
@@ -24,7 +24,7 @@ private:
|
||||
static void *processingThread(void *);
|
||||
|
||||
public:
|
||||
PulseAudioStream(const AudioDriverSettings &settings, QSynth &synth, const quint32 sampleRate);
|
||||
PulseAudioStream(const AudioDriverSettings &settings, SynthRoute &synthRoute, const quint32 sampleRate);
|
||||
~PulseAudioStream();
|
||||
bool start();
|
||||
void close();
|
||||
@@ -34,7 +34,7 @@ class PulseAudioDefaultDevice : public AudioDevice {
|
||||
friend class PulseAudioDriver;
|
||||
PulseAudioDefaultDevice(PulseAudioDriver &driver);
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class PulseAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
}
|
||||
stream.updateTimeInfo(nanosNow, framesInAudioBuffer);
|
||||
uint framesToRender = uint(len >> 2);
|
||||
stream.synth.render((Bit16s *)data, framesToRender);
|
||||
stream.synthRoute.render((Bit16s *)data, framesToRender);
|
||||
stream.renderedFramesCount += framesToRender;
|
||||
return len;
|
||||
}
|
||||
@@ -79,8 +79,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
QtAudioStream::QtAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate)
|
||||
QtAudioStream::QtAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate)
|
||||
{
|
||||
// Creating QAudioOutput in a thread leads to smooth rendering
|
||||
// Rendering will be performed in the main thread otherwise
|
||||
@@ -128,8 +128,8 @@ void QtAudioStream::close() {
|
||||
|
||||
QtAudioDefaultDevice::QtAudioDefaultDevice(QtAudioDriver &driver) : AudioDevice(driver, "Default") {}
|
||||
|
||||
AudioStream *QtAudioDefaultDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
return new QtAudioStream(driver.getAudioSettings(), synth, sampleRate);
|
||||
AudioStream *QtAudioDefaultDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
return new QtAudioStream(driver.getAudioSettings(), synthRoute, sampleRate);
|
||||
}
|
||||
|
||||
QtAudioDriver::QtAudioDriver(Master *useMaster) : AudioDriver("qtaudio", "QtAudio") {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
class Master;
|
||||
class WaveGenerator;
|
||||
class QSynth;
|
||||
class SynthRoute;
|
||||
class QAudioOutput;
|
||||
class QtAudioDriver;
|
||||
|
||||
@@ -21,7 +21,7 @@ private:
|
||||
WaveGenerator *waveGenerator;
|
||||
|
||||
public:
|
||||
QtAudioStream(const AudioDriverSettings &useSettings, QSynth &useSynth, const quint32 useSampleRate);
|
||||
QtAudioStream(const AudioDriverSettings &useSettings, SynthRoute &useSynthRoute, const quint32 useSampleRate);
|
||||
~QtAudioStream();
|
||||
void start();
|
||||
void close();
|
||||
@@ -32,7 +32,7 @@ class QtAudioDefaultDevice : public AudioDevice {
|
||||
private:
|
||||
QtAudioDefaultDevice(QtAudioDriver &driver);
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class QtAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -29,8 +29,8 @@ static const DWORD FRAME_SIZE = 4;
|
||||
// Latency for MIDI processing. 15 ms is the offset of interprocess timeGetTime() difference.
|
||||
static const DWORD DEFAULT_MIDI_LATENCY = 15;
|
||||
|
||||
WinMMAudioStream::WinMMAudioStream(const AudioDriverSettings &useSettings, bool useRingBufferMode, QSynth &useSynth, const uint useSampleRate) :
|
||||
AudioStream(useSettings, useSynth, useSampleRate),
|
||||
WinMMAudioStream::WinMMAudioStream(const AudioDriverSettings &useSettings, bool useRingBufferMode, SynthRoute &useSynthRoute, const uint useSampleRate) :
|
||||
AudioStream(useSettings, useSynthRoute, useSampleRate),
|
||||
hWaveOut(NULL), waveHdr(NULL), hEvent(NULL), hWaitableTimer(NULL), stopProcessing(false),
|
||||
processor(*this), ringBufferMode(useRingBufferMode), prevPlayPosition(0L)
|
||||
{
|
||||
@@ -102,7 +102,7 @@ void WinMMAudioProcessor::run() {
|
||||
const DWORD playCursor = stream.getCurrentPlayPosition();
|
||||
if (playCursor == (DWORD)-1) {
|
||||
stream.stopProcessing = true;
|
||||
stream.synth.close();
|
||||
stream.synthRoute.audioStreamFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@ void WinMMAudioProcessor::run() {
|
||||
}
|
||||
DWORD framesInAudioBuffer = playCursor < renderPos ? renderPos - playCursor : (renderPos + stream.audioLatencyFrames) - playCursor;
|
||||
stream.updateTimeInfo(nanosNow, framesInAudioBuffer);
|
||||
stream.synth.render(buf, frameCount);
|
||||
stream.synthRoute.render(buf, frameCount);
|
||||
stream.renderedFramesCount += frameCount;
|
||||
if (!stream.ringBufferMode && waveOutWrite(stream.hWaveOut, waveHdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
|
||||
qDebug() << "WinMMAudioDriver: waveOutWrite failed, thread stopped";
|
||||
stream.stopProcessing = true;
|
||||
stream.synth.close();
|
||||
stream.synthRoute.audioStreamFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -264,9 +264,9 @@ WinMMAudioDevice::WinMMAudioDevice(WinMMAudioDriver &driver, int useDeviceIndex,
|
||||
AudioDevice(driver, useDeviceName), deviceIndex(useDeviceIndex) {
|
||||
}
|
||||
|
||||
AudioStream *WinMMAudioDevice::startAudioStream(QSynth &synth, const uint sampleRate) const {
|
||||
AudioStream *WinMMAudioDevice::startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const {
|
||||
WinMMAudioDriver &winDriver = (WinMMAudioDriver &)driver;
|
||||
WinMMAudioStream *stream = new WinMMAudioStream(winDriver.getAudioStreamSettings(), winDriver.isRingBufferMode(), synth, sampleRate);
|
||||
WinMMAudioStream *stream = new WinMMAudioStream(winDriver.getAudioStreamSettings(), winDriver.isRingBufferMode(), synthRoute, sampleRate);
|
||||
if (stream->start(deviceIndex)) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
|
||||
class Master;
|
||||
class SynthRoute;
|
||||
class WinMMAudioDriver;
|
||||
class WinMMAudioDevice;
|
||||
class WinMMAudioStream;
|
||||
@@ -32,7 +33,7 @@ class WinMMAudioStream : public AudioStream {
|
||||
friend class WinMMAudioProcessor;
|
||||
private:
|
||||
HWAVEOUT hWaveOut;
|
||||
WAVEHDR *waveHdr;
|
||||
WAVEHDR *waveHdr;
|
||||
HANDLE hEvent;
|
||||
HANDLE hWaitableTimer;
|
||||
|
||||
@@ -47,7 +48,7 @@ private:
|
||||
DWORD getCurrentPlayPosition();
|
||||
|
||||
public:
|
||||
WinMMAudioStream(const AudioDriverSettings &useSettings, bool ringBufferMode, QSynth &useSynth, uint useSampleRate);
|
||||
WinMMAudioStream(const AudioDriverSettings &useSettings, bool ringBufferMode, SynthRoute &synthRoute, uint useSampleRate);
|
||||
~WinMMAudioStream();
|
||||
bool start(int deviceIndex);
|
||||
void close();
|
||||
@@ -59,7 +60,7 @@ private:
|
||||
UINT deviceIndex;
|
||||
WinMMAudioDevice(WinMMAudioDriver &driver, int useDeviceIndex, QString useDeviceName);
|
||||
public:
|
||||
AudioStream *startAudioStream(QSynth &synth, const uint sampleRate) const;
|
||||
AudioStream *startAudioStream(SynthRoute &synthRoute, const uint sampleRate) const;
|
||||
};
|
||||
|
||||
class WinMMAudioDriver : public AudioDriver {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -92,7 +92,7 @@ void ALSAMidiDriver::processSeqEvents() {
|
||||
showBalloon("Connected application:", appName);
|
||||
qDebug() << "ALSAMidiDriver: Connected application" << appName;
|
||||
}
|
||||
if (processSeqEvent(seq_event, midiSession->getSynthRoute())) {
|
||||
if (processSeqEvent(seq_event, midiSession)) {
|
||||
break;
|
||||
}
|
||||
} while (!stopProcessing && snd_seq_event_input_pending(snd_seq, 1));
|
||||
@@ -134,7 +134,8 @@ MidiSession *ALSAMidiDriver::findMidiSessionForClient(unsigned int clientAddr) {
|
||||
return midiSessions.at(i);
|
||||
}
|
||||
|
||||
bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *synthRoute) {
|
||||
bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, MidiSession *midiSession) {
|
||||
SynthRoute *synthRoute = midiSession->getSynthRoute();
|
||||
MT32Emu::Bit32u msg = 0;
|
||||
switch(seq_event->type) {
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
@@ -142,7 +143,7 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
msg |= seq_event->data.note.channel;
|
||||
msg |= seq_event->data.note.note << 8;
|
||||
msg |= seq_event->data.note.velocity << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
@@ -150,7 +151,7 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
msg |= seq_event->data.note.channel;
|
||||
msg |= seq_event->data.note.note << 8;
|
||||
msg |= seq_event->data.note.velocity << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
@@ -158,7 +159,7 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
msg |= seq_event->data.control.channel;
|
||||
msg |= seq_event->data.control.param << 8;
|
||||
msg |= seq_event->data.control.value << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CONTROL14:
|
||||
@@ -169,7 +170,7 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
msg |= seq_event->data.control.channel;
|
||||
msg |= seq_event->data.control.param << 8;
|
||||
msg |= (seq_event->data.control.value >> 7) << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NONREGPARAM:
|
||||
@@ -181,23 +182,23 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
if (seq_event->data.control.param != 0) break;
|
||||
msg = 0x64B0;
|
||||
msg |= seq_event->data.control.channel;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
|
||||
msg &= 0xFF;
|
||||
msg |= 0x6500;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
|
||||
msg &= 0xFF;
|
||||
msg |= 0x0600;
|
||||
msg |= ((seq_event->data.control.value >> 7) & 0x7F) << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
msg = 0xC0;
|
||||
msg |= seq_event->data.control.channel;
|
||||
msg |= seq_event->data.control.value << 8;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
@@ -207,11 +208,10 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
bend = seq_event->data.control.value + 8192;
|
||||
msg |= (bend & 0x7F) << 8;
|
||||
msg |= ((bend >> 7) & 0x7F) << 16;
|
||||
synthRoute->pushMIDIShortMessage(msg, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDIShortMessage(*midiSession, msg, MasterClock::getClockNanos());
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_SYSEX:
|
||||
{
|
||||
case SND_SEQ_EVENT_SYSEX: {
|
||||
MT32Emu::Bit8u *sysexData = (MT32Emu::Bit8u *)seq_event->data.ext.ptr;
|
||||
uint sysexLength = seq_event->data.ext.len;
|
||||
|
||||
@@ -222,13 +222,13 @@ bool ALSAMidiDriver::processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *syn
|
||||
bool hasSysexStart = sysexData[0] == MIDI_CMD_COMMON_SYSEX;
|
||||
bool hasSysexEnd = sysexData[sysexLength - 1] == MIDI_CMD_COMMON_SYSEX_END;
|
||||
if (hasSysexStart && hasSysexEnd) {
|
||||
synthRoute->pushMIDISysex(sysexData, sysexLength, MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDISysex(*midiSession, sysexData, sysexLength, MasterClock::getClockNanos());
|
||||
break;
|
||||
}
|
||||
// OK, accumulate SysEx data received so far and commit when ready.
|
||||
sysexBuffer.append(sysexData, sysexLength);
|
||||
if (hasSysexEnd) {
|
||||
synthRoute->pushMIDISysex(sysexBuffer.constData(), sysexBuffer.size(), MasterClock::getClockNanos());
|
||||
synthRoute->pushMIDISysex(*midiSession, sysexBuffer.constData(), sysexBuffer.size(), MasterClock::getClockNanos());
|
||||
sysexBuffer.clear();
|
||||
}
|
||||
break;
|
||||
@@ -276,12 +276,15 @@ int ALSAMidiDriver::alsa_setup_midi() {
|
||||
}
|
||||
|
||||
snd_seq_set_client_name(snd_seq, "Munt MT-32");
|
||||
seqPort = snd_seq_create_simple_port(snd_seq, "Standard",
|
||||
SND_SEQ_PORT_CAP_SUBS_WRITE |
|
||||
SND_SEQ_PORT_CAP_WRITE,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_MIDI_MT32 |
|
||||
SND_SEQ_PORT_TYPE_SYNTHESIZER);
|
||||
seqPort = snd_seq_create_simple_port(
|
||||
snd_seq,
|
||||
"Standard",
|
||||
SND_SEQ_PORT_CAP_SUBS_WRITE |
|
||||
SND_SEQ_PORT_CAP_WRITE,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_MIDI_MT32 |
|
||||
SND_SEQ_PORT_TYPE_SYNTHESIZER
|
||||
);
|
||||
if (seqPort < 0) {
|
||||
qDebug() << "ALSAMidiDriver: Error creating sequencer port";
|
||||
return -1;
|
||||
|
||||
@@ -31,7 +31,7 @@ private:
|
||||
static void *processingThread(void *userData);
|
||||
int alsa_setup_midi();
|
||||
void processSeqEvents();
|
||||
bool processSeqEvent(snd_seq_event_t *seq_event, SynthRoute *synthRoute);
|
||||
bool processSeqEvent(snd_seq_event_t *seq_event, MidiSession *midiSession);
|
||||
unsigned int getSourceAddr(snd_seq_event_t *seq_event);
|
||||
QString getClientName(unsigned int clientAddr);
|
||||
MidiSession *findMidiSessionForClient(unsigned int clientAddr);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -40,19 +40,19 @@ MasterClockNanos JACKMidiDriver::jackFrameTimeToMasterClockNanos(MasterClockNano
|
||||
bool JACKMidiDriver::pushMIDIMessage(MidiSession *midiSession, MasterClockNanos eventTimestamp, size_t midiBufferSize, uchar *midiBuffer) {
|
||||
SynthRoute *synthRoute = midiSession->getSynthRoute();
|
||||
if (*midiBuffer == 0xF0) {
|
||||
return synthRoute->pushMIDISysex(midiBuffer, uint(midiBufferSize), eventTimestamp);
|
||||
return synthRoute->pushMIDISysex(*midiSession, midiBuffer, uint(midiBufferSize), eventTimestamp);
|
||||
}
|
||||
quint32 message = midiBufferToShortMessage(midiBufferSize, midiBuffer);
|
||||
return synthRoute->pushMIDIShortMessage(message, eventTimestamp);
|
||||
return synthRoute->pushMIDIShortMessage(*midiSession, message, eventTimestamp);
|
||||
}
|
||||
|
||||
bool JACKMidiDriver::playMIDIMessage(MidiSession *midiSession, quint64 eventTimestamp, size_t midiBufferSize, uchar *midiBuffer) {
|
||||
SynthRoute *synthRoute = midiSession->getSynthRoute();
|
||||
if (*midiBuffer == 0xF0) {
|
||||
return synthRoute->playMIDISysex(midiBuffer, quint32(midiBufferSize), eventTimestamp);
|
||||
return synthRoute->playMIDISysex(*midiSession, midiBuffer, quint32(midiBufferSize), eventTimestamp);
|
||||
}
|
||||
quint32 message = midiBufferToShortMessage(midiBufferSize, midiBuffer);
|
||||
return synthRoute->playMIDIShortMessage(message, eventTimestamp);
|
||||
return synthRoute->playMIDIShortMessage(*midiSession, message, eventTimestamp);
|
||||
}
|
||||
|
||||
JACKMidiDriver::JACKMidiDriver(Master *master) : MidiDriver(master) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -132,10 +132,10 @@ void SMFProcessor::run() {
|
||||
if (stopProcessing || synthRoute->getState() != SynthRouteState_OPEN) break;
|
||||
switch (e.getType()) {
|
||||
case SHORT_MESSAGE:
|
||||
synthRoute->pushMIDIShortMessage(e.getShortMessage(), currentNanos);
|
||||
synthRoute->pushMIDIShortMessage(*session, e.getShortMessage(), currentNanos);
|
||||
break;
|
||||
case SYSEX:
|
||||
synthRoute->pushMIDISysex(e.getSysexData(), e.getSysexLen(), currentNanos);
|
||||
synthRoute->pushMIDISysex(*session, e.getSysexData(), e.getSysexLen(), currentNanos);
|
||||
break;
|
||||
case SET_TEMPO: {
|
||||
uint tempo = e.getShortMessage();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -42,10 +42,10 @@ void TestProcessor::run() {
|
||||
qDebug() << currentNanos;
|
||||
bool alt = false;
|
||||
while (!stopProcessing) {
|
||||
session1->getSynthRoute()->pushMIDIShortMessage(0, currentNanos);
|
||||
session1->getSynthRoute()->pushMIDIShortMessage(*session1, 0, currentNanos);
|
||||
// Test 2 sends an event at the same time as every second Test 1 event
|
||||
if(alt && session2 != NULL)
|
||||
session2->getSynthRoute()->pushMIDIShortMessage(0, currentNanos);
|
||||
session2->getSynthRoute()->pushMIDIShortMessage(*session2, 0, currentNanos);
|
||||
alt = !alt;
|
||||
currentNanos += TEST1_EVENT_INTERVAL_NANOS;
|
||||
MasterClock::sleepUntilClockNanos(currentNanos);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011-2019 Jerome Fisher, Sergey V. Mikayev
|
||||
/* Copyright (C) 2011-2020 Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* 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
|
||||
@@ -176,7 +176,7 @@ LRESULT CALLBACK Win32MidiDriver::midiInProc(HWND hwnd, UINT uMsg, WPARAM wParam
|
||||
}
|
||||
LARGE_INTEGER t = { { data[2], (LONG)data[3] } };
|
||||
// qDebug() << "D" << 1e-6 * ((t.QuadPart - startMasterClock) - MasterClock::getClockNanos());
|
||||
midiSession->getSynthRoute()->pushMIDIShortMessage(data[4], t.QuadPart - startMasterClock);
|
||||
midiSession->getSynthRoute()->pushMIDIShortMessage(*midiSession, data[4], t.QuadPart - startMasterClock);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
@@ -186,7 +186,7 @@ LRESULT CALLBACK Win32MidiDriver::midiInProc(HWND hwnd, UINT uMsg, WPARAM wParam
|
||||
qDebug() << "Win32MidiDriver: Invalid midiSession ID supplied:" << "0x" + QString::number(midiSessionID, 16);
|
||||
return 0;
|
||||
}
|
||||
midiSession->getSynthRoute()->pushMIDISysex((MT32Emu::Bit8u *)cds->lpData, cds->cbData, MasterClock::getClockNanos());
|
||||
midiSession->getSynthRoute()->pushMIDISysex(*midiSession, (MT32Emu::Bit8u *)cds->lpData, cds->cbData, MasterClock::getClockNanos());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -392,7 +392,7 @@ void CALLBACK Win32MidiIn::midiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwIn
|
||||
if (wMsg == MIM_DATA) {
|
||||
// No need to use QMidiStreamParser for short messages: they are guaranteed to have explicit status byte
|
||||
// if ((dwParam1 & 0xF8) == 0xF8) // No support for System Realtime yet
|
||||
midiSession->getSynthRoute()->pushMIDIShortMessage(dwParam1, MasterClock::getClockNanos());
|
||||
midiSession->getSynthRoute()->pushMIDIShortMessage(*midiSession, dwParam1, MasterClock::getClockNanos());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user