Chords!
This commit is contained in:
parent
6611512b1f
commit
80bc4c039c
@ -113,7 +113,7 @@ Interpreter::Interpreter()
|
|||||||
global.force_define("play", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
global.force_define("play", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||||
for (auto &arg : args) {
|
for (auto &arg : args) {
|
||||||
assert(arg.type == Value::Type::Music, "Only music values can be played"); // TODO(assert)
|
assert(arg.type == Value::Type::Music, "Only music values can be played"); // TODO(assert)
|
||||||
i.play(arg.note);
|
i.play(arg.chord);
|
||||||
}
|
}
|
||||||
return Value{};
|
return Value{};
|
||||||
});
|
});
|
||||||
@ -264,16 +264,31 @@ void Interpreter::leave_scope()
|
|||||||
env = env->leave();
|
env = env->leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::play(Note note)
|
void Interpreter::play(Chord chord)
|
||||||
{
|
{
|
||||||
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
|
assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection");
|
||||||
|
|
||||||
auto &ctx = context_stack.back();
|
auto &ctx = context_stack.back();
|
||||||
note = ctx.fill(std::move(note));
|
|
||||||
|
|
||||||
auto dur = ctx.length_to_duration(note.length);
|
// Fill all notes that don't have octave or length with defaults
|
||||||
|
std::transform(chord.notes.begin(), chord.notes.end(), chord.notes.begin(), [&](Note note) { return ctx.fill(note); });
|
||||||
|
|
||||||
midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
// Sort that we have smaller times first
|
||||||
std::this_thread::sleep_for(dur);
|
std::sort(chord.notes.begin(), chord.notes.end(), [](Note const& lhs, Note const& rhs) { return lhs.length < rhs.length; });
|
||||||
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
|
||||||
|
Number max_time = *chord.notes.back().length;
|
||||||
|
|
||||||
|
// Turn all notes on
|
||||||
|
for (auto const& note : chord.notes) {
|
||||||
|
midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn off each note at right time
|
||||||
|
for (auto const& note : chord.notes) {
|
||||||
|
if (max_time != Number(0)) {
|
||||||
|
max_time -= *note.length;
|
||||||
|
std::this_thread::sleep_for(ctx.length_to_duration(*note.length));
|
||||||
|
}
|
||||||
|
midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
11
src/lexer.cc
11
src/lexer.cc
@ -105,12 +105,7 @@ auto Lexer::next_token() -> Result<Token>
|
|||||||
// Allow `c#`
|
// Allow `c#`
|
||||||
consume_if('#');
|
consume_if('#');
|
||||||
|
|
||||||
// Any of the following sequences are allowed
|
while (consume_if('_') || consume_if(unicode::is_digit)) {}
|
||||||
// c,,,,,,,,,,,,,,,,
|
|
||||||
// c1,,,,2,3212
|
|
||||||
// c1234'''''
|
|
||||||
// during lexing
|
|
||||||
while (consume_if(",'") || consume_if(unicode::is_digit)) {}
|
|
||||||
|
|
||||||
// If we encounter any letter that is not part of chord declaration,
|
// If we encounter any letter that is not part of chord declaration,
|
||||||
// then we have symbol, not chord declaration
|
// then we have symbol, not chord declaration
|
||||||
@ -125,9 +120,7 @@ auto Lexer::next_token() -> Result<Token>
|
|||||||
if (consume_if(std::bind(unicode::is_identifier, _1, unicode::First_Character::Yes))) {
|
if (consume_if(std::bind(unicode::is_identifier, _1, unicode::First_Character::Yes))) {
|
||||||
symbol_lexing:
|
symbol_lexing:
|
||||||
for (auto predicate = std::bind(unicode::is_identifier, _1, unicode::First_Character::No);
|
for (auto predicate = std::bind(unicode::is_identifier, _1, unicode::First_Character::No);
|
||||||
consume_if(predicate);
|
consume_if(predicate);) {}
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Token t = { Token::Type::Symbol, finish(), token_location };
|
Token t = { Token::Type::Symbol, finish(), token_location };
|
||||||
if (std::find(Keywords.begin(), Keywords.end(), t.source) != Keywords.end()) {
|
if (std::find(Keywords.begin(), Keywords.end(), t.source) != Keywords.end()) {
|
||||||
|
@ -494,6 +494,17 @@ struct Note
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Note const& note);
|
std::ostream& operator<<(std::ostream& os, Note const& note);
|
||||||
|
|
||||||
|
struct Chord
|
||||||
|
{
|
||||||
|
std::vector<Note> notes;
|
||||||
|
|
||||||
|
static Chord from(std::string_view source);
|
||||||
|
|
||||||
|
bool operator==(Chord const&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Chord const& chord);
|
||||||
|
|
||||||
/// Eager Array
|
/// Eager Array
|
||||||
struct Array
|
struct Array
|
||||||
{
|
{
|
||||||
@ -523,6 +534,7 @@ struct Value
|
|||||||
static Value from(Block &&l);
|
static Value from(Block &&l);
|
||||||
static Value from(Array &&array);
|
static Value from(Array &&array);
|
||||||
static Value from(Note n);
|
static Value from(Note n);
|
||||||
|
static Value from(Chord chord);
|
||||||
|
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
@ -533,7 +545,7 @@ struct Value
|
|||||||
Intrinsic,
|
Intrinsic,
|
||||||
Block,
|
Block,
|
||||||
Array,
|
Array,
|
||||||
Music
|
Music,
|
||||||
};
|
};
|
||||||
|
|
||||||
Value() = default;
|
Value() = default;
|
||||||
@ -551,7 +563,7 @@ struct Value
|
|||||||
Number n;
|
Number n;
|
||||||
Intrinsic intr;
|
Intrinsic intr;
|
||||||
Block blk;
|
Block blk;
|
||||||
Note note;
|
Chord chord;
|
||||||
Array array;
|
Array array;
|
||||||
|
|
||||||
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||||
@ -647,7 +659,7 @@ struct Interpreter
|
|||||||
void leave_scope();
|
void leave_scope();
|
||||||
|
|
||||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||||
void play(Note n);
|
void play(Chord);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
|
67
src/value.cc
67
src/value.cc
@ -83,7 +83,7 @@ Result<Value> Value::from(Token t)
|
|||||||
return Value::from(*maybe_note);
|
return Value::from(*maybe_note);
|
||||||
}
|
}
|
||||||
|
|
||||||
unimplemented("only simple note values (like c or e#) are supported now");
|
return Value::from(Chord::from(t.source));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
unimplemented();
|
unimplemented();
|
||||||
@ -150,7 +150,15 @@ Value Value::from(Note n)
|
|||||||
{
|
{
|
||||||
Value v;
|
Value v;
|
||||||
v.type = Type::Music;
|
v.type = Type::Music;
|
||||||
v.note = n;
|
v.chord = { .notes = { n } };
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value Value::from(Chord chord)
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
v.type = Type::Music;
|
||||||
|
v.chord = std::move(chord);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +172,12 @@ Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args)
|
|||||||
assert(args.size() == 1 || args.size() == 2, "music value can be called only in form note <octave> [<length>]"); // TODO(assert)
|
assert(args.size() == 1 || args.size() == 2, "music value can be called only in form note <octave> [<length>]"); // TODO(assert)
|
||||||
assert(args[0].type == Type::Number, "expected octave to be a number"); // TODO(assert)
|
assert(args[0].type == Type::Number, "expected octave to be a number"); // TODO(assert)
|
||||||
|
|
||||||
note.octave = args[0].n.as_int();
|
assert(args.size() == 2 ? args[1].type == Type::Number : true, "expected length to be a number"); // TODO(assert)
|
||||||
|
for (auto ¬e : chord.notes) {
|
||||||
if (args.size() == 2) {
|
note.octave = args[0].n.as_int();
|
||||||
assert(args[1].type == Type::Number, "expected length to be a number"); // TODO(assert)
|
if (args.size() == 2) {
|
||||||
note.length = args[1].n;
|
note.length = args[1].n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -224,7 +233,7 @@ bool Value::operator==(Value const& other) const
|
|||||||
case Type::Intrinsic: return intr == other.intr;
|
case Type::Intrinsic: return intr == other.intr;
|
||||||
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
||||||
case Type::Bool: return b == other.b;
|
case Type::Bool: return b == other.b;
|
||||||
case Type::Music: return note == other.note;
|
case Type::Music: return chord == other.chord;
|
||||||
case Type::Array: return array == other.array;
|
case Type::Array: return array == other.array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +281,7 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
|||||||
return os << v.array;
|
return os << v.array;
|
||||||
|
|
||||||
case Value::Type::Music:
|
case Value::Type::Music:
|
||||||
return os << v.note;
|
return os << v.chord;
|
||||||
}
|
}
|
||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
@ -374,18 +383,13 @@ u8 Note::into_midi_note(i8 default_octave) const
|
|||||||
std::ostream& operator<<(std::ostream& os, Note const& note)
|
std::ostream& operator<<(std::ostream& os, Note const& note)
|
||||||
{
|
{
|
||||||
os << note_index_to_string(note.base);
|
os << note_index_to_string(note.base);
|
||||||
os << ":oct=";
|
|
||||||
if (note.octave) {
|
if (note.octave) {
|
||||||
os << int(*note.octave);
|
os << ":oct=" << int(*note.octave);
|
||||||
} else {
|
|
||||||
os << '_';
|
|
||||||
}
|
}
|
||||||
os << ":len=";
|
|
||||||
if (note.length) {
|
if (note.length) {
|
||||||
os << *note.length;
|
os << ":len=" << *note.length;
|
||||||
} else {
|
|
||||||
os << '_';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,3 +397,32 @@ bool Note::operator==(Note const& other) const
|
|||||||
{
|
{
|
||||||
return octave == other.octave && base == other.base && length == other.length;
|
return octave == other.octave && base == other.base && length == other.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Chord Chord::from(std::string_view source)
|
||||||
|
{
|
||||||
|
auto note = Note::from(source);
|
||||||
|
assert(note.has_value(), "don't know how this could happen");
|
||||||
|
|
||||||
|
Chord chord;
|
||||||
|
source.remove_prefix(1 + (source[1] == '#'));
|
||||||
|
chord.notes.push_back(*std::move(note));
|
||||||
|
|
||||||
|
for (char digit : source) {
|
||||||
|
chord.notes.push_back(Note { .base = u8(digit - '0') });
|
||||||
|
}
|
||||||
|
|
||||||
|
return chord;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Chord const& chord)
|
||||||
|
{
|
||||||
|
os << "chord[";
|
||||||
|
|
||||||
|
for (auto it = chord.notes.begin(); it != chord.notes.end(); ++it) {
|
||||||
|
os << *it;
|
||||||
|
if (std::next(it) != chord.notes.end())
|
||||||
|
os << "; ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return os << ']';
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user