Added virtual port creation as default action. Restored port connection

Removed input ports support
This commit is contained in:
Robert Bendun 2022-10-14 16:24:42 +02:00
parent 0d7f4b1eb5
commit e9d67178da
8 changed files with 30 additions and 172 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Release package now with compiled Windows binary
* `:load` REPL command to load Musique files inside Musique session. Allows for delayed file execution after a connection
* `:quit` REPL command that mirrors `:exit` command
* Virtual MIDI output port creation as default action (--output connects to existing one)
### Changed

View File

@ -1,7 +1,6 @@
#include <musique/algo.hh>
#include <musique/interpreter/env.hh>
#include <musique/guard.hh>
#include <musique/interpreter/incoming_midi.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/try.hh>
@ -12,13 +11,6 @@
#include <chrono>
#include <thread>
void Interpreter::register_callbacks()
{
ensure(callbacks == nullptr, "This field should be uninitialized");
callbacks = std::make_unique<Interpreter::Incoming_Midi_Callbacks>();
callbacks->add_callbacks(*midi_connection, *this);
}
/// Check if type has index method
template<typename T>
concept With_Index_Method = requires (T &t, Interpreter interpreter, usize position) {
@ -276,7 +268,7 @@ static std::optional<Error> action_play(Interpreter &i, Value v)
template<With_Index_Operator Container = std::vector<Value>>
static inline Result<Value> builtin_play(Interpreter &i, Container args)
{
Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "play"));
Try(ensure_midi_connection_available(i, "play"));
auto previous_action = std::exchange(i.default_action, action_play);
i.context_stack.push_back(i.context_stack.back());
@ -298,7 +290,7 @@ static inline Result<Value> builtin_play(Interpreter &i, Container args)
/// Play first argument while playing all others
static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "par"));
Try(ensure_midi_connection_available(i, "par"));
ensure(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
if (args.size() == 1) {
@ -911,28 +903,6 @@ static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args)
};
}
/// Add handler for incoming midi messages
static Result<Value> builtin_incoming(Interpreter &i, std::vector<Value> args)
{
if (auto a = match<Symbol, Function>(args)) {
auto& [symbol, fun] = *a;
if (symbol == "note_on" || symbol == "noteon") {
i.callbacks->note_on = std::move(args[1]);
} else if (symbol == "note_off" || symbol == "noteoff") {
i.callbacks->note_off = std::move(args[1]);
} else {
}
return Value{};
}
return errors::Unsupported_Types_For {
.type = errors::Unsupported_Types_For::Function,
.name = "incoming",
.possibilities = { "(symbol, function) -> nil" }
};
}
/// Interleaves arguments
static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
{
@ -999,7 +969,6 @@ void Interpreter::register_builtin_functions()
global.force_define("for", builtin_for);
global.force_define("hash", builtin_hash);
global.force_define("if", builtin_if);
global.force_define("incoming", builtin_incoming);
global.force_define("instrument", builtin_program_change);
global.force_define("len", builtin_len);
global.force_define("max", builtin_max);

View File

@ -1,60 +0,0 @@
#ifndef MUSIQUE_INCOMING_MIDI
#define MUSIQUE_INCOMING_MIDI
#include <musique/interpreter/interpreter.hh>
struct Interpreter::Incoming_Midi_Callbacks
{
Value note_on{};
Value note_off{};
inline Incoming_Midi_Callbacks() = default;
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks &&) = delete;
Incoming_Midi_Callbacks(Incoming_Midi_Callbacks const&) = delete;
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks &&) = delete;
Incoming_Midi_Callbacks& operator=(Incoming_Midi_Callbacks const&) = delete;
inline void add_callbacks(midi::Connection &midi, Interpreter &interpreter)
{
register_callback(midi.note_on_callback, note_on, interpreter);
register_callback(midi.note_off_callback, note_off, interpreter);
}
template<typename ...T>
inline void register_callback(std::function<void(T...)> &target, Value &callback, Interpreter &i)
{
if (&callback == &note_on || &callback == &note_off) {
// This messages have MIDI note number as second value, so they should be represented
// in our own note abstraction, not as numbers.
target = [interpreter = &i, callback = &callback](T ...source_args)
{
if (!std::holds_alternative<Nil>(callback->data)) {
std::vector<Value> args { Number(source_args)... };
args[1] = Note {
.base = i32(std::get<Number>(args[1].data).num % 12),
.octave = std::get<Number>(args[1].data).num / 12
};
auto result = (*callback)(*interpreter, std::move(args));
// We discard this since callback is running in another thread.
(void) result;
}
};
} else {
// Generic case, preserve all passed parameters as numbers
target = [interpreter = &i, callback = &callback](T ...source_args)
{
if (!std::holds_alternative<Nil>(callback->data)) {
auto result = (*callback)(*interpreter, { Number(source_args)... });
// We discard this since callback is running in another thread.
(void) result;
}
};
}
}
};
#endif

