Program Listing for File block.hpp
↰ Return to documentation for file (include/eat/process/block.hpp)
//
// Created by Richard Bailey on 13/05/2022.
//
#pragma once
#include <cassert>
#include <cstdint>
#include <string>
#include <vector>
#include "eat/framework/exceptions.hpp"
#include "eat/framework/process.hpp"
#include "eat/framework/value_ptr.hpp"
namespace eat::process {
struct BlockDescription {
std::size_t sample_count;
std::size_t channel_count;
unsigned int sample_rate;
};
class InterleavedSampleBlock {
public:
InterleavedSampleBlock(std::vector<float> samples, BlockDescription blockInfo)
: samples_(std::move(samples)), info_{blockInfo} {
framework::always_assert(samples_.size() == info_.sample_count * info_.channel_count,
"wrong number of samples in SampleBlock");
}
InterleavedSampleBlock(BlockDescription blockInfo)
: samples_(blockInfo.sample_count * blockInfo.channel_count), info_{blockInfo} {}
[[nodiscard]] BlockDescription const &info() const { return info_; }
[[nodiscard]] float sample(size_t channel, size_t sample) const {
assert(channel < info_.channel_count);
assert(sample < info_.sample_count);
return samples_[info_.channel_count * sample + channel];
};
float &sample(size_t channel, size_t sample) {
assert(channel < info_.channel_count);
assert(sample < info_.sample_count);
return samples_[info_.channel_count * sample + channel];
}
[[nodiscard]] const float *data() const { return samples_.data(); }
float *data() { return samples_.data(); }
private:
std::vector<float> samples_;
BlockDescription info_;
};
using InterleavedBlockPtr = framework::ValuePtr<InterleavedSampleBlock>;
class PlanarSampleBlock {
public:
PlanarSampleBlock(std::vector<float> samples, BlockDescription blockInfo)
: samples_(std::move(samples)), info_{blockInfo} {
framework::always_assert(samples_.size() == info_.sample_count * info_.channel_count,
"wrong number of samples in SampleBlock");
}
PlanarSampleBlock(BlockDescription blockInfo)
: samples_(blockInfo.sample_count * blockInfo.channel_count), info_{blockInfo} {}
[[nodiscard]] BlockDescription const &info() const { return info_; }
[[nodiscard]] float sample(size_t channel, size_t sample) const {
return const_cast<PlanarSampleBlock *>(this)->sample(channel, sample);
};
float &sample(size_t channel, size_t sample) {
assert(channel < info_.channel_count);
assert(sample < info_.sample_count);
return samples_[info_.sample_count * channel + sample];
}
[[nodiscard]] const float *data() const { return samples_.data(); }
float *data() { return samples_.data(); }
private:
std::vector<float> samples_;
BlockDescription info_;
};
using PlanarBlockPtr = framework::ValuePtr<PlanarSampleBlock>;
class InterleavedStreamingAudioSource : public framework::StreamingAtomicProcess {
public:
InterleavedStreamingAudioSource(std::string const &name, std::vector<float> samples, BlockDescription blockInfo)
: StreamingAtomicProcess(name),
source(std::move(samples)),
block_info(blockInfo),
position(source.begin()),
out(add_out_port<framework::StreamPort<InterleavedBlockPtr>>("out_samples")) {
framework::always_assert(samples.size() % blockInfo.channel_count == 0,
"number of samples must be divisible by channel count");
}
void process() override {
if (auto samplesLeft = static_cast<std::size_t>(source.end() - position); samplesLeft > 0) {
auto framesLeft = samplesLeft / block_info.channel_count;
auto nextBlockSize = std::min(block_info.sample_count, framesLeft);
auto nextPosition = position + static_cast<std::ptrdiff_t>(nextBlockSize * block_info.channel_count);
auto nextInfo = block_info;
nextInfo.sample_count = nextBlockSize;
auto block = std::make_shared<InterleavedSampleBlock>(std::vector<float>(position, nextPosition), nextInfo);
out->push(block);
position = nextPosition;
} else {
out->close();
}
}
private:
std::vector<float> source;
BlockDescription block_info;
std::vector<float>::const_iterator position;
framework::StreamPortPtr<InterleavedBlockPtr> out;
};
class InterleavedStreamingAudioSink : public framework::StreamingAtomicProcess {
public:
explicit InterleavedStreamingAudioSink(std::string const &name)
: StreamingAtomicProcess(name), in(add_in_port<framework::StreamPort<InterleavedBlockPtr>>("in_samples")) {}
std::vector<float> const &get() { return samples; }
InterleavedSampleBlock get_block() { return {samples, info}; }
void initialise() override { has_input = false; }
void process() override {
while (in->available()) {
auto block = in->pop().read();
auto &block_info = block->info();
if (has_input) {
framework::always_assert(info.channel_count == block_info.channel_count, "channel count changed mid-stream");
framework::always_assert(info.sample_rate == block_info.sample_rate, "sample rate changed mid-stream");
} else {
has_input = true;
info.channel_count = block_info.channel_count;
info.sample_rate = block_info.sample_rate;
info.sample_count = 0;
}
info.sample_count += block_info.sample_count;
for (auto sample_number = 0ul; sample_number != block_info.sample_count; ++sample_number) {
for (auto channel_number = 0ul; channel_number != block_info.channel_count; ++channel_number) {
samples.push_back(block->sample(channel_number, sample_number));
}
}
}
}
private:
framework::StreamPortPtr<InterleavedBlockPtr> in;
bool has_input = false;
BlockDescription info;
std::vector<float> samples;
};
} // namespace eat::process
// overloads for buffering InterleavedSampleBlock objects to disk
namespace eat::framework {
template <>
ProcessPtr MakeBuffer<process::InterleavedBlockPtr>::get_buffer_reader(const std::string &name);
template <>
ProcessPtr MakeBuffer<process::InterleavedBlockPtr>::get_buffer_writer(const std::string &name);
} // namespace eat::framework