Added asynchronous note playing
This commit is contained in:
parent
da8a6b5d6c
commit
e7f3cf2cfb
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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); }
|
||||||
|
);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user