Note duration calculation in current BPM context
This commit is contained in:
parent
2e2c34ce21
commit
ba463139d6
1
Makefile
1
Makefile
@ -8,6 +8,7 @@ LDFLAGS=-L./lib/midi/
|
||||
LDLIBS=-lmidi-alsa -lasound
|
||||
|
||||
Obj= \
|
||||
context.o \
|
||||
environment.o \
|
||||
errors.o \
|
||||
interpreter.o \
|
||||
|
14
src/context.cc
Normal file
14
src/context.cc
Normal 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);
|
||||
}
|
@ -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<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();
|
||||
auto &global = *Env::global;
|
||||
global.force_define("if", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||
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> {
|
||||
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<Value> args) -> Result<Value> {
|
||||
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<Value> args) -> Result<Value> {
|
||||
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("len", +[](Interpreter &, std::vector<Value> args) -> Result<Value> {
|
||||
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<Value> args) -> Result<Value> {
|
||||
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<std::plus<>>();
|
||||
operators["-"] = binary_operator<std::minus<>>();
|
||||
operators["*"] = binary_operator<std::multiplies<>>();
|
||||
operators["/"] = binary_operator<std::divides<>>();
|
||||
|
||||
global.force_define("len", +[](Interpreter &, std::vector<Value> args) -> Result<Value> {
|
||||
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<std::less<>>();
|
||||
operators[">"] = comparison_operator<std::greater<>>();
|
||||
operators["<="] = comparison_operator<std::less_equal<>>();
|
||||
operators[">="] = comparison_operator<std::greater_equal<>>();
|
||||
|
||||
operators["+"] = binary_operator<std::plus<>>();
|
||||
operators["-"] = binary_operator<std::minus<>>();
|
||||
operators["*"] = binary_operator<std::multiplies<>>();
|
||||
operators["/"] = binary_operator<std::divides<>>();
|
||||
operators["=="] = equality_operator<std::equal_to<>>();
|
||||
operators["!="] = equality_operator<std::not_equal_to<>>();
|
||||
|
||||
operators["<"] = comparison_operator<std::less<>>();
|
||||
operators[">"] = comparison_operator<std::greater<>>();
|
||||
operators["<="] = comparison_operator<std::less_equal<>>();
|
||||
operators[">="] = comparison_operator<std::greater_equal<>>();
|
||||
|
||||
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());
|
||||
};
|
||||
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)
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@ -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<float> length_to_duration(std::optional<Number> length) const;
|
||||
};
|
||||
|
||||
struct Interpreter
|
||||
{
|
||||
/// Output of IO builtins like `say`
|
||||
@ -568,6 +588,10 @@ struct Interpreter
|
||||
/// Current environment (current scope)
|
||||
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();
|
||||
explicit Interpreter(std::ostream& out);
|
||||
|
39
src/tests/context.cc
Normal file
39
src/tests/context.cc
Normal 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);
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user