Skip to content

Commit

Permalink
Merge pull request #149 from anokta/task-dev
Browse files Browse the repository at this point in the history
refactors task api
  • Loading branch information
anokta authored Jan 21, 2025
2 parents 23a778d + 0a345d2 commit d2e8c69
Show file tree
Hide file tree
Showing 60 changed files with 2,087 additions and 2,096 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ jobs:
fail-fast: false

steps:
- name: env
shell: bash
run: echo "ref_name=${GITHUB_REF_NAME//\//-}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: benchmark.json
key: benchmark-${{ matrix.os }}-${{ github.ref_name }}-${{ github.sha }}
key: benchmark-${{ matrix.os }}-${{ env.ref_name }}-${{ github.sha }}
restore-keys: |
benchmark-${{ matrix.os }}-${{ github.ref_name }}
benchmark-${{ matrix.os }}-${{ env.ref_name }}
benchmark-${{ matrix.os }}
- name: install
shell: bash
Expand All @@ -40,7 +43,7 @@ jobs:
--benchmark_compare="${{ github.workspace }}/benchmark_cached.json"
- uses: actions/upload-artifact@v4
with:
name: benchmark-${{ matrix.os }}-${{ github.ref_name }}-${{ github.sha }}
name: benchmark-${{ matrix.os }}-${{ env.ref_name }}-${{ github.sha }}
path: benchmark.json

test:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Build artifacts.
build/

# OSX.
# macOS.
.DS_Store

