Compare commits

...

5 Commits

Author SHA1 Message Date
Robert Bendun
e7f3cf2cfb Added asynchronous note playing 2022-08-18 22:05:42 +02:00
Robert Bendun
da8a6b5d6c Update scheduler dependency 2022-08-18 22:04:05 +02:00
Robert Bendun
a08024d6c9 Makefile builds dependencies automatically 2022-08-18 22:03:47 +02:00
Robert Bendun
afcd9f27b1 Updated scheduler dependency 2022-08-18 22:03:29 +02:00
Robert Bendun
2f3cfab999 Adding scheduler dependency 2022-08-18 22:03:20 +02:00
8 changed files with 86 additions and 39 deletions

3
.gitmodules vendored
View File

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

View File

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

View File

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

@ -0,0 +1 @@
Subproject commit 011490455439041db6d5ddbee9d64664ab608025

View File

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

View File

@ -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); }
midi_connection->send_note_off(0, *note.into_midi_note(), 127); );
if (duration > max_duration) {
max_duration = duration;
last_job_id = job_id;
} }
} }
return last_job_id;
}

View File

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

View File

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