#include "track.h" #include "remotebuffer.h" #include "timing/time_units.h" #include "timing/warp.h" #include #include #include #include Track::Track(int sampleRate, float vuAlpha, float gain, float pan, bool muted, const std::shared_ptr &meter, const std::shared_ptr &filterChain, const std::vector> &clips) : gain(gain), pan(pan), muted(muted ? 1 : 0), clips(clips), tempBuffer(2, 4096), silentBuffer(2, 4096), meter(meter), filterChain(filterChain) { assert(std::isfinite(gain)); assert(std::isfinite(pan)); silentBuffer.fill(0.f); } float Track::getGain() const { return emscripten_atomic_load_f32(&gain); } void Track::setGain(float gain) { assert(std::isfinite(gain)); emscripten_atomic_store_f32(&this->gain, gain); } float Track::getPan() const { return emscripten_atomic_load_f32(&pan); } void Track::setPan(float pan) { assert(std::isfinite(pan)); emscripten_atomic_store_f32(&this->pan, pan); } bool Track::isMuted() const { return emscripten_atomic_load_u32(&muted) != 0; } void Track::setMuted(bool muted) { emscripten_atomic_store_u32(&this->muted, muted ? 1 : 0); } std::shared_ptr Track::getClip(int index) const { if (index < 0 || index >= clips.size()) { return nullptr; } return clips[index]; } void Track::readSegment(const RenderContext *renderContext, BufferF32 output) { output.fill(0); if (isMuted()) { if (meter) { meter->update(output); } return; } const auto gainHere = getGain(); const auto panHere = getPan(); auto workBuffer = tempBuffer.slice(0, output.getFrameCount(), output.getChannelCount()); // pan: -1 to 1, where -1 is full left and 1 is full right // TODO: need panning rules and mixdown for mono output const float channelGains[2] = {gainHere * std::clamp(1.f - panHere, 0.f, 1.f), gainHere * std::clamp(1.f + panHere, 0.f, 1.f)}; const auto renderStart = renderContext->position; const auto renderEnd = renderContext->position + renderContext->duration; for (const auto &clip : clips) { const auto clipTimelineStart = time_units::Beats(clip->getTimelineStartBeats()); const auto clipTimelineEnd = time_units::Beats(clip->getTimelineEndBeats()); if (renderEnd < clipTimelineStart || renderStart > clipTimelineEnd) { continue; } workBuffer.fill(0); clip->readSegment(renderContext, workBuffer); for (int channel = 0; channel < workBuffer.getChannelCount(); channel++) { auto channelData = workBuffer.getChannelData(channel); auto channelGain = channelGains[channel]; #pragma clang loop vectorize(enable) for (int i = 0; i < workBuffer.getFrameCount(); i++) { channelData[i] *= channelGain; } } output.sumWith(workBuffer); } if (filterChain) { filterChain->processBuffer(output); } if (meter) { meter->update(output); } } void Track::progressSegment(int channelCount, int frameCount) { if (meter) { meter->update(silentBuffer.slice(0, frameCount, channelCount)); } } EMSCRIPTEN_BINDINGS(track) { using namespace emscripten; class_("Track") .smart_ptr>("Track") .property("gain", &Track::getGain, &Track::setGain) .property("pan", &Track::getPan, &Track::setPan) .property("muted", &Track::isMuted, &Track::setMuted) .property("meter", &Track::meter) .property("filterChain", &Track::filterChain) .function("getClipCount", &Track::getClipCount) .function("getClip(index)", &Track::getClip); }; TEST_CASE("track memory tests", "[track]") { using namespace time_units; auto warpMap = std::make_shared>( std::vector::WarpMarker>{ {Beats(0.0), Seconds(0.0)}, {Beats(1.0), Seconds(1.0)}}); auto buf = std::make_shared(2, 44100); buf->noise(); BufferF32 out(2, 1024); double clock = 0.0; auto rab = std::make_shared(44100, buf); auto clip = std::make_shared(rab, warpMap, 44100, 1.f, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.f, 120.0, false, Uuid()); auto meter = std::make_shared(2, 44100, 10, 10, 0.99f); Track track(44100, 0.99f, 1.f, 0.f, false, meter, nullptr, std::vector>{clip}); /* TODO track.setMuted(true); track.readSegment(0.0, 2.0, out); REQUIRE(out.peak() == 0.f); REQUIRE(track.meter->getPpmHoldIndicated(0) < -100.f); REQUIRE(track.meter->getPpmIndicated(0) < -100.f); REQUIRE(track.meter->getVuIndicated(0) < -100.f); track.setMuted(false); track.readSegment(0.0, 2.0, out); REQUIRE(out.peak() > 0.f); REQUIRE(track.meter->getPpmHoldIndicated(0) > -100.f); REQUIRE(track.meter->getPpmIndicated(0) > -100.f); REQUIRE(track.meter->getVuIndicated(0) > -100.f); track.setMuted(true); track.readSegment(0.0, 2.0, out); REQUIRE(out.peak() == 0.f); REQUIRE(track.meter->getPpmHoldIndicated(0) > -100.f); REQUIRE(track.meter->getPpmIndicated(0) < -100.f); */ }