Note duration calculation in current BPM context

This commit is contained in:
Robert Bendun 2022-05-24 02:58:15 +02:00
parent 2e2c34ce21
commit ba463139d6
5 changed files with 137 additions and 53 deletions

View File

@ -8,6 +8,7 @@ LDFLAGS=-L./lib/midi/
LDLIBS=-lmidi-alsa -lasound LDLIBS=-lmidi-alsa -lasound
Obj= \ Obj= \
context.o \
environment.o \ environment.o \
errors.o \ errors.o \
interpreter.o \ interpreter.o \

14
src/context.cc Normal file
View File

@ -0,0 +1,14 @@
#include <musique.hh>
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<float> Context::length_to_duration(std::optional<Number> length) const
{
auto const len = length ? *length : this->length;
return std::chrono::duration<float>(float(len.num * (60.f / (float(bpm) / 4))) / len.den);
}

View File

@ -64,66 +64,72 @@ Interpreter::~Interpreter()
Interpreter::Interpreter(std::ostream& out) Interpreter::Interpreter(std::ostream& out)
: out(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<Value> args) -> Result<Value> {
assert(args.size() == 1, "typeof expects only one argument");
return Value::symbol(std::string(type_name(args.front().type)));
});
env = Env::global = Env::make(); global.force_define("if", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
auto &global = *Env::global; assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if <condition> <then> [<else>]");
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<Value> args) -> Result<Value> { global.force_define("say", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 1, "typeof expects only one argument"); for (auto it = args.begin(); it != args.end(); ++it) {
return Value::symbol(std::string(type_name(args.front().type))); i.out << *it;
}); if (std::next(it) != args.end())
i.out << ' ';
}
i.out << '\n';
return {};
});
global.force_define("if", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("len", +[](Interpreter &, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if <condition> <then> [<else>]"); assert(args.size() == 1, "len only accepts one argument");
if (args.front().truthy()) { assert(args.front().type == Value::Type::Block, "Only blocks can be measure");
return args[1](i, {}); if (args.front().blk.body.type != Ast::Type::Sequence) {
} else if (args.size() == 3) { return Value::number(Number(1));
return args[2](i, {}); } else {
} else { return Value::number(Number(args.front().blk.body.arguments.size()));
return Value{}; }
} });
});
global.force_define("say", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { operators["+"] = binary_operator<std::plus<>>();
for (auto it = args.begin(); it != args.end(); ++it) { operators["-"] = binary_operator<std::minus<>>();
i.out << *it; operators["*"] = binary_operator<std::multiplies<>>();
if (std::next(it) != args.end()) operators["/"] = binary_operator<std::divides<>>();
i.out << ' ';
}
i.out << '\n';
return {};
});
global.force_define("len", +[](Interpreter &, std::vector<Value> args) -> Result<Value> { operators["<"] = comparison_operator<std::less<>>();
assert(args.size() == 1, "len only accepts one argument"); operators[">"] = comparison_operator<std::greater<>>();
assert(args.front().type == Value::Type::Block, "Only blocks can be measure"); operators["<="] = comparison_operator<std::less_equal<>>();
if (args.front().blk.body.type != Ast::Type::Sequence) { operators[">="] = comparison_operator<std::greater_equal<>>();
return Value::number(Number(1));
} else {
return Value::number(Number(args.front().blk.body.arguments.size()));
}
});
operators["+"] = binary_operator<std::plus<>>(); operators["=="] = equality_operator<std::equal_to<>>();
operators["-"] = binary_operator<std::minus<>>(); operators["!="] = equality_operator<std::not_equal_to<>>();
operators["*"] = binary_operator<std::multiplies<>>();
operators["/"] = binary_operator<std::divides<>>();
operators["<"] = comparison_operator<std::less<>>(); operators["."] = +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
operators[">"] = comparison_operator<std::greater<>>(); assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert)
operators["<="] = comparison_operator<std::less_equal<>>(); assert(args.front().type == Value::Type::Block, "Only blocks can be indexed"); // TODO(assert)
operators[">="] = comparison_operator<std::greater_equal<>>(); 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["=="] = equality_operator<std::equal_to<>>(); };
operators["!="] = equality_operator<std::not_equal_to<>>(); }
operators["."] = +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
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<Value> Interpreter::eval(Ast &&ast) Result<Value> Interpreter::eval(Ast &&ast)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <chrono>
#include <concepts> #include <concepts>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@ -557,6 +558,25 @@ private:
Env() = default; 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<float> length_to_duration(std::optional<Number> length) const;
};
struct Interpreter struct Interpreter
{ {
/// Output of IO builtins like `say` /// Output of IO builtins like `say`
@ -568,6 +588,10 @@ struct Interpreter
/// Current environment (current scope) /// Current environment (current scope)
std::shared_ptr<Env> env; std::shared_ptr<Env> env;
/// Context stack. `constext_stack.back()` is a current context.
/// There is always at least one context
std::vector<Context> context_stack;
Interpreter(); Interpreter();
~Interpreter(); ~Interpreter();
explicit Interpreter(std::ostream& out); explicit Interpreter(std::ostream& out);

39
src/tests/context.cc Normal file
View File

@ -0,0 +1,39 @@
#include <boost/ut.hpp>
#include <musique.hh>
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<decltype(dur)::period, std::ratio<1, 1>>,
"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);
};
};