View File

@ -1,5 +1,4 @@
#include <musique/interpreter/env.hh>
#include <musique/interpreter/incoming_midi.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/try.hh>
@ -223,7 +222,7 @@ void Interpreter::leave_scope()
std::optional<Error> Interpreter::play(Chord chord)
{
Try(ensure_midi_connection_available(*this, Midi_Connection_Type::Output, "play"));
Try(ensure_midi_connection_available(*this, "play"));
auto &ctx = context_stack.back();
if (chord.notes.size() == 0) {
@ -260,33 +259,16 @@ std::optional<Error> Interpreter::play(Chord chord)
return {};
}
std::optional<Error> ensure_midi_connection_available(Interpreter &i, Midi_Connection_Type m, std::string_view operation_name)
std::optional<Error> ensure_midi_connection_available(Interpreter &i, std::string_view operation_name)
{
switch (m) {
break; case Midi_Connection_Type::Output:
if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) {
return Error {
.details = errors::Operation_Requires_Midi_Connection {
.is_input = false,
.name = std::string(operation_name),
},
.location = {}
};
}
break; case Midi_Connection_Type::Input:
if (i.midi_connection == nullptr || !i.midi_connection->supports_input()) {
return Error {
.details = errors::Operation_Requires_Midi_Connection {
.is_input = false,
.name = std::string(operation_name),
},
.location = {}
};
}
break; default:
unreachable();
if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) {
return Error {
.details = errors::Operation_Requires_Midi_Connection {
.is_input = false,
.name = std::string(operation_name),
},
.location = {}
};
}
return {};
}

View File

@ -25,10 +25,6 @@ struct Interpreter
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
struct Incoming_Midi_Callbacks;
std::unique_ptr<Incoming_Midi_Callbacks> callbacks;
void register_callbacks();
Interpreter();
~Interpreter();
Interpreter(Interpreter const&) = delete;
@ -57,7 +53,6 @@ struct Interpreter
void register_builtin_operators();
};
enum class Midi_Connection_Type { Output, Input };
std::optional<Error> ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name);
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
#endif

View File

