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:
sergm
2020-09-04 20:01:33 +03:00
parent 2ffcea8556
commit ab3ea4ed07
34 changed files with 325 additions and 198 deletions

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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 *)

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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") {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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();

View File

@@ -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);

View File

@@ -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());
}
}