Added asynchronous note playing

This commit is contained in:
Robert Bendun 2022-08-18 20:20:47 +02:00
parent da8a6b5d6c
commit e7f3cf2cfb
4 changed files with 56 additions and 29 deletions

View File

@ -7,8 +7,8 @@ Note Context::fill(Note note) const
return note; return note;
} }
std::chrono::duration<float> Context::length_to_duration(std::optional<Number> length) const Context::Duration Context::length_to_duration(std::optional<Number> length) const
{ {
auto const len = length ? *length : this->length; auto const len = length ? *length : this->length;
return std::chrono::duration<float>(float(len.num * (60.f / (float(bpm) / 4))) / len.den); return Context::Duration(float(len.num * (60.f / (float(bpm) / 4))) / len.den);
} }

View File

@ -61,13 +61,27 @@ void Interpreter::register_callbacks()
{ {
assert(callbacks != nullptr, "Interpreter constructor should initialize this field"); assert(callbacks != nullptr, "Interpreter constructor should initialize this field");
callbacks->add_callbacks(*midi_connection, *this); callbacks->add_callbacks(*midi_connection, *this);
callbacks->note_on = Value(+[](Interpreter &, std::vector<Value> args) -> Result<Value> {
std::cout << "Received: " << args[1] << "\r\n" << std::flush;
return Value{};
});
} }
template<size_t N>
static inline void await(Interpreter &interpreter, std::array<scheduler::Job_Id, N> dependencies)
{
std::mutex mu;
std::unique_lock lock(mu);
std::condition_variable wait_for_finish;
bool finished = false;
interpreter.scheduler.schedule(dependencies, [&] { finished = true; wait_for_finish.notify_one(); });
wait_for_finish.wait(lock, [&finished] { return finished; });
}
static inline void await(Interpreter &interpreter, scheduler::Job_Id dependency)
{
return await(interpreter, std::array { dependency });
}
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature /// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types) static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
{ {
@ -345,6 +359,7 @@ static inline Result<void> create_chord(std::vector<Note> &chord, Interpreter &i
return {}; return {};
} }
template<Iterable T> template<Iterable T>
static inline Result<void> play_notes(Interpreter &interpreter, T args) static inline Result<void> play_notes(Interpreter &interpreter, T args)
{ {
@ -360,13 +375,13 @@ static inline Result<void> play_notes(Interpreter &interpreter, T args)
case Value::Type::Array: case Value::Type::Array:
case Value::Type::Block: case Value::Type::Block:
Try(play_notes(interpreter, std::move(arg))); Try(play_notes(interpreter, std::move(arg)));
break;
case Value::Type::Music: break; case Value::Type::Music:
interpreter.play(arg.chord); {
break; await(interpreter, std::array{ interpreter.play(arg.chord) });
}
default: break; default:
assert(false, "this type does not support playing"); assert(false, "this type does not support playing");
} }
} }
@ -565,7 +580,7 @@ error:
global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert) assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
if (args.size() == 1) { if (args.size() == 1) {
i.play(std::move(args.front()).chord); await(i, i.play(std::move(args.front()).chord));
return Value{}; return Value{};
} }
@ -578,7 +593,7 @@ error:
} }
for (auto it = std::next(args.begin()); it != args.end(); ++it) { for (auto it = std::next(args.begin()); it != args.end(); ++it) {
i.play(std::move(*it).chord); await(i, i.play(std::move(*it).chord));
} }
for (auto const& note : chord.notes) { for (auto const& note : chord.notes) {
@ -886,7 +901,7 @@ void Interpreter::leave_scope()
env = env->leave(); env = env->leave();
} }
void Interpreter::play(Chord chord) scheduler::Job_Id Interpreter::play(Chord chord)
{ {
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection"); assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
@ -895,22 +910,26 @@ void Interpreter::play(Chord chord)
// Fill all notes that don't have octave or length with defaults // Fill all notes that don't have octave or length with defaults
std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); }); std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); });
// Sort that we have smaller times first
std::sort(chord.notes.begin(), chord.notes.end(), [](Note const& lhs, Note const& rhs) { return lhs.length < rhs.length; });
Number max_time = *chord.notes.back().length;
// Turn all notes on // Turn all notes on
for (auto const& note : chord.notes) { for (auto const& note : chord.notes) {
midi_connection->send_note_on(0, *note.into_midi_note(), 127); midi_connection->send_note_on(0, *note.into_midi_note(), 127);
} }
// Turn off each note at right time Context::Duration max_duration;
auto last_job_id = 0;
// Register all note endings to scheduler
for (auto const& note : chord.notes) { for (auto const& note : chord.notes) {
if (max_time != Number(0)) { auto duration = ctx.length_to_duration(*note.length);
max_time -= *note.length; auto job_id = scheduler.schedule(
std::this_thread::sleep_for(ctx.length_to_duration(*note.length)); std::chrono::duration_cast<scheduler::Clock::duration>(duration),
} [this, note = note] { midi_connection->send_note_off(0, *note.into_midi_note(), 127); }
midi_connection->send_note_off(0, *note.into_midi_note(), 127); );
if (duration > max_duration) {
max_duration = duration;
last_job_id = job_id;
} }
} }
return last_job_id;
}

