From 81f512d69d7671fbd7a5ecdaf3e589aa2fed5789 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Thu, 18 Aug 2022 22:37:40 +0200 Subject: [PATCH] Builtin function handlers definition now in separate file Same as in commit before, we try to cleanup src/interpreter.cc from all of builtin function noise. --- Makefile | 1 + src/builtin_functions.cc | 443 ++++++++++++++++++++++++++++++++++ src/interpreter.cc | 506 +-------------------------------------- src/musique.hh | 53 ++++ 4 files changed, 505 insertions(+), 498 deletions(-) create mode 100644 src/builtin_functions.cc diff --git a/Makefile b/Makefile index dec47b6..bcb62e8 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ LDFLAGS=-L./lib/midi/ LDLIBS=-lmidi-alsa -lasound -lpthread Obj= \ + builtin_functions.o \ builtin_operators.o \ context.o \ environment.o \ diff --git a/src/builtin_functions.cc b/src/builtin_functions.cc new file mode 100644 index 0000000..c5b6390 --- /dev/null +++ b/src/builtin_functions.cc @@ -0,0 +1,443 @@ +#include + +#include +#include +#include + + +void Interpreter::register_callbacks() +{ + assert(callbacks == nullptr, "This field should be uninitialized"); + callbacks = std::make_unique(); + callbacks->add_callbacks(*midi_connection, *this); + + callbacks->note_on = Value(+[](Interpreter &, std::vector args) -> Result { + std::cout << "Received: " << args[1] << "\r\n" << std::flush; + return Value{}; + }); +} + +template +concept With_Index_Method = requires (T t, Interpreter interpreter, usize position) { + { t.index(interpreter, position) } -> std::convertible_to>; +}; + +template +concept With_Index_Operator = requires (T t, unsigned i) { + { t[i] } -> std::convertible_to; +}; + +template +concept Iterable = (With_Index_Method || With_Index_Operator) && requires (T const t) { + { t.size() } -> std::convertible_to; +}; + +template +static inline Result create_chord(std::vector &chord, Interpreter &interpreter, T args) +{ + for (auto i = 0u; i < args.size(); ++i) { + Value arg; + if constexpr (With_Index_Method) { + arg = Try(args.index(interpreter, i)); + } else { + arg = std::move(args[i]); + } + + switch (arg.type) { + case Value::Type::Array: + case Value::Type::Block: + Try(create_chord(chord, interpreter, std::move(arg))); + break; + + case Value::Type::Music: + std::move(arg.chord.notes.begin(), arg.chord.notes.end(), std::back_inserter(chord)); + break; + + default: + assert(false, "this type is not supported inside chord"); + } + } + + return {}; +} + +template +static inline Result play_notes(Interpreter &interpreter, T args) +{ + for (auto i = 0u; i < args.size(); ++i) { + Value arg; + if constexpr (With_Index_Method) { + arg = Try(args.index(interpreter, i)); + } else { + arg = std::move(args[i]); + } + + switch (arg.type) { + 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; + + default: + assert(false, "this type does not support playing"); + } + } + + return {}; +} + +template +Result ctx_read_write_property(Interpreter &interpreter, std::vector args) +{ + assert(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert) + + using Member_Type = std::remove_cvref_t().*(Mem_Ptr))>; + + if (args.size() == 0) { + return Value::from(Number(interpreter.context_stack.back().*(Mem_Ptr))); + } + + assert(args.front().type == Value::Type::Number, "Ctx only holds numeric values"); + + if constexpr (std::is_same_v) { + interpreter.context_stack.back().*(Mem_Ptr) = args.front().n; + } else { + interpreter.context_stack.back().*(Mem_Ptr) = static_cast(args.front().n.as_int()); + } + + return Value{}; +} + +static Result into_flat_array(Interpreter &i, std::span args) +{ + Array array; + for (auto &arg : args) { + switch (arg.type) { + case Value::Type::Array: + std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); + break; + + case Value::Type::Block: + for (auto j = 0u; j < arg.blk.size(); ++j) { + array.elements.push_back(Try(arg.blk.index(i, j))); + } + break; + + default: + array.elements.push_back(std::move(arg)); + } + } + return array; +} + +static Result into_flat_array(Interpreter &i, std::vector args) +{ + return into_flat_array(i, std::span(args)); +} + +void Interpreter::register_builtin_functions() +{ + auto &global = *Env::global; + + global.force_define("update", +[](Interpreter &i, std::vector args) -> Result { + assert(args.size() == 3, "Update requires 3 arguments"); // TODO(assert) + using Eager_And_Number = Shape; + using Lazy_And_Number = Shape; + + if (Eager_And_Number::typecheck_front(args)) { + auto [v, index] = Eager_And_Number::move_from(args); + v.elements[index.as_int()] = std::move(args.back()); + return Value::from(std::move(v)); + } + + if (Lazy_And_Number::typecheck_front(args)) { + auto [v, index] = Lazy_And_Number::move_from(args); + auto array = Try(into_flat_array(i, { Value::from(std::move(v)) })); + array.elements[index.as_int()] = std::move(args.back()); + return Value::from(std::move(array)); + } + + unimplemented("Wrong shape of update function"); + }); + + global.force_define("typeof", +[](Interpreter&, std::vector args) -> Result { + assert(args.size() == 1, "typeof expects only one argument"); + return Value::from(std::string(type_name(args.front().type))); + }); + + global.force_define("if", +[](Interpreter &i, std::vector args) -> Result { + if (args.size() != 2 && args.size() != 3) { +error: + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "if", + .possibilities = { + "(any, function) -> any", + "(any, function, function) -> any" + } + } + }; + } + if (args.front().truthy()) { + if (not is_callable(args[1].type)) goto error; + return args[1](i, {}); + } else if (args.size() == 3) { + if (not is_callable(args[2].type)) goto error; + return args[2](i, {}); + } else { + return Value{}; + } + }); + + global.force_define("len", +[](Interpreter &i, std::vector args) -> Result { + if (args.size() != 1 || !is_indexable(args.front().type)) { + return ctx_read_write_property<&Context::length>(i, std::move(args)); + } + return Value::from(Number(args.front().size())); + }); + + global.force_define("play", +[](Interpreter &i, std::vector args) -> Result { + Try(play_notes(i, std::move(args))); + return Value{}; + }); + + global.force_define("flat", +[](Interpreter &i, std::vector args) -> Result { + return Value::from(Try(into_flat_array(i, std::move(args)))); + }); + + global.force_define("shuffle", +[](Interpreter &i, std::vector args) -> Result { + static std::mt19937 rnd{std::random_device{}()}; + auto array = Try(into_flat_array(i, std::move(args))); + std::shuffle(array.elements.begin(), array.elements.end(), rnd); + return Value::from(std::move(array)); + }); + + global.force_define("permute", +[](Interpreter &i, std::vector args) -> Result { + auto array = Try(into_flat_array(i, std::move(args))); + std::next_permutation(array.elements.begin(), array.elements.end()); + return Value::from(std::move(array)); + }); + + global.force_define("sort", +[](Interpreter &i, std::vector args) -> Result { + auto array = Try(into_flat_array(i, std::move(args))); + std::sort(array.elements.begin(), array.elements.end()); + return Value::from(std::move(array)); + }); + + global.force_define("reverse", +[](Interpreter &i, std::vector args) -> Result { + auto array = Try(into_flat_array(i, std::move(args))); + std::reverse(array.elements.begin(), array.elements.end()); + return Value::from(std::move(array)); + }); + + global.force_define("min", +[](Interpreter &i, std::vector args) -> Result { + auto array = Try(into_flat_array(i, std::move(args))); + auto min = std::min_element(array.elements.begin(), array.elements.end()); + if (min == array.elements.end()) + return Value{}; + return *min; + }); + + global.force_define("max", +[](Interpreter &i, std::vector args) -> Result { + auto array = Try(into_flat_array(i, std::move(args))); + auto max = std::max_element(array.elements.begin(), array.elements.end()); + if (max == array.elements.end()) + return Value{}; + return *max; + }); + + global.force_define("partition", +[](Interpreter &i, std::vector args) -> Result { + assert(!args.empty(), "partition requires function to partition with"); // TODO(assert) + auto predicate = std::move(args.front()); + assert(is_callable(predicate.type), "partition requires function to partition with"); // TODO(assert) + + auto array = Try(into_flat_array(i, std::span(args).subspan(1))); + + Array tuple[2] = {}; + for (auto &value : array.elements) { + tuple[Try(predicate(i, { std::move(value) })).truthy()].elements.push_back(std::move(value)); + } + + return Value::from(Array { .elements = { + Value::from(std::move(tuple[true])), + Value::from(std::move(tuple[false])) + }}); + }); + + global.force_define("rotate", +[](Interpreter &i, std::vector args) -> Result { + assert(!args.empty(), "rotate requires offset"); // TODO(assert) + auto offset = std::move(args.front()).n.as_int(); + auto array = Try(into_flat_array(i, std::span(args).subspan(1))); + if (offset > 0) { + offset = offset % array.elements.size(); + std::rotate(array.elements.begin(), array.elements.begin() + offset, array.elements.end()); + } else if (offset < 0) { + offset = -offset % array.elements.size(); + std::rotate(array.elements.rbegin(), array.elements.rbegin() + offset, array.elements.rend()); + } + return Value::from(std::move(array)); + }); + + global.force_define("chord", +[](Interpreter &i, std::vector args) -> Result { + Chord chord; + Try(create_chord(chord.notes, i, std::move(args))); + return Value::from(std::move(chord)); + }); + + global.force_define("bpm", &ctx_read_write_property<&Context::bpm>); + global.force_define("oct", &ctx_read_write_property<&Context::octave>); + + global.force_define("par", +[](Interpreter &i, std::vector args) -> Result { + 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); + return Value{}; + } + + auto &ctx = i.context_stack.back(); + auto chord = std::move(args.front()).chord; + std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); }); + + for (auto const& note : chord.notes) { + i.midi_connection->send_note_on(0, *note.into_midi_note(), 127); + } + + for (auto it = std::next(args.begin()); it != args.end(); ++it) { + i.play(std::move(*it).chord); + } + + for (auto const& note : chord.notes) { + i.midi_connection->send_note_off(0, *note.into_midi_note(), 127); + } + + return Value{}; + }); + + global.force_define("note_on", +[](Interpreter &i, std::vector args) -> Result { + using Channel_Note_Velocity = Shape; + using Channel_Music_Velocity = Shape; + + if (Channel_Note_Velocity::typecheck(args)) { + auto [chan, note, vel] = Channel_Note_Velocity::move_from(args); + i.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int()); + return Value {}; + } + + if (Channel_Music_Velocity::typecheck(args)) { + auto [chan, chord, vel] = Channel_Music_Velocity::move_from(args); + + for (auto note : chord.notes) { + note = i.context_stack.back().fill(note); + i.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int()); + } + } + + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "note_on", + .possibilities = { + "(number, music, number) -> nil" + "(number, number, number) -> nil", + } + }, + .location = {} + }; + }); + + global.force_define("note_off", +[](Interpreter &i, std::vector args) -> Result { + using Channel_Note = Shape; + using Channel_Music = Shape; + + if (Channel_Note::typecheck(args)) { + auto [chan, note] = Channel_Note::move_from(args); + i.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127); + return Value {}; + } + + if (Channel_Music::typecheck(args)) { + auto [chan, chord] = Channel_Music::move_from(args); + + for (auto note : chord.notes) { + note = i.context_stack.back().fill(note); + i.midi_connection->send_note_off(chan.as_int(), *note.into_midi_note(), 127); + } + } + + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "note_off", + .possibilities = { + "(number, music) -> nil" + "(number, number) -> nil", + } + }, + .location = {} + }; + }); + + global.force_define("incoming", +[](Interpreter &i, std::vector args) -> Result { + if (args.size() != 2 || args[0].type != Value::Type::Symbol || !is_callable(args[1].type)) { + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "incoming", + .possibilities = { "(symbol, function) -> nil" } + }, + .location = {} + }; + } + + std::string const& symbol = args[0].s; + + if (symbol == "note_on" || symbol == "noteon") { + i.callbacks->note_on = std::move(args[1]); + } else if (symbol == "note_off" || symbol == "noteoff") { + i.callbacks->note_off = std::move(args[1]); + } else { + + } + return Value{}; + }); + + { + constexpr auto pgmchange = +[](Interpreter &i, std::vector args) -> Result { + using Program = Shape; + using Channel_Program = Shape; + + if (Program::typecheck(args)) { + auto [program] = Program::move_from(args); + i.midi_connection->send_program_change(0, program.as_int()); + return Value{}; + } + + if (Channel_Program::typecheck(args)) { + auto [chan, program] = Channel_Program::move_from(args); + i.midi_connection->send_program_change(chan.as_int(), program.as_int()); + return Value{}; + } + + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "note_off", + .possibilities = { + "(number) -> nil", + "(number, number) -> nil", + } + }, + .location = {} + }; + }; + + global.force_define("instrument", pgmchange); + global.force_define("pgmchange", pgmchange); + global.force_define("program_change", pgmchange); + } +} diff --git a/src/interpreter.cc b/src/interpreter.cc index 6ed6df8..783ab76 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -5,97 +5,6 @@ #include #include -struct Interpreter::Incoming_Midi_Callbacks -{ - Value note_on{}; - Value note_off{}; - - Incoming_Midi_Callbacks() = default; - - Incoming_Midi_Callbacks(Incoming_Midi_Callbacks &&) = delete; - Incoming_Midi_Callbacks(Incoming_Midi_Callbacks const&) = delete; - - Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks &&) = delete; - Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks const&) = delete; - - void add_callbacks(midi::Connection &midi, Interpreter &interpreter) - { - register_callback(midi.note_on_callback, note_on, interpreter); - register_callback(midi.note_off_callback, note_off, interpreter); - } - - template - void register_callback(std::function &target, Value &callback, Interpreter &i) - { - if (&callback == ¬e_on || &callback == ¬e_off) { - // This messages have MIDI note number as second value, so they should be represented - // in our own note abstraction, not as numbers. - target = [interpreter = &i, callback = &callback](T ...source_args) - { - if (callback->type != Value::Type::Nil) { - std::vector args { Value::from(Number(source_args))... }; - args[1] = Value::from(Chord { .notes { Note { - .base = i32(args[1].n.num % 12), - .octave = args[1].n.num / 12 - }}}); - auto result = (*callback)(*interpreter, std::move(args)); - // We discard this since callback is running in another thread. - (void) result; - } - }; - } else { - // Generic case, preserve all passed parameters as numbers - target = [interpreter = &i, callback = &callback](T ...source_args) - { - if (callback->type != Value::Type::Nil) { - auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... }); - // We discard this since callback is running in another thread. - (void) result; - } - }; - } - } -}; - -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 args) -> Result { - std::cout << "Received: " << args[1] << "\r\n" << std::flush; - return Value{}; - }); -} - -static Result into_flat_array(Interpreter &i, std::span args) -{ - Array array; - for (auto &arg : args) { - switch (arg.type) { - case Value::Type::Array: - std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); - break; - - case Value::Type::Block: - for (auto j = 0u; j < arg.blk.size(); ++j) { - array.elements.push_back(Try(arg.blk.index(i, j))); - } - break; - - default: - array.elements.push_back(std::move(arg)); - } - } - return array; -} - -static Result into_flat_array(Interpreter &i, std::vector args) -{ - return into_flat_array(i, std::span(args)); -} - - /// Registers constants like `fn = full note = 1/1` static inline void register_note_length_constants() { @@ -117,418 +26,19 @@ static inline void register_note_length_constants() global.force_define("dtn", Value::from(Number(3, 64))); } -template -concept With_Index_Method = requires (T t, Interpreter interpreter, usize position) { - { t.index(interpreter, position) } -> std::convertible_to>; -}; - -template -concept With_Index_Operator = requires (T t, unsigned i) { - { t[i] } -> std::convertible_to; -}; - -template -concept Iterable = (With_Index_Method || With_Index_Operator) && requires (T const t) { - { t.size() } -> std::convertible_to; -}; - -template -static inline Result create_chord(std::vector &chord, Interpreter &interpreter, T args) -{ - for (auto i = 0u; i < args.size(); ++i) { - Value arg; - if constexpr (With_Index_Method) { - arg = Try(args.index(interpreter, i)); - } else { - arg = std::move(args[i]); - } - - switch (arg.type) { - case Value::Type::Array: - case Value::Type::Block: - Try(create_chord(chord, interpreter, std::move(arg))); - break; - - case Value::Type::Music: - std::move(arg.chord.notes.begin(), arg.chord.notes.end(), std::back_inserter(chord)); - break; - - default: - assert(false, "this type is not supported inside chord"); - } - } - - return {}; -} - -template -static inline Result play_notes(Interpreter &interpreter, T args) -{ - for (auto i = 0u; i < args.size(); ++i) { - Value arg; - if constexpr (With_Index_Method) { - arg = Try(args.index(interpreter, i)); - } else { - arg = std::move(args[i]); - } - - switch (arg.type) { - 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; - - default: - assert(false, "this type does not support playing"); - } - } - - return {}; -} - -template -Result ctx_read_write_property(Interpreter &interpreter, std::vector args) -{ - assert(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert) - - using Member_Type = std::remove_cvref_t().*(Mem_Ptr))>; - - if (args.size() == 0) { - return Value::from(Number(interpreter.context_stack.back().*(Mem_Ptr))); - } - - assert(args.front().type == Value::Type::Number, "Ctx only holds numeric values"); - - if constexpr (std::is_same_v) { - interpreter.context_stack.back().*(Mem_Ptr) = args.front().n; - } else { - interpreter.context_stack.back().*(Mem_Ptr) = static_cast(args.front().n.as_int()); - } - - return Value{}; -} - Interpreter::Interpreter() { - { // Context initialization - context_stack.emplace_back(); - } - { // Environment initlialization - assert(!bool(Env::global), "Only one instance of interpreter can be at one time"); - env = Env::global = Env::make(); - } - { // MIDI input callbacks initialization - callbacks = std::make_unique(); - } - { // Global default functions initialization - auto &global = *Env::global; + // Context initialization + context_stack.emplace_back(); - register_note_length_constants(); - - global.force_define("update", +[](Interpreter &i, std::vector args) -> Result { - assert(args.size() == 3, "Update requires 3 arguments"); // TODO(assert) - using Eager_And_Number = Shape; - using Lazy_And_Number = Shape; - - if (Eager_And_Number::typecheck_front(args)) { - auto [v, index] = Eager_And_Number::move_from(args); - v.elements[index.as_int()] = std::move(args.back()); - return Value::from(std::move(v)); - } - - if (Lazy_And_Number::typecheck_front(args)) { - auto [v, index] = Lazy_And_Number::move_from(args); - auto array = Try(into_flat_array(i, { Value::from(std::move(v)) })); - array.elements[index.as_int()] = std::move(args.back()); - return Value::from(std::move(array)); - } - - unimplemented("Wrong shape of update function"); - }); - - global.force_define("typeof", +[](Interpreter&, std::vector args) -> Result { - assert(args.size() == 1, "typeof expects only one argument"); - return Value::from(std::string(type_name(args.front().type))); - }); - - global.force_define("if", +[](Interpreter &i, std::vector args) -> Result { - if (args.size() != 2 && args.size() != 3) { -error: - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "if", - .possibilities = { - "(any, function) -> any", - "(any, function, function) -> any" - } - } - }; - } - if (args.front().truthy()) { - if (not is_callable(args[1].type)) goto error; - return args[1](i, {}); - } else if (args.size() == 3) { - if (not is_callable(args[2].type)) goto error; - return args[2](i, {}); - } else { - return Value{}; - } - }); - - global.force_define("len", +[](Interpreter &i, std::vector args) -> Result { - if (args.size() != 1 || !is_indexable(args.front().type)) { - return ctx_read_write_property<&Context::length>(i, std::move(args)); - } - return Value::from(Number(args.front().size())); - }); - - global.force_define("play", +[](Interpreter &i, std::vector args) -> Result { - Try(play_notes(i, std::move(args))); - return Value{}; - }); - - global.force_define("flat", +[](Interpreter &i, std::vector args) -> Result { - return Value::from(Try(into_flat_array(i, std::move(args)))); - }); - - global.force_define("shuffle", +[](Interpreter &i, std::vector args) -> Result { - static std::mt19937 rnd{std::random_device{}()}; - auto array = Try(into_flat_array(i, std::move(args))); - std::shuffle(array.elements.begin(), array.elements.end(), rnd); - return Value::from(std::move(array)); - }); - - global.force_define("permute", +[](Interpreter &i, std::vector args) -> Result { - auto array = Try(into_flat_array(i, std::move(args))); - std::next_permutation(array.elements.begin(), array.elements.end()); - return Value::from(std::move(array)); - }); - - global.force_define("sort", +[](Interpreter &i, std::vector args) -> Result { - auto array = Try(into_flat_array(i, std::move(args))); - std::sort(array.elements.begin(), array.elements.end()); - return Value::from(std::move(array)); - }); - - global.force_define("reverse", +[](Interpreter &i, std::vector args) -> Result { - auto array = Try(into_flat_array(i, std::move(args))); - std::reverse(array.elements.begin(), array.elements.end()); - return Value::from(std::move(array)); - }); - - global.force_define("min", +[](Interpreter &i, std::vector args) -> Result { - auto array = Try(into_flat_array(i, std::move(args))); - auto min = std::min_element(array.elements.begin(), array.elements.end()); - if (min == array.elements.end()) - return Value{}; - return *min; - }); - - global.force_define("max", +[](Interpreter &i, std::vector args) -> Result { - auto array = Try(into_flat_array(i, std::move(args))); - auto max = std::max_element(array.elements.begin(), array.elements.end()); - if (max == array.elements.end()) - return Value{}; - return *max; - }); - - global.force_define("partition", +[](Interpreter &i, std::vector args) -> Result { - assert(!args.empty(), "partition requires function to partition with"); // TODO(assert) - auto predicate = std::move(args.front()); - assert(is_callable(predicate.type), "partition requires function to partition with"); // TODO(assert) - - auto array = Try(into_flat_array(i, std::span(args).subspan(1))); - - Array tuple[2] = {}; - for (auto &value : array.elements) { - tuple[Try(predicate(i, { std::move(value) })).truthy()].elements.push_back(std::move(value)); - } - - return Value::from(Array { .elements = { - Value::from(std::move(tuple[true])), - Value::from(std::move(tuple[false])) - }}); - }); - - global.force_define("rotate", +[](Interpreter &i, std::vector args) -> Result { - assert(!args.empty(), "rotate requires offset"); // TODO(assert) - auto offset = std::move(args.front()).n.as_int(); - auto array = Try(into_flat_array(i, std::span(args).subspan(1))); - if (offset > 0) { - offset = offset % array.elements.size(); - std::rotate(array.elements.begin(), array.elements.begin() + offset, array.elements.end()); - } else if (offset < 0) { - offset = -offset % array.elements.size(); - std::rotate(array.elements.rbegin(), array.elements.rbegin() + offset, array.elements.rend()); - } - return Value::from(std::move(array)); - }); - - global.force_define("chord", +[](Interpreter &i, std::vector args) -> Result { - Chord chord; - Try(create_chord(chord.notes, i, std::move(args))); - return Value::from(std::move(chord)); - }); - - global.force_define("bpm", &ctx_read_write_property<&Context::bpm>); - global.force_define("oct", &ctx_read_write_property<&Context::octave>); - - global.force_define("par", +[](Interpreter &i, std::vector args) -> Result { - 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); - return Value{}; - } - - auto &ctx = i.context_stack.back(); - auto chord = std::move(args.front()).chord; - std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); }); - - for (auto const& note : chord.notes) { - i.midi_connection->send_note_on(0, *note.into_midi_note(), 127); - } - - for (auto it = std::next(args.begin()); it != args.end(); ++it) { - i.play(std::move(*it).chord); - } - - for (auto const& note : chord.notes) { - i.midi_connection->send_note_off(0, *note.into_midi_note(), 127); - } - - return Value{}; - }); - - global.force_define("note_on", +[](Interpreter &i, std::vector args) -> Result { - using Channel_Note_Velocity = Shape; - using Channel_Music_Velocity = Shape; - - if (Channel_Note_Velocity::typecheck(args)) { - auto [chan, note, vel] = Channel_Note_Velocity::move_from(args); - i.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int()); - return Value {}; - } - - if (Channel_Music_Velocity::typecheck(args)) { - auto [chan, chord, vel] = Channel_Music_Velocity::move_from(args); - - for (auto note : chord.notes) { - note = i.context_stack.back().fill(note); - i.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int()); - } - } - - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "note_on", - .possibilities = { - "(number, music, number) -> nil" - "(number, number, number) -> nil", - } - }, - .location = {} - }; - }); - - global.force_define("note_off", +[](Interpreter &i, std::vector args) -> Result { - using Channel_Note = Shape; - using Channel_Music = Shape; - - if (Channel_Note::typecheck(args)) { - auto [chan, note] = Channel_Note::move_from(args); - i.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127); - return Value {}; - } - - if (Channel_Music::typecheck(args)) { - auto [chan, chord] = Channel_Music::move_from(args); - - for (auto note : chord.notes) { - note = i.context_stack.back().fill(note); - i.midi_connection->send_note_off(chan.as_int(), *note.into_midi_note(), 127); - } - } - - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "note_off", - .possibilities = { - "(number, music) -> nil" - "(number, number) -> nil", - } - }, - .location = {} - }; - }); - - global.force_define("incoming", +[](Interpreter &i, std::vector args) -> Result { - if (args.size() != 2 || args[0].type != Value::Type::Symbol || !is_callable(args[1].type)) { - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "incoming", - .possibilities = { "(symbol, function) -> nil" } - }, - .location = {} - }; - } - - std::string const& symbol = args[0].s; - - if (symbol == "note_on" || symbol == "noteon") { - i.callbacks->note_on = std::move(args[1]); - } else if (symbol == "note_off" || symbol == "noteoff") { - i.callbacks->note_off = std::move(args[1]); - } else { - - } - return Value{}; - }); - - { - constexpr auto pgmchange = +[](Interpreter &i, std::vector args) -> Result { - using Program = Shape; - using Channel_Program = Shape; - - if (Program::typecheck(args)) { - auto [program] = Program::move_from(args); - i.midi_connection->send_program_change(0, program.as_int()); - return Value{}; - } - - if (Channel_Program::typecheck(args)) { - auto [chan, program] = Channel_Program::move_from(args); - i.midi_connection->send_program_change(chan.as_int(), program.as_int()); - return Value{}; - } - - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "note_off", - .possibilities = { - "(number) -> nil", - "(number, number) -> nil", - } - }, - .location = {} - }; - }; - - global.force_define("instrument", pgmchange); - global.force_define("pgmchange", pgmchange); - global.force_define("program_change", pgmchange); - } - } + // Environment initlialization + assert(!bool(Env::global), "Only one instance of interpreter can be at one time"); + env = Env::global = Env::make(); + // Builtins initialization + register_note_length_constants(); register_builtin_operators(); + register_builtin_functions(); } Interpreter::~Interpreter() diff --git a/src/musique.hh b/src/musique.hh index 71444b1..9bba33c 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -1023,3 +1023,56 @@ static inline bool may_be_vectorized(std::vector const& args) { return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type)); } + +struct Interpreter::Incoming_Midi_Callbacks +{ + Value note_on{}; + Value note_off{}; + + inline Incoming_Midi_Callbacks() = default; + + Incoming_Midi_Callbacks(Incoming_Midi_Callbacks &&) = delete; + Incoming_Midi_Callbacks(Incoming_Midi_Callbacks const&) = delete; + + Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks &&) = delete; + Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks const&) = delete; + + + inline void add_callbacks(midi::Connection &midi, Interpreter &interpreter) + { + register_callback(midi.note_on_callback, note_on, interpreter); + register_callback(midi.note_off_callback, note_off, interpreter); + } + + template + inline void register_callback(std::function &target, Value &callback, Interpreter &i) + { + if (&callback == ¬e_on || &callback == ¬e_off) { + // This messages have MIDI note number as second value, so they should be represented + // in our own note abstraction, not as numbers. + target = [interpreter = &i, callback = &callback](T ...source_args) + { + if (callback->type != Value::Type::Nil) { + std::vector args { Value::from(Number(source_args))... }; + args[1] = Value::from(Chord { .notes { Note { + .base = i32(args[1].n.num % 12), + .octave = args[1].n.num / 12 + }}}); + auto result = (*callback)(*interpreter, std::move(args)); + // We discard this since callback is running in another thread. + (void) result; + } + }; + } else { + // Generic case, preserve all passed parameters as numbers + target = [interpreter = &i, callback = &callback](T ...source_args) + { + if (callback->type != Value::Type::Nil) { + auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... }); + // We discard this since callback is running in another thread. + (void) result; + } + }; + } + } +};