Playing MIDI!

This commit is contained in:
Robert Bendun 2022-05-24 03:35:15 +02:00
parent 5560549c7c
commit 2c19db7354
4 changed files with 80 additions and 21 deletions

View File

@ -1,6 +1,8 @@
#include <musique.hh> #include <musique.hh>
#include <chrono>
#include <iostream> #include <iostream>
#include <thread>
template<typename Binary_Operation> template<typename Binary_Operation>
constexpr auto binary_operator() constexpr auto binary_operator()
@ -110,6 +112,14 @@ Interpreter::Interpreter(std::ostream& out)
} }
}); });
global.force_define("play", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
for (auto &arg : args) {
assert(arg.type == Value::Type::Music, "Only music values can be played"); // TODO(assert)
i.play(arg.note);
}
return Value{};
});
operators["+"] = binary_operator<std::plus<>>(); operators["+"] = binary_operator<std::plus<>>();
operators["-"] = binary_operator<std::minus<>>(); operators["-"] = binary_operator<std::minus<>>();
operators["*"] = binary_operator<std::multiplies<>>(); operators["*"] = binary_operator<std::multiplies<>>();
@ -230,3 +240,17 @@ void Interpreter::leave_scope()
assert(env != Env::global, "Cannot leave global scope"); assert(env != Env::global, "Cannot leave global scope");
env = env->leave(); env = env->leave();
} }
void Interpreter::play(Note note)
{
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
auto &ctx = context_stack.back();
note = ctx.fill(std::move(note));
auto dur = ctx.length_to_duration(note.length);
midi_connection->send_note_on(0, *note.into_midi_note(), 127);
std::this_thread::sleep_for(dur);
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
}

View File

@ -1,10 +1,11 @@
#define MIDI_ENABLE_ALSA_SUPPORT
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <span> #include <span>
#include <musique.hh> #include <musique.hh>
#define MIDI_ENABLE_ALSA_SUPPORT
#include <midi.hh> #include <midi.hh>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -34,36 +35,54 @@ void usage()
std::exit(1); std::exit(1);
} }
static Result<void> run(std::string_view source, std::string_view filename) struct Runner
{ {
midi::ALSA alsa;
Interpreter interpreter;
Runner(std::string port)
: alsa("musique"), interpreter(std::cout)
{
alsa.init_sequencer();
alsa.connect(port);
interpreter.midi_connection = &alsa;
}
Result<void> run(std::string_view source, std::string_view filename)
{
auto ast = Try(Parser::parse(source, filename)); auto ast = Try(Parser::parse(source, filename));
if (ast_only_mode) { if (ast_only_mode) {
dump(ast); dump(ast);
return {}; return {};
} }
Interpreter interpreter;
std::cout << Try(interpreter.eval(std::move(ast))) << std::endl; std::cout << Try(interpreter.eval(std::move(ast))) << std::endl;
return {}; return {};
} }
};
// We make sure that through life of interpreter source code is allways allocated // We make sure that through life of interpreter source code is allways allocated
std::vector<std::string> eternal_sources; std::vector<std::string> eternal_sources;
struct Run
{
bool is_file = true;
std::string_view argument;
};
static Result<void> Main(std::span<char const*> args) static Result<void> Main(std::span<char const*> args)
{ {
if (args.empty()) { if (args.empty()) {
usage(); usage();
} }
bool runned_something = false; std::vector<Run> runnables;
std::vector<std::string_view> files;
while (not args.empty()) { while (not args.empty()) {
std::string_view arg = pop(args); std::string_view arg = pop(args);
if (arg == "-" || !arg.starts_with('-')) { if (arg == "-" || !arg.starts_with('-')) {
files.push_back(std::move(arg)); runnables.push_back({ .is_file = true, .argument = std::move(arg) });
continue; continue;
} }
@ -79,10 +98,7 @@ static Result<void> Main(std::span<char const*> args)
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl; std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
std::exit(1); std::exit(1);
} }
runnables.push_back({ .is_file = false, .argument = pop(args) });
auto const source = pop(args);
Try(run(source, "arguments"));
runned_something = true;
continue; continue;
} }
@ -95,11 +111,18 @@ static Result<void> Main(std::span<char const*> args)
std::exit(1); std::exit(1);
} }
if (!runned_something && files.empty()) { if (runnables.empty()) {
usage(); usage();
} }
for (auto const& path : files) { Runner runner("14");
for (auto const& [is_file, argument] : runnables) {
if (!is_file) {
Try(runner.run(argument, "<arguments>"));
continue;
}
auto const path = argument;
if (path == "-") { if (path == "-") {
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>()); eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
} else { } else {
@ -111,7 +134,7 @@ static Result<void> Main(std::span<char const*> args)
eternal_sources.emplace_back(std::istreambuf_iterator<char>(source_file), std::istreambuf_iterator<char>()); eternal_sources.emplace_back(std::istreambuf_iterator<char>(source_file), std::istreambuf_iterator<char>());
} }
Try(run(eternal_sources.back(), path)); Try(runner.run(eternal_sources.back(), path));
} }
return {}; return {};

View File

@ -9,9 +9,11 @@
#include <ostream> #include <ostream>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <tl/expected.hpp>
#include <variant> #include <variant>
#include <midi.hh>
#include <tl/expected.hpp>
#if defined(__cpp_lib_source_location) #if defined(__cpp_lib_source_location)
#include <source_location> #include <source_location>
#endif #endif
@ -579,6 +581,10 @@ struct Context
struct Interpreter struct Interpreter
{ {
/// MIDI connection that is used to play music.
/// It's optional for simple interpreter testing.
midi::Connection *midi_connection = nullptr;
/// Output of IO builtins like `say` /// Output of IO builtins like `say`
std::ostream &out; std::ostream &out;
@ -603,6 +609,9 @@ struct Interpreter
// Scope managment // Scope managment
void enter_scope(); void enter_scope();
void leave_scope(); void leave_scope();
/// Play note resolving any missing parameters with context via `midi_connection` member.
void play(Note n);
}; };
namespace errors namespace errors

View File

@ -8,7 +8,10 @@ concept Indexable = requires(T t, Index i) {
/// Create hash out of note literal like `c` or `e#` /// Create hash out of note literal like `c` or `e#`
constexpr u16 hash_note(Indexable<usize, char> auto const& note) constexpr u16 hash_note(Indexable<usize, char> auto const& note)
{ {
return u8(note[0]) | (note[1] << 8); /// TODO Some assertion that we have snd character
u8 snd = note[1];
if (snd != '#') snd = 0;
return u8(note[0]) | (snd << 8);
} }
/// Finds numeric value of note. This form is later used as in /// Finds numeric value of note. This form is later used as in