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
|
||||
* `: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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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/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 {};
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user