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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return last_job_id;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user