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
|
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
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)
|
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)
|
||||||
|
@ -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
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