View File

@ -81,6 +81,7 @@ struct Runner
midi::ALSA alsa; midi::ALSA alsa;
Interpreter interpreter; Interpreter interpreter;
std::thread midi_input_event_loop; std::thread midi_input_event_loop;
std::optional<std::jthread> scheduler_thread;
std::stop_source stop_source; std::stop_source stop_source;
/// Setup interpreter and midi connection with given port /// Setup interpreter and midi connection with given port
@ -110,6 +111,8 @@ struct Runner
midi_input_event_loop.detach(); midi_input_event_loop.detach();
} }
scheduler_thread = std::jthread([this](std::stop_token tok) { interpreter.scheduler.activate(tok); });
Env::global->force_define("say", +[](Interpreter&, std::vector<Value> args) -> Result<Value> { Env::global->force_define("say", +[](Interpreter&, std::vector<Value> args) -> Result<Value> {
for (auto it = args.begin(); it != args.end(); ++it) { for (auto it = args.begin(); it != args.end(); ++it) {
std::cout << *it; std::cout << *it;

View File

@ -12,6 +12,7 @@
#include <variant> #include <variant>
#include <midi.hh> #include <midi.hh>
#include <scheduler.hh>
#include <tl/expected.hpp> #include <tl/expected.hpp>
#if defined(__cpp_lib_source_location) #if defined(__cpp_lib_source_location)
@ -890,6 +891,8 @@ private:
/// Context holds default values for music related actions /// Context holds default values for music related actions
struct Context struct Context
{ {
using Duration = std::chrono::duration<float>;
/// Default note octave /// Default note octave
i8 octave = 4; i8 octave = 4;
@ -903,7 +906,7 @@ struct Context
Note fill(Note) const; Note fill(Note) const;
/// Converts length to seconds with current bpm /// Converts length to seconds with current bpm
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const; Duration length_to_duration(std::optional<Number> length) const;
}; };
/// Given program tree evaluates it into Value /// Given program tree evaluates it into Value
@ -912,6 +915,7 @@ struct Interpreter
/// MIDI connection that is used to play music. /// MIDI connection that is used to play music.
/// It's optional for simple interpreter testing. /// It's optional for simple interpreter testing.
midi::Connection *midi_connection = nullptr; midi::Connection *midi_connection = nullptr;
scheduler::Scheduler scheduler;
/// Operators defined for language /// Operators defined for language
std::unordered_map<std::string, Intrinsic> operators; std::unordered_map<std::string, Intrinsic> operators;
@ -930,7 +934,7 @@ struct Interpreter
Interpreter(); Interpreter();
~Interpreter(); ~Interpreter();
Interpreter(Interpreter const&) = delete; Interpreter(Interpreter const&) = delete;
Interpreter(Interpreter &&) = default; Interpreter(Interpreter &&) = delete;
/// Try to evaluate given program tree /// Try to evaluate given program tree
Result<Value> eval(Ast &&ast); Result<Value> eval(Ast &&ast);
@ -941,8 +945,9 @@ struct Interpreter
// Leave scope by changing current environment // Leave scope by changing current environment
void leave_scope(); void leave_scope();
/// Play note resolving any missing parameters with context via `midi_connection` member. /// Play note resolving any missing parameters with context via `midi_connection` member
void play(Chord); /// @returns Total time of notes played
scheduler::Job_Id play(Chord);
}; };
namespace errors namespace errors