@ -66,8 +66,6 @@ static T pop(std::span<char const*> &span)
"usage: musique <options> [filename]\n"
" where filename is path to file with Musique code that will be executed\n"
" where options are:\n"
" -i,--input PORT\n"
" provides input port, a place where Musique receives MIDI messages\n"
" -o,--output PORT\n"
" provides output port, a place where Musique produces MIDI messages\n"
" -l,--list\n"
@ -130,24 +128,20 @@ struct Runner
Interpreter interpreter;
/// Setup interpreter and midi connection with given port
Runner(std::optional<unsigned> input_port, std::optional<unsigned> output_port)
explicit Runner(std::optional<unsigned> output_port)
: midi()
, interpreter{}
{
ensure(the == nullptr, "Only one instance of runner is supported");
the = this;
bool const midi_go = bool(input_port) || bool(output_port);
if (midi_go) {
interpreter.midi_connection = &midi;
}
interpreter.midi_connection = &midi;
if (output_port) {
std::cout << "Connected MIDI output to port " << *output_port << ". Ready to play!" << std::endl;
midi.connect_output(*output_port);
}
if (input_port) {
std::cout << "Connected MIDI input to port " << *input_port << ". Ready for incoming messages!" << std::endl;
midi.connect_input(*input_port);
std::cout << "Connected MIDI output to port " << *output_port << ". Ready to play!" << std::endl;
} else {
midi.connect_output();
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> {
@ -297,7 +291,6 @@ static std::optional<Error> Main(std::span<char const*> args)
};
// Arbitraly chosen for conviniance of the author
std::optional<unsigned> input_port{};
std::optional<unsigned> output_port{};
std::vector<Run> runnables;
@ -334,15 +327,6 @@ static std::optional<Error> Main(std::span<char const*> args)
continue;
}
if (arg == "-i" || arg == "--input") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
std::exit(1);
}
input_port = pop<unsigned>(args);
continue;
}
if (arg == "-o" || arg == "--output") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
@ -360,7 +344,7 @@ static std::optional<Error> Main(std::span<char const*> args)
std::exit(1);
}
Runner runner{input_port, output_port};
Runner runner{output_port};
for (auto const& [is_file, argument] : runnables) {
if (!is_file) {

View File

@ -13,7 +13,6 @@ namespace midi
virtual ~Connection() = default;
virtual bool supports_output() const = 0;
virtual bool supports_input () const = 0;
virtual void send_note_on (uint8_t channel, uint8_t note_number, uint8_t velocity) = 0;
virtual void send_note_off(uint8_t channel, uint8_t note_number, uint8_t velocity) = 0;
@ -21,33 +20,28 @@ namespace midi
virtual void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) = 0;
void send_all_sounds_off(uint8_t channel);
std::function<void(uint8_t, uint8_t, uint8_t)> note_on_callback = nullptr;
std::function<void(uint8_t, uint8_t)> note_off_callback = nullptr;
};
struct Rt_Midi : Connection
{
~Rt_Midi() override = default;
/// Connect with MIDI virtual port
void connect_output();
/// Connect with specific MIDI port for outputing MIDI messages
void connect_output(unsigned target);
/// Connect with specific MIDI port for reading MIDI messages
void connect_input(unsigned target);
/// List available ports
void list_ports(std::ostream &out) const;
bool supports_output() const override;
bool supports_input () const override;
void send_note_on (uint8_t channel, uint8_t note_number, uint8_t velocity) override;
void send_note_off(uint8_t channel, uint8_t note_number, uint8_t velocity) override;
void send_program_change(uint8_t channel, uint8_t program) override;
void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) override;
std::optional<RtMidiIn> input;
std::optional<RtMidiOut> output;
};

View File

@ -34,24 +34,22 @@ try {
std::exit(33);
}
void midi::Rt_Midi::connect_output(unsigned target)
void midi::Rt_Midi::connect_output()
try {
ensure(not output.has_value(), "Reconeccting is not supported yet");
output.emplace();
output->openVirtualPort("Musique output port");
// output->openPort(target, "Musique output port");
output->openVirtualPort("Musique");
} catch (RtMidiError &error) {
// TODO(error)
std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl;
std::exit(33);
}
void midi::Rt_Midi::connect_input(unsigned target)
void midi::Rt_Midi::connect_output(unsigned target)
try {
ensure(not input.has_value(), "Reconeccting is not supported yet");
input.emplace();
input->openPort(target, "Musique input port");
ensure(not output.has_value(), "Reconeccting is not supported yet");
output.emplace();
output->openPort(target);
} catch (RtMidiError &error) {
// TODO(error)
std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl;
@ -63,11 +61,6 @@ bool midi::Rt_Midi::supports_output() const
return bool(output);
}
bool midi::Rt_Midi::supports_input() const
{
return bool(input);
}
template<std::size_t N>
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
try {