# VS Code.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ if(ENABLE_DAISY)
FetchContent_Declare(
libdaisy
GIT_REPOSITORY https://github.com/electro-smith/libDaisy.git
GIT_TAG 15e1dd53368f23c4207ecc4b17ce1a529166fa5a
GIT_TAG 8d106a2c822f687bcb8c86bed0b6f2c22f680999
)
FetchContent_MakeAvailable(libdaisy)
set_target_properties(
Expand Down
51 changes: 24 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ barelymusician is a real-time music engine for interactive systems.
It provides a modern C/C++ API to generate and perform musical sounds from scratch in a sample
accurate way.

This repository includes build targets for Linux, OSX, Windows, Android, and Daisy platforms, in
addition to a native Unity game engine plugin[*][iOS].

[iOS]: ## "see issue #112 for the status of the upcoming iOS platform support"
This repository includes the build targets for Linux, macOS, Windows, Android, and Daisy platforms,
in addition to a native Unity game engine plugin.

To use in a project, simply include [barelymusician.h](include/barelymusician.h).

Expand All @@ -31,52 +29,51 @@ Example usage
// Create the musician.
barely::Musician musician(/*sample_rate=*/48000);

// Set the global tempo to 124 beats per minute.
// Set the global tempo.
musician.SetTempo(/*tempo=*/124.0);

// Add an instrument.
auto instrument = musician.AddInstrument();
// Create a new instrument.
auto instrument = musician.CreateInstrument();

// Set the instrument gain to -6dB.
// Set the instrument gain.
instrument.SetControl(barely::ControlType::kGain, /*value=*/-6.0f);

// Set an instrument note on.
//
// Note pitch is centered around the reference frequency, and measured in octaves. Fractional values
// adjust the frequency logarithmically to maintain perceived pitch intervals in each octave.
// The note pitch is centered around the reference frequency and measured in octaves. Fractional
// note values adjust the frequency logarithmically to ensure equally perceived pitch intervals
// within each octave.
constexpr float kC4Pitch = 0.0f;
instrument.SetNoteOn(kC4Pitch, /*intensity=*/0.25f);

// Check if the instrument note is on.
const bool is_note_on = instrument.IsNoteOn(kC4Pitch); // will return true.

// Add a performer.
auto performer = musician.AddPerformer();
// Create a new performer.
auto performer = musician.CreatePerformer();

// Set the performer to loop.
// Set the performer to looping.
performer.SetLooping(/*is_looping=*/true);

// Add a looping task that plays an instrument note every beat.
auto task = performer.AddTask(
[&]() {
// Set an instrument note on.
instrument.SetNoteOn(/*pitch=*/1.0f);
// Schedule a one-off task to set the instrument note off after half a beat.
performer.ScheduleOneOffTask([&]() { instrument.SetNoteOff(/*pitch=*/1.0f); },
performer.GetPosition() + 0.5);
},
/*position=*/0.0);
// Create a new task that plays an instrument note every beat.
auto task = performer.CreateTask(/*position=*/0.0, /*duration=*/1.0, [&](barely::TaskState state) {
constexpr float kC3Pitch = -1.0f;
if (state == barely::TaskState::kBegin) {
instrument.SetNoteOn(kC3Pitch);
} else if (state == barely::TaskState::kEnd) {
instrument.SetNoteOff(kC3Pitch);
}
});

// Start the performer.
performer.Start();

// Update the musician timestamp in seconds.
//
// Timestamp updates must occur before processing instruments with their respective timestamps.
// Otherwise, such `Process` calls will be *late* to receive the relevant state changes. To
// compensate for this, `Update` should typically be called from a main thread update callback with
// an additional "lookahead" to avoid potential thread synchronization issues that could arise in
// real-time audio applications.
// Otherwise, `Process` calls may be *late* in receiving the relevant changes to the instruments. To
// address this, `Update` should typically be called from the main thread update callback using a
// lookahead to prevent potential thread synchronization issues in real-time audio applications.
constexpr double kLookahead = 0.1;
double timestamp = 0.0;
musician.Update(timestamp + kLookahead);
Expand Down
1 change: 0 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,3 @@ include_directories(.)
add_subdirectory(common)
add_subdirectory(data)
add_subdirectory(demo)
add_subdirectory(performers)
2 changes: 0 additions & 2 deletions examples/demo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ add_demo(
examples_common_audio_clock
examples_common_audio_output
examples_common_console_log
examples_performers_metronome
examples_common_input_manager
)

Expand All @@ -62,7 +61,6 @@ add_demo(
examples_common_audio_output
examples_common_console_log
examples_common_input_manager
examples_performers_metronome
examples_common_wav_file
examples_data_data
)
Expand Down
9 changes: 4 additions & 5 deletions examples/demo/arpeggiator_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,18 @@ int main(int /*argc*/, char* /*argv*/[]) {
Musician musician(kSampleRate);
musician.SetTempo(kInitialTempo);

auto instrument = musician.AddInstrument();
auto instrument = musician.CreateInstrument();
instrument.SetControl(ControlType::kGain, kGain);
instrument.SetControl(ControlType::kOscillatorShape, kOscillatorShape);
instrument.SetControl(ControlType::kAttack, kAttack);
instrument.SetControl(ControlType::kRelease, kRelease);
instrument.SetControl(ControlType::kVoiceCount, kVoiceCount);

instrument.SetNoteOnEvent({[](float pitch, float /*intensity*/, void* /*user_data*/) {
ConsoleLog() << "Note(" << pitch << ")";
}});
instrument.SetNoteOnCallback(
[](float pitch, float /*intensity*/) { ConsoleLog() << "Note(" << pitch << ")"; });

Arpeggiator arpeggiator(musician);
arpeggiator.SetInstrument(instrument);
arpeggiator.SetInstrument(&instrument);
arpeggiator.SetGateRatio(kInitialGateRatio);
arpeggiator.SetRate(kInitialRate);
arpeggiator.SetStyle(kInitialStyle);
Expand Down
9 changes: 4 additions & 5 deletions examples/demo/instrument_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,17 @@ int main(int /*argc*/, char* /*argv*/[]) {

Musician musician(kSampleRate);

auto instrument = musician.AddInstrument();
auto instrument = musician.CreateInstrument();
instrument.SetControl(ControlType::kGain, kGain);
instrument.SetControl(ControlType::kOscillatorShape, kOscillatorShape);
instrument.SetControl(ControlType::kAttack, kAttack);
instrument.SetControl(ControlType::kRelease, kRelease);
instrument.SetControl(ControlType::kVoiceCount, kVoiceCount);

instrument.SetNoteOnEvent({[](float pitch, float intensity, void* /*user_data*/) {
instrument.SetNoteOnCallback([](float pitch, float intensity) {
ConsoleLog() << "NoteOn(" << pitch << ", " << intensity << ")";
}});
instrument.SetNoteOffEvent(
{[](float pitch, void* /*user_data*/) { ConsoleLog() << "NoteOff(" << pitch << ") "; }});
});
instrument.SetNoteOffCallback([](float pitch) { ConsoleLog() << "NoteOff(" << pitch << ") "; });

// Audio process callback.
audio_output.SetProcessCallback([&](std::span<float> output_samples) {
Expand Down
14 changes: 8 additions & 6 deletions examples/demo/metronome_demo.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cmath>
#include <span>
#include <thread>

Expand All @@ -9,18 +10,17 @@
#include "common/audio_output.h"
#include "common/console_log.h"
#include "common/input_manager.h"
#include "performers/metronome.h"

namespace {

using ::barely::ControlType;
using ::barely::Instrument;
using ::barely::Musician;
using ::barely::OscillatorShape;
using ::barely::examples::AudioClock;
using ::barely::examples::AudioOutput;
using ::barely::examples::ConsoleLog;
using ::barely::examples::InputManager;
using ::barely::examples::Metronome;

// System audio settings.
constexpr int kSampleRate = 48000;
Expand Down Expand Up @@ -55,16 +55,17 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
musician.SetTempo(kInitialTempo);

// Create the metronome instrument.
auto instrument = musician.AddInstrument();
auto instrument = musician.CreateInstrument();
instrument.SetControl(ControlType::kGain, kGain);
instrument.SetControl(ControlType::kOscillatorShape, kOscillatorShape);
instrument.SetControl(ControlType::kAttack, kAttack);
instrument.SetControl(ControlType::kRelease, kRelease);
instrument.SetControl(ControlType::kVoiceCount, kVoiceCount);

// Create the metronome with a beat callback.
Metronome metronome(musician);
metronome.SetBeatCallback([&](int beat) {
auto metronome = musician.CreatePerformer();
metronome.SetBeatCallback([&]() {
const int beat = static_cast<int>(metronome.GetPosition());
const int current_bar = (beat / kBeatCount) + 1;
const int current_beat = (beat % kBeatCount) + 1;
ConsoleLog() << "Tick " << current_bar << "." << current_beat;
Expand Down Expand Up @@ -101,7 +102,8 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
}
return;
case '\r':
metronome.Reset();
metronome.Stop();
metronome.SetPosition(0.0);
ConsoleLog() << "Metronome reset";
return;
case 'O':
Expand Down
60 changes: 31 additions & 29 deletions examples/demo/midi_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
namespace {

using ::barely::ControlType;
using ::barely::InstrumentHandle;
using ::barely::Instrument;
using ::barely::Musician;
using ::barely::OscillatorShape;
using ::barely::PerformerHandle;
using ::barely::Performer;
using ::barely::Task;
using ::barely::TaskState;
using ::barely::examples::AudioClock;
using ::barely::examples::AudioOutput;
using ::barely::examples::ConsoleLog;
Expand All @@ -50,24 +52,28 @@ constexpr char kMidiFileName[] = "midi/sample.mid";
constexpr double kTempo = 132.0;

// Builds the score for the given `midi_events`.
bool BuildScore(const smf::MidiEventList& midi_events, int ticks_per_beat,
InstrumentHandle& instrument, PerformerHandle& performer) {
bool BuildScore(const smf::MidiEventList& midi_events, int ticks_per_beat, Instrument& instrument,
Performer& performer, std::vector<Task>& tasks) {
const auto get_position_fn = [ticks_per_beat](int tick) -> double {
return static_cast<double>(tick) / static_cast<double>(ticks_per_beat);
};
bool has_notes = false;
tasks.reserve(2 * midi_events.size());
for (int i = 0; i < midi_events.size(); ++i) {
const auto& midi_event = midi_events[i];
if (midi_event.isNoteOn()) {
const double position = get_position_fn(midi_event.tick);
const double duration = get_position_fn(midi_event.getTickDuration());
const float pitch = static_cast<float>(midi_event.getKeyNumber() - 60) / 12.0f;
const float intensity = static_cast<float>(midi_event.getVelocity()) / 127.0f;
performer.ScheduleOneOffTask(
[&instrument, pitch, intensity]() mutable { instrument.SetNoteOn(pitch, intensity); },
position);
performer.ScheduleOneOffTask([&instrument, pitch]() mutable { instrument.SetNoteOff(pitch); },
position + duration);
tasks.emplace_back(
performer.CreateTask(position, duration, [&, pitch, intensity](TaskState state) noexcept {
if (state == TaskState::kBegin) {
instrument.SetNoteOn(pitch, intensity);
} else if (state == TaskState::kEnd) {
instrument.SetNoteOff(pitch);
}
}));
has_notes = true;
}
}
Expand Down Expand Up @@ -97,30 +103,26 @@ int main(int /*argc*/, char* argv[]) {
Musician musician(kSampleRate);
musician.SetTempo(kTempo);

std::vector<std::tuple<InstrumentHandle, PerformerHandle, size_t>> tracks;
std::vector<std::tuple<Instrument, Performer, std::vector<Task>, size_t>> tracks;
tracks.reserve(track_count);
for (int i = 0; i < track_count; ++i) {
tracks.emplace_back(musician.AddInstrument(), musician.AddPerformer(), tracks.size() + 1);
auto& [instrument, performer, track_index] = tracks.back();
tracks.emplace_back(musician.CreateInstrument(), musician.CreatePerformer(),
std::vector<Task>{}, tracks.size() + 1);
auto& [instrument, performer, tasks, track_index] = tracks.back();
// Build the score to perform.
if (!BuildScore(midi_file[i], ticks_per_quarter, instrument, performer)) {
if (!BuildScore(midi_file[i], ticks_per_quarter, instrument, performer, tasks)) {
ConsoleLog() << "Empty MIDI track: " << i;
musician.RemoveInstrument(instrument);
musician.RemovePerformer(performer);
tracks.pop_back();
continue;
}
// Set the instrument settings.
instrument.SetNoteOnEvent({[](float pitch, float intensity, void* user_data) {
ConsoleLog() << "MIDI track #" << *static_cast<size_t*>(user_data)
<< ": NoteOn(" << pitch << ", " << intensity << ")";
},
static_cast<void*>(&track_index)});
instrument.SetNoteOffEvent({[](float pitch, void* user_data) {
ConsoleLog() << "MIDI track #" << *static_cast<size_t*>(user_data)
<< ": NoteOff(" << pitch << ")";
},
static_cast<void*>(&track_index)});
instrument.SetNoteOnCallback([track_index](float pitch, float intensity) {
ConsoleLog() << "MIDI track #" << track_index << ": NoteOn(" << pitch << ", " << intensity
<< ")";
});
instrument.SetNoteOffCallback([track_index](float pitch) {
ConsoleLog() << "MIDI track #" << track_index << ": NoteOff(" << pitch << ")";
});
instrument.SetControl(ControlType::kGain, kInstrumentGain);
instrument.SetControl(ControlType::kOscillatorShape, kInstrumentOscillatorShape);
instrument.SetControl(ControlType::kAttack, kInstrumentEnvelopeAttack);
Expand All @@ -133,7 +135,7 @@ int main(int /*argc*/, char* argv[]) {
std::vector<float> mix_buffer(kSampleCount);
const auto process_callback = [&](std::span<float> output_samples) {
std::fill_n(output_samples.begin(), kSampleCount, 0.0f);
for (auto& [instrument, performer, _] : tracks) {
for (auto& [instrument, performer, tasks, _] : tracks) {
instrument.Process(mix_buffer, clock.GetTimestamp());
std::transform(mix_buffer.begin(), mix_buffer.end(), output_samples.begin(),
output_samples.begin(), std::plus<>());
Expand All @@ -157,7 +159,7 @@ int main(int /*argc*/, char* argv[]) {
ConsoleLog() << "Starting audio stream";
audio_output.Start();
musician.Update(kLookahead);
for (auto& [instrument, performer, _] : tracks) {
for (auto& [instrument, performer, tasks, _] : tracks) {
performer.Start();
}

Expand All @@ -169,9 +171,9 @@ int main(int /*argc*/, char* argv[]) {

// Stop the demo.
ConsoleLog() << "Stopping audio stream";
for (auto& [instrument, performer, _] : tracks) {
for (auto& [instrument, performer, tasks, _] : tracks) {
performer.Stop();
instrument.SetAllNotesOff();
tasks.clear();
}
audio_output.Stop();

Expand Down
Loading

0 comments on commit d2e8c69

Please sign in to comment.