Playing MIDI!
This commit is contained in:
parent
5560549c7c
commit
2c19db7354
@ -1,6 +1,8 @@
|
||||
#include <musique.hh>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
template<typename Binary_Operation>
|
||||
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::minus<>>();
|
||||
operators["*"] = binary_operator<std::multiplies<>>();
|
||||
@ -230,3 +240,17 @@ void Interpreter::leave_scope()
|
||||
assert(env != Env::global, "Cannot leave global scope");
|
||||
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);
|
||||
}
|
||||
|
61
src/main.cc
61
src/main.cc
@ -1,10 +1,11 @@
|
||||
#define MIDI_ENABLE_ALSA_SUPPORT
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
|
||||
#include <musique.hh>
|
||||
#define MIDI_ENABLE_ALSA_SUPPORT
|
||||
#include <midi.hh>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@ -34,36 +35,54 @@ void usage()
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
static Result<void> run(std::string_view source, std::string_view filename)
|
||||
struct Runner
|
||||
{
|
||||
auto ast = Try(Parser::parse(source, filename));
|
||||
midi::ALSA alsa;
|
||||
Interpreter interpreter;
|
||||
|
||||
if (ast_only_mode) {
|
||||
dump(ast);
|
||||
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));
|
||||
|
||||
if (ast_only_mode) {
|
||||
dump(ast);
|
||||
return {};
|
||||
}
|
||||
std::cout << Try(interpreter.eval(std::move(ast))) << std::endl;
|
||||
return {};
|
||||
}
|
||||
Interpreter interpreter;
|
||||
std::cout << Try(interpreter.eval(std::move(ast))) << std::endl;
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// We make sure that through life of interpreter source code is allways allocated
|
||||
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)
|
||||
{
|
||||
if (args.empty()) {
|
||||
usage();
|
||||
}
|
||||
|
||||
bool runned_something = false;
|
||||
std::vector<std::string_view> files;
|
||||
std::vector<Run> runnables;
|
||||
|
||||
while (not args.empty()) {
|
||||
std::string_view arg = pop(args);
|
||||
|
||||
if (arg == "-" || !arg.starts_with('-')) {
|
||||
files.push_back(std::move(arg));
|
||||
runnables.push_back({ .is_file = true, .argument = std::move(arg) });
|
||||
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::exit(1);
|
||||
}
|
||||
|
||||
auto const source = pop(args);
|
||||
Try(run(source, "arguments"));
|
||||
runned_something = true;
|
||||
runnables.push_back({ .is_file = false, .argument = pop(args) });
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -95,11 +111,18 @@ static Result<void> Main(std::span<char const*> args)
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
if (!runned_something && files.empty()) {
|
||||
if (runnables.empty()) {
|
||||
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 == "-") {
|
||||
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
|
||||
} 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>());
|
||||
}
|
||||
|
||||
Try(run(eternal_sources.back(), path));
|
||||
Try(runner.run(eternal_sources.back(), path));
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -9,9 +9,11 @@
|
||||
#include <ostream>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <tl/expected.hpp>
|
||||
#include <variant>
|
||||
|
||||
#include <midi.hh>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#if defined(__cpp_lib_source_location)
|
||||
#include <source_location>
|
||||
#endif
|
||||
@ -579,6 +581,10 @@ struct Context
|
||||
|
||||
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`
|
||||
std::ostream &out;
|
||||
|
||||
@ -603,6 +609,9 @@ struct Interpreter
|
||||
// Scope managment
|
||||
void enter_scope();
|
||||
void leave_scope();
|
||||
|
||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||
void play(Note n);
|
||||
};
|
||||
|
||||
namespace errors
|
||||
|
@ -8,7 +8,10 @@ concept Indexable = requires(T t, Index i) {
|
||||
/// Create hash out of note literal like `c` or `e#`
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user