tidal-link-1.2.1: link/include/ableton/link_audio/SinkProcessor.hpp
/* Copyright 2025, Ableton AG, Berlin. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you would like to incorporate Link into a proprietary software application,
* please contact <link-devs@ableton.com>.
*/
#pragma once
#include <ableton/link_audio/Encoder.hpp>
#include <ableton/link_audio/Id.hpp>
#include <ableton/link_audio/Receivers.hpp>
#include <ableton/link_audio/Sink.hpp>
#include <ableton/util/Injected.hpp>
#include <memory>
#include <string>
namespace ableton
{
namespace link_audio
{
template <typename GetSender, typename GetNodeId, typename IoContext>
struct SinkProcessor
{
SinkProcessor(util::Injected<IoContext> io,
std::shared_ptr<Sink> pSink,
util::Injected<GetSender> getSender,
util::Injected<GetNodeId> getNodeId)
: mpImpl(std::make_shared<Impl>(
std::move(io), pSink, std::move(getSender), std::move(getNodeId)))
{
}
bool process() { return mpImpl->process(); }
const Id& id() const { return mpImpl->id(); }
std::string name() const { return mpImpl->name(); }
bool nameChanged() { return mpImpl->nameChanged(); }
template <typename Request>
void receiveChannelRequest(Request request, uint8_t ttl)
{
mpImpl->receiveChannelRequest(std::move(request), ttl);
}
struct Impl : public std::enable_shared_from_this<Impl>
{
struct Sender
{
void operator()(const AudioBuffer& buffer)
{
auto end =
v1::audioBufferMessage((*mpImpl->mGetNodeId)(), buffer, mBuffer.begin());
try
{
mpImpl->mReceivers(mBuffer.data(), std::distance(mBuffer.begin(), end));
}
catch (const std::runtime_error& err)
{
debug(mpImpl->mIo->log()) << "Failed to send message: " << err.what();
}
}
Impl* mpImpl;
std::array<uint8_t, v1::kMaxMessageSize> mBuffer{};
};
Impl(util::Injected<IoContext> io,
std::shared_ptr<Sink> pSink,
util::Injected<GetSender> getSender,
util::Injected<GetNodeId> getNodeId)
: mIo(std::move(io))
, mpSink(pSink)
, mQueueReader(pSink->reader())
, mEncoder(util::injectVal(Sender{this}), mpSink->id())
, mReceivers(util::injectRef(*mIo), std::move(getSender))
, mGetNodeId(std::move(getNodeId))
{
}
bool process()
{
if (mpSink.use_count() <= 1)
{
return false;
}
while (mQueueReader.retainSlot())
{
if (!mReceivers.empty() && mQueueReader[0]->mTempo > link::Tempo{0})
{
mEncoder(*mQueueReader[0]);
}
if (mQueueReader[0]->mSamples.size() < mpSink->maxNumSamples())
{
mQueueReader[0]->mSamples.resize(mpSink->maxNumSamples());
}
mQueueReader.releaseSlot();
}
return true;
}
const Id& id() const { return mpSink->id(); }
std::string name() const { return mpSink->name(); }
bool nameChanged() { return mpSink->nameChanged(); }
template <typename Request>
void receiveChannelRequest(Request request, uint8_t ttl)
{
mReceivers.receiveChannelRequest(std::move(request), ttl);
mpSink->setIsConnected(!mReceivers.empty());
}
private:
util::Injected<IoContext> mIo;
std::shared_ptr<Sink> mpSink;
Queue<Buffer<int16_t>>::Reader mQueueReader;
Encoder<Sender, int16_t> mEncoder;
Receivers<GetSender, IoContext> mReceivers;
util::Injected<GetNodeId> mGetNodeId;
};
std::shared_ptr<Impl> mpImpl;
};
} // namespace link_audio
} // namespace ableton