C++ standard only interrupts implementation
This commit is contained in:
parent
e26474a2d7
commit
2d61896824
@ -430,7 +430,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
|
|
||||||
for (auto const& note : chord->notes) {
|
for (auto const& note : chord->notes) {
|
||||||
if (note.base) {
|
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<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
|
|
||||||
for (auto const& note : chord->notes) {
|
for (auto const& note : chord->notes) {
|
||||||
if (note.base) {
|
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;
|
return result;
|
||||||
@ -548,12 +552,16 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
|
|||||||
for (auto const& instruction : schedule) {
|
for (auto const& instruction : schedule) {
|
||||||
auto const dur = ctx.length_to_duration({instruction.when});
|
auto const dur = ctx.length_to_duration({instruction.when});
|
||||||
if (start_time < dur) {
|
if (start_time < dur) {
|
||||||
std::this_thread::sleep_for(dur - start_time);
|
interpreter.sleep(dur - start_time);
|
||||||
start_time = dur;
|
start_time = dur;
|
||||||
}
|
}
|
||||||
switch (instruction.action) {
|
switch (instruction.action) {
|
||||||
break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127);
|
break; case Instruction::On:
|
||||||
break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127);
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
|
std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
|
||||||
|
|
||||||
@ -53,6 +55,8 @@ Interpreter::~Interpreter()
|
|||||||
|
|
||||||
Result<Value> Interpreter::eval(Ast &&ast)
|
Result<Value> Interpreter::eval(Ast &&ast)
|
||||||
{
|
{
|
||||||
|
handle_potential_interrupt();
|
||||||
|
|
||||||
switch (ast.type) {
|
switch (ast.type) {
|
||||||
case Ast::Type::Literal:
|
case Ast::Type::Literal:
|
||||||
switch (ast.token.type) {
|
switch (ast.token.type) {
|
||||||
@ -248,8 +252,10 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
Try(ensure_midi_connection_available(*this, "play"));
|
Try(ensure_midi_connection_available(*this, "play"));
|
||||||
auto &ctx = *current_context;
|
auto &ctx = *current_context;
|
||||||
|
|
||||||
|
handle_potential_interrupt();
|
||||||
|
|
||||||
if (chord.notes.size() == 0) {
|
if (chord.notes.size() == 0) {
|
||||||
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length));
|
sleep(ctx.length_to_duration(ctx.length));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +279,7 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
if (max_time != Number(0)) {
|
if (max_time != Number(0)) {
|
||||||
max_time -= *note.length;
|
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) {
|
if (note.base) {
|
||||||
current_context->port->send_note_off(0, *note.into_midi_note(), 127);
|
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;
|
out << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO This only supports single-threaded interpreter execution
|
||||||
|
static std::atomic<bool> 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<float> 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{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,12 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
struct KeyboardInterrupt : std::exception
|
||||||
|
{
|
||||||
|
~KeyboardInterrupt() = default;
|
||||||
|
char const* what() const noexcept override { return "KeyboardInterrupt"; }
|
||||||
|
};
|
||||||
|
|
||||||
/// Given program tree evaluates it into Value
|
/// Given program tree evaluates it into Value
|
||||||
struct Interpreter
|
struct Interpreter
|
||||||
{
|
{
|
||||||
@ -25,7 +31,6 @@ struct Interpreter
|
|||||||
|
|
||||||
std::multiset<std::pair<unsigned, unsigned>> active_notes;
|
std::multiset<std::pair<unsigned, unsigned>> active_notes;
|
||||||
|
|
||||||
|
|
||||||
Starter starter;
|
Starter starter;
|
||||||
|
|
||||||
Interpreter();
|
Interpreter();
|
||||||
@ -58,10 +63,19 @@ struct Interpreter
|
|||||||
/// Dumps snapshot of interpreter into stream
|
/// Dumps snapshot of interpreter into stream
|
||||||
void snapshot(std::ostream& out);
|
void snapshot(std::ostream& out);
|
||||||
|
|
||||||
|
/// Turn all notes that have been played but don't finished playing
|
||||||
void turn_off_all_active_notes();
|
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<float>);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
|
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -38,12 +38,6 @@ static unsigned repl_line_number = 1;
|
|||||||
|
|
||||||
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
|
#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
|
/// Pop string from front of an array
|
||||||
template<typename T = std::string_view>
|
template<typename T = std::string_view>
|
||||||
@ -437,33 +431,8 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
static Runner runner;
|
||||||
static auto thread = pthread_self();
|
std::signal(SIGINT, [](int sig) { if (sig == SIGINT) runner.interpreter.issue_interrupt(); });
|
||||||
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;
|
|
||||||
|
|
||||||
for (auto const& [type, argument] : runnables) {
|
for (auto const& [type, argument] : runnables) {
|
||||||
if (type == Run::Argument) {
|
if (type == Run::Argument) {
|
||||||
|
Loading…
Reference in New Issue
Block a user