From 2ea8f0667ad00eed51bd3ffb37dd2384f71bc24d Mon Sep 17 00:00:00 2001 From: CrSjimo Date: Sun, 7 Jul 2024 16:13:12 +0800 Subject: [PATCH] add MidiMessageIntegrator and MidiNoteSynthesizer --- src/core/source/NoteSynthesizer_p.h | 4 +- src/midi/device/MidiInputDevice.cpp | 1 - src/midi/device/MidiMessageListener.cpp | 2 +- src/midi/integrator/MidiMessageIntegrator.cpp | 86 ++++++++++++++++ src/midi/integrator/MidiMessageIntegrator.h | 54 ++++++++++ src/midi/integrator/MidiMessageIntegrator_p.h | 70 +++++++++++++ src/midi/integrator/MidiNoteSynthesizer.cpp | 98 +++++++++++++++++++ src/midi/integrator/MidiNoteSynthesizer.h | 55 +++++++++++ src/midi/integrator/MidiNoteSynthesizer_p.h | 49 ++++++++++ .../InteractiveTests/NoteSynthesizer/main.cpp | 4 +- tests/InteractiveTests/midi/main.cpp | 29 ++++-- 11 files changed, 437 insertions(+), 15 deletions(-) create mode 100644 src/midi/integrator/MidiMessageIntegrator.cpp create mode 100644 src/midi/integrator/MidiMessageIntegrator.h create mode 100644 src/midi/integrator/MidiMessageIntegrator_p.h create mode 100644 src/midi/integrator/MidiNoteSynthesizer.cpp create mode 100644 src/midi/integrator/MidiNoteSynthesizer.h create mode 100644 src/midi/integrator/MidiNoteSynthesizer_p.h diff --git a/src/core/source/NoteSynthesizer_p.h b/src/core/source/NoteSynthesizer_p.h index 7fd21eb..529e2dc 100644 --- a/src/core/source/NoteSynthesizer_p.h +++ b/src/core/source/NoteSynthesizer_p.h @@ -48,7 +48,7 @@ namespace talcs { vel /= d->attackRate; if (vel > velFactor) vel = velFactor; - } else { + } else if (!isAttack) { vel *= d->releaseRate; if (vel < .005) vel = .0; @@ -58,7 +58,7 @@ namespace talcs { } }; double attackRate = .005; - double releaseRate = 0; + double releaseRate = .0; QList keys; struct GenerateSineWave { diff --git a/src/midi/device/MidiInputDevice.cpp b/src/midi/device/MidiInputDevice.cpp index b772658..1dfee56 100644 --- a/src/midi/device/MidiInputDevice.cpp +++ b/src/midi/device/MidiInputDevice.cpp @@ -41,7 +41,6 @@ namespace talcs { void MidiInputDevicePrivate::rtmidiCallback(double timeStamp, std::vector *message, void *userData) { auto d = reinterpret_cast(userData); - qDebug() << timeStamp; MidiMessage msg(message->data(), static_cast(message->size()), timeStamp); d->listener.messageCallback(msg); } diff --git a/src/midi/device/MidiMessageListener.cpp b/src/midi/device/MidiMessageListener.cpp index 5cb6202..4d67bb1 100644 --- a/src/midi/device/MidiMessageListener.cpp +++ b/src/midi/device/MidiMessageListener.cpp @@ -51,7 +51,7 @@ namespace talcs { QMutexLocker locker(&d->filterMutex); return std::any_of(d->filters.cbegin(), d->filters.cend(), [&message](MidiMessageListener *filter) { return filter->messageCallback(message); - }) && processMessage(message); + }) || processMessage(message); } void MidiMessageListener::errorCallback(const QString &errorString) { diff --git a/src/midi/integrator/MidiMessageIntegrator.cpp b/src/midi/integrator/MidiMessageIntegrator.cpp new file mode 100644 index 0000000..ac87575 --- /dev/null +++ b/src/midi/integrator/MidiMessageIntegrator.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#include "MidiMessageIntegrator.h" +#include "MidiMessageIntegrator_p.h" + +#include + +namespace talcs { + MidiMessageIntegrator::MidiMessageIntegrator() : MidiMessageIntegrator(*new MidiMessageIntegratorPrivate) { + + } + + MidiMessageIntegrator::~MidiMessageIntegrator() = default; + + bool MidiMessageIntegrator::open(qint64 bufferSize, double sampleRate) { + Q_D(MidiMessageIntegrator); + d->queue.clear(); + return AbstractMidiMessageIntegrator::open(bufferSize, sampleRate); + } + + void MidiMessageIntegrator::close() { + Q_D(MidiMessageIntegrator); + d->queue.clear(); + AbstractMidiMessageIntegrator::close(); + } + + bool MidiMessageIntegrator::processDeviceWillStart(MidiInputDevice *device) { + Q_D(MidiMessageIntegrator); + d->queue.clear(); + return true; + } + + void MidiMessageIntegrator::processDeviceStopped() { + Q_D(MidiMessageIntegrator); + d->queue.clear(); + } + + bool MidiMessageIntegrator::processMessage(const MidiMessage &message) { + Q_D(MidiMessageIntegrator); + d->queue.push(message); + return false; + } + + void MidiMessageIntegrator::processError(const QString &errorString) { + Q_D(MidiMessageIntegrator); + d->queue.push({0xf0, 0xf7, -qInf()}); + } + + QList MidiMessageIntegrator::fetch(qint64 length) { + Q_D(MidiMessageIntegrator); + if (d->queue.empty()) + return {}; + QList midiEvents = {{0, d->queue.top()}}; + d->queue.pop(); + while (!d->queue.empty()) { + auto message = d->queue.top(); + auto position = static_cast((message.getTimeStamp() - midiEvents[0].message.getTimeStamp()) * sampleRate()); + if (position >= length) + break; + d->queue.pop(); + midiEvents.append({position, message}); + } + return midiEvents; + } + + MidiMessageIntegrator::MidiMessageIntegrator(MidiMessageIntegratorPrivate &d) : AbstractMidiMessageIntegrator(d) { + + } +} // talcs \ No newline at end of file diff --git a/src/midi/integrator/MidiMessageIntegrator.h b/src/midi/integrator/MidiMessageIntegrator.h new file mode 100644 index 0000000..bb5c107 --- /dev/null +++ b/src/midi/integrator/MidiMessageIntegrator.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#ifndef TALCS_MIDIMESSAGEINTEGRATOR_H +#define TALCS_MIDIMESSAGEINTEGRATOR_H + +#include +#include + +namespace talcs { + + class MidiMessageIntegratorPrivate; + + class TALCSMIDI_EXPORT MidiMessageIntegrator : public AbstractMidiMessageIntegrator, public MidiMessageListener { + Q_DECLARE_PRIVATE_D(AbstractMidiMessageIntegrator::d_ptr, MidiMessageIntegrator) + public: + explicit MidiMessageIntegrator(); + ~MidiMessageIntegrator() override; + + bool open(qint64 bufferSize, double sampleRate) override; + + void close() override; + + protected: + bool processDeviceWillStart(MidiInputDevice *device) override; + void processDeviceStopped() override; + bool processMessage(const MidiMessage &message) override; + void processError(const QString &errorString) override; + + QList fetch(qint64 length) override; + + explicit MidiMessageIntegrator(MidiMessageIntegratorPrivate &d); + + }; + +} // talcs + +#endif //TALCS_MIDIMESSAGEINTEGRATOR_H diff --git a/src/midi/integrator/MidiMessageIntegrator_p.h b/src/midi/integrator/MidiMessageIntegrator_p.h new file mode 100644 index 0000000..39fd721 --- /dev/null +++ b/src/midi/integrator/MidiMessageIntegrator_p.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#ifndef TALCS_MIDIMESSAGEINTEGRATOR_P_H +#define TALCS_MIDIMESSAGEINTEGRATOR_P_H + +#include + +#include + +namespace talcs { + + struct MidiMessageIntegratorQueue { + MidiMessage p[1024]; + QAtomicInteger head = 0; + size_t tail = 0; + QAtomicInteger counter = 0; + void push(const MidiMessage &message) { + if (counter.loadAcquire() == 1024) + return; + counter.fetchAndAddOrdered(1); + p[tail++] = message; + tail %= 1024; + } + void clear() { + counter.storeRelease(0); + tail = head; + tail %= 1024; + } + bool empty() { + return !counter; + } + MidiMessage top() { + if (!counter.loadAcquire()) + return {0xf0, 0xf7, -qInf()}; + return p[head]; + } + void pop() { + if (!counter.loadAcquire()) + return; + counter.fetchAndAddOrdered(-1); + head++; + head = head % 1024; + } + }; + + class MidiMessageIntegratorPrivate : public AbstractMidiMessageIntegratorPrivate { + Q_DECLARE_PUBLIC(MidiMessageIntegrator) + public: + MidiMessageIntegratorQueue queue; + }; +} + +#endif //TALCS_MIDIMESSAGEINTEGRATOR_P_H diff --git a/src/midi/integrator/MidiNoteSynthesizer.cpp b/src/midi/integrator/MidiNoteSynthesizer.cpp new file mode 100644 index 0000000..c1bd11b --- /dev/null +++ b/src/midi/integrator/MidiNoteSynthesizer.cpp @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#include "MidiNoteSynthesizer.h" +#include "MidiNoteSynthesizer_p.h" + +namespace talcs { + MidiNoteSynthesizer::MidiNoteSynthesizer() : MidiNoteSynthesizer(new NoteSynthesizer, true) { + + } + + MidiNoteSynthesizer::MidiNoteSynthesizer(NoteSynthesizer *noteSynthesizer, bool takeOwnership) : MidiNoteSynthesizer(*new MidiNoteSynthesizerPrivate) { + Q_D(MidiNoteSynthesizer); + d->noteSynthesizer = noteSynthesizer; + d->takeOwnership = takeOwnership; + d->noteSynthesizer->setDetector(d); + } + + MidiNoteSynthesizer::~MidiNoteSynthesizer() { + Q_D(MidiNoteSynthesizer); + if (d->takeOwnership) + delete d->noteSynthesizer; + } + + bool MidiNoteSynthesizer::open(qint64 bufferSize, double sampleRate) { + Q_D(MidiNoteSynthesizer); + if (!d->noteSynthesizer->open(bufferSize, sampleRate)) + return false; + return AudioMidiStream::open(bufferSize, sampleRate); + } + + void MidiNoteSynthesizer::close() { + Q_D(MidiNoteSynthesizer); + d->noteSynthesizer->close(); + AudioMidiStream::close(); + } + + void MidiNoteSynthesizer::setFrequencyOfA(double frequency) { + Q_D(MidiNoteSynthesizer); + d->frequencyOfA = frequency; + } + + double MidiNoteSynthesizer::frequencyOfA() const { + Q_D(const MidiNoteSynthesizer); + return d->frequencyOfA; + } + + NoteSynthesizer *MidiNoteSynthesizer::noteSynthesizer() const { + Q_D(const MidiNoteSynthesizer); + return d->noteSynthesizer; + } + + void MidiNoteSynthesizerPrivate::detectInterval(qint64 intervalLength) { + + } + + NoteSynthesizerDetectorMessage MidiNoteSynthesizerPrivate::nextMessage() { + while (midiEventsIterator != midiEvents.cend() && !midiEventsIterator->message.isNoteOnOrOff()) + midiEventsIterator++; + if (midiEventsIterator == midiEvents.cend()) + return {-1}; + NoteSynthesizerDetectorMessage ret = { + midiEventsIterator->position, + MidiMessage::getMidiNoteInHertz(midiEventsIterator->message.getNoteNumber(), frequencyOfA), + midiEventsIterator->message.getFloatVelocity(), + midiEventsIterator->message.isNoteOn(), + }; + midiEventsIterator++; + return ret; + } + + qint64 MidiNoteSynthesizer::processReading(const AudioSourceReadData &readData, const QList &midiEvents) { + Q_D(MidiNoteSynthesizer); + d->midiEvents = midiEvents; + d->midiEventsIterator = d->midiEvents.cbegin(); + return d->noteSynthesizer->read(readData); + } + + MidiNoteSynthesizer::MidiNoteSynthesizer(MidiNoteSynthesizerPrivate &d) : d_ptr(&d) { + d.q_ptr = this; + } +} // talcs \ No newline at end of file diff --git a/src/midi/integrator/MidiNoteSynthesizer.h b/src/midi/integrator/MidiNoteSynthesizer.h new file mode 100644 index 0000000..eeb8717 --- /dev/null +++ b/src/midi/integrator/MidiNoteSynthesizer.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#ifndef TALCS_MIDINOTESYNTHESIZER_H +#define TALCS_MIDINOTESYNTHESIZER_H + +#include + +namespace talcs { + + class NoteSynthesizer; + + class MidiNoteSynthesizerPrivate; + + class TALCSMIDI_EXPORT MidiNoteSynthesizer : public AudioMidiStream { + Q_DECLARE_PRIVATE(MidiNoteSynthesizer) + public: + explicit MidiNoteSynthesizer(); + explicit MidiNoteSynthesizer(NoteSynthesizer *noteSynthesizer, bool takeOwnership = false); + ~MidiNoteSynthesizer() override; + + bool open(qint64 bufferSize, double sampleRate) override; + void close() override; + + void setFrequencyOfA(double frequency); + double frequencyOfA() const; + + NoteSynthesizer *noteSynthesizer() const; + + protected: + qint64 processReading(const AudioSourceReadData &readData, const QList &midiEvents) override; + + explicit MidiNoteSynthesizer(MidiNoteSynthesizerPrivate &d); + QScopedPointer d_ptr; + }; + +} // talcs + +#endif //TALCS_MIDINOTESYNTHESIZER_H diff --git a/src/midi/integrator/MidiNoteSynthesizer_p.h b/src/midi/integrator/MidiNoteSynthesizer_p.h new file mode 100644 index 0000000..cb00e5d --- /dev/null +++ b/src/midi/integrator/MidiNoteSynthesizer_p.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright (c) 2024 CrSjimo * + * * + * This file is part of TALCS. * + * * + * TALCS is free software: you can redistribute it and/or modify it under the * + * terms of the GNU Lesser General Public License as published by the Free * + * Software Foundation, either version 3 of the License, or (at your option) * + * any later version. * + * * + * TALCS is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * + * more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with TALCS. If not, see . * + ******************************************************************************/ + +#ifndef TALCS_MIDINOTESYNTHESIZER_P_H +#define TALCS_MIDINOTESYNTHESIZER_P_H + +#include + +#include + +#include +#include + +namespace talcs { + class MidiNoteSynthesizerPrivate : public NoteSynthesizerDetector { + Q_DECLARE_PUBLIC(MidiNoteSynthesizer) + public: + MidiNoteSynthesizer *q_ptr; + + NoteSynthesizer *noteSynthesizer; + bool takeOwnership = false; + + double frequencyOfA = 440.0; + + QList midiEvents; + QList::const_iterator midiEventsIterator; + + void detectInterval(qint64 intervalLength) override; + NoteSynthesizerDetectorMessage nextMessage() override; + }; +} + +#endif //TALCS_MIDINOTESYNTHESIZER_P_H diff --git a/tests/InteractiveTests/NoteSynthesizer/main.cpp b/tests/InteractiveTests/NoteSynthesizer/main.cpp index 7eac80e..e726843 100644 --- a/tests/InteractiveTests/NoteSynthesizer/main.cpp +++ b/tests/InteractiveTests/NoteSynthesizer/main.cpp @@ -126,8 +126,8 @@ int main(int argc, char **argv) { Detector detector; NoteSynthesizer src; - src.setAttackRate(std::pow(0.99, 20000.0 / 48000.0)); - src.setReleaseRate(std::pow(0.99, 20000.0 / 48000.0)); +// src.setAttackRate(std::pow(0.99, 20000.0 / 48000.0)); +// src.setReleaseRate(std::pow(0.99, 20000.0 / 48000.0)); src.setGenerator(NoteSynthesizer::Square); src.setDetector(&detector); AudioSourcePlayback playback(&src); diff --git a/tests/InteractiveTests/midi/main.cpp b/tests/InteractiveTests/midi/main.cpp index 4b2defa..6d75480 100644 --- a/tests/InteractiveTests/midi/main.cpp +++ b/tests/InteractiveTests/midi/main.cpp @@ -20,8 +20,12 @@ #include #include +#include + #include #include +#include +#include #include #include @@ -40,17 +44,24 @@ int main(int argc, char **argv) { qDebug() << MidiInputDevice::devices(); MidiInputDevice dev(1); -// auto mgr = AudioDriverManager::createBuiltInDriverManager(); -// auto drv = mgr->driver(mgr->drivers()[0]); -// drv->initialize(); -// auto audioDev = drv->defaultDevice().isEmpty() ? drv->createDevice(drv->devices()[0]) : drv->createDevice(drv->defaultDevice()); + auto mgr = AudioDriverManager::createBuiltInDriverManager(); + auto drv = mgr->driver(mgr->drivers()[0]); + drv->initialize(); + auto audioDev = drv->defaultDevice().isEmpty() ? drv->createDevice(drv->devices()[0]) : drv->createDevice(drv->defaultDevice()); + + auto src = new MidiMessageIntegrator; + auto midiSynth = new MidiNoteSynthesizer; + src->setStream(midiSynth); + + midiSynth->noteSynthesizer()->setGenerator(talcs::NoteSynthesizer::Square); + midiSynth->noteSynthesizer()->setAttackRate(std::pow(0.99, 20000.0 / 48000.0)); + midiSynth->noteSynthesizer()->setReleaseRate(std::pow(0.99, 20000.0 / 48000.0)); -// auto midiSynth = new MidiSineWaveSynthesizer; -// auto playback = new AudioSourcePlayback(midiSynth); -// dev.addListener(midiSynth); + auto playback = new AudioSourcePlayback(src); + dev.listener()->addFilter(src); -// audioDev->open(audioDev->preferredBufferSize(), audioDev->preferredSampleRate()); -// audioDev->start(playback); + audioDev->open(audioDev->preferredBufferSize(), audioDev->preferredSampleRate()); + audioDev->start(playback); dev.open(); return a.exec(); } \ No newline at end of file