Add partially working blocking recording - very buggy

pull/261/head
James H Ball 2024-11-24 23:23:32 +00:00 zatwierdzone przez James H Ball
rodzic 4045cb4c27
commit e40356e71a
10 zmienionych plików z 1333 dodań i 90 usunięć

Wyświetl plik

@ -1,6 +1,7 @@
#include <JuceHeader.h>
#include "obj/Camera.h"
#include "mathter/Common/Approx.hpp"
#include "concurrency/BufferConsumer.h"
class FrustumTest : public juce::UnitTest {
public:
@ -114,7 +115,77 @@ public:
}
};
class ProducerThread : public juce::Thread {
public:
ProducerThread(BufferConsumer& consumer) : juce::Thread("Producer Thread"), consumer(consumer) {}
void run() override {
for (int i = 0; i < 1024 * 100; i++) {
consumer.write(OsciPoint(counter++));
}
}
private:
BufferConsumer& consumer;
int counter = 0;
};
class ConsumerThread : public juce::Thread {
public:
ConsumerThread(BufferConsumer& consumer) : juce::Thread("Consumer Thread"), consumer(consumer) {}
void run() override {
for (int i = 0; i < 100; i++) {
consumer.waitUntilFull();
auto buffer = consumer.getBuffer();
for (auto& point : buffer) {
values.push_back(point.x);
}
}
}
bool containsGap() {
for (int i = 0; i < values.size() - 1; i++) {
if (values[i] + 1 != values[i + 1]) {
return true;
}
}
return false;
}
private:
BufferConsumer& consumer;
std::vector<double> values;
};
class BufferConsumerTest : public juce::UnitTest {
public:
BufferConsumerTest() : juce::UnitTest("Buffer Consumer") {}
void runTest() override {
beginTest("All data received");
BufferConsumer consumer(1024);
ProducerThread producer(consumer);
ConsumerThread consumerThread(consumer);
consumer.setBlockOnWrite(true);
for (int i = 0; i < 100; i++) {
producer.startThread();
consumerThread.startThread();
producer.waitForThreadToExit(-1);
consumerThread.waitForThreadToExit(-1);
expect(!consumerThread.containsGap(), "There was a gap in the data");
}
}
};
static FrustumTest frustumTest;
static BufferConsumerTest bufferConsumerTest;
int main(int argc, char* argv[]) {
juce::UnitTestRunner runner;

Wyświetl plik

@ -28,7 +28,7 @@ void AudioBackgroundThread::prepare(double sampleRate, int samplesPerBlock) {
}
}
void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning) {
void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning, std::function<void()> stopCallback) {
if (!isPrepared && shouldBeRunning) {
prepare(manager.sampleRate, manager.samplesPerBlock);
}
@ -36,6 +36,9 @@ void AudioBackgroundThread::setShouldBeRunning(bool shouldBeRunning) {
this->shouldBeRunning = shouldBeRunning;
if (!shouldBeRunning && isThreadRunning()) {
if (stopCallback) {
stopCallback();
}
stop();
} else if (isPrepared && shouldBeRunning && !isThreadRunning()) {
start();
@ -57,6 +60,10 @@ void AudioBackgroundThread::run() {
}
}
void AudioBackgroundThread::setBlockOnAudioThread(bool block) {
consumer->setBlockOnWrite(block);
}
void AudioBackgroundThread::start() {
startThread();
}

Wyświetl plik

@ -11,8 +11,9 @@ public:
~AudioBackgroundThread() override;
void prepare(double sampleRate, int samplesPerBlock);
void setShouldBeRunning(bool shouldBeRunning);
void setShouldBeRunning(bool shouldBeRunning, std::function<void()> stopCallback = nullptr);
void write(const OsciPoint& point);
void setBlockOnAudioThread(bool block);
private:

Wyświetl plik

@ -4,6 +4,7 @@
#include "../shape/OsciPoint.h"
#include <mutex>
#include <condition_variable>
#include "readerwritercircularbuffer.h"
// FROM https://gist.github.com/Kuxe/6bdd5b748b5f11b303a5cccbb8c8a731
/** General semaphore with N permissions **/
@ -45,45 +46,82 @@ public:
class BufferConsumer {
public:
BufferConsumer(std::size_t size) {
returnBuffer.resize(size);
buffer1.resize(size);
buffer2.resize(size);
queue = std::make_unique<moodycamel::BlockingReaderWriterCircularBuffer<OsciPoint>>(2 * size);
}
~BufferConsumer() {}
// CONSUMER
// loop dequeuing until full
// get buffer
// PRODUCER
// enqueue point
void waitUntilFull() {
sema.acquire();
if (blockOnWrite) {
for (int i = 0; i < returnBuffer.size() && blockOnWrite; i++) {
queue->wait_dequeue(returnBuffer[i]);
}
} else {
sema.acquire();
}
}
// to be used when the audio thread is being destroyed to
// make sure that everything waiting on it stops waiting.
void forceNotify() {
sema.release();
queue->try_enqueue(OsciPoint());
}
void write(OsciPoint point) {
if (offset >= buffer->size()) {
{
juce::SpinLock::ScopedLockType scope(bufferLock);
buffer = buffer == &buffer1 ? &buffer2 : &buffer1;
if (blockOnWrite) {
queue->wait_enqueue(point);
} else {
if (offset >= buffer->size()) {
{
juce::SpinLock::ScopedLockType scope(bufferLock);
buffer = buffer == &buffer1 ? &buffer2 : &buffer1;
}
offset = 0;
sema.release();
}
offset = 0;
sema.release();
(*buffer)[offset++] = point;
}
}
std::vector<OsciPoint>& getBuffer() {
if (blockOnWrite) {
return returnBuffer;
} else {
// whatever buffer is not currently being written to
juce::SpinLock::ScopedLockType scope(bufferLock);
return buffer == &buffer1 ? buffer2 : buffer1;
}
(*buffer)[offset++] = point;
}
// whatever buffer is not currently being written to
std::vector<OsciPoint>& getBuffer() {
juce::SpinLock::ScopedLockType scope(bufferLock);
return buffer == &buffer1 ? buffer2 : buffer1;
void setBlockOnWrite(bool block) {
blockOnWrite = block;
if (blockOnWrite) {
sema.release();
} else {
OsciPoint item;
queue->try_dequeue(item);
}
}
private:
std::unique_ptr<moodycamel::BlockingReaderWriterCircularBuffer<OsciPoint>> queue;
std::vector<OsciPoint> returnBuffer;
std::vector<OsciPoint> buffer1;
std::vector<OsciPoint> buffer2;
juce::SpinLock bufferLock;
std::atomic<bool> blockOnWrite = false;
std::vector<OsciPoint>* buffer = &buffer1;
Semaphore sema{0};
int offset = 0;

Wyświetl plik

@ -0,0 +1,772 @@
// ©2013-2016 Cameron Desrochers.
// Distributed under the simplified BSD license (see the license file that
// should have come with this header).
// Uses Jeff Preshing's semaphore implementation (under the terms of its
// separate zlib license, embedded below).
#pragma once
// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation
// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment).
// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees).
// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols.
#include <cerrno>
#include <cassert>
#include <type_traits>
#include <cerrno>
#include <cstdint>
#include <ctime>
// Platform detection
#if defined(__INTEL_COMPILER)
#define AE_ICC
#elif defined(_MSC_VER)
#define AE_VCPP
#elif defined(__GNUC__)
#define AE_GCC
#endif
#if defined(_M_IA64) || defined(__ia64__)
#define AE_ARCH_IA64
#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)
#define AE_ARCH_X64
#elif defined(_M_IX86) || defined(__i386__)
#define AE_ARCH_X86
#elif defined(_M_PPC) || defined(__powerpc__)
#define AE_ARCH_PPC
#else
#define AE_ARCH_UNKNOWN
#endif
// AE_UNUSED
#define AE_UNUSED(x) ((void)x)
// AE_NO_TSAN/AE_TSAN_ANNOTATE_*
// For GCC
#if defined(__SANITIZE_THREAD__)
#define AE_TSAN_IS_ENABLED
#endif
// For clang
#if defined(__has_feature)
#if __has_feature(thread_sanitizer) && !defined(AE_TSAN_IS_ENABLED)
#define AE_TSAN_IS_ENABLED
#endif
#endif
#ifdef AE_TSAN_IS_ENABLED
#if __cplusplus >= 201703L // inline variables require C++17
namespace moodycamel { inline int ae_tsan_global; }
#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))
#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global))
extern "C" void AnnotateHappensBefore(const char*, int, void*);
extern "C" void AnnotateHappensAfter(const char*, int, void*);
#else // when we can't work with tsan, attempt to disable its warnings
#define AE_NO_TSAN __attribute__((no_sanitize("thread")))
#endif
#endif
#ifndef AE_NO_TSAN
#define AE_NO_TSAN
#endif
#ifndef AE_TSAN_ANNOTATE_RELEASE
#define AE_TSAN_ANNOTATE_RELEASE()
#define AE_TSAN_ANNOTATE_ACQUIRE()
#endif
// AE_FORCEINLINE
#if defined(AE_VCPP) || defined(AE_ICC)
#define AE_FORCEINLINE __forceinline
#elif defined(AE_GCC)
//#define AE_FORCEINLINE __attribute__((always_inline))
#define AE_FORCEINLINE inline
#else
#define AE_FORCEINLINE inline
#endif
// AE_ALIGN
#if defined(AE_VCPP) || defined(AE_ICC)
#define AE_ALIGN(x) __declspec(align(x))
#elif defined(AE_GCC)
#define AE_ALIGN(x) __attribute__((aligned(x)))
#else
// Assume GCC compliant syntax...
#define AE_ALIGN(x) __attribute__((aligned(x)))
#endif
// Portable atomic fences implemented below:
namespace moodycamel {
enum memory_order {
memory_order_relaxed,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst,
// memory_order_sync: Forces a full sync:
// #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad
memory_order_sync = memory_order_seq_cst
};
} // end namespace moodycamel
#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600)
// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences
#include <intrin.h>
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
#define AeFullSync _mm_mfence
#define AeLiteSync _mm_mfence
#elif defined(AE_ARCH_IA64)
#define AeFullSync __mf
#define AeLiteSync __mf
#elif defined(AE_ARCH_PPC)
#include <ppcintrinsics.h>
#define AeFullSync __sync
#define AeLiteSync __lwsync
#endif
#ifdef AE_VCPP
#pragma warning(push)
#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert`
#ifdef __cplusplus_cli
#pragma managed(push, off)
#endif
#endif
namespace moodycamel {
AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN
{
switch (order) {
case memory_order_relaxed: break;
case memory_order_acquire: _ReadBarrier(); break;
case memory_order_release: _WriteBarrier(); break;
case memory_order_acq_rel: _ReadWriteBarrier(); break;
case memory_order_seq_cst: _ReadWriteBarrier(); break;
default: assert(false);
}
}
// x86/x64 have a strong memory model -- all loads and stores have
// acquire and release semantics automatically (so only need compiler
// barriers for those).
#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
{
switch (order) {
case memory_order_relaxed: break;
case memory_order_acquire: _ReadBarrier(); break;
case memory_order_release: _WriteBarrier(); break;
case memory_order_acq_rel: _ReadWriteBarrier(); break;
case memory_order_seq_cst:
_ReadWriteBarrier();
AeFullSync();
_ReadWriteBarrier();
break;
default: assert(false);
}
}
#else
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
{
// Non-specialized arch, use heavier memory barriers everywhere just in case :-(
switch (order) {
case memory_order_relaxed:
break;
case memory_order_acquire:
_ReadBarrier();
AeLiteSync();
_ReadBarrier();
break;
case memory_order_release:
_WriteBarrier();
AeLiteSync();
_WriteBarrier();
break;
case memory_order_acq_rel:
_ReadWriteBarrier();
AeLiteSync();
_ReadWriteBarrier();
break;
case memory_order_seq_cst:
_ReadWriteBarrier();
AeFullSync();
_ReadWriteBarrier();
break;
default: assert(false);
}
}
#endif
} // end namespace moodycamel
#else
// Use standard library of atomics
#include <atomic>
namespace moodycamel {
AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN
{
switch (order) {
case memory_order_relaxed: break;
case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break;
case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break;
case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break;
case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break;
default: assert(false);
}
}
AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN
{
switch (order) {
case memory_order_relaxed: break;
case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break;
case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break;
case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break;
case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break;
default: assert(false);
}
}
} // end namespace moodycamel
#endif
#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))
#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
#endif
#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
#include <atomic>
#endif
#include <utility>
// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:
// Provides basic support for atomic variables -- no memory ordering guarantees are provided.
// The guarantee of atomicity is only made for types that already have atomic load and store guarantees
// at the hardware level -- on most platforms this generally means aligned pointers and integers (only).
namespace moodycamel {
template<typename T>
class weak_atomic
{
public:
AE_NO_TSAN weak_atomic() : value() { }
#ifdef AE_VCPP
#pragma warning(push)
#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning
#endif
template<typename U> AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) { }
#ifdef __cplusplus_cli
// Work around bug with universal reference/nullptr combination that only appears when /clr is on
AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { }
#endif
AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) { }
AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) { }
#ifdef AE_VCPP
#pragma warning(pop)
#endif
AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); }
#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
template<typename U> AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward<U>(x); return *this; }
AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; }
AE_FORCEINLINE T load() const AE_NO_TSAN { return value; }
AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN
{
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
#if defined(_M_AMD64)
else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
#endif
#else
#error Unsupported platform
#endif
assert(false && "T must be either a 32 or 64 bit type");
return value;
}
AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN
{
#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
#if defined(_M_AMD64)
else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
#endif
#else
#error Unsupported platform
#endif
assert(false && "T must be either a 32 or 64 bit type");
return value;
}
#else
template<typename U>
AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN
{
value.store(std::forward<U>(x), std::memory_order_relaxed);
return *this;
}
AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN
{
value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);
return *this;
}
AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); }
AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN
{
return value.fetch_add(increment, std::memory_order_acquire);
}
AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN
{
return value.fetch_add(increment, std::memory_order_release);
}
#endif
private:
#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
// No std::atomic support, but still need to circumvent compiler optimizations.
// `volatile` will make memory access slow, but is guaranteed to be reliable.
volatile T value;
#else
std::atomic<T> value;
#endif
};
} // end namespace moodycamel
// Portable single-producer, single-consumer semaphore below:
#if defined(_WIN32)
// Avoid including windows.h in a header; we only need a handful of
// items, so we'll redeclare them here (this is relatively safe since
// the API generally has to remain stable between Windows versions).
// I know this is an ugly hack but it still beats polluting the global
// namespace with thousands of generic names or adding a .cpp for nothing.
extern "C" {
struct _SECURITY_ATTRIBUTES;
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
}
#elif defined(__MACH__)
#include <mach/mach.h>
#elif defined(__unix__)
#include <semaphore.h>
#elif defined(FREERTOS)
#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>
#endif
namespace moodycamel
{
// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's
// portable + lightweight semaphore implementations, originally from
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
// LICENSE:
// Copyright (c) 2015 Jeff Preshing
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgement in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
namespace spsc_sema
{
#if defined(_WIN32)
class Semaphore
{
private:
void* m_hSema;
Semaphore(const Semaphore& other);
Semaphore& operator=(const Semaphore& other);
public:
AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema()
{
assert(initialCount >= 0);
const long maxLong = 0x7fffffff;
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
assert(m_hSema);
}
AE_NO_TSAN ~Semaphore()
{
CloseHandle(m_hSema);
}
bool wait() AE_NO_TSAN
{
const unsigned long infinite = 0xffffffff;
return WaitForSingleObject(m_hSema, infinite) == 0;
}
bool try_wait() AE_NO_TSAN
{
return WaitForSingleObject(m_hSema, 0) == 0;
}
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
{
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
}
void signal(int count = 1) AE_NO_TSAN
{
while (!ReleaseSemaphore(m_hSema, count, nullptr));
}
};
#elif defined(__MACH__)
//---------------------------------------------------------
// Semaphore (Apple iOS and OSX)
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
//---------------------------------------------------------
class Semaphore
{
private:
semaphore_t m_sema;
Semaphore(const Semaphore& other);
Semaphore& operator=(const Semaphore& other);
public:
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
{
assert(initialCount >= 0);
kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
assert(rc == KERN_SUCCESS);
AE_UNUSED(rc);
}
AE_NO_TSAN ~Semaphore()
{
semaphore_destroy(mach_task_self(), m_sema);
}
bool wait() AE_NO_TSAN
{
return semaphore_wait(m_sema) == KERN_SUCCESS;
}
bool try_wait() AE_NO_TSAN
{
return timed_wait(0);
}
bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN
{
mach_timespec_t ts;
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
kern_return_t rc = semaphore_timedwait(m_sema, ts);
return rc == KERN_SUCCESS;
}
void signal() AE_NO_TSAN
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
void signal(int count) AE_NO_TSAN
{
while (count-- > 0)
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
}
};
#elif defined(__unix__)
//---------------------------------------------------------
// Semaphore (POSIX, Linux)
//---------------------------------------------------------
class Semaphore
{
private:
sem_t m_sema;
Semaphore(const Semaphore& other);
Semaphore& operator=(const Semaphore& other);
public:
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
{
assert(initialCount >= 0);
int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
assert(rc == 0);
AE_UNUSED(rc);
}
AE_NO_TSAN ~Semaphore()
{
sem_destroy(&m_sema);
}
bool wait() AE_NO_TSAN
{
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
int rc;
do
{
rc = sem_wait(&m_sema);
}
while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool try_wait() AE_NO_TSAN
{
int rc;
do {
rc = sem_trywait(&m_sema);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
{
struct timespec ts;
const int usecs_in_1_sec = 1000000;
const int nsecs_in_1_sec = 1000000000;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);
ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
// so we have to clean things up before passing it in
if (ts.tv_nsec >= nsecs_in_1_sec) {
ts.tv_nsec -= nsecs_in_1_sec;
++ts.tv_sec;
}
int rc;
do {
rc = sem_timedwait(&m_sema, &ts);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
void signal() AE_NO_TSAN
{
while (sem_post(&m_sema) == -1);
}
void signal(int count) AE_NO_TSAN
{
while (count-- > 0)
{
while (sem_post(&m_sema) == -1);
}
}
};
#elif defined(FREERTOS)
//---------------------------------------------------------
// Semaphore (FreeRTOS)
//---------------------------------------------------------
class Semaphore
{
private:
SemaphoreHandle_t m_sema;
Semaphore(const Semaphore& other);
Semaphore& operator=(const Semaphore& other);
public:
AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema()
{
assert(initialCount >= 0);
m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull), static_cast<UBaseType_t>(initialCount));
assert(m_sema);
}
AE_NO_TSAN ~Semaphore()
{
vSemaphoreDelete(m_sema);
}
bool wait() AE_NO_TSAN
{
return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;
}
bool try_wait() AE_NO_TSAN
{
// Note: In an ISR context, if this causes a task to unblock,
// the caller won't know about it
if (xPortIsInsideInterrupt())
return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;
return xSemaphoreTake(m_sema, 0) == pdTRUE;
}
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
{
std::uint64_t msecs = usecs / 1000;
TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);
if (ticks == 0)
return try_wait();
return xSemaphoreTake(m_sema, ticks) == pdTRUE;
}
void signal() AE_NO_TSAN
{
// Note: In an ISR context, if this causes a task to unblock,
// the caller won't know about it
BaseType_t rc;
if (xPortIsInsideInterrupt())
rc = xSemaphoreGiveFromISR(m_sema, NULL);
else
rc = xSemaphoreGive(m_sema);
assert(rc == pdTRUE);
AE_UNUSED(rc);
}
void signal(int count) AE_NO_TSAN
{
while (count-- > 0)
signal();
}
};
#else
#error Unsupported platform! (No semaphore wrapper available)
#endif
//---------------------------------------------------------
// LightweightSemaphore
//---------------------------------------------------------
class LightweightSemaphore
{
public:
typedef std::make_signed<std::size_t>::type ssize_t;
private:
weak_atomic<ssize_t> m_count;
Semaphore m_sema;
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN
{
ssize_t oldCount;
// Is there a better way to set the initial spin count?
// If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
// as threads start hitting the kernel semaphore.
int spin = 1024;
while (--spin >= 0)
{
if (m_count.load() > 0)
{
m_count.fetch_add_acquire(-1);
return true;
}
compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop.
}
oldCount = m_count.fetch_add_acquire(-1);
if (oldCount > 0)
return true;
if (timeout_usecs < 0)
{
if (m_sema.wait())
return true;
}
if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))
return true;
// At this point, we've timed out waiting for the semaphore, but the
// count is still decremented indicating we may still be waiting on
// it. So we have to re-adjust the count, but only if the semaphore
// wasn't signaled enough times for us too since then. If it was, we
// need to release the semaphore too.
while (true)
{
oldCount = m_count.fetch_add_release(1);
if (oldCount < 0)
return false; // successfully restored things to the way they were
// Oh, the producer thread just signaled the semaphore after all. Try again:
oldCount = m_count.fetch_add_acquire(-1);
if (oldCount > 0 && m_sema.try_wait())
return true;
}
}
public:
AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema()
{
assert(initialCount >= 0);
}
bool tryWait() AE_NO_TSAN
{
if (m_count.load() > 0)
{
m_count.fetch_add_acquire(-1);
return true;
}
return false;
}
bool wait() AE_NO_TSAN
{
return tryWait() || waitWithPartialSpinning();
}
bool wait(std::int64_t timeout_usecs) AE_NO_TSAN
{
return tryWait() || waitWithPartialSpinning(timeout_usecs);
}
void signal(ssize_t count = 1) AE_NO_TSAN
{
assert(count >= 0);
ssize_t oldCount = m_count.fetch_add_release(count);
assert(oldCount >= -1);
if (oldCount < 0)
{
m_sema.signal(1);
}
}
std::size_t availableApprox() const AE_NO_TSAN
{
ssize_t count = m_count.load();
return count > 0 ? static_cast<std::size_t>(count) : 0;
}
};
} // end namespace spsc_sema
} // end namespace moodycamel
#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))
#pragma warning(pop)
#ifdef __cplusplus_cli
#pragma managed(pop)
#endif
#endif

