diff --git a/musique/interpreter/builtin_functions.cc b/musique/interpreter/builtin_functions.cc index 56c6225..71b3838 100644 --- a/musique/interpreter/builtin_functions.cc +++ b/musique/interpreter/builtin_functions.cc @@ -322,13 +322,13 @@ Forward_Implementation(builtin_down, range) static auto builtin_program_change(Interpreter &i, std::vector args) -> Result { if (auto a = match(args)) { auto [program] = *a; - i.midi_connection->send_program_change(0, program.as_int()); + i.current_context->port->send_program_change(0, program.as_int()); return Value{}; } if (auto a = match(args)) { auto [chan, program] = *a; - i.midi_connection->send_program_change(chan.as_int(), program.as_int()); + i.current_context->port->send_program_change(chan.as_int(), program.as_int()); return Value{}; } @@ -430,7 +430,7 @@ static Result builtin_par(Interpreter &interpreter, std::vector ar for (auto const& note : chord->notes) { if (note.base) { - interpreter.midi_connection->send_note_on(0, *note.into_midi_note(), 127); + interpreter.current_context->port->send_note_on(0, *note.into_midi_note(), 127); } } @@ -438,7 +438,7 @@ static Result builtin_par(Interpreter &interpreter, std::vector ar for (auto const& note : chord->notes) { if (note.base) { - interpreter.midi_connection->send_note_off(0, *note.into_midi_note(), 127); + interpreter.current_context->port->send_note_off(0, *note.into_midi_note(), 127); } } return result; @@ -552,8 +552,8 @@ static Result builtin_sim(Interpreter &interpreter, std::vector ar start_time = dur; } switch (instruction.action) { - break; case Instruction::On: interpreter.midi_connection->send_note_on(0, instruction.note, 127); - break; case Instruction::Off: interpreter.midi_connection->send_note_off(0, instruction.note, 127); + break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127); + break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127); } } @@ -1375,7 +1375,7 @@ static Result builtin_note_on(Interpreter &interpreter, std::vector(args)) { auto [chan, note, vel] = *a; - interpreter.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int()); + interpreter.current_context->port->send_note_on(chan.as_int(), note.as_int(), vel.as_int()); return Value {}; } @@ -1383,7 +1383,7 @@ static Result builtin_note_on(Interpreter &interpreter, std::vectorfill(note); - interpreter.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int()); + interpreter.current_context->port->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int()); } return Value{}; } @@ -1412,7 +1412,7 @@ static Result builtin_note_off(Interpreter &interpreter, std::vector(args)) { auto [chan, note] = *a; - interpreter.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127); + interpreter.current_context->port->send_note_off(chan.as_int(), note.as_int(), 127); return Value {}; } @@ -1421,7 +1421,7 @@ static Result builtin_note_off(Interpreter &interpreter, std::vectorfill(note); - interpreter.midi_connection->send_note_off(chan.as_int(), *note.into_midi_note(), 127); + interpreter.current_context->port->send_note_off(chan.as_int(), *note.into_midi_note(), 127); } return Value{}; } @@ -1581,6 +1581,39 @@ static Result builtin_call(Interpreter &i, std::vector args) return callable(i, std::move(args)); } +static Result builtin_port(Interpreter &interpreter, std::vector args) +{ + if (args.empty()) { + for (auto const& [key, port] : Context::established_connections) { + if (port == interpreter.current_context->port) { + return std::visit(Overloaded { + [](midi::connections::Virtual_Port) { return Value(Symbol("virtual")); }, + [](midi::connections::Established_Port port) { return Value(Number(port)); }, + }, key); + } + } + unreachable(); + } + + if (auto a = match(args)) { + auto [port_number] = *a; + Try(interpreter.current_context->connect(port_number.floor().as_int())); + return {}; + } + + if (auto a = match(args)) { + auto [port_type] = *a; + if (port_type == "virtual") { + Try(interpreter.current_context->connect(std::nullopt)); + return {}; + } + + unimplemented(); + } + + unimplemented(); +} + void Interpreter::register_builtin_functions() { auto &global = *Env::global; @@ -1614,6 +1647,7 @@ void Interpreter::register_builtin_functions() global.force_define("pgmchange", builtin_program_change); global.force_define("pick", builtin_pick); global.force_define("play", builtin_play); + global.force_define("port", builtin_port); global.force_define("program_change", builtin_program_change); global.force_define("range", builtin_range); global.force_define("reverse", builtin_reverse); diff --git a/musique/interpreter/context.cc b/musique/interpreter/context.cc index 281860f..c2cc51f 100644 --- a/musique/interpreter/context.cc +++ b/musique/interpreter/context.cc @@ -12,3 +12,49 @@ std::chrono::duration Context::length_to_duration(std::optional l auto const len = length ? *length : this->length; return std::chrono::duration(float(len.num * (60.f / (float(bpm) / 4))) / len.den); } + +template<> +struct std::hash +{ + std::size_t operator()(midi::connections::Key const& value) const + { + using namespace midi::connections; + return hash_combine(value.index(), std::visit(Overloaded { + [](Virtual_Port) { return 0u; }, + [](Established_Port port) { return port; }, + }, value)); + } +}; + +std::unordered_map> Context::established_connections; + +/// Establish connection to given port +std::optional Context::connect(std::optional port_number) +{ + // FIXME This function doesn't support creating virtual ports when established ports are available + using namespace midi::connections; + auto const key = port_number ? Key(*port_number) : Key(Virtual_Port{}); + + if (auto it = established_connections.find(key); it != established_connections.end()) { + port = it->second; + return std::nullopt; + } + + if (port_number) { + auto connection = std::make_shared(); + connection->connect_output(*port_number); + established_connections[*port_number] = connection; + port = connection; + return std::nullopt; + } + + auto connection = std::make_shared(); + if (connection->connect_or_create_output()) { + established_connections[0u] = connection; + } else { + established_connections[Virtual_Port{}] = connection; + } + port = connection; + return std::nullopt; +} + diff --git a/musique/interpreter/context.hh b/musique/interpreter/context.hh index c85c0e2..5ab8412 100644 --- a/musique/interpreter/context.hh +++ b/musique/interpreter/context.hh @@ -1,11 +1,25 @@ #ifndef MUSIQUE_CONTEXT_HH #define MUSIQUE_CONTEXT_HH -#include -#include -#include #include #include +#include +#include +#include +#include +#include + +namespace midi::connections +{ + using Established_Port = unsigned int; + + struct Virtual_Port + { + bool operator==(Virtual_Port const&) const = default; + }; + + using Key = std::variant; +} /// Context holds default values for music related actions struct Context @@ -19,6 +33,19 @@ struct Context /// Default BPM unsigned bpm = 120; + /// Port that is currently used + std::shared_ptr port; + + using Port_Number = unsigned int; + + /// Connections that have been established so far + static std::unordered_map> established_connections; + + /// Establish connection to given port + /// + /// If port number wasn't provided connect to first existing one or create one + std::optional connect(std::optional); + /// Fills empty places in Note like octave and length with default values from context Note fill(Note) const; diff --git a/musique/interpreter/interpreter.cc b/musique/interpreter/interpreter.cc index ed4aa73..8a94169 100644 --- a/musique/interpreter/interpreter.cc +++ b/musique/interpreter/interpreter.cc @@ -7,7 +7,6 @@ #include #include -midi::Connection *Interpreter::midi_connection = nullptr; std::unordered_map Interpreter::operators {}; /// Registers constants like `fn = full note = 1/1` @@ -249,7 +248,7 @@ std::optional Interpreter::play(Chord chord) // Turn all notes on for (auto const& note : chord.notes) { if (note.base) { - midi_connection->send_note_on(0, *note.into_midi_note(), 127); + current_context->port->send_note_on(0, *note.into_midi_note(), 127); } } @@ -260,16 +259,16 @@ std::optional Interpreter::play(Chord chord) std::this_thread::sleep_for(ctx.length_to_duration(*note.length)); } if (note.base) { - midi_connection->send_note_off(0, *note.into_midi_note(), 127); + current_context->port->send_note_off(0, *note.into_midi_note(), 127); } } return {}; } -std::optional ensure_midi_connection_available(Interpreter &i, std::string_view operation_name) +std::optional ensure_midi_connection_available(Interpreter &interpreter, std::string_view operation_name) { - if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) { + if (interpreter.current_context->port == nullptr || !interpreter.current_context->port->supports_output()) { return Error { .details = errors::Operation_Requires_Midi_Connection { .is_input = false, diff --git a/musique/interpreter/interpreter.hh b/musique/interpreter/interpreter.hh index e00edf1..ae6fefd 100644 --- a/musique/interpreter/interpreter.hh +++ b/musique/interpreter/interpreter.hh @@ -9,10 +9,6 @@ /// Given program tree evaluates it into Value struct Interpreter { - /// MIDI connection that is used to play music. - /// It's optional for simple interpreter testing. - static midi::Connection *midi_connection; - /// Operators defined for language static std::unordered_map operators; diff --git a/musique/main.cc b/musique/main.cc index 3ae5fc7..d6dbe2d 100644 --- a/musique/main.cc +++ b/musique/main.cc @@ -160,33 +160,16 @@ struct Runner { static inline Runner *the; - midi::Rt_Midi midi; Interpreter interpreter; /// Setup interpreter and midi connection with given port explicit Runner(std::optional output_port) - : midi() - , interpreter{} + : interpreter{} { ensure(the == nullptr, "Only one instance of runner is supported"); the = this; - interpreter.midi_connection = &midi; - if (output_port) { - midi.connect_output(*output_port); - if (!quiet_mode) { - std::cout << "Connected MIDI output to port " << *output_port << ". Ready to play!" << std::endl; - } - } else { - bool connected_to_existing_port = midi.connect_or_create_output(); - if (!quiet_mode) { - if (connected_to_existing_port) { - std::cout << "Connected MIDI output to port " << midi.output->getPortName(0) << std::endl; - } else { - std::cout << "Created new MIDI output port 'Musique'. Ready to play!" << std::endl; - } - } - } + interpreter.current_context->connect(output_port); Env::global->force_define("say", +[](Interpreter &interpreter, std::vector args) -> Result { for (auto it = args.begin(); it != args.end(); ++it) {