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> {
|
||||
for (auto &arg : args) {
|
||||
assert(arg.type == Value::Type::Music, "Only music values can be played"); // TODO(assert)
|
||||
i.play(arg.note);
|
||||
i.play(arg.chord);
|
||||
}
|
||||
return Value{};
|
||||
});
|
||||
@ -264,16 +264,31 @@ void Interpreter::leave_scope()
|
||||
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");
|
||||
|
||||
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); });
|
||||
|
||||
// Sort that we have smaller times first
|
||||
std::sort(chord.notes.begin(), chord.notes.end(), [](Note const& lhs, Note const& rhs) { return lhs.length < rhs.length; });
|
||||
|
||||
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);
|
||||
std::this_thread::sleep_for(dur);
|
||||
}
|
||||
|
||||
// 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#`
|
||||
consume_if('#');
|
||||
|
||||
// Any of the following sequences are allowed
|
||||
// c,,,,,,,,,,,,,,,,
|
||||
// c1,,,,2,3212
|
||||
// c1234'''''
|
||||
// during lexing
|
||||
while (consume_if(",'") || consume_if(unicode::is_digit)) {}
|
||||
while (consume_if('_') || consume_if(unicode::is_digit)) {}
|
||||
|
||||
// If we encounter any letter that is not part of 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))) {
|
||||
symbol_lexing:
|
||||
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 };
|
||||
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);
|
||||
|
||||
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
|
||||
struct Array
|
||||
{
|
||||
@ -523,6 +534,7 @@ struct Value
|
||||
static Value from(Block &&l);
|
||||
static Value from(Array &&array);
|
||||
static Value from(Note n);
|
||||
static Value from(Chord chord);
|
||||
|
||||
enum class Type
|
||||
{
|
||||
@ -533,7 +545,7 @@ struct Value
|
||||
Intrinsic,
|
||||
Block,
|
||||
Array,
|
||||
Music
|
||||
Music,
|
||||
};
|
||||
|
||||
Value() = default;
|
||||
@ -551,7 +563,7 @@ struct Value
|
||||
Number n;
|
||||
Intrinsic intr;
|
||||
Block blk;
|
||||
Note note;
|
||||
Chord chord;
|
||||
Array array;
|
||||
|
||||
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||
@ -647,7 +659,7 @@ struct Interpreter
|
||||
void leave_scope();
|
||||
|
||||
/// Play note resolving any missing parameters with context via `midi_connection` member.
|
||||
void play(Note n);
|
||||
void play(Chord);
|
||||
};
|
||||
|
||||
namespace errors
|
||||
|
61
src/value.cc
61
src/value.cc
@ -83,7 +83,7 @@ Result<Value> Value::from(Token t)
|
||||
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:
|
||||
unimplemented();
|
||||
@ -150,7 +150,15 @@ Value Value::from(Note n)
|
||||
{
|
||||
Value v;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -164,12 +172,13 @@ 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[0].type == Type::Number, "expected octave to be a number"); // TODO(assert)
|
||||
|
||||
assert(args.size() == 2 ? args[1].type == Type::Number : true, "expected length to be a number"); // TODO(assert)
|
||||
for (auto ¬e : chord.notes) {
|
||||
note.octave = args[0].n.as_int();
|
||||
|
||||
if (args.size() == 2) {
|
||||
assert(args[1].type == Type::Number, "expected length to be a number"); // TODO(assert)
|
||||
note.length = args[1].n;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -224,7 +233,7 @@ bool Value::operator==(Value const& other) const
|
||||
case Type::Intrinsic: return intr == other.intr;
|
||||
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
||||
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;
|
||||
}
|
||||
|
||||
@ -272,7 +281,7 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
return os << v.array;
|
||||
|
||||
case Value::Type::Music:
|
||||
return os << v.note;
|
||||
return os << v.chord;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
@ -374,18 +383,13 @@ u8 Note::into_midi_note(i8 default_octave) const
|
||||
std::ostream& operator<<(std::ostream& os, Note const& note)
|
||||
{
|
||||
os << note_index_to_string(note.base);
|
||||
os << ":oct=";
|
||||
if (note.octave) {
|
||||
os << int(*note.octave);
|
||||
} else {
|
||||
os << '_';
|
||||
os << ":oct=" << int(*note.octave);
|
||||
}
|
||||
os << ":len=";
|
||||
if (note.length) {
|
||||
os << *note.length;
|
||||
} else {
|
||||
os << '_';
|
||||
os << ":len=" << *note.length;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
@ -393,3 +397,32 @@ bool Note::operator==(Note const& other) const
|
||||
{
|
||||
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