Adding support for MacOS #36
@ -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
|
* 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
|
* `: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
|
* `:quit` REPL command that mirrors `:exit` command
|
||||||
|
* Virtual MIDI output port creation as default action (--output connects to existing one)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <musique/algo.hh>
|
#include <musique/algo.hh>
|
||||||
#include <musique/interpreter/env.hh>
|
#include <musique/interpreter/env.hh>
|
||||||
#include <musique/guard.hh>
|
#include <musique/guard.hh>
|
||||||
#include <musique/interpreter/incoming_midi.hh>
|
|
||||||
#include <musique/interpreter/interpreter.hh>
|
#include <musique/interpreter/interpreter.hh>
|
||||||
#include <musique/try.hh>
|
#include <musique/try.hh>
|
||||||
|
|
||||||
@ -12,13 +11,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#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
|
/// Check if type has index method
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept With_Index_Method = requires (T &t, Interpreter interpreter, usize position) {
|
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>>
|
template<With_Index_Operator Container = std::vector<Value>>
|
||||||
static inline Result<Value> builtin_play(Interpreter &i, Container args)
|
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);
|
auto previous_action = std::exchange(i.default_action, action_play);
|
||||||
i.context_stack.push_back(i.context_stack.back());
|
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
|
/// Play first argument while playing all others
|
||||||
static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
|
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)
|
ensure(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
||||||
if (args.size() == 1) {
|
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
|
/// Interleaves arguments
|
||||||
static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
|
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("for", builtin_for);
|
||||||
global.force_define("hash", builtin_hash);
|
global.force_define("hash", builtin_hash);
|
||||||
global.force_define("if", builtin_if);
|
global.force_define("if", builtin_if);
|
||||||
global.force_define("incoming", builtin_incoming);
|
|
||||||
global.force_define("instrument", builtin_program_change);
|
global.force_define("instrument", builtin_program_change);
|
||||||
global.force_define("len", builtin_len);
|
global.force_define("len", builtin_len);
|
||||||
global.force_define("max", builtin_max);
|
global.force_define("max", builtin_max);
|
||||||
|
@ -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 == ¬e_on || &callback == ¬e_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
|
|
@ -1,5 +1,4 @@
|
|||||||
#include <musique/interpreter/env.hh>
|
#include <musique/interpreter/env.hh>
|
||||||
#include <musique/interpreter/incoming_midi.hh>
|
|
||||||
#include <musique/interpreter/interpreter.hh>
|
#include <musique/interpreter/interpreter.hh>
|
||||||
#include <musique/try.hh>
|
#include <musique/try.hh>
|
||||||
|
|
||||||
@ -223,7 +222,7 @@ void Interpreter::leave_scope()
|
|||||||
|
|
||||||
std::optional<Error> Interpreter::play(Chord chord)
|
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();
|
auto &ctx = context_stack.back();
|
||||||
|
|
||||||
if (chord.notes.size() == 0) {
|
if (chord.notes.size() == 0) {
|
||||||
@ -260,33 +259,16 @@ std::optional<Error> Interpreter::play(Chord chord)
|
|||||||
return {};
|
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) {
|
if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) {
|
||||||
break; case Midi_Connection_Type::Output:
|
return Error {
|
||||||
if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) {
|
.details = errors::Operation_Requires_Midi_Connection {
|
||||||
return Error {
|
.is_input = false,
|
||||||
.details = errors::Operation_Requires_Midi_Connection {
|
.name = std::string(operation_name),
|
||||||
.is_input = false,
|
},
|
||||||
.name = std::string(operation_name),
|
.location = {}
|
||||||
},
|
};
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,6 @@ struct Interpreter
|
|||||||
|
|
||||||
std::function<std::optional<Error>(Interpreter&, Value)> default_action;
|
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();
|
||||||
Interpreter(Interpreter const&) = delete;
|
Interpreter(Interpreter const&) = delete;
|
||||||
@ -57,7 +53,6 @@ struct Interpreter
|
|||||||
void register_builtin_operators();
|
void register_builtin_operators();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Midi_Connection_Type { Output, Input };
|
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
|
||||||
std::optional<Error> ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -66,8 +66,6 @@ static T pop(std::span<char const*> &span)
|
|||||||
"usage: musique <options> [filename]\n"
|
"usage: musique <options> [filename]\n"
|
||||||
" where filename is path to file with Musique code that will be executed\n"
|
" where filename is path to file with Musique code that will be executed\n"
|
||||||
" where options are:\n"
|
" where options are:\n"
|
||||||
" -i,--input PORT\n"
|
|
||||||
" provides input port, a place where Musique receives MIDI messages\n"
|
|
||||||
" -o,--output PORT\n"
|
" -o,--output PORT\n"
|
||||||
" provides output port, a place where Musique produces MIDI messages\n"
|
" provides output port, a place where Musique produces MIDI messages\n"
|
||||||
" -l,--list\n"
|
" -l,--list\n"
|
||||||
@ -130,24 +128,20 @@ struct Runner
|
|||||||
Interpreter interpreter;
|
Interpreter interpreter;
|
||||||
|
|
||||||
/// Setup interpreter and midi connection with given port
|
/// 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()
|
: 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;
|
||||||
|
|
||||||
bool const midi_go = bool(input_port) || bool(output_port);
|
interpreter.midi_connection = &midi;
|
||||||
if (midi_go) {
|
|
||||||
interpreter.midi_connection = &midi;
|
|
||||||
}
|
|
||||||
if (output_port) {
|
if (output_port) {
|
||||||
std::cout << "Connected MIDI output to port " << *output_port << ". Ready to play!" << std::endl;
|
|
||||||
midi.connect_output(*output_port);
|
midi.connect_output(*output_port);
|
||||||
}
|
std::cout << "Connected MIDI output to port " << *output_port << ". Ready to play!" << std::endl;
|
||||||
if (input_port) {
|
} else {
|
||||||
std::cout << "Connected MIDI input to port " << *input_port << ". Ready for incoming messages!" << std::endl;
|
midi.connect_output();
|
||||||
midi.connect_input(*input_port);
|
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> {
|
||||||
@ -297,7 +291,6 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Arbitraly chosen for conviniance of the author
|
// Arbitraly chosen for conviniance of the author
|
||||||
std::optional<unsigned> input_port{};
|
|
||||||
std::optional<unsigned> output_port{};
|
std::optional<unsigned> output_port{};
|
||||||
|
|
||||||
std::vector<Run> runnables;
|
std::vector<Run> runnables;
|
||||||
@ -334,15 +327,6 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
continue;
|
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 (arg == "-o" || arg == "--output") {
|
||||||
if (args.empty()) {
|
if (args.empty()) {
|
||||||
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
|
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);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Runner runner{input_port, output_port};
|
Runner runner{output_port};
|
||||||
|
|
||||||
for (auto const& [is_file, argument] : runnables) {
|
for (auto const& [is_file, argument] : runnables) {
|
||||||
if (!is_file) {
|
if (!is_file) {
|
||||||
|
@ -13,7 +13,6 @@ namespace midi
|
|||||||
virtual ~Connection() = default;
|
virtual ~Connection() = default;
|
||||||
|
|
||||||
virtual bool supports_output() const = 0;
|
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_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;
|
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;
|
virtual void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) = 0;
|
||||||
|
|
||||||
void send_all_sounds_off(uint8_t channel);
|
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
|
struct Rt_Midi : Connection
|
||||||
{
|
{
|
||||||
~Rt_Midi() override = default;
|
~Rt_Midi() override = default;
|
||||||
|
|
||||||
|
/// Connect with MIDI virtual port
|
||||||
|
void connect_output();
|
||||||
|
|
||||||
/// Connect with specific MIDI port for outputing MIDI messages
|
/// Connect with specific MIDI port for outputing MIDI messages
|
||||||
void connect_output(unsigned target);
|
void connect_output(unsigned target);
|
||||||
|
|
||||||
/// Connect with specific MIDI port for reading MIDI messages
|
|
||||||
void connect_input(unsigned target);
|
|
||||||
|
|
||||||
/// List available ports
|
/// List available ports
|
||||||
void list_ports(std::ostream &out) const;
|
void list_ports(std::ostream &out) const;
|
||||||
|
|
||||||
bool supports_output() const override;
|
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_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_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_program_change(uint8_t channel, uint8_t program) override;
|
||||||
void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) override;
|
void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) override;
|
||||||
|
|
||||||
std::optional<RtMidiIn> input;
|
|
||||||
std::optional<RtMidiOut> output;
|
std::optional<RtMidiOut> output;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,24 +34,22 @@ try {
|
|||||||
std::exit(33);
|
std::exit(33);
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi::Rt_Midi::connect_output(unsigned target)
|
void midi::Rt_Midi::connect_output()
|
||||||
try {
|
try {
|
||||||
ensure(not output.has_value(), "Reconeccting is not supported yet");
|
ensure(not output.has_value(), "Reconeccting is not supported yet");
|
||||||
output.emplace();
|
output.emplace();
|
||||||
output->openVirtualPort("Musique output port");
|
output->openVirtualPort("Musique");
|
||||||
|
|
||||||
// output->openPort(target, "Musique output port");
|
|
||||||
} catch (RtMidiError &error) {
|
} catch (RtMidiError &error) {
|
||||||
// TODO(error)
|
// TODO(error)
|
||||||
std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl;
|
std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl;
|
||||||
std::exit(33);
|
std::exit(33);
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi::Rt_Midi::connect_input(unsigned target)
|
void midi::Rt_Midi::connect_output(unsigned target)
|
||||||
try {
|
try {
|
||||||
ensure(not input.has_value(), "Reconeccting is not supported yet");
|
ensure(not output.has_value(), "Reconeccting is not supported yet");
|
||||||
input.emplace();
|
output.emplace();
|
||||||
input->openPort(target, "Musique input port");
|
output->openPort(target);
|
||||||
} catch (RtMidiError &error) {
|
} catch (RtMidiError &error) {
|
||||||
// TODO(error)
|
// TODO(error)
|
||||||
std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl;
|
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);
|
return bool(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool midi::Rt_Midi::supports_input() const
|
|
||||||
{
|
|
||||||
return bool(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::size_t N>
|
template<std::size_t N>
|
||||||
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
|
inline void send_message(RtMidiOut &out, std::array<std::uint8_t, N> message)
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user