diff --git a/src/builtin_functions.cc b/src/builtin_functions.cc index e6d00ca..e804453 100644 --- a/src/builtin_functions.cc +++ b/src/builtin_functions.cc @@ -5,6 +5,8 @@ #include #include #include +#include +#include void Interpreter::register_callbacks() { @@ -309,6 +311,107 @@ static Result builtin_par(Interpreter &i, std::vector args) { return result; } +/// Plays each argument simultaneously +static Result builtin_sim(Interpreter &interpreter, std::vector 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> tracks(args.size()); + + struct { + Interpreter &interpreter; + + Result operator()(std::vector &track, Value &arg) + { + if (arg.type == Value::Type::Music) { + track.push_back(std::move(arg).chord); + return {}; + } + + if (is_indexable(arg.type)) { + for (auto i = 0u; i < arg.size(); ++i) { + auto value = Try(arg.index(interpreter, i)); + Try((*this)(track, value)); + } + return {}; + } + + unimplemented(); + } + } 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 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(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 @@ -852,6 +955,7 @@ void Interpreter::register_builtin_functions() global.force_define("rotate", builtin_rotate); global.force_define("round", apply_numeric_transform<&Number::round>); 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); diff --git a/src/value.cc b/src/value.cc index 44a1d1d..f3605f2 100644 --- a/src/value.cc +++ b/src/value.cc @@ -487,8 +487,10 @@ Chord Chord::from(std::string_view source) source.remove_prefix(1 + (source[1] == '#')); chord.notes.push_back(*std::move(note)); - for (char digit : source) { - chord.notes.push_back(Note { .base = u8(digit - '0') }); + if (note->base) { + for (char digit : source) { + chord.notes.push_back(Note { .base = note->base.value() + i32(digit - '0') }); + } } return chord;