diff --git a/musique/interpreter/builtin_functions.cc b/musique/interpreter/builtin_functions.cc index e88800d..95df7dc 100644 --- a/musique/interpreter/builtin_functions.cc +++ b/musique/interpreter/builtin_functions.cc @@ -430,7 +430,9 @@ static Result builtin_par(Interpreter &interpreter, std::vector ar for (auto const& note : chord->notes) { if (note.base) { - interpreter.current_context->port->send_note_on(0, *note.into_midi_note(), 127); + auto const n = *note.into_midi_note(); + interpreter.current_context->port->send_note_on(0, n, 127); + interpreter.active_notes.insert({ 0, n }); } } @@ -438,7 +440,9 @@ static Result builtin_par(Interpreter &interpreter, std::vector ar for (auto const& note : chord->notes) { if (note.base) { - interpreter.current_context->port->send_note_off(0, *note.into_midi_note(), 127); + auto const n = *note.into_midi_note(); + interpreter.current_context->port->send_note_off(0, n, 127); + interpreter.active_notes.erase({ 0, n }); } } return result; @@ -548,12 +552,16 @@ static Result builtin_sim(Interpreter &interpreter, std::vector ar for (auto const& instruction : schedule) { auto const dur = ctx.length_to_duration({instruction.when}); if (start_time < dur) { - std::this_thread::sleep_for(dur - start_time); + interpreter.sleep(dur - start_time); start_time = dur; } switch (instruction.action) { - break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127); - break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127); + break; case Instruction::On: + interpreter.current_context->port->send_note_on(0, instruction.note, 127); + interpreter.active_notes.insert({ 0, instruction.note }); + break; case Instruction::Off: + interpreter.current_context->port->send_note_off(0, instruction.note, 127); + interpreter.active_notes.erase({ 0, instruction.note }); } } diff --git a/musique/interpreter/interpreter.cc b/musique/interpreter/interpreter.cc index d8a70e3..0f99a36 100644 --- a/musique/interpreter/interpreter.cc +++ b/musique/interpreter/interpreter.cc @@ -6,6 +6,8 @@ #include #include #include +#include +#include std::unordered_map Interpreter::operators {}; @@ -53,6 +55,8 @@ Interpreter::~Interpreter() Result Interpreter::eval(Ast &&ast) { + handle_potential_interrupt(); + switch (ast.type) { case Ast::Type::Literal: switch (ast.token.type) { @@ -248,8 +252,10 @@ std::optional Interpreter::play(Chord chord) Try(ensure_midi_connection_available(*this, "play")); auto &ctx = *current_context; + handle_potential_interrupt(); + if (chord.notes.size() == 0) { - std::this_thread::sleep_for(ctx.length_to_duration(ctx.length)); + sleep(ctx.length_to_duration(ctx.length)); return {}; } @@ -273,7 +279,7 @@ std::optional Interpreter::play(Chord chord) 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)); + sleep(ctx.length_to_duration(*note.length)); } if (note.base) { current_context->port->send_note_off(0, *note.into_midi_note(), 127); @@ -442,3 +448,31 @@ void Interpreter::snapshot(std::ostream& out) } out << std::flush; } + +// TODO This only supports single-threaded interpreter execution +static std::atomic interrupted = false; +static std::condition_variable condvar; +static std::mutex mu; + +void Interpreter::handle_potential_interrupt() +{ + if (interrupted) { + interrupted = false; + throw KeyboardInterrupt{}; + } +} + +void Interpreter::issue_interrupt() +{ + interrupted = true; + condvar.notify_all(); +} + +void Interpreter::sleep(std::chrono::duration time) +{ + if (std::unique_lock lock(mu); condvar.wait_for(lock, time) == std::cv_status::no_timeout) { + ensure(interrupted, "Only interruption can result in quiting conditional variable without timeout"); + interrupted = false; + throw KeyboardInterrupt{}; + } +} diff --git a/musique/interpreter/interpreter.hh b/musique/interpreter/interpreter.hh index 6d190ff..2d381c8 100644 --- a/musique/interpreter/interpreter.hh +++ b/musique/interpreter/interpreter.hh @@ -8,6 +8,12 @@ #include #include +struct KeyboardInterrupt : std::exception +{ + ~KeyboardInterrupt() = default; + char const* what() const noexcept override { return "KeyboardInterrupt"; } +}; + /// Given program tree evaluates it into Value struct Interpreter { @@ -25,7 +31,6 @@ struct Interpreter std::multiset> active_notes; - Starter starter; Interpreter(); @@ -58,10 +63,19 @@ struct Interpreter /// Dumps snapshot of interpreter into stream void snapshot(std::ostream& out); + /// Turn all notes that have been played but don't finished playing void turn_off_all_active_notes(); + + /// Handles interrupt if any occured + void handle_potential_interrupt(); + + /// Issue new interrupt + void issue_interrupt(); + + /// Sleep for at least given time or until interrupt + void sleep(std::chrono::duration); }; std::optional ensure_midi_connection_available(Interpreter&, std::string_view operation_name); - #endif diff --git a/musique/main.cc b/musique/main.cc index e8cf1ff..994f1e7 100644 --- a/musique/main.cc +++ b/musique/main.cc @@ -38,12 +38,6 @@ static unsigned repl_line_number = 1; #define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0) -struct KeyboardInterrupt : std::exception -{ - ~KeyboardInterrupt() = default; - char const* what() const noexcept override { return "KeyboardInterrupt"; } -}; - /// Pop string from front of an array template @@ -437,33 +431,8 @@ static std::optional Main(std::span args) std::exit(1); } - { - static auto thread = pthread_self(); - static auto thread_id = std::this_thread::get_id(); - - // TODO IS this a good solution? - static auto *handler = +[](int sig, siginfo_t*, void*) { - struct sigaction sa; - sigaction(SIGINT, nullptr, &sa); - pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, nullptr); - if (thread_id == std::this_thread::get_id()) { - if (sig == SIGINT) { - throw KeyboardInterrupt{}; - } - } else { - pthread_kill(thread, SIGINT); - } - }; - - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sigaddset(&sa.sa_mask, SIGINT); - sa.sa_sigaction = handler; - sa.sa_flags = 0; - sigaction(SIGINT, &sa, nullptr); - } - - Runner runner; + static Runner runner; + std::signal(SIGINT, [](int sig) { if (sig == SIGINT) runner.interpreter.issue_interrupt(); }); for (auto const& [type, argument] : runnables) { if (type == Run::Argument) {