introduced multiple port connections

This commit is contained in:
Robert Bendun 2023-01-09 21:29:33 +01:00
parent 0b8fd86680
commit 8fbeaea80d
6 changed files with 126 additions and 41 deletions

View File

@ -322,13 +322,13 @@ Forward_Implementation(builtin_down, range<Range_Direction::Down>)
static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> { static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> {
if (auto a = match<Number>(args)) { if (auto a = match<Number>(args)) {
auto [program] = *a; 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{}; return Value{};
} }
if (auto a = match<Number, Number>(args)) { if (auto a = match<Number, Number>(args)) {
auto [chan, program] = *a; 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{}; return Value{};
} }
@ -430,7 +430,7 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
for (auto const& note : chord->notes) { for (auto const& note : chord->notes) {
if (note.base) { 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<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
for (auto const& note : chord->notes) { for (auto const& note : chord->notes) {
if (note.base) { 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; return result;
@ -552,8 +552,8 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
start_time = dur; start_time = dur;
} }
switch (instruction.action) { switch (instruction.action) {
break; case Instruction::On: interpreter.midi_connection->send_note_on(0, instruction.note, 127); break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127);
break; case Instruction::Off: interpreter.midi_connection->send_note_off(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<Value> builtin_note_on(Interpreter &interpreter, std::vector<Value
{ {
if (auto a = match<Number, Number, Number>(args)) { if (auto a = match<Number, Number, Number>(args)) {
auto [chan, note, vel] = *a; 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 {}; return Value {};
} }
@ -1383,7 +1383,7 @@ static Result<Value> builtin_note_on(Interpreter &interpreter, std::vector<Value
auto [chan, chord, vel] = *a; auto [chan, chord, vel] = *a;
for (auto note : chord.notes) { for (auto note : chord.notes) {
note = interpreter.current_context->fill(note); note = interpreter.current_context->fill(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{}; return Value{};
} }
@ -1412,7 +1412,7 @@ static Result<Value> builtin_note_off(Interpreter &interpreter, std::vector<Valu
{ {
if (auto a = match<Number, Number>(args)) { if (auto a = match<Number, Number>(args)) {
auto [chan, note] = *a; 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 {}; return Value {};
} }
@ -1421,7 +1421,7 @@ static Result<Value> builtin_note_off(Interpreter &interpreter, std::vector<Valu
for (auto note : chord.notes) { for (auto note : chord.notes) {
note = interpreter.current_context->fill(note); note = interpreter.current_context->fill(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{}; return Value{};
} }
@ -1581,6 +1581,39 @@ static Result<Value> builtin_call(Interpreter &i, std::vector<Value> args)
return callable(i, std::move(args)); return callable(i, std::move(args));
} }
static Result<Value> builtin_port(Interpreter &interpreter, std::vector<Value> 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<Number>(args)) {
auto [port_number] = *a;
Try(interpreter.current_context->connect(port_number.floor().as_int()));
return {};
}
if (auto a = match<Symbol>(args)) {
auto [port_type] = *a;
if (port_type == "virtual") {
Try(interpreter.current_context->connect(std::nullopt));
return {};
}
unimplemented();
}
unimplemented();
}
void Interpreter::register_builtin_functions() void Interpreter::register_builtin_functions()
{ {
auto &global = *Env::global; auto &global = *Env::global;
@ -1614,6 +1647,7 @@ void Interpreter::register_builtin_functions()
global.force_define("pgmchange", builtin_program_change); global.force_define("pgmchange", builtin_program_change);
global.force_define("pick", builtin_pick); global.force_define("pick", builtin_pick);
global.force_define("play", builtin_play); global.force_define("play", builtin_play);
global.force_define("port", builtin_port);
global.force_define("program_change", builtin_program_change); global.force_define("program_change", builtin_program_change);
global.force_define("range", builtin_range); global.force_define("range", builtin_range);
global.force_define("reverse", builtin_reverse); global.force_define("reverse", builtin_reverse);

View File

@ -12,3 +12,49 @@ std::chrono::duration<float> Context::length_to_duration(std::optional<Number> l
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 std::chrono::duration<float>(float(len.num * (60.f / (float(bpm) / 4))) / len.den);
} }
template<>
struct std::hash<midi::connections::Key>
{
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<midi::connections::Key, std::shared_ptr<midi::Connection>> Context::established_connections;
/// Establish connection to given port
std::optional<Error> Context::connect(std::optional<Port_Number> 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<midi::Rt_Midi>();
connection->connect_output(*port_number);
established_connections[*port_number] = connection;
port = connection;
return std::nullopt;
}
auto connection = std::make_shared<midi::Rt_Midi>();
if (connection->connect_or_create_output()) {
established_connections[0u] = connection;
} else {
established_connections[Virtual_Port{}] = connection;
}
port = connection;
return std::nullopt;
}

View File

@ -1,11 +1,25 @@
#ifndef MUSIQUE_CONTEXT_HH #ifndef MUSIQUE_CONTEXT_HH
#define MUSIQUE_CONTEXT_HH #define MUSIQUE_CONTEXT_HH
#include <musique/common.hh>
#include <musique/value/note.hh>
#include <musique/value/number.hh>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <musique/common.hh>
#include <musique/errors.hh>
#include <musique/midi/midi.hh>
#include <musique/value/note.hh>
#include <musique/value/number.hh>
namespace midi::connections
{
using Established_Port = unsigned int;
struct Virtual_Port
{
bool operator==(Virtual_Port const&) const = default;
};
using Key = std::variant<Established_Port, Virtual_Port>;
}
/// Context holds default values for music related actions /// Context holds default values for music related actions
struct Context struct Context
@ -19,6 +33,19 @@ struct Context
/// Default BPM /// Default BPM
unsigned bpm = 120; unsigned bpm = 120;
/// Port that is currently used
std::shared_ptr<midi::Connection> port;
using Port_Number = unsigned int;
/// Connections that have been established so far
static std::unordered_map<midi::connections::Key, std::shared_ptr<midi::Connection>> established_connections;
/// Establish connection to given port
///
/// If port number wasn't provided connect to first existing one or create one
std::optional<Error> connect(std::optional<Port_Number>);
/// Fills empty places in Note like octave and length with default values from context /// Fills empty places in Note like octave and length with default values from context
Note fill(Note) const; Note fill(Note) const;

View File

@ -7,7 +7,6 @@
#include <random> #include <random>
#include <thread> #include <thread>
midi::Connection *Interpreter::midi_connection = nullptr;
std::unordered_map<std::string, Intrinsic> Interpreter::operators {}; std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
/// Registers constants like `fn = full note = 1/1` /// Registers constants like `fn = full note = 1/1`
@ -249,7 +248,7 @@ std::optional<Error> Interpreter::play(Chord chord)
// Turn all notes on // Turn all notes on
for (auto const& note : chord.notes) { for (auto const& note : chord.notes) {
if (note.base) { 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<Error> Interpreter::play(Chord chord)
std::this_thread::sleep_for(ctx.length_to_duration(*note.length)); std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
} }
if (note.base) { 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 {}; return {};
} }
std::optional<Error> ensure_midi_connection_available(Interpreter &i, std::string_view operation_name) std::optional<Error> 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 { return Error {
.details = errors::Operation_Requires_Midi_Connection { .details = errors::Operation_Requires_Midi_Connection {
.is_input = false, .is_input = false,

View File

@ -9,10 +9,6 @@
/// Given program tree evaluates it into Value /// Given program tree evaluates it into Value
struct Interpreter 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 /// Operators defined for language
static std::unordered_map<std::string, Intrinsic> operators; static std::unordered_map<std::string, Intrinsic> operators;

View File

@ -160,33 +160,16 @@ struct Runner
{ {
static inline Runner *the; static inline Runner *the;
midi::Rt_Midi midi;
Interpreter interpreter; Interpreter interpreter;
/// Setup interpreter and midi connection with given port /// Setup interpreter and midi connection with given port
explicit Runner(std::optional<unsigned> output_port) explicit Runner(std::optional<unsigned> output_port)
: midi() : interpreter{}
, interpreter{}
{ {
ensure(the == nullptr, "Only one instance of runner is supported"); ensure(the == nullptr, "Only one instance of runner is supported");
the = this; the = this;
interpreter.midi_connection = &midi; interpreter.current_context->connect(output_port);
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;
}
}
}
Env::global->force_define("say", +[](Interpreter &interpreter, std::vector<Value> args) -> Result<Value> { Env::global->force_define("say", +[](Interpreter &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) {