Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactors task api #149

Merged
merged 22 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading