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"]
|
||||
path = lib/midi
|
||||
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)"
|
||||
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
|
||||
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
|
||||
CXX=g++
|
||||
|
||||
LDFLAGS=-L./lib/midi/
|
||||
LDLIBS=-lmidi-alsa -lasound -lpthread
|
||||
LDFLAGS=-L./lib/midi/ -L./lib/scheduler/
|
||||
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= \
|
||||
context.o \
|
||||
@ -49,11 +60,14 @@ bin/%.o: src/%.cc src/*.hh
|
||||
@echo "CXX $@"
|
||||
@$(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 $@"
|
||||
@$(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 $@"
|
||||
@$(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 $@"
|
||||
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(DEBUG_FLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
clean: clean-dependencies
|
||||
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)
|
||||
|
@ -7,18 +7,17 @@ Interpreter języka Musique. Możliwy do wykorzystywania jako:
|
||||
|
||||
## 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
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
$ (cd lib/midi; make)
|
||||
```
|
||||
|
||||
A następnie zbuduj interpreter:
|
||||
|
||||
```console
|
||||
$ make bin/musique
|
||||
$ make
|
||||
```
|
||||
|
||||
## 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;
|
||||
}
|
||||
|
||||
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;
|
||||
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");
|
||||
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
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
||||
template<Iterable T>
|
||||
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::Block:
|
||||
Try(play_notes(interpreter, std::move(arg)));
|
||||
break;
|
||||
|
||||
case Value::Type::Music:
|
||||
interpreter.play(arg.chord);
|
||||
break;
|
||||
break; case Value::Type::Music:
|
||||
{
|
||||
await(interpreter, std::array{ interpreter.play(arg.chord) });
|
||||
}
|
||||
|
||||
default:
|
||||
break; default:
|
||||
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> {
|
||||
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
||||
if (args.size() == 1) {
|
||||
i.play(std::move(args.front()).chord);
|
||||
await(i, i.play(std::move(args.front()).chord));
|
||||
return Value{};
|
||||
}
|
||||
|
||||
@ -578,7 +593,7 @@ error:
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -886,7 +901,7 @@ void Interpreter::leave_scope()
|
||||
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");
|
||||
|
||||
@ -895,22 +910,26 @@ void Interpreter::play(Chord chord)
|
||||
// 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); });
|
||||
|
||||
// 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
|
||||
for (auto const& note : chord.notes) {
|
||||
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) {
|
||||
if (max_time != Number(0)) {
|
||||
max_time -= *note.length;
|
||||
std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
|
||||
auto duration = ctx.length_to_duration(*note.length);
|
||||
auto job_id = scheduler.schedule(
|
||||
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;
|
||||
Interpreter interpreter;
|
||||
std::thread midi_input_event_loop;
|
||||
std::optional<std::jthread> scheduler_thread;
|
||||
std::stop_source stop_source;
|
||||
|
||||
/// Setup interpreter and midi connection with given port
|
||||
@ -110,6 +111,8 @@ struct Runner
|
||||
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> {
|
||||
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||
std::cout << *it;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include <midi.hh>
|
||||
#include <scheduler.hh>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#if defined(__cpp_lib_source_location)
|
||||
@ -890,6 +891,8 @@ private:
|
||||
/// Context holds default values for music related actions
|
||||
struct Context
|
||||
{
|
||||
using Duration = std::chrono::duration<float>;
|
||||
|
||||
/// Default note octave
|
||||
i8 octave = 4;
|
||||
|
||||
@ -903,7 +906,7 @@ struct Context
|
||||
Note fill(Note) const;
|
||||
|
||||
/// 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
|
||||
@ -912,6 +915,7 @@ struct Interpreter
|
||||
/// MIDI connection that is used to play music.
|
||||
/// It's optional for simple interpreter testing.
|
||||
midi::Connection *midi_connection = nullptr;
|
||||
scheduler::Scheduler scheduler;
|
||||
|
||||
/// Operators defined for language
|
||||
std::unordered_map<std::string, Intrinsic> operators;
|
||||
@ -930,7 +934,7 @@ struct Interpreter
|
||||
Interpreter();
|
||||
~Interpreter();
|
||||
Interpreter(Interpreter const&) = delete;
|
||||
Interpreter(Interpreter &&) = default;
|
||||
Interpreter(Interpreter &&) = delete;
|
||||
|
||||
/// Try to evaluate given program tree
|
||||
Result<Value> eval(Ast &&ast);
|
||||
@ -941,8 +945,9 @@ struct Interpreter
|
||||
// Leave scope by changing current environment
|
||||
void leave_scope();
|
||||
|
||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||
void play(Chord);
|
||||
/// Play note resolving any missing parameters with context via `midi_connection` member
|
||||
/// @returns Total time of notes played
|
||||
scheduler::Job_Id play(Chord);
|
||||
};
|
||||
|
||||
namespace errors
|
||||
|
Loading…
Reference in New Issue
Block a user