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
Obj= \
context.o \
environment.o \
errors.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,11 +64,16 @@ Interpreter::~Interpreter()
Interpreter::Interpreter(std::ostream& out)
: out(out)
{
{ // 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)));
@ -125,6 +130,7 @@ Interpreter::Interpreter(std::ostream& out)
return std::move(args.front()).blk.index(i, std::move(args.back()).n.as_int());
};
}
}
Result<Value> Interpreter::eval(Ast &&ast)
{

View File

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