Wyświetl plik

@ -0,0 +1,323 @@
// ©2020 Cameron Desrochers.
// Distributed under the simplified BSD license (see the license file that
// should have come with this header).
// Provides a C++11 implementation of a single-producer, single-consumer wait-free concurrent
// circular buffer (fixed-size queue).
#pragma once
#include <utility>
#include <chrono>
#include <memory>
#include <cstdlib>
#include <cstdint>
#include <cassert>
// Note that this implementation is fully modern C++11 (not compatible with old MSVC versions)
// but we still include atomicops.h for its LightweightSemaphore implementation.
#ifdef MOODYCAMEL_ATOMICOPS
#include "atomicops.h"
#endif
#ifndef MOODYCAMEL_CACHE_LINE_SIZE
#define MOODYCAMEL_CACHE_LINE_SIZE 64
#endif
namespace moodycamel {
template<typename T>
class BlockingReaderWriterCircularBuffer
{
public:
typedef T value_type;
public:
explicit BlockingReaderWriterCircularBuffer(std::size_t capacity)
: maxcap(capacity), mask(), rawData(), data(),
slots_(new spsc_sema::LightweightSemaphore(static_cast<spsc_sema::LightweightSemaphore::ssize_t>(capacity))),
items(new spsc_sema::LightweightSemaphore(0)),
nextSlot(0), nextItem(0)
{
// Round capacity up to power of two to compute modulo mask.
// Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
--capacity;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
for (std::size_t i = 1; i < sizeof(std::size_t); i <<= 1)
capacity |= capacity >> (i << 3);
mask = capacity++;
rawData = static_cast<char*>(std::malloc(capacity * sizeof(T) + std::alignment_of<T>::value - 1));
data = align_for<T>(rawData);
}
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer&& other)
: maxcap(0), mask(0), rawData(nullptr), data(nullptr),
slots_(new spsc_sema::LightweightSemaphore(0)),
items(new spsc_sema::LightweightSemaphore(0)),
nextSlot(), nextItem()
{
swap(other);
}
BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer const&) = delete;
// Note: The queue should not be accessed concurrently while it's
// being deleted. It's up to the user to synchronize this.
~BlockingReaderWriterCircularBuffer()
{
for (std::size_t i = 0, n = items->availableApprox(); i != n; ++i)
reinterpret_cast<T*>(data)[(nextItem + i) & mask].~T();
std::free(rawData);
}
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer&& other) noexcept
{
swap(other);
return *this;
}
BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer const&) = delete;
// Swaps the contents of this buffer with the contents of another.
// Not thread-safe.
void swap(BlockingReaderWriterCircularBuffer& other) noexcept
{
std::swap(maxcap, other.maxcap);
std::swap(mask, other.mask);
std::swap(rawData, other.rawData);
std::swap(data, other.data);
std::swap(slots_, other.slots_);
std::swap(items, other.items);
std::swap(nextSlot, other.nextSlot);
std::swap(nextItem, other.nextItem);
}
// Enqueues a single item (by copying it).
// Fails if not enough room to enqueue.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool try_enqueue(T const& item)
{
if (!slots_->tryWait())
return false;
inner_enqueue(item);
return true;
}
// Enqueues a single item (by moving it, if possible).
// Fails if not enough room to enqueue.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool try_enqueue(T&& item)
{
if (!slots_->tryWait())
return false;
inner_enqueue(std::move(item));
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// then enqueues it (via copy).
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
void wait_enqueue(T const& item)
{
while (!slots_->wait());
inner_enqueue(item);
}
// Blocks the current thread until there's enough space to enqueue the given item,
// then enqueues it (via move, if possible).
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
void wait_enqueue(T&& item)
{
while (!slots_->wait());
inner_enqueue(std::move(item));
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via copy) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool wait_enqueue_timed(T const& item, std::int64_t timeout_usecs)
{
if (!slots_->wait(timeout_usecs))
return false;
inner_enqueue(item);
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via move, if possible) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
bool wait_enqueue_timed(T&& item, std::int64_t timeout_usecs)
{
if (!slots_->wait(timeout_usecs))
return false;
inner_enqueue(std::move(item));
return true;
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via copy) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
template<typename Rep, typename Period>
inline bool wait_enqueue_timed(T const& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_enqueue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Blocks the current thread until there's enough space to enqueue the given item,
// or the timeout expires. Returns false without enqueueing the item if the timeout
// expires, otherwise enqueues the item (via move, if possible) and returns true.
// Thread-safe when called by producer thread.
// No exception guarantee (state will be corrupted) if constructor of T throws.
template<typename Rep, typename Period>
inline bool wait_enqueue_timed(T&& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_enqueue_timed(std::move(item), std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue a single item.
// Returns false if the buffer is empty.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
bool try_dequeue(U& item)
{
if (!items->tryWait())
return false;
inner_dequeue(item);
return true;
}
// Blocks the current thread until there's something to dequeue, then dequeues it.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
void wait_dequeue(U& item)
{
while (!items->wait());
inner_dequeue(item);
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U>
bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
{
if (!items->wait(timeout_usecs))
return false;
inner_dequeue(item);
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Thread-safe when called by consumer thread.
// No exception guarantee (state will be corrupted) if assignment operator of U throws.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Returns a pointer to the next element in the queue (the one that would
// be removed next by a call to `try_dequeue` or `try_pop`). If the queue
// appears empty at the time the method is called, returns nullptr instead.
// Thread-safe when called by consumer thread.
inline T* peek()
{
if (!items->availableApprox())
return nullptr;
return inner_peek();
}
// Pops the next element from the queue, if there is one.
// Thread-safe when called by consumer thread.
inline bool try_pop()
{
if (!items->tryWait())
return false;
inner_pop();
return true;
}
// Returns a (possibly outdated) snapshot of the total number of elements currently in the buffer.
// Thread-safe.
inline std::size_t size_approx() const
{
return items->availableApprox();
}
// Returns the maximum number of elements that this circular buffer can hold at once.
// Thread-safe.
inline std::size_t max_capacity() const
{
return maxcap;
}
private:
template<typename U>
void inner_enqueue(U&& item)
{
std::size_t i = nextSlot++;
new (reinterpret_cast<T*>(data) + (i & mask)) T(std::forward<U>(item));
items->signal();
}
template<typename U>
void inner_dequeue(U& item)
{
std::size_t i = nextItem++;
T& element = reinterpret_cast<T*>(data)[i & mask];
item = std::move(element);
element.~T();
slots_->signal();
}
T* inner_peek()
{
return reinterpret_cast<T*>(data) + (nextItem & mask);
}
void inner_pop()
{
std::size_t i = nextItem++;
reinterpret_cast<T*>(data)[i & mask].~T();
slots_->signal();
}
template<typename U>
static inline char* align_for(char* ptr)
{
const std::size_t alignment = std::alignment_of<U>::value;
return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
}
private:
std::size_t maxcap; // actual (non-power-of-two) capacity
std::size_t mask; // circular buffer capacity mask (for cheap modulo)
char* rawData; // raw circular buffer memory
char* data; // circular buffer memory aligned to element alignment
std::unique_ptr<spsc_sema::LightweightSemaphore> slots_; // number of slots currently free (named with underscore to accommodate Qt's 'slots' macro)
std::unique_ptr<spsc_sema::LightweightSemaphore> items; // number of elements currently enqueued
char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr<spsc_sema::LightweightSemaphore>) * 2];
std::size_t nextSlot; // index of next free slot to enqueue into
char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(std::size_t)];
std::size_t nextItem; // index of next element to dequeue from
};
}

Wyświetl plik

@ -14,17 +14,16 @@
VisualiserComponent::VisualiserComponent(AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent", threadManager), parent(parent) {
addAndMakeVisible(record);
//record.setPulseAnimation(true);
record.setPulseAnimation(true);
record.onClick = [this] {
toggleRecording();
//stopwatch.stop();
//stopwatch.reset();
/*
stopwatch.stop();
stopwatch.reset();
if (record.getToggleState()) {
stopwatch.start();
}
*/
record.setToggleState(false, juce::NotificationType::dontSendNotification);
resized();
};
@ -63,6 +62,9 @@ VisualiserComponent::VisualiserComponent(AudioBackgroundThreadManager& threadMan
VisualiserComponent::~VisualiserComponent() {
openGLContext.detach();
setShouldBeRunning(false, [this] {
renderingSemaphore.release();
});
}
void VisualiserComponent::setFullScreenCallback(std::function<void(FullScreenMode)> callback) {
@ -80,24 +82,40 @@ void VisualiserComponent::mouseDoubleClick(const juce::MouseEvent& event) {
enableFullScreen();
}
void VisualiserComponent::setBuffer(const std::vector<OsciPoint>& buffer) {
juce::CriticalSection::ScopedLockType lock(samplesLock);
xSamples.clear();
ySamples.clear();
zSamples.clear();
for (auto& point : buffer) {
OsciPoint smoothPoint = settings.parameters.smoothEffect->apply(0, point);
xSamples.push_back(smoothPoint.x);
ySamples.push_back(smoothPoint.y);
zSamples.push_back(smoothPoint.z);
void VisualiserComponent::runTask(const std::vector<OsciPoint>& points) {
{
juce::CriticalSection::ScopedLockType lock(samplesLock);
xSamples.clear();
ySamples.clear();
zSamples.clear();
for (auto& point : points) {
OsciPoint smoothPoint = settings.parameters.smoothEffect->apply(0, point);
xSamples.push_back(smoothPoint.x);
ySamples.push_back(smoothPoint.y);
zSamples.push_back(smoothPoint.z);
}
sampleBufferCount++;
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
int newResampledSize = xSamples.size() * RESAMPLE_RATIO;
smoothedXSamples.resize(newResampledSize);
smoothedYSamples.resize(newResampledSize);
smoothedZSamples.resize(newResampledSize);
smoothedZSamples.resize(newResampledSize);
xResampler.process(xSamples.data(), smoothedXSamples.data(), xSamples.size());
yResampler.process(ySamples.data(), smoothedYSamples.data(), ySamples.size());
zResampler.process(zSamples.data(), smoothedZSamples.data(), zSamples.size());
}
}
// this just triggers a repaint
triggerAsyncUpdate();
}
void VisualiserComponent::runTask(const std::vector<OsciPoint>& points) {
setBuffer(points);
// wait for rendering to complete
renderingSemaphore.acquire();
}
int VisualiserComponent::prepareTask(double sampleRate, int bufferSize) {
@ -113,6 +131,7 @@ int VisualiserComponent::prepareTask(double sampleRate, int bufferSize) {
void VisualiserComponent::setPaused(bool paused) {
active = !paused;
renderingSemaphore.release();
setShouldBeRunning(active);
repaint();
}
@ -137,11 +156,7 @@ bool VisualiserComponent::keyPressed(const juce::KeyPress& key) {
void VisualiserComponent::setFullScreen(bool fullScreen) {}
void VisualiserComponent::toggleRecording() {
chooser = std::make_unique<juce::FileChooser>("Choose a .wav file to render...", juce::File(), "*.wav;*.flac");
auto chooserFlags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles;
chooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc) {
audioFile = fc.getResult();
});
setBlockOnAudioThread(record.getToggleState());
}
void VisualiserComponent::haltRecording() {
@ -262,46 +277,30 @@ void VisualiserComponent::openGLContextClosing() {
}
void VisualiserComponent::handleAsyncUpdate() {
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
juce::CriticalSection::ScopedLockType lock(samplesLock);
int newResampledSize = xSamples.size() * RESAMPLE_RATIO;
smoothedXSamples.resize(newResampledSize);
smoothedYSamples.resize(newResampledSize);
smoothedZSamples.resize(newResampledSize);
smoothedZSamples.resize(newResampledSize);
xResampler.process(xSamples.data(), smoothedXSamples.data(), xSamples.size());
yResampler.process(ySamples.data(), smoothedYSamples.data(), ySamples.size());
zResampler.process(zSamples.data(), smoothedZSamples.data(), zSamples.size());
}
repaint();
}
void VisualiserComponent::renderOpenGL() {
if (openGLContext.isActive()) {
if (sampleRate != oldSampleRate) {
oldSampleRate = sampleRate;
setupArrays(RESAMPLE_RATIO * sampleRate / FRAME_RATE);
}
time += 0.01f;
intensity = settings.getIntensity() * (41000.0f / sampleRate);
if (active) {
juce::OpenGLHelpers::clear(juce::Colours::black);
// we have a new buffer to render
if (sampleBufferCount != prevSampleBufferCount) {
prevSampleBufferCount = sampleBufferCount;
juce::CriticalSection::ScopedLockType lock(samplesLock);
if (audioFile != juce::File{}) {
renderAudioFile(audioFile, FILE_RENDER_QOI);
audioFile = juce::File{};
}
if (settings.parameters.upsamplingEnabled->getBoolValue()) {
renderScope(smoothedXSamples, smoothedYSamples, smoothedZSamples);
} else {
renderScope(xSamples, ySamples, zSamples);
}
renderingSemaphore.release();
}
// render texture to screen
activateTargetTexture(std::nullopt);
setShader(texturedShader.get());
drawTexture(renderTexture);
}
}
@ -380,7 +379,6 @@ void VisualiserComponent::setupTextures() {
screenTexture = createScreenTexture();
glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind
}
Texture VisualiserComponent::makeTexture(int width, int height) {
@ -402,13 +400,13 @@ Texture VisualiserComponent::makeTexture(int width, int height) {
return { textureID, width, height };
}
void VisualiserComponent::drawLineTexture(const std::vector<float>& xP, const std::vector<float>& yP, const std::vector<float>& zP) {
void VisualiserComponent::drawLineTexture(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
using namespace juce::gl;
fadeAmount = juce::jmin(1.0, std::pow(0.5, settings.getPersistence()) * 0.4);
activateTargetTexture(lineTexture);
fade();
drawLine(xP, yP, zP);
drawLine(xPoints, yPoints, zPoints);
glBindTexture(GL_TEXTURE_2D, targetTexture.value().id);
}
@ -550,21 +548,21 @@ void VisualiserComponent::setNormalBlending() {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void VisualiserComponent::drawLine(const std::vector<float>& xP, const std::vector<float>& yP, const std::vector<float>& zP) {
void VisualiserComponent::drawLine(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
using namespace juce::gl;
setAdditiveBlending();
int nPoints = xP.size();
int nPoints = xPoints.size();
// Without this, there's an access violation that seems to occur only on some systems
if (scratchVertices.size() != nPoints * 12) scratchVertices.resize(nPoints * 12);
for (int i = 0; i < nPoints; ++i) {
int p = i * 12;
scratchVertices[p] = scratchVertices[p + 3] = scratchVertices[p + 6] = scratchVertices[p + 9] = xP[i];
scratchVertices[p + 1] = scratchVertices[p + 4] = scratchVertices[p + 7] = scratchVertices[p + 10] = yP[i];
scratchVertices[p + 2] = scratchVertices[p + 5] = scratchVertices[p + 8] = scratchVertices[p + 11] = zP[i];
scratchVertices[p] = scratchVertices[p + 3] = scratchVertices[p + 6] = scratchVertices[p + 9] = xPoints[i];
scratchVertices[p + 1] = scratchVertices[p + 4] = scratchVertices[p + 7] = scratchVertices[p + 10] = yPoints[i];
scratchVertices[p + 2] = scratchVertices[p + 5] = scratchVertices[p + 8] = scratchVertices[p + 11] = zPoints[i];
}
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
@ -599,7 +597,7 @@ void VisualiserComponent::drawLine(const std::vector<float>& xP, const std::vect
lineShader->setUniform("uNEdges", (GLfloat) nEdges);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
int nEdgesThisTime = xP.size() - 1;
int nEdgesThisTime = xPoints.size() - 1;
glDrawElements(GL_TRIANGLES, nEdgesThisTime * 6, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(glGetAttribLocation(lineShader->getProgramID(), "aStart"));
@ -626,6 +624,7 @@ void VisualiserComponent::fade() {
void VisualiserComponent::drawCRT() {
using namespace juce::gl;
setNormalBlending();
activateTargetTexture(blur1Texture);
@ -665,19 +664,15 @@ void VisualiserComponent::drawCRT() {
setShader(outputShader.get());
float brightness = std::pow(2, settings.getBrightness() - 2);
outputShader->setUniform("uExposure", brightness);
outputShader->setUniform("uSaturation", (float)settings.getSaturation());
outputShader->setUniform("uNoise", (float)settings.getNoise());
outputShader->setUniform("uSaturation", (float) settings.getSaturation());
outputShader->setUniform("uNoise", (float) settings.getNoise());
outputShader->setUniform("uTime", time);
outputShader->setUniform("uGlow", (float)settings.getGlow());
outputShader->setUniform("uGlow", (float) settings.getGlow());
outputShader->setUniform("uResizeForCanvas", lineTexture.width / 1024.0f);
juce::Colour colour = juce::Colour::fromHSV(settings.getHue() / 360.0f, 1.0, 1.0, 1.0);
outputShader->setUniform("uColour", colour.getFloatRed(), colour.getFloatGreen(), colour.getFloatBlue());
activateTargetTexture(renderTexture);
drawTexture(lineTexture, blur1Texture, blur3Texture, screenTexture);
activateTargetTexture(std::nullopt);
setShader(texturedShader.get());
drawTexture(renderTexture);
}
Texture VisualiserComponent::createScreenTexture() {
@ -781,16 +776,24 @@ void VisualiserComponent::paint(juce::Graphics& g) {
}
}
void VisualiserComponent::renderScope(const std::vector<float>& xp, const std::vector<float>& yp, const std::vector<float>& zp) {
void VisualiserComponent::renderScope(const std::vector<float>& xPoints, const std::vector<float>& yPoints, const std::vector<float>& zPoints) {
time += 0.01f;
if (graticuleEnabled != settings.getGraticuleEnabled() || smudgesEnabled != settings.getSmudgesEnabled()) {
graticuleEnabled = settings.getGraticuleEnabled();
smudgesEnabled = settings.getSmudgesEnabled();
screenTexture = createScreenTexture();
}
if (sampleRate != oldSampleRate) {
oldSampleRate = sampleRate;
setupArrays(RESAMPLE_RATIO * sampleRate / FRAME_RATE);
}
intensity = settings.getIntensity() * (41000.0f / sampleRate);
renderScale = (float)openGLContext.getRenderingScale();
drawLineTexture(xp, yp, zp);
drawLineTexture(xPoints, yPoints, zPoints);
checkGLErrors("drawLineTexture");
drawCRT();
checkGLErrors("drawCRT");
@ -880,11 +883,10 @@ int VisualiserComponent::renderAudioFile(juce::File& sourceAudio, int method, in
saveTextureToQOI(renderTexture, destDir.getChildFile(fileName + ".qoi"));
break;
};
time += 0.01f;
}
// cleanup
delete reader;
return f;
}
}

Wyświetl plik

@ -37,7 +37,6 @@ public:
void enableFullScreen();
void setFullScreenCallback(std::function<void(FullScreenMode)> callback);
void mouseDoubleClick(const juce::MouseEvent& event) override;
void setBuffer(const std::vector<OsciPoint>& buffer);
void resized() override;
void paint(juce::Graphics& g) override;
int prepareTask(double sampleRate, int samplesPerBlock) override;
@ -84,6 +83,8 @@ private:
StopwatchComponent stopwatch;
SvgButton record{"Record", BinaryData::record_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};
Semaphore renderingSemaphore{0};
void popoutWindow();
// OPENGL
@ -110,6 +111,8 @@ private:
std::vector<float> smoothedXSamples;
std::vector<float> smoothedYSamples;
std::vector<float> smoothedZSamples;
int sampleBufferCount = 0;
int prevSampleBufferCount = 0;
std::vector<float> scratchVertices;
std::vector<float> fullScreenQuad;

Wyświetl plik

@ -1,9 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="Dt5Xh7" name="osci-render-test" projectType="consoleapp"
useAppConfig="0" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
useAppConfig="0" addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1"
defines="MOODYCAMEL_ATOMICOPS=1">
<MAINGROUP id="ztPpnM" name="osci-render-test">
<GROUP id="{4373FA2A-1E20-AAA6-40E0-740C13D88B75}" name="Source">
<GROUP id="{6D2F76BF-0825-85C1-CE15-B08BF1AA1218}" name="concurrency">
<FILE id="QdcNi7" name="atomicops.h" compile="0" resource="0" file="Source/concurrency/atomicops.h"/>
<FILE id="iTTsXj" name="AudioBackgroundThread.cpp" compile="1" resource="0"
file="Source/concurrency/AudioBackgroundThread.cpp"/>
<FILE id="kyWcaF" name="AudioBackgroundThread.h" compile="0" resource="0"
file="Source/concurrency/AudioBackgroundThread.h"/>
<FILE id="kfeXov" name="AudioBackgroundThreadManager.cpp" compile="1"
resource="0" file="Source/concurrency/AudioBackgroundThreadManager.cpp"/>
<FILE id="TZuY5S" name="AudioBackgroundThreadManager.h" compile="0"
resource="0" file="Source/concurrency/AudioBackgroundThreadManager.h"/>
<FILE id="nqi7hn" name="BufferConsumer.h" compile="0" resource="0"
file="Source/concurrency/BufferConsumer.h"/>
<FILE id="fTCFX5" name="readerwritercircularbuffer.h" compile="0" resource="0"
file="Source/concurrency/readerwritercircularbuffer.h"/>
</GROUP>
<GROUP id="{DB7C86A4-CC9B-5846-B0C3-6EB553450542}" name="mathter">
<GROUP id="{3743CC14-52E9-72AB-1A61-DA053869B50F}" name="Common">
<FILE id="aQA6tH" name="Approx.hpp" compile="0" resource="0" file="Source/mathter/Common/Approx.hpp"/>
@ -102,6 +118,14 @@
<FILE id="V3Q6n2" name="Frustum.cpp" compile="1" resource="0" file="Source/obj/Frustum.cpp"/>
<FILE id="m9wauB" name="Frustum.h" compile="0" resource="0" file="Source/obj/Frustum.h"/>
</GROUP>
<GROUP id="{4C47E086-E440-AB0D-BB6E-B419AEBA3583}" name="shape">
<FILE id="rGoWAa" name="Line.cpp" compile="1" resource="0" file="Source/shape/Line.cpp"/>
<FILE id="Q4nxsV" name="Line.h" compile="0" resource="0" file="Source/shape/Line.h"/>
<FILE id="HZzR9I" name="OsciPoint.cpp" compile="1" resource="0" file="Source/shape/OsciPoint.cpp"/>
<FILE id="XRWdZW" name="OsciPoint.h" compile="0" resource="0" file="Source/shape/OsciPoint.h"/>
<FILE id="Fb0uH1" name="Shape.cpp" compile="1" resource="0" file="Source/shape/Shape.cpp"/>
<FILE id="PGbdTP" name="Shape.h" compile="0" resource="0" file="Source/shape/Shape.h"/>
</GROUP>
<FILE id="bQ1rDR" name="TestMain.cpp" compile="1" resource="0" file="Source/TestMain.cpp"/>
</GROUP>
</MAINGROUP>

Wyświetl plik

@ -184,6 +184,8 @@
<FILE id="F5kUMH" name="BlockingQueue.h" compile="0" resource="0" file="Source/concurrency/BlockingQueue.h"/>
<FILE id="WQ2W15" name="BufferConsumer.h" compile="0" resource="0"
file="Source/concurrency/BufferConsumer.h"/>
<FILE id="L9aCHY" name="readerwritercircularbuffer.h" compile="0" resource="0"
file="Source/concurrency/readerwritercircularbuffer.h"/>
</GROUP>
<GROUP id="{A3E24187-62A5-AB8D-8837-14043B89A640}" name="gpla">
<FILE id="KvDV8j" name="LineArtParser.cpp" compile="1" resource="0"