Compare commits
5 Commits
main
...
with-sched
Author | SHA1 | Date | |
---|---|---|---|
|
e7f3cf2cfb | ||
|
da8a6b5d6c | ||
|
a08024d6c9 | ||
|
afcd9f27b1 | ||
|
2f3cfab999 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "lib/midi"]
|
[submodule "lib/midi"]
|
||||||
path = lib/midi
|
path = lib/midi
|
||||||
url = https://git.wmi.amu.edu.pl/s416496/midi.git
|
url = https://git.wmi.amu.edu.pl/s416496/midi.git
|
||||||
|
[submodule "lib/scheduler"]
|
||||||
|
path = lib/scheduler
|
||||||
|
url = https://github.com/RobertBendun/scheduler.git
|
||||||
|
31
Makefile
31
Makefile
@ -1,12 +1,23 @@
|
|||||||
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized
|
||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Ilib/midi/include -Isrc/ -Ilib/bestline/
|
CPPFLAGS:=$(CPPFLAGS) \
|
||||||
|
-Ilib/bestline/ \
|
||||||
|
-Ilib/expected/ \
|
||||||
|
-Ilib/midi/include \
|
||||||
|
-Ilib/scheduler \
|
||||||
|
-Ilib/ut/ \
|
||||||
|
-Isrc/
|
||||||
RELEASE_FLAGS=-O3
|
RELEASE_FLAGS=-O3
|
||||||
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
|
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
|
||||||
CXX=g++
|
CXX=g++
|
||||||
|
|
||||||
LDFLAGS=-L./lib/midi/
|
LDFLAGS=-L./lib/midi/ -L./lib/scheduler/
|
||||||
LDLIBS=-lmidi-alsa -lasound -lpthread
|
LDLIBS=-lscheduler -lmidi-alsa -lasound -lpthread
|
||||||
|
|
||||||
|
Bestline_Dependency = bin/bestline.o
|
||||||
|
Scheduler_Dependency = lib/scheduler/libscheduler.a
|
||||||
|
Midi_Dependency = lib/midi/libmidi-alsa.a
|
||||||
|
External_Dependencies = $(Scheduler_Dependency) $(Midi_Dependency) $(Bestline_Dependency)
|
||||||
|
|
||||||
Obj= \
|
Obj= \
|
||||||
context.o \
|
context.o \
|
||||||
@ -49,11 +60,14 @@ bin/%.o: src/%.cc src/*.hh
|
|||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
|
||||||
|
|
||||||
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o src/*.hh lib/midi/libmidi-alsa.a
|
lib/%.a:
|
||||||
|
(cd $(shell dirname $@); make)
|
||||||
|
|
||||||
|
bin/musique: $(Release_Obj) bin/main.o src/*.hh $(External_Dependencies)
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS)
|
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o bin/main.o $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
bin/debug/musique: $(Debug_Obj) bin/debug/main.o bin/bestline.o src/*.hh
|
bin/debug/musique: $(Debug_Obj) bin/debug/main.o src/*.hh $(External_Dependencies)
|
||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS)
|
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $(Debug_Obj) bin/bestline.o bin/debug/main.o $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
@ -89,9 +103,12 @@ bin/unit-tests: $(Test_Obj) $(Debug_Obj)
|
|||||||
@echo "CXX $@"
|
@echo "CXX $@"
|
||||||
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(DEBUG_FLAGS) -o $@ $^
|
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(DEBUG_FLAGS) -o $@ $^
|
||||||
|
|
||||||
clean:
|
clean: clean-dependencies
|
||||||
rm -rf bin coverage
|
rm -rf bin coverage
|
||||||
|
|
||||||
.PHONY: clean doc doc-open all test unit-tests
|
clean-dependencies:
|
||||||
|
rm -f $(External_Dependencies)
|
||||||
|
|
||||||
|
.PHONY: clean clean-dependencies doc doc-open all test unit-tests
|
||||||
|
|
||||||
$(shell mkdir -p bin/debug/tests)
|
$(shell mkdir -p bin/debug/tests)
|
||||||
|
@ -7,18 +7,17 @@ Interpreter języka Musique. Możliwy do wykorzystywania jako:
|
|||||||
|
|
||||||
## Budowanie interpretera
|
## Budowanie interpretera
|
||||||
|
|
||||||
Jeśli nie posiadasz zależności `lib/midi` to:
|
Jeśli nie posiadasz zależności `lib/midi` lub `lib/scheduler` to:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ git submodule init
|
$ git submodule init
|
||||||
$ git submodule update
|
$ git submodule update
|
||||||
$ (cd lib/midi; make)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A następnie zbuduj interpreter:
|
A następnie zbuduj interpreter:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make bin/musique
|
$ make
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dostępne komendy
|
## Dostępne komendy
|
||||||
|
1
lib/scheduler
Submodule
1
lib/scheduler
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 011490455439041db6d5ddbee9d64664ab608025
|
@ -7,8 +7,8 @@ Note Context::fill(Note note) const
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::duration<float> Context::length_to_duration(std::optional<Number> length) const
|
Context::Duration Context::length_to_duration(std::optional<Number> length) const
|
||||||
{
|
{
|
||||||
auto const len = length ? *length : this->length;
|
auto const len = length ? *length : this->length;
|
||||||
return std::chrono::duration<float>(float(len.num * (60.f / (float(bpm) / 4))) / len.den);
|
return Context::Duration(float(len.num * (60.f / (float(bpm) / 4))) / len.den);
|
||||||
}
|
}
|
||||||
|
@ -61,13 +61,27 @@ void Interpreter::register_callbacks()
|
|||||||
{
|
{
|
||||||
assert(callbacks != nullptr, "Interpreter constructor should initialize this field");
|
assert(callbacks != nullptr, "Interpreter constructor should initialize this field");
|
||||||
callbacks->add_callbacks(*midi_connection, *this);
|
callbacks->add_callbacks(*midi_connection, *this);
|
||||||
|
|
||||||
callbacks->note_on = Value(+[](Interpreter &, std::vector<Value> args) -> Result<Value> {
|
|
||||||
std::cout << "Received: " << args[1] << "\r\n" << std::flush;
|
|
||||||
return Value{};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
static inline void await(Interpreter &interpreter, std::array<scheduler::Job_Id, N> dependencies)
|
||||||
|
{
|
||||||
|
std::mutex mu;
|
||||||
|
std::unique_lock lock(mu);
|
||||||
|
|
||||||
|
std::condition_variable wait_for_finish;
|
||||||
|
bool finished = false;
|
||||||
|
|
||||||
|
interpreter.scheduler.schedule(dependencies, [&] { finished = true; wait_for_finish.notify_one(); });
|
||||||
|
wait_for_finish.wait(lock, [&finished] { return finished; });
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void await(Interpreter &interpreter, scheduler::Job_Id dependency)
|
||||||
|
{
|
||||||
|
return await(interpreter, std::array { dependency });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
|
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
|
||||||
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
|
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
|
||||||
{
|
{
|
||||||
@ -345,6 +359,7 @@ static inline Result<void> create_chord(std::vector<Note> &chord, Interpreter &i
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template<Iterable T>
|
template<Iterable T>
|
||||||
static inline Result<void> play_notes(Interpreter &interpreter, T args)
|
static inline Result<void> play_notes(Interpreter &interpreter, T args)
|
||||||
{
|
{
|
||||||
@ -360,13 +375,13 @@ static inline Result<void> play_notes(Interpreter &interpreter, T args)
|
|||||||
case Value::Type::Array:
|
case Value::Type::Array:
|
||||||
case Value::Type::Block:
|
case Value::Type::Block:
|
||||||
Try(play_notes(interpreter, std::move(arg)));
|
Try(play_notes(interpreter, std::move(arg)));
|
||||||
break;
|
|
||||||
|
|
||||||
case Value::Type::Music:
|
break; case Value::Type::Music:
|
||||||
interpreter.play(arg.chord);
|
{
|
||||||
break;
|
await(interpreter, std::array{ interpreter.play(arg.chord) });
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
break; default:
|
||||||
assert(false, "this type does not support playing");
|
assert(false, "this type does not support playing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -565,7 +580,7 @@ error:
|
|||||||
global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||||
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
||||||
if (args.size() == 1) {
|
if (args.size() == 1) {
|
||||||
i.play(std::move(args.front()).chord);
|
await(i, i.play(std::move(args.front()).chord));
|
||||||
return Value{};
|
return Value{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,7 +593,7 @@ error:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = std::next(args.begin()); it != args.end(); ++it) {
|
for (auto it = std::next(args.begin()); it != args.end(); ++it) {
|
||||||
i.play(std::move(*it).chord);
|
await(i, i.play(std::move(*it).chord));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
@ -886,7 +901,7 @@ void Interpreter::leave_scope()
|
|||||||
env = env->leave();
|
env = env->leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::play(Chord chord)
|
scheduler::Job_Id Interpreter::play(Chord chord)
|
||||||
{
|
{
|
||||||
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
|
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
|
||||||
|
|
||||||
@ -895,22 +910,26 @@ void Interpreter::play(Chord chord)
|
|||||||
// Fill all notes that don't have octave or length with defaults
|
// Fill all notes that don't have octave or length with defaults
|
||||||
std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); });
|
std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); });
|
||||||
|
|
||||||
// Sort that we have smaller times first
|
|
||||||
std::sort(chord.notes.begin(), chord.notes.end(), [](Note const& lhs, Note const& rhs) { return lhs.length < rhs.length; });
|
|
||||||
|
|
||||||
Number max_time = *chord.notes.back().length;
|
|
||||||
|
|
||||||
// Turn all notes on
|
// Turn all notes on
|
||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn off each note at right time
|
Context::Duration max_duration;
|
||||||
|
auto last_job_id = 0;
|
||||||
|
|
||||||
|
// Register all note endings to scheduler
|
||||||
for (auto const& note : chord.notes) {
|
for (auto const& note : chord.notes) {
|
||||||
if (max_time != Number(0)) {
|
auto duration = ctx.length_to_duration(*note.length);
|
||||||
max_time -= *note.length;
|
auto job_id = scheduler.schedule(
|
||||||
std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
|
std::chrono::duration_cast<scheduler::Clock::duration>(duration),
|
||||||
|
[this, note = note] { midi_connection->send_note_off(0, *note.into_midi_note(), 127); }
|
||||||
|
);
|
||||||
|
if (duration > max_duration) {
|
||||||
|
max_duration = duration;
|
||||||
|
last_job_id = job_id;
|
||||||
}
|
}
|
||||||
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return last_job_id;
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ struct Runner
|
|||||||
midi::ALSA alsa;
|
midi::ALSA alsa;
|
||||||
Interpreter interpreter;
|
Interpreter interpreter;
|
||||||
std::thread midi_input_event_loop;
|
std::thread midi_input_event_loop;
|
||||||
|
std::optional<std::jthread> scheduler_thread;
|
||||||
std::stop_source stop_source;
|
std::stop_source stop_source;
|
||||||
|
|
||||||
/// Setup interpreter and midi connection with given port
|
/// Setup interpreter and midi connection with given port
|
||||||
@ -110,6 +111,8 @@ struct Runner
|
|||||||
midi_input_event_loop.detach();
|
midi_input_event_loop.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduler_thread = std::jthread([this](std::stop_token tok) { interpreter.scheduler.activate(tok); });
|
||||||
|
|
||||||
Env::global->force_define("say", +[](Interpreter&, std::vector<Value> args) -> Result<Value> {
|
Env::global->force_define("say", +[](Interpreter&, std::vector<Value> args) -> Result<Value> {
|
||||||
for (auto it = args.begin(); it != args.end(); ++it) {
|
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||||
std::cout << *it;
|
std::cout << *it;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <midi.hh>
|
#include <midi.hh>
|
||||||
|
#include <scheduler.hh>
|
||||||
#include <tl/expected.hpp>
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
#if defined(__cpp_lib_source_location)
|
#if defined(__cpp_lib_source_location)
|
||||||
@ -890,6 +891,8 @@ private:
|
|||||||
/// Context holds default values for music related actions
|
/// Context holds default values for music related actions
|
||||||
struct Context
|
struct Context
|
||||||
{
|
{
|
||||||
|
using Duration = std::chrono::duration<float>;
|
||||||
|
|
||||||
/// Default note octave
|
/// Default note octave
|
||||||
i8 octave = 4;
|
i8 octave = 4;
|
||||||
|
|
||||||
@ -903,7 +906,7 @@ struct Context
|
|||||||
Note fill(Note) const;
|
Note fill(Note) const;
|
||||||
|
|
||||||
/// Converts length to seconds with current bpm
|
/// Converts length to seconds with current bpm
|
||||||
std::chrono::duration<float> length_to_duration(std::optional<Number> length) const;
|
Duration length_to_duration(std::optional<Number> length) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Given program tree evaluates it into Value
|
/// Given program tree evaluates it into Value
|
||||||
@ -912,6 +915,7 @@ struct Interpreter
|
|||||||
/// MIDI connection that is used to play music.
|
/// MIDI connection that is used to play music.
|
||||||
/// It's optional for simple interpreter testing.
|
/// It's optional for simple interpreter testing.
|
||||||
midi::Connection *midi_connection = nullptr;
|
midi::Connection *midi_connection = nullptr;
|
||||||
|
scheduler::Scheduler scheduler;
|
||||||
|
|
||||||
/// Operators defined for language
|
/// Operators defined for language
|
||||||
std::unordered_map<std::string, Intrinsic> operators;
|
std::unordered_map<std::string, Intrinsic> operators;
|
||||||
@ -930,7 +934,7 @@ struct Interpreter
|
|||||||
Interpreter();
|
Interpreter();
|
||||||
~Interpreter();
|
~Interpreter();
|
||||||
Interpreter(Interpreter const&) = delete;
|
Interpreter(Interpreter const&) = delete;
|
||||||
Interpreter(Interpreter &&) = default;
|
Interpreter(Interpreter &&) = delete;
|
||||||
|
|
||||||
/// Try to evaluate given program tree
|
/// Try to evaluate given program tree
|
||||||
Result<Value> eval(Ast &&ast);
|
Result<Value> eval(Ast &&ast);
|
||||||
@ -941,8 +945,9 @@ struct Interpreter
|
|||||||
// Leave scope by changing current environment
|
// Leave scope by changing current environment
|
||||||
void leave_scope();
|
void leave_scope();
|
||||||
|
|
||||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
/// Play note resolving any missing parameters with context via `midi_connection` member
|
||||||
void play(Chord);
|
/// @returns Total time of notes played
|
||||||
|
scheduler::Job_Id play(Chord);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
|
Loading…
Reference in New Issue
Block a user