1033 lines
29 KiB
C++
1033 lines
29 KiB
C++
#include <musique/algo.hh>
|
|
#include <musique/interpreter/env.hh>
|
|
#include <musique/guard.hh>
|
|
#include <musique/interpreter/incoming_midi.hh>
|
|
#include <musique/interpreter/interpreter.hh>
|
|
#include <musique/try.hh>
|
|
|
|
#include <random>
|
|
#include <memory>
|
|
#include <iostream>
|
|
#include <unordered_set>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
void Interpreter::register_callbacks()
|
|
{
|
|
assert(callbacks == nullptr, "This field should be uninitialized");
|
|
callbacks = std::make_unique<Interpreter::Incoming_Midi_Callbacks>();
|
|
callbacks->add_callbacks(*midi_connection, *this);
|
|
}
|
|
|
|
/// Check if type has index method
|
|
template<typename T>
|
|
concept With_Index_Method = requires (T &t, Interpreter interpreter, usize position) {
|
|
{ t.index(interpreter, position) } -> std::convertible_to<Result<Value>>;
|
|
};
|
|
|
|
/// Check if type has either (index operator or method) and size() method
|
|
template<typename T>
|
|
concept Iterable = (With_Index_Method<T> || With_Index_Operator<T>) && requires (T const& t) {
|
|
{ t.size() } -> std::convertible_to<usize>;
|
|
};
|
|
|
|
static Result<std::vector<Value>> deep_flat(Interpreter &interpreter, Iterable auto const& array)
|
|
{
|
|
std::vector<Value> result;
|
|
|
|
for (auto i = 0u; i < array.size(); ++i) {
|
|
Value element;
|
|
if constexpr (With_Index_Method<decltype(array)>) {
|
|
element = Try(array.index(interpreter, i));
|
|
} else {
|
|
element = std::move(array[i]);
|
|
}
|
|
|
|
if (auto collection = get_if<Collection>(element)) {
|
|
auto array = Try(deep_flat(interpreter, *collection));
|
|
std::move(array.begin(), array.end(), std::back_inserter(result));
|
|
} else {
|
|
result.push_back(std::move(element));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Create chord out of given notes
|
|
template<Iterable T>
|
|
static inline std::optional<Error> create_chord(std::vector<Note> &chord, Interpreter &interpreter, T&& args)
|
|
{
|
|
for (auto i = 0u; i < args.size(); ++i) {
|
|
Value arg;
|
|
if constexpr (With_Index_Method<T>) {
|
|
arg = Try(args.index(interpreter, i));
|
|
} else {
|
|
arg = std::move(args[i]);
|
|
}
|
|
|
|
if (auto arg_chord = get_if<Chord>(arg)) {
|
|
std::ranges::copy_if(
|
|
arg_chord->notes,
|
|
std::back_inserter(chord),
|
|
[](Note const& n) { return n.base.has_value(); }
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (auto collection = get_if<Collection>(arg)) {
|
|
Try(create_chord(chord, interpreter, *collection));
|
|
continue;
|
|
}
|
|
|
|
assert(false, "this type is not supported inside chord"); // TODO(assert)
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/// Define handler for members of context that allow reading and writing to them
|
|
template<auto Mem_Ptr>
|
|
static Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vector<Value> 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<decltype(std::declval<Context>().*(Mem_Ptr))>;
|
|
|
|
if (args.size() == 0) {
|
|
return Number(interpreter.context_stack.back().*(Mem_Ptr));
|
|
}
|
|
|
|
assert(std::holds_alternative<Number>(args.front().data), "Ctx only holds numeric values");
|
|
|
|
if constexpr (std::is_same_v<Member_Type, Number>) {
|
|
interpreter.context_stack.back().*(Mem_Ptr) = std::get<Number>(args.front().data);
|
|
} else {
|
|
interpreter.context_stack.back().*(Mem_Ptr) = static_cast<Member_Type>(
|
|
std::get<Number>(args.front().data).as_int()
|
|
);
|
|
}
|
|
|
|
return Value{};
|
|
}
|
|
|
|
/// Iterate over array and it's subarrays to create one flat array
|
|
static Result<Array> into_flat_array(Interpreter &interpreter, std::span<Value> args)
|
|
{
|
|
Array target;
|
|
for (auto &arg : args) {
|
|
std::visit(Overloaded {
|
|
[&target](Array &&array) -> std::optional<Error> {
|
|
std::ranges::move(array.elements, std::back_inserter(target.elements));
|
|
return {};
|
|
},
|
|
[&target, &interpreter](Block &&block) -> std::optional<Error> {
|
|
for (auto i = 0u; i < block.size(); ++i) {
|
|
target.elements.push_back(Try(block.index(interpreter, i)));
|
|
}
|
|
return {};
|
|
},
|
|
[&target, &arg](auto&&) -> std::optional<Error> {
|
|
target.elements.push_back(std::move(arg));
|
|
return {};
|
|
},
|
|
}, std::move(arg.data));
|
|
}
|
|
return target;
|
|
}
|
|
|
|
static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
return into_flat_array(i, std::span(args));
|
|
}
|
|
|
|
/// Helper to convert method to it's name
|
|
template<auto> struct Number_Method_Name;
|
|
template<> struct Number_Method_Name<&Number::floor> { static constexpr auto value = "floor"; };
|
|
template<> struct Number_Method_Name<&Number::ceil> { static constexpr auto value = "ceil"; };
|
|
template<> struct Number_Method_Name<&Number::round> { static constexpr auto value = "round"; };
|
|
|
|
/// Apply method like Number::floor to arguments
|
|
template<auto Method>
|
|
static Result<Value> apply_numeric_transform(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
if (args.size()) {
|
|
if (auto number = get_if<Number>(args.front().data)) {
|
|
return (number->*Method)();
|
|
}
|
|
}
|
|
|
|
auto array = Try(into_flat_array(i, std::span(args)));
|
|
for (Value &arg : array.elements) {
|
|
if (auto number = get_if<Number>(arg.data)) {
|
|
*number = (number->*Method)();
|
|
} else {
|
|
goto invalid_argument_type;
|
|
}
|
|
}
|
|
return array;
|
|
|
|
|
|
invalid_argument_type:
|
|
return Error {
|
|
.details = errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = Number_Method_Name<Method>::value,
|
|
.possibilities = {
|
|
"(number | array of number ...) -> number",
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Direction used in range definition (up -> 1, 2, 3; down -> 3, 2, 1)
|
|
enum class Range_Direction { Up, Down };
|
|
|
|
/// Create range according to direction and specification, similar to python
|
|
template<Range_Direction dir>
|
|
static Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
|
|
{
|
|
auto start = Number(0), stop = Number(0), step = Number(1);
|
|
|
|
if (0) {}
|
|
else if (auto a = match<Number>(args)) { std::tie(stop) = *a; }
|
|
else if (auto a = match<Number, Number>(args)) { std::tie(start, stop) = *a; }
|
|
else if (auto a = match<Number, Number, Number>(args)) { std::tie(start, stop, step) = *a; }
|
|
else {
|
|
return errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = "range",
|
|
.possibilities = {
|
|
"(stop: number) -> array of number",
|
|
"(start: number, stop: number) -> array of number",
|
|
"(start: number, stop: number, step: number) -> array of number",
|
|
}
|
|
};
|
|
}
|
|
|
|
Array array;
|
|
if constexpr (dir == Range_Direction::Up) {
|
|
for (; start < stop; start += step) {
|
|
array.elements.push_back(start);
|
|
}
|
|
} else {
|
|
for (; stop > start; stop -= step) {
|
|
array.elements.push_back(stop - Number(1));
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/// Send MIDI Program Change message
|
|
static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
|
if (auto a = match<Number>(args)) {
|
|
auto [program] = *a;
|
|
i.midi_connection->send_program_change(0, program.as_int());
|
|
return Value{};
|
|
}
|
|
|
|
if (auto a = match<Number, Number>(args)) {
|
|
auto [chan, program] = *a;
|
|
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 = "program_change",
|
|
.possibilities = {
|
|
"(number) -> nil",
|
|
"(number, number) -> nil",
|
|
}
|
|
},
|
|
.location = {}
|
|
};
|
|
}
|
|
|
|
/// Plays sequentialy notes walking into arrays and evaluation blocks
|
|
///
|
|
/// @invariant default_action is play one
|
|
static inline std::optional<Error> sequential_play(Interpreter &i, Value v)
|
|
{
|
|
if (auto array = get_if<Array>(v)) {
|
|
for (auto &el : array->elements) {
|
|
Try(sequential_play(i, std::move(el)));
|
|
}
|
|
}
|
|
else if (auto block = get_if<Block>(v)) {
|
|
Try(sequential_play(i, Try(i.eval(std::move(block->body)))));
|
|
}
|
|
else if (auto chord = get_if<Chord>(v)) {
|
|
return i.play(*chord);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/// Play what's given
|
|
static std::optional<Error> action_play(Interpreter &i, Value v)
|
|
{
|
|
Try(sequential_play(i, std::move(v)));
|
|
return {};
|
|
}
|
|
|
|
/// Play notes
|
|
template<With_Index_Operator Container = std::vector<Value>>
|
|
static inline Result<Value> builtin_play(Interpreter &i, Container args)
|
|
{
|
|
Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "play"));
|
|
auto previous_action = std::exchange(i.default_action, action_play);
|
|
i.context_stack.push_back(i.context_stack.back());
|
|
|
|
auto const finally = [&] {
|
|
i.default_action = std::move(previous_action);
|
|
i.context_stack.pop_back();
|
|
};
|
|
|
|
for (auto &el : args) {
|
|
if (std::optional<Error> error = sequential_play(i, std::move(el))) {
|
|
finally();
|
|
return *std::move(error);
|
|
}
|
|
}
|
|
|
|
finally();
|
|
return {};
|
|
}
|
|
|
|
/// Play first argument while playing all others
|
|
static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
|
|
Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "par"));
|
|
|
|
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
|
if (args.size() == 1) {
|
|
auto chord = get_if<Chord>(args.front());
|
|
assert(chord, "Par expects music value as first argument"); // TODO(assert)
|
|
Try(i.play(std::move(*chord)));
|
|
return Value{};
|
|
}
|
|
|
|
// Create chord that should sustain during playing of all other notes
|
|
auto &ctx = i.context_stack.back();
|
|
auto chord = get_if<Chord>(args.front());
|
|
assert(chord, "par expects music value as first argument"); // TODO(assert)
|
|
|
|
std::for_each(chord->notes.begin(), chord->notes.end(), [&](Note ¬e) { note = ctx.fill(note); });
|
|
|
|
for (auto const& note : chord->notes) {
|
|
if (note.base) {
|
|
i.midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
|
}
|
|
}
|
|
|
|
auto result = builtin_play(i, std::span(args).subspan(1));
|
|
|
|
for (auto const& note : chord->notes) {
|
|
if (note.base) {
|
|
i.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Plays each argument simultaneously
|
|
static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> args)
|
|
{
|
|
// Simplest solution that will allow arbitrary code will be to clone Interpreter
|
|
// and execute code recording all messages that are beeing sent. Then sort it
|
|
// accordingly and start playing. Unfortuanatelly this solution will not respect
|
|
// cause and effect chains - [play c; say 42; play d] will first say 42,
|
|
// then play c and d
|
|
//
|
|
// The next solution is to run code in multithreaded context, making sure that
|
|
// all operations will be locked accordingly. This would require support from
|
|
// parser to well behave under multithreaded environment which now cannot be
|
|
// guaranteeed.
|
|
//
|
|
// The final solution which is only partially working is to traverse arguments
|
|
// in this function and build a schedule that will be executed
|
|
|
|
// 1. Resolve all notes from arguments to tracks
|
|
|
|
std::vector<std::vector<Chord>> tracks(args.size());
|
|
|
|
struct {
|
|
Interpreter &interpreter;
|
|
|
|
std::optional<Error> operator()(std::vector<Chord> &track, Value &arg)
|
|
{
|
|
if (auto chord = get_if<Chord>(arg)) {
|
|
track.push_back(*chord);
|
|
return {};
|
|
}
|
|
|
|
if (auto collection = get_if<Collection>(arg)) {
|
|
for (auto i = 0u; i < collection->size(); ++i) {
|
|
auto value = Try(collection->index(interpreter, i));
|
|
Try((*this)(track, value));
|
|
}
|
|
return {};
|
|
}
|
|
|
|
// Invalid type for sim function
|
|
return Error{errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = "sim",
|
|
.possibilities = {
|
|
"(music | array of music)+"
|
|
},
|
|
}};
|
|
}
|
|
} append { interpreter };
|
|
|
|
for (auto i = 0u; i < args.size(); ++i) {
|
|
Try(append(tracks[i], args[i]));
|
|
}
|
|
|
|
// 2. Translate tracks of notes into one timeline with on and off messages
|
|
struct Instruction
|
|
{
|
|
Number when;
|
|
enum { On, Off } action;
|
|
uint8_t note;
|
|
};
|
|
std::vector<Instruction> schedule;
|
|
|
|
auto const& ctx = interpreter.context_stack.back();
|
|
|
|
for (auto const& track : tracks) {
|
|
auto passed_time = Number(0);
|
|
|
|
for (auto const& chord : track) {
|
|
auto chord_length = Number(0);
|
|
for (auto ¬e : chord.notes) {
|
|
auto n = ctx.fill(note);
|
|
auto const length = n.length.value();
|
|
|
|
if (note.base) {
|
|
auto const midi_note = n.into_midi_note().value();
|
|
schedule.push_back({ .when = passed_time, .action = Instruction::On, .note = midi_note });
|
|
schedule.push_back({ .when = passed_time + length, .action = Instruction::Off, .note = midi_note });
|
|
}
|
|
|
|
chord_length = std::max(n.length.value(), chord_length);
|
|
}
|
|
passed_time += chord_length;
|
|
}
|
|
}
|
|
|
|
// 3. Sort timeline so events will be played at right time
|
|
std::sort(schedule.begin(), schedule.end(), [](Instruction const& lhs, Instruction const& rhs) {
|
|
return lhs.when < rhs.when;
|
|
});
|
|
|
|
// 4. Play according to timeline
|
|
auto start_time = std::chrono::duration<float>(0);
|
|
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);
|
|
start_time = dur;
|
|
}
|
|
switch (instruction.action) {
|
|
break; case Instruction::On: interpreter.midi_connection->send_note_on(0, instruction.note, 127);
|
|
break; case Instruction::Off: interpreter.midi_connection->send_note_off(0, instruction.note, 127);
|
|
}
|
|
}
|
|
|
|
return Value{};
|
|
}
|
|
|
|
/// Calculate upper bound for sieve that has to yield n primes
|
|
///
|
|
/// Based on https://math.stackexchange.com/a/3678200
|
|
static inline size_t upper_sieve_bound_to_yield_n_primes(size_t n_primes)
|
|
{
|
|
if (n_primes < 4) { return 10; }
|
|
|
|
float n = n_primes;
|
|
float xprev = 0;
|
|
float x = n * std::log(n);
|
|
float const max_diff = 0.5;
|
|
|
|
while (x - xprev > max_diff) {
|
|
xprev = x;
|
|
x = n*std::log(x);
|
|
}
|
|
|
|
return std::ceil(x);
|
|
}
|
|
|
|
/// Generate n primes
|
|
static Result<Value> builtin_primes(Interpreter&, std::vector<Value> args)
|
|
{
|
|
if (auto a = match<Number>(args)) {
|
|
auto [n_frac] = *a;
|
|
// Better sieve could be Sieve of Atkin, but it's more complicated
|
|
// so for now we would use Eratosthenes one.
|
|
if (n_frac.simplify_inplace(); n_frac.num <= 1) {
|
|
return Array{};
|
|
}
|
|
size_t n = n_frac.floor().as_int();
|
|
|
|
// Would preallocation be faster? Needs to be tested.
|
|
// Using approximation of prime-counting formula pi(n) ≈ x / ln(x)
|
|
size_t const approx_prime_count = n / std::log(float(n));
|
|
std::vector<Value> results;
|
|
results.reserve(approx_prime_count);
|
|
|
|
// False means we have prime here
|
|
auto const sieve_size = upper_sieve_bound_to_yield_n_primes(n);
|
|
std::vector<bool> sieve(sieve_size, false);
|
|
|
|
for (uint i = 2; i*i < sieve.size(); ++i) {
|
|
if (sieve[i] == false) {
|
|
for (uint j = i * i; j < sieve.size(); j += i) {
|
|
sieve[j] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint i = 2; i < sieve.size() && results.size() != n; ++i) {
|
|
if (!sieve[i]) {
|
|
results.push_back(Number(i));
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
return Error {
|
|
.details = errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = "nprimes",
|
|
.possibilities = {
|
|
"(number) -> array of number"
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Iterate over container
|
|
static Result<Value> builtin_for(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
constexpr auto guard = Guard<1> {
|
|
.name = "for",
|
|
.possibilities = { "(array, callback) -> any" }
|
|
};
|
|
|
|
if (auto a = match<Collection, Function>(args)) {
|
|
auto& [collection, func] = *a;
|
|
Value result{};
|
|
for (size_t n = 0; n < collection.size(); ++n) {
|
|
result = Try(func(i, { Try(collection.index(i, n)) }));
|
|
}
|
|
return result;
|
|
} else {
|
|
return guard.yield_error();
|
|
}
|
|
}
|
|
|
|
/// Fold container
|
|
static Result<Value> builtin_fold(Interpreter &interpreter, std::vector<Value> args) {
|
|
constexpr auto guard = Guard<2> {
|
|
.name = "fold",
|
|
.possibilities = {
|
|
"(array, callback) -> any",
|
|
"(array, init, callback) -> any"
|
|
}
|
|
};
|
|
|
|
Value array, init, callback;
|
|
switch (args.size()) {
|
|
break; case 2:
|
|
array = std::move(args[0]);
|
|
callback = std::move(args[1]);
|
|
if (array.size() != 0) {
|
|
init = Try(array.index(interpreter, 0));
|
|
}
|
|
break; case 3:
|
|
array = std::move(args[0]);
|
|
init = std::move(args[1]);
|
|
callback = std::move(args[2]);
|
|
break; default:
|
|
return guard.yield_error();
|
|
}
|
|
|
|
auto collection = Try(guard.match<Collection>(array));
|
|
auto function = Try(guard.match<Function>(callback));
|
|
|
|
for (auto i = 0u; i < collection->size(); ++i) {
|
|
auto element = Try(collection->index(interpreter, i));
|
|
init = Try((*function)(interpreter, { std::move(init), std::move(element) }));
|
|
}
|
|
return init;
|
|
}
|
|
|
|
/// Scan computes inclusive prefix sum
|
|
static Result<Value> builtin_scan(Interpreter &interpreter, std::vector<Value> args)
|
|
{
|
|
if (args.size()) {
|
|
if (auto p = get_if<Function>(args.front())) {
|
|
auto xs = Try(flatten(interpreter, std::span(args).subspan(1)));
|
|
for (auto i = 1u; i < xs.size(); ++i) {
|
|
xs[i] = Try((*p)(interpreter, { xs[i-1], xs[i] }));
|
|
}
|
|
return xs;
|
|
}
|
|
}
|
|
|
|
return errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = "scan",
|
|
.possibilities = {
|
|
"(callback, ...array) -> any"
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Execute blocks depending on condition
|
|
static Result<Value> builtin_if(Interpreter &i, std::vector<Value> args) {
|
|
static constexpr auto guard = Guard<2> {
|
|
.name = "if",
|
|
.possibilities = {
|
|
"(any, function) -> any",
|
|
"(any, function, function) -> any"
|
|
}
|
|
};
|
|
|
|
if (args.size() != 2 && args.size() != 3) {
|
|
return guard.yield_error();
|
|
}
|
|
|
|
if (args.front().truthy()) {
|
|
auto fun = Try(guard.match<Function>(args[1]));
|
|
return (*fun)(i, {});
|
|
} else if (args.size() == 3) {
|
|
auto fun = Try(guard.match<Function>(args[2]));
|
|
return (*fun)(i, {});
|
|
}
|
|
|
|
return Value{};
|
|
}
|
|
|
|
/// Try executing all but last block and if it fails execute last one
|
|
static Result<Value> builtin_try(Interpreter &interpreter, std::vector<Value> args)
|
|
{
|
|
constexpr auto guard = Guard<1> {
|
|
.name = "try",
|
|
.possibilities = {
|
|
"(...function) -> any"
|
|
}
|
|
};
|
|
|
|
if (args.size() == 1) {
|
|
auto callable = Try(guard.match<Function>(args[0]));
|
|
return std::move(*callable)(interpreter, {}).value_or(Value{});
|
|
}
|
|
|
|
Value success;
|
|
|
|
for (usize i = 0; i+1 < args.size(); ++i) {
|
|
auto callable = Try(guard.match<Function>(args[i]));
|
|
if (auto result = std::move(*callable)(interpreter, {})) {
|
|
success = *std::move(result);
|
|
} else {
|
|
auto callable = Try(guard.match<Function>(args.back()));
|
|
return std::move(*callable)(interpreter, {});
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/// Update value inside of array
|
|
static Result<Value> builtin_update(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto const guard = Guard<1> {
|
|
.name = "update",
|
|
.possibilities = {
|
|
"array index:number new_value"
|
|
}
|
|
};
|
|
|
|
if (args.size() != 3) {
|
|
return guard.yield_error();
|
|
}
|
|
|
|
if (auto a = match<Array, Number, Value>(args)) {
|
|
auto& [v, index, value] = *a;
|
|
v.elements[index.as_int()] = std::move(std::move(value));
|
|
return std::move(v);
|
|
}
|
|
|
|
if (auto a = match<Block, Number, Value>(args)) {
|
|
auto& [v, index, value] = *a;
|
|
auto array = Try(flatten(i, { std::move(v) }));
|
|
array[index.as_int()] = std::move(args.back());
|
|
return array;
|
|
}
|
|
|
|
return guard.yield_error();
|
|
}
|
|
|
|
/// Return typeof variable
|
|
static Result<Value> builtin_typeof(Interpreter&, std::vector<Value> args)
|
|
{
|
|
assert(args.size() == 1, "typeof expects only one argument"); // TODO(assert)
|
|
return Symbol(type_name(args.front()));
|
|
}
|
|
|
|
/// Return length of container or set/get default length to play
|
|
static Result<Value> builtin_len(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
if (args.size() == 1) {
|
|
if (auto coll = get_if<Collection>(args.front())) {
|
|
return Number(coll->size());
|
|
}
|
|
}
|
|
// TODO Add overload that tells length of array to error reporting
|
|
return ctx_read_write_property<&Context::length>(i, std::move(args));
|
|
}
|
|
|
|
/// Join arguments into flat array
|
|
static Result<Value> builtin_flat(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
return Try(into_flat_array(i, std::move(args)));
|
|
}
|
|
|
|
/// Shuffle arguments
|
|
static Result<Value> builtin_shuffle(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
static std::mt19937 rnd{std::random_device{}()};
|
|
auto array = Try(flatten(i, std::move(args)));
|
|
std::ranges::shuffle(array, rnd);
|
|
return array;
|
|
}
|
|
|
|
/// Permute arguments
|
|
static Result<Value> builtin_permute(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(flatten(i, std::move(args)));
|
|
std::ranges::next_permutation(array);
|
|
return array;
|
|
}
|
|
|
|
/// Sort arguments
|
|
static Result<Value> builtin_sort(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(flatten(i, std::move(args)));
|
|
std::ranges::sort(array);
|
|
return array;
|
|
}
|
|
|
|
/// Reverse arguments
|
|
static Result<Value> builtin_reverse(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(flatten(i, std::move(args)));
|
|
std::ranges::reverse(array);
|
|
return array;
|
|
}
|
|
|
|
/// Get minimum of arguments
|
|
static Result<Value> builtin_min(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(deep_flat(i, args));
|
|
if (auto min = std::ranges::min_element(array); min != array.end())
|
|
return *min;
|
|
return Value{};
|
|
}
|
|
|
|
/// Get maximum of arguments
|
|
static Result<Value> builtin_max(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(deep_flat(i, args));
|
|
if (auto max = std::ranges::max_element(array); max != array.end())
|
|
return *max;
|
|
return Value{};
|
|
}
|
|
|
|
/// Parition arguments into 2 arrays based on predicate
|
|
static Result<Value> builtin_partition(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
constexpr auto guard = Guard<1> {
|
|
.name = "partition",
|
|
.possibilities = { "(function, ...array) -> array" }
|
|
};
|
|
|
|
if (args.empty()) {
|
|
return guard.yield_error();
|
|
}
|
|
|
|
auto& predicate = *Try(guard.match<Function>(args.front()));
|
|
auto array = Try(flatten(i, std::span(args).subspan(1)));
|
|
|
|
Array tuple[2] = {};
|
|
for (auto &value : array) {
|
|
tuple[Try(predicate(i, { value })).truthy()].elements.push_back(std::move(value));
|
|
}
|
|
|
|
return Array {{
|
|
std::move(tuple[true]),
|
|
std::move(tuple[false])
|
|
}};
|
|
}
|
|
|
|
/// Rotate arguments by n steps
|
|
static Result<Value> builtin_rotate(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
constexpr auto guard = Guard<1> {
|
|
.name = "rotate",
|
|
.possibilities = { "(number, ...array) -> array" }
|
|
};
|
|
|
|
|
|
if (args.size()) {
|
|
if (auto const offset_source = get_if<Number>(args.front())) {
|
|
auto offset = offset_source->as_int();
|
|
auto array = Try(flatten(i, std::span(args).subspan(1)));
|
|
if (offset > 0) {
|
|
offset = offset % array.size();
|
|
std::rotate(array.begin(), array.begin() + offset, array.end());
|
|
} else if (offset < 0) {
|
|
offset = -offset % array.size();
|
|
std::rotate(array.rbegin(), array.rbegin() + offset, array.rend());
|
|
}
|
|
return array;
|
|
}
|
|
}
|
|
|
|
return guard.yield_error();
|
|
}
|
|
|
|
/// Returns unique collection of arguments
|
|
static Result<Value> builtin_unique(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(flatten(i, args));
|
|
std::unordered_set<Value> seen;
|
|
|
|
std::vector<Value> result;
|
|
for (auto &el : array) {
|
|
if (!seen.contains(el)) {
|
|
seen.insert(el);
|
|
result.push_back(std::move(el));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Returns arguments with all successive copies eliminated
|
|
static Result<Value> builtin_uniq(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto array = Try(flatten(i, args));
|
|
|
|
std::optional<Value> previous;
|
|
std::vector<Value> result;
|
|
|
|
for (auto &el : array) {
|
|
if (previous && *previous == el)
|
|
continue;
|
|
result.push_back(el);
|
|
previous = std::move(el);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Result<Value> builtin_hash(Interpreter&, std::vector<Value> args)
|
|
{
|
|
return Number(
|
|
std::accumulate(args.cbegin(), args.cend(), size_t(0), [](size_t h, Value const& v) {
|
|
return hash_combine(h, std::hash<Value>{}(v));
|
|
})
|
|
);
|
|
}
|
|
|
|
/// Build chord from arguments
|
|
static Result<Value> builtin_chord(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
Chord chord;
|
|
Try(create_chord(chord.notes, i, std::move(args)));
|
|
return chord;
|
|
}
|
|
|
|
/// Send MIDI message Note On
|
|
static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
if (auto a = match<Number, Number, Number>(args)) {
|
|
auto [chan, note, vel] = *a;
|
|
i.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int());
|
|
return Value {};
|
|
}
|
|
|
|
if (auto a = match<Number, Chord, Number>(args)) {
|
|
auto [chan, chord, vel] = *a;
|
|
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 = {}
|
|
};
|
|
}
|
|
|
|
/// Send MIDI message Note Off
|
|
static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
if (auto a = match<Number, Number>(args)) {
|
|
auto [chan, note] = *a;
|
|
i.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127);
|
|
return Value {};
|
|
}
|
|
|
|
if (auto a = match<Number, Chord>(args)) {
|
|
auto& [chan, chord] = *a;
|
|
|
|
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 = {}
|
|
};
|
|
}
|
|
|
|
/// Add handler for incoming midi messages
|
|
static Result<Value> builtin_incoming(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
if (auto a = match<Symbol, Function>(args)) {
|
|
auto& [symbol, fun] = *a;
|
|
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{};
|
|
}
|
|
|
|
return errors::Unsupported_Types_For {
|
|
.type = errors::Unsupported_Types_For::Function,
|
|
.name = "incoming",
|
|
.possibilities = { "(symbol, function) -> nil" }
|
|
};
|
|
}
|
|
|
|
/// Interleaves arguments
|
|
static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
std::vector<Value> result;
|
|
|
|
std::unordered_map<std::size_t, std::size_t> indicies;
|
|
|
|
size_t awaiting_containers = std::count_if(args.begin(), args.end(), holds_alternative<Collection>);
|
|
|
|
// Algorithm description:
|
|
// Repeat until all arguments were exhausted:
|
|
// For each argument:
|
|
// If it can be iterated then add current element to the list and move to the next one.
|
|
// If next element cannot be retrived mark container as exhausted and start from beginning
|
|
// Otherwise append element to the list
|
|
do {
|
|
for (size_t idx = 0; idx < args.size(); ++idx) {
|
|
if (auto coll = get_if<Collection>(args[idx])) {
|
|
result.push_back(Try(coll->index(i, indicies[idx]++ % coll->size())));
|
|
if (indicies[idx] == coll->size()) {
|
|
awaiting_containers--;
|
|
}
|
|
} else {
|
|
result.push_back(args[idx]);
|
|
}
|
|
}
|
|
} while (awaiting_containers);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Call operator. Calls first argument with remaining arguments
|
|
static Result<Value> builtin_call(Interpreter &i, std::vector<Value> args)
|
|
{
|
|
auto const guard = Guard<1> {
|
|
.name = "call",
|
|
.possibilities = {
|
|
"(function, ...args) -> any"
|
|
}
|
|
};
|
|
|
|
if (args.empty()) {
|
|
return guard.yield_error();
|
|
}
|
|
|
|
auto fst = std::move(args.front());
|
|
auto &callable = *Try(guard.match<Function>(fst));
|
|
args.erase(args.begin());
|
|
return callable(i, std::move(args));
|
|
}
|
|
|
|
void Interpreter::register_builtin_functions()
|
|
{
|
|
auto &global = *Env::global;
|
|
|
|
global.force_define("bpm", ctx_read_write_property<&Context::bpm>);
|
|
global.force_define("call", builtin_call);
|
|
global.force_define("ceil", apply_numeric_transform<&Number::ceil>);
|
|
global.force_define("chord", builtin_chord);
|
|
global.force_define("down", builtin_range<Range_Direction::Down>);
|
|
global.force_define("flat", builtin_flat);
|
|
global.force_define("floor", apply_numeric_transform<&Number::floor>);
|
|
global.force_define("fold", builtin_fold);
|
|
global.force_define("for", builtin_for);
|
|
global.force_define("hash", builtin_hash);
|
|
global.force_define("if", builtin_if);
|
|
global.force_define("incoming", builtin_incoming);
|
|
global.force_define("instrument", builtin_program_change);
|
|
global.force_define("len", builtin_len);
|
|
global.force_define("max", builtin_max);
|
|
global.force_define("min", builtin_min);
|
|
global.force_define("mix", builtin_mix);
|
|
global.force_define("note_off", builtin_note_off);
|
|
global.force_define("note_on", builtin_note_on);
|
|
global.force_define("nprimes", builtin_primes);
|
|
global.force_define("oct", ctx_read_write_property<&Context::octave>);
|
|
global.force_define("par", builtin_par);
|
|
global.force_define("partition", builtin_partition);
|
|
global.force_define("permute", builtin_permute);
|
|
global.force_define("pgmchange", builtin_program_change);
|
|
global.force_define("play", builtin_play);
|
|
global.force_define("program_change", builtin_program_change);
|
|
global.force_define("range", builtin_range<Range_Direction::Up>);
|
|
global.force_define("reverse", builtin_reverse);
|
|
global.force_define("rotate", builtin_rotate);
|
|
global.force_define("round", apply_numeric_transform<&Number::round>);
|
|
global.force_define("scan", builtin_scan);
|
|
global.force_define("shuffle", builtin_shuffle);
|
|
global.force_define("sim", builtin_sim);
|
|
global.force_define("sort", builtin_sort);
|
|
global.force_define("try", builtin_try);
|
|
global.force_define("typeof", builtin_typeof);
|
|
global.force_define("uniq", builtin_uniq);
|
|
global.force_define("unique", builtin_unique);
|
|
global.force_define("up", builtin_range<Range_Direction::Up>);
|
|
global.force_define("update", builtin_update);
|
|
}
|