Merge branch 'port-in-context'
This commit is contained in:
commit
588e15a8b6
@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Negative numbers!
|
- Negative numbers!
|
||||||
- Version command via `:version` in REPL or `--version`, `-v` in command line.
|
- Version command via `:version` in REPL or `--version`, `-v` in command line.
|
||||||
- Introduced start synchronization with builtins: `peers` and `start`
|
- Introduced start synchronization with builtins: `peers` and `start`
|
||||||
|
- Connection with MIDI ports via parameters dropped in favour of function using context system: `port`
|
||||||
|
- Listing ports via REPL command: `:ports` instead of commandline parameter
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1377,7 +1377,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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1385,7 +1385,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{};
|
||||||
}
|
}
|
||||||
@ -1414,7 +1414,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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1423,7 +1423,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{};
|
||||||
}
|
}
|
||||||
@ -1601,6 +1601,39 @@ static Result<Value> builtin_peers(Interpreter &interpreter, std::vector<Value>)
|
|||||||
return Number(interpreter.starter.peers());
|
return Number(interpreter.starter.peers());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@ -1635,6 +1668,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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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`
|
||||||
@ -265,7 +264,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,16 +275,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,
|
||||||
|
@ -10,10 +10,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;
|
||||||
|
|
||||||
|
@ -68,11 +68,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"
|
||||||
" -o,--output PORT\n"
|
|
||||||
" provides output port, a place where Musique produces MIDI messages\n"
|
|
||||||
" -l,--list\n"
|
|
||||||
" lists all available MIDI ports and quit\n"
|
|
||||||
"\n"
|
|
||||||
" -c,--run CODE\n"
|
" -c,--run CODE\n"
|
||||||
" executes given code\n"
|
" executes given code\n"
|
||||||
" -I,--interactive,--repl\n"
|
" -I,--interactive,--repl\n"
|
||||||
@ -105,6 +100,7 @@ void print_repl_help()
|
|||||||
":!<command> - allows for execution of any shell command\n"
|
":!<command> - allows for execution of any shell command\n"
|
||||||
":clear - clears screen\n"
|
":clear - clears screen\n"
|
||||||
":load <file> - loads file into Musique session\n"
|
":load <file> - loads file into Musique session\n"
|
||||||
|
":ports - print list available ports"
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,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()
|
||||||
: 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(std::nullopt);
|
||||||
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) {
|
||||||
@ -340,6 +319,14 @@ static Result<bool> handle_repl_session_commands(std::string_view input, Runner
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Command {
|
||||||
|
"ports",
|
||||||
|
+[](Runner&, std::optional<std::string_view>) -> std::optional<Error> {
|
||||||
|
midi::Rt_Midi{}.list_ports(std::cout);
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (input.starts_with('!')) {
|
if (input.starts_with('!')) {
|
||||||
@ -384,9 +371,6 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
std::string_view argument;
|
std::string_view argument;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Arbitraly chosen for conviniance of the author
|
|
||||||
std::optional<unsigned> output_port{};
|
|
||||||
|
|
||||||
std::vector<Run> runnables;
|
std::vector<Run> runnables;
|
||||||
|
|
||||||
while (not args.empty()) {
|
while (not args.empty()) {
|
||||||
@ -397,11 +381,6 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "-l" || arg == "--list") {
|
|
||||||
midi::Rt_Midi{}.list_ports(std::cout);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "-c" || arg == "--run") {
|
if (arg == "-c" || arg == "--run") {
|
||||||
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;
|
||||||
@ -439,15 +418,6 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "-o" || arg == "--output") {
|
|
||||||
if (args.empty()) {
|
|
||||||
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
output_port = pop<unsigned>(args);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "-h" || arg == "--help") {
|
if (arg == "-h" || arg == "--help") {
|
||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
@ -456,7 +426,7 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Runner runner{output_port};
|
Runner runner;
|
||||||
|
|
||||||
for (auto const& [type, argument] : runnables) {
|
for (auto const& [type, argument] : runnables) {
|
||||||
if (type == Run::Argument) {
|
if (type == Run::Argument) {
|
||||||
|
Loading…
Reference in New Issue
Block a user