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;
}
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;
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");
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
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 {};
}
template<Iterable T>
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::Block:
Try(play_notes(interpreter, std::move(arg)));
break;
case Value::Type::Music:
interpreter.play(arg.chord);
break;
break; case Value::Type::Music:
{
await(interpreter, std::array{ interpreter.play(arg.chord) });
}
default:
break; default:
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> {
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
if (args.size() == 1) {
i.play(std::move(args.front()).chord);
await(i, i.play(std::move(args.front()).chord));
return Value{};
}
@ -578,7 +593,7 @@ error:
}
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) {
@ -886,7 +901,7 @@ void Interpreter::leave_scope()
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");
@ -895,22 +910,26 @@ void Interpreter::play(Chord chord)
// 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); });
// 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
for (auto const& note : chord.notes) {
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) {
if (max_time != Number(0)) {
max_time -= *note.length;
std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
auto duration = ctx.length_to_duration(*note.length);
auto job_id = scheduler.schedule(
std::chrono::duration_cast<scheduler::Clock::duration>(duration),
[this, note = note] { midi_connection->send_note_off(0, *note.into_midi_note(), 127); }
);
if (duration > max_duration) {
max_duration = duration;
last_job_id = job_id;
}
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
}
return last_job_id;
}

View File

@ -81,6 +81,7 @@ struct Runner
midi::ALSA alsa;
Interpreter interpreter;
std::thread midi_input_event_loop;
std::optional<std::jthread> scheduler_thread;
std::stop_source stop_source;
/// Setup interpreter and midi connection with given port
@ -110,6 +111,8 @@ struct Runner
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> {
for (auto it = args.begin(); it != args.end(); ++it) {
std::cout << *it;

View File

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