From ba463139d6c23fed837f5aa6d64f9d6739fe5324 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Tue, 24 May 2022 02:58:15 +0200 Subject: [PATCH] Note duration calculation in current BPM context --- Makefile | 1 + src/context.cc | 14 ++++++ src/interpreter.cc | 112 +++++++++++++++++++++++-------------------- src/musique.hh | 24 ++++++++++ src/tests/context.cc | 39 +++++++++++++++ 5 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 src/context.cc create mode 100644 src/tests/context.cc diff --git a/Makefile b/Makefile index b18b820..7a9c018 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ LDFLAGS=-L./lib/midi/ LDLIBS=-lmidi-alsa -lasound Obj= \ + context.o \ environment.o \ errors.o \ interpreter.o \ diff --git a/src/context.cc b/src/context.cc new file mode 100644 index 0000000..0ba805c --- /dev/null +++ b/src/context.cc @@ -0,0 +1,14 @@ +#include + +Note Context::fill(Note note) const +{ + if (not note.octave) note.octave = octave; + if (not note.length) note.length = length; + return note; +} + +std::chrono::duration Context::length_to_duration(std::optional length) const +{ + auto const len = length ? *length : this->length; + return std::chrono::duration(float(len.num * (60.f / (float(bpm) / 4))) / len.den); +} diff --git a/src/interpreter.cc b/src/interpreter.cc index 644ac29..748f733 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -64,66 +64,72 @@ Interpreter::~Interpreter() Interpreter::Interpreter(std::ostream& out) : out(out) { - assert(!bool(Env::global), "Only one instance of interpreter can be at one time"); + { // 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(); + } + { // Global default functions initialization + // TODO move `say` to `src/main.cc` since it's platform depdendent + auto &global = *Env::global; + global.force_define("typeof", +[](Interpreter&, std::vector args) -> Result { + assert(args.size() == 1, "typeof expects only one argument"); + return Value::symbol(std::string(type_name(args.front().type))); + }); - env = Env::global = Env::make(); - auto &global = *Env::global; + global.force_define("if", +[](Interpreter &i, std::vector args) -> Result { + assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if []"); + if (args.front().truthy()) { + return args[1](i, {}); + } else if (args.size() == 3) { + return args[2](i, {}); + } else { + return Value{}; + } + }); - global.force_define("typeof", +[](Interpreter&, std::vector args) -> Result { - assert(args.size() == 1, "typeof expects only one argument"); - return Value::symbol(std::string(type_name(args.front().type))); - }); + global.force_define("say", +[](Interpreter &i, std::vector args) -> Result { + for (auto it = args.begin(); it != args.end(); ++it) { + i.out << *it; + if (std::next(it) != args.end()) + i.out << ' '; + } + i.out << '\n'; + return {}; + }); - global.force_define("if", +[](Interpreter &i, std::vector args) -> Result { - assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if []"); - if (args.front().truthy()) { - return args[1](i, {}); - } else if (args.size() == 3) { - return args[2](i, {}); - } else { - return Value{}; - } - }); + global.force_define("len", +[](Interpreter &, std::vector args) -> Result { + assert(args.size() == 1, "len only accepts one argument"); + assert(args.front().type == Value::Type::Block, "Only blocks can be measure"); + if (args.front().blk.body.type != Ast::Type::Sequence) { + return Value::number(Number(1)); + } else { + return Value::number(Number(args.front().blk.body.arguments.size())); + } + }); - global.force_define("say", +[](Interpreter &i, std::vector args) -> Result { - for (auto it = args.begin(); it != args.end(); ++it) { - i.out << *it; - if (std::next(it) != args.end()) - i.out << ' '; - } - i.out << '\n'; - return {}; - }); + operators["+"] = binary_operator>(); + operators["-"] = binary_operator>(); + operators["*"] = binary_operator>(); + operators["/"] = binary_operator>(); - global.force_define("len", +[](Interpreter &, std::vector args) -> Result { - assert(args.size() == 1, "len only accepts one argument"); - assert(args.front().type == Value::Type::Block, "Only blocks can be measure"); - if (args.front().blk.body.type != Ast::Type::Sequence) { - return Value::number(Number(1)); - } else { - return Value::number(Number(args.front().blk.body.arguments.size())); - } - }); + operators["<"] = comparison_operator>(); + operators[">"] = comparison_operator>(); + operators["<="] = comparison_operator>(); + operators[">="] = comparison_operator>(); - operators["+"] = binary_operator>(); - operators["-"] = binary_operator>(); - operators["*"] = binary_operator>(); - operators["/"] = binary_operator>(); + operators["=="] = equality_operator>(); + operators["!="] = equality_operator>(); - operators["<"] = comparison_operator>(); - operators[">"] = comparison_operator>(); - operators["<="] = comparison_operator>(); - operators[">="] = comparison_operator>(); - - operators["=="] = equality_operator>(); - operators["!="] = equality_operator>(); - - operators["."] = +[](Interpreter &i, std::vector args) -> Result { - assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert) - assert(args.front().type == Value::Type::Block, "Only blocks can be indexed"); // TODO(assert) - assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert) - return std::move(args.front()).blk.index(i, std::move(args.back()).n.as_int()); - }; + operators["."] = +[](Interpreter &i, std::vector args) -> Result { + assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert) + assert(args.front().type == Value::Type::Block, "Only blocks can be indexed"); // TODO(assert) + assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert) + return std::move(args.front()).blk.index(i, std::move(args.back()).n.as_int()); + }; + } } Result Interpreter::eval(Ast &&ast) diff --git a/src/musique.hh b/src/musique.hh index 175016d..185b917 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -557,6 +558,25 @@ private: Env() = default; }; +/// Context holds default values for music related actions +struct Context +{ + /// Default note octave + i8 octave = 4; + + /// Default note length + Number length = Number(1, 4); + + /// Default BPM + unsigned bpm = 120; + + /// Fills empty places in Note like octave and length with default values from context + Note fill(Note) const; + + /// Converts length to seconds with current bpm + std::chrono::duration length_to_duration(std::optional length) const; +}; + struct Interpreter { /// Output of IO builtins like `say` @@ -568,6 +588,10 @@ struct Interpreter /// Current environment (current scope) std::shared_ptr env; + /// Context stack. `constext_stack.back()` is a current context. + /// There is always at least one context + std::vector context_stack; + Interpreter(); ~Interpreter(); explicit Interpreter(std::ostream& out); diff --git a/src/tests/context.cc b/src/tests/context.cc new file mode 100644 index 0000000..8c3e192 --- /dev/null +++ b/src/tests/context.cc @@ -0,0 +1,39 @@ +#include +#include + +using namespace boost::ut; +using namespace std::string_view_literals; + +void test_seconds_compute( + unsigned bpm, + Number length, + float value, + reflection::source_location const& sl = reflection::source_location::current()) +{ + Context const ctx { .bpm = bpm }; + auto const dur = ctx.length_to_duration(length); + + static_assert(std::same_as>, + "tests provided by this function expects Context::length_to_duration to return seconds"); + + expect(dur.count() == _f(value), sl); +} + +suite context_suite = [] { + "Context duration resolution"_test = [] { + test_seconds_compute(60, Number(2,1), 8); + test_seconds_compute(60, Number(1,1), 4); + test_seconds_compute(60, Number(1,2), 2); + test_seconds_compute(60, Number(1,4), 1); + + test_seconds_compute(96, Number(2,1), 5); + test_seconds_compute(96, Number(1,1), 2.5); + test_seconds_compute(96, Number(1,2), 1.25); + test_seconds_compute(96, Number(1,4), 0.625); + + test_seconds_compute(120, Number(2,1), 4); + test_seconds_compute(120, Number(1,1), 2); + test_seconds_compute(120, Number(1,2), 1); + test_seconds_compute(120, Number(1,4), 0.5); + }; +};