Skip to content

Commit

Permalink
Merge pull request #2854 from maksutovic/v6-c++
Browse files Browse the repository at this point in the history
V6 c++
  • Loading branch information
wtholliday committed Aug 8, 2023
2 parents 5ac5f58 + 892a115 commit 2cd27a7
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Sources/Audio/Internals/Engine/ThreadPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ final class ThreadPool {
}

deinit {
// Shut down workers.
// Shut down workers. WOTWU
for worker in workers {
worker.exit()
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Audio/Internals/Engine/sysex.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/

#warning("Can we mark this for deletion as we are moving to MIDIKit?")

import AudioUnit
import Foundation

Expand Down
86 changes: 86 additions & 0 deletions Sources/CAudio/AudioProgram.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include <functional>
#include <atomic>
#include <memory>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AVFoundation/AVFoundation.h>
#include "WorkStealingQueue.hpp"

namespace AudioKit {

using std::vector;
using std::shared_ptr;
using std::atomic;
using RenderJobVector = vector<shared_ptr<RenderJob>>;
class AudioProgram {

public:
vector<int> generatorIndices;

AudioProgram(const RenderJobVector& jobs, vector<int> generatorIndices)
: jobs(jobs), generatorIndices(generatorIndices), finished(new atomic<int>[jobs.size()])
{
reset();
}

void reset() {
for (int i=0;i < jobs.size(); ++i) {
finished[i].store(0);
}
remaining.store(static_cast<int>(jobs.size()));
}

~AudioProgram() {
delete[] finished;
}

void run(AudioUnitRenderActionFlags* actionFlags,
const AudioTimeStamp* timeStamp,
AUAudioFrameCount frameCount,
AudioBufferList* outputBufferList,
int workerIndex,
std::vector<WorkStealingQueue<int>>& runQueues) {

auto exec = [&](int index) {
auto& job = jobs[index];

job->render(actionFlags, timeStamp, frameCount,
(index == jobs.size() - 1) ? outputBufferList : nullptr);

// Increment outputs.
for (int outputIndex : job->outputIndices) {
if (finished[outputIndex].fetch_add(1, std::memory_order_relaxed) == jobs[outputIndex]->inputCount()) {
runQueues[workerIndex].push(outputIndex);
}
}

remaining.fetch_sub(1, std::memory_order_relaxed);
};

while (remaining.load(std::memory_order_relaxed) > 0) {
// Pop an index off our queue.
if (auto index = runQueues[workerIndex].pop()) {
exec(*index);
} else {
// Try to steal an index. Start with the next worker and wrap around,
// but don't steal from ourselves.
for (int i = 0; i < runQueues.size() - 1; ++i) {
int victim = (workerIndex + i) % runQueues.size();
if (auto index = runQueues[victim].steal()) {
exec(*index);
break;
}
}
}
}
}

private:
vector<shared_ptr<RenderJob>> jobs;
atomic<int>* finished;
atomic<int> remaining = {0};
};

} //namespace AudioKit
5 changes: 5 additions & 0 deletions Sources/CAudio/CAudio.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
#include "WorkStealingQueue.hpp"
#include "SynchronizedAudioBufferList.h"
#include "RingBuffer.h"
#include "RenderJob.h"
#include "RenderEvents.h"
#include "AudioProgram.h"
#include "Sysex.h"
#include "WorkerThread.h"
38 changes: 38 additions & 0 deletions Sources/CAudio/RenderEvents.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
#pragma once
#include <AudioToolbox/AudioToolbox.h>

/// Handles the ickyness of accessing AURenderEvents without reading off the end of the struct.
///
/// - Parameters:
/// - events: render event list
/// - midi: callback for midi events
/// - sysex: callback for sysex events
/// - param: callback for param events
template<typename MidiFunc, typename SysexFunc, typename ParamFunc>
void processRenderEvents(AURenderEvent* events,
MidiFunc midiFunc,
SysexFunc sysexFunc,
ParamFunc paramFunc) {

while(events) {
switch(events->head.eventType) {
case AURenderEventMIDI:
midiFunc(&events->MIDI);
break;
case AURenderEventMIDISysEx:
sysexFunc(&events->MIDI);
break;
case AURenderEventParameter:
paramFunc(&events->parameter);
break;
case AURenderEventParameterRamp:
paramFunc(&events->parameter);
break;
case AURenderEventMIDIEventList:
midiFunc(&events->MIDI);
break;
}
events = events->head.next;
}
}
86 changes: 86 additions & 0 deletions Sources/CAudio/RenderJob.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
#pragma once
#include <vector>
#include <functional>
#include <atomic>
#include <memory>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AVFoundation/AVFoundation.h>
#include "SynchronizedAudioBufferList.h"

typedef int RenderJobIndex;

namespace AudioKit {

class RenderJob {
private:
/// Buffer we're writing to, unless overridden by buffer passed to render.
std::shared_ptr<SynrchonizedAudioBufferList2> outputBuffer;

/// Block called to render.
AURenderBlock renderBlock;

/// Input block passed to the renderBlock. We don't chain AUs recursively.
AURenderPullInputBlock inputBlock;

/// Indices of jobs feeding this one
std::vector<int> inputIndices;

public:

/// Indices of jobs that this one feeds.
std::vector<int> outputIndices;

RenderJob(std::shared_ptr<SynrchonizedAudioBufferList2> outputBuffer,
AURenderBlock renderBlock,
AURenderPullInputBlock inputBlock,
std::vector<int> inputIndices)
: outputBuffer(outputBuffer), renderBlock(renderBlock), inputBlock(inputBlock), inputIndices(inputIndices) {
}

/// Number of inputs feeding this AU.
int32_t inputCount() {
return static_cast<int32_t>(inputIndices.size());
}

void render(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timeStamp, AUAudioFrameCount frameCount, AudioBufferList* outputBufferList=nullptr) {

AudioBufferList* out = outputBufferList ? outputBufferList : outputBuffer->abl;

// AUs may change the output size, so reset it.
out->mBuffers[0].mDataByteSize = frameCount * sizeof(float);
out->mBuffers[1].mDataByteSize = frameCount * sizeof(float);

// Do the actual DSP.
AUAudioUnitStatus status = renderBlock(actionFlags, timeStamp, frameCount, 0, out, inputBlock);

// Propagate errors.
if (status != noErr) {
switch (status) {
case kAudioUnitErr_NoConnection:
printf("got kAudioUnitErr_NoConnection\n");
break;
case kAudioUnitErr_TooManyFramesToProcess:
printf("got kAudioUnitErr_TooManyFramesToProcess\n");
break;
case AVAudioEngineManualRenderingErrorNotRunning:
printf("got AVAudioEngineManualRenderingErrorNotRunning\n");
break;
case kAudio_ParamError:
printf("got kAudio_ParamError\n");
break;
default:
printf("unkown rendering error\n");
break;
}
}

// Indicate that we're done writing to the output.
outputBuffer->endWriting();
}
};

} // namespace AudioKit


101 changes: 101 additions & 0 deletions Sources/CAudio/Sysex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
#pragma once
#include <vector>
#include <cstdint>
#include <cassert>
#include <type_traits>
#include <AudioUnit/AudioUnit.h>



/// Encode a value in a MIDI sysex message. Value must be plain-old-data.
template<typename T>
std::vector<uint8_t> encodeSysex(T value) {
// static_assert to check if T is POD at compile time.
static_assert(std::is_pod<T>::value, "T must be plain-old-data");

// Start with a sysex header.
std::vector<uint8_t> result {0xF0, 0x00};

// Encode the value as a sequence of nibbles.
// There might be some more efficient way to do this,
// but we can't clash with the 0xF7 end-of-message.
// We may not actually need to encode a valid MIDI sysex
// message, but that could be implementation dependent
// and change over time. Best to be safe.
uint8_t* value_ptr = reinterpret_cast<uint8_t*>(&value);
for(size_t i = 0; i < sizeof(T); i++) {
uint8_t byte = value_ptr[i];
result.push_back(byte >> 4);
result.push_back(byte & 0xF);
}

result.push_back(0xF7);
return result;
}

/// Decode a sysex message into a value. Value must be plain-old-data.
///
/// We can't return a value because we can't assume the value can be
/// default constructed.
///
/// - Parameters:
/// - bytes: the sysex message
/// - count: number of bytes in message
/// - value: the value we're writing to
///
template<typename T>
void decodeSysex(const uint8_t* bytes, size_t count, T& value) {
// static_assert to check if T is POD at compile time.
static_assert(std::is_pod<T>::value, "T must be plain-old-data");

assert(count == 2 * sizeof(T) + 3);

uint8_t* value_ptr = reinterpret_cast<uint8_t*>(&value);
for(size_t i = 0; i < sizeof(T); i++) {
value_ptr[i] = (bytes[2 * i + 2] << 4) | bytes[2 * i + 3];
}
}

/// Call a function with a pointer to the midi data in the AURenderEvent.
///
/// We need this function because event.pointee.MIDI.data is just a tuple of three midi bytes. This is
/// fine for simple midi messages like note on/off, but some messages are longer, so we need
/// access to the full array, which extends off the end of the structure (one of those variable-length C structs).
///
/// - Parameters:
/// - event: pointer to the AURenderEvent
/// - f: function to call

void withMidiData(const AUMIDIEvent* event, void (*f)(const uint8_t*)) {
assert(event->eventType == AURenderEventMIDISysEx || event->eventType == AURenderEventMIDI);

intptr_t offset = reinterpret_cast<intptr_t>(&(event->data)) - reinterpret_cast<intptr_t>(event);
const uint8_t* raw = reinterpret_cast<const uint8_t*>(event) + offset;

f(raw);
}


/// Decode a value from a sysex AURenderEvent.
///
/// We can't return a value because we can't assume the value can be
/// default constructed.
///
/// - Parameters:
/// - event: pointer to the AURenderEvent
/// - value: where we will store the value
template<typename T>
void decodeSysex(const AUMIDIEvent* event, T& value) {
static_assert(std::is_trivial<T>::value && std::is_standard_layout<T>::value, "Type T must be POD");

assert(event->eventType == AURenderEventMIDISysEx);

uint16_t length = event->length;

withMidiData(event, [&](const uint8_t* ptr) {
decodeSysex(ptr, static_cast<int>(length), value);
});
}


1 change: 1 addition & 0 deletions Sources/CAudio/Tap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#pragma once

0 comments on commit 2cd27a7

Please sign in to comment.