Closed JasonStorey closed 8 years ago
I've been thinking about a feature exactly like this! Let me just ponder it for a little while, and I'll get back to you - cheers Jason!
Ace! Ponder away.
Feel free to tear it apart... I was learning your library as I went so there are certainly better ways to do this.
So Jason. First of all, this is a very important feature! Chord progressions are one part of the "horizontal axis". We/I'll need to figure out ways of representing and working with note sequences as well.
Anyway, what do you think about making this a npm module? E.g.:
var teoria = require('teoria');
var progression = require('teoria-chord-progression'); // or whatever name
var toArabic = require('roman-numerals').toArabic;
var scale = teoria.scale('c', 'major');
var jazzyCadence = progression(scale, [2, 5, 1]);
var authenticCadence = progression(scale, ['V', 'I'].map(toArabic));
Representing note sequences would be great! I guess a 'melody' object is what we want. I think that belongs in a separate module.
Similarly, I suppose, the progression object should be in a separate module. Objects like melody and progression are higher level than note, interval, scale, and chord.
Okay, so pulling this out into a separate module is probably wise, but I don't see how it works in your example... Progression depends on Teoria (it uses chord, interval, etc.) so you'd have to pass it a reference to teoria to use internally.
Something like...
var teoria = require('teoria');
var progression = require('teoria-chord-progression')(teoria);
//Progression is passed a reference to Teoria,
//and can extend it by adding the teoria.progression method to the API
var scale = teoria.scale('c', 'major');
var jazzyCadence = teoria.progression(scale, [2, 5, 1]);
So there's a fundemental problem (for now) with teoria - it's not modularized properly. Which means that you can't just require the few modules you need. However in this particular case you shouldn't have to pass a reference to teoria, as you can just use the TeoriaNote
objects directly when passed as arguments.
// module: teoria-chord-progression
var piu = require('piu'); // A module I've made for inferring chord names from a set of notes
module.exports = function(scale, progress) {
var notes = scale.notes(), length = notes.length;
chordlength = chordlength || 3;
var chords = progression.map(function(cindex) {
for(var i = 0, chordnotes = []; i < chordlength; i++)
chordnotes.push(notes[(cindex - 1 + 2 * i) % length]);
return piu.name(piu.infer(chordnotes)[0]);
});
return chords;
}
see how there's no reference to teoria needed?
Sure, using the notes directly isn't a problem, but shouldn't progression contain an array of TeoriaChord
s?
There is no reference to TeoriaChord
in the scale object. Were you imagining it would work differently?
You can use the TeoriaNote#chord
method to create the chords, since you've already got an array of TeoriaNote
s from scale.notes()
?
I know this is a bit of a problem, and I'm working to find a solution to make teoria more modular (while still maintaining a "sugar" version with syntax like the current version)
Ah yes... I understand now. I might move this functionality into a separate 'teoria-chord-progression' repo then. Or would you rather do it?
No please, go ahead Jason. If you could message me somehow when you've done it, I'd be grateful.
I had a chance to start on the weekend... https://github.com/JasonStorey/teoria-chord-progression
Not yet published to NPM. Super limited functionality at the moment. Let me know what you think so far.
There is one sad part I've found; perhaps you have a better solution...
To construct chords using the TeoriaNote.chord() method, I need to pass the chord name string without the note name prefix -
var chordNotes = [/* TeoriaNotes for F#maj7 */]
var chordName = piu.name(piu.infer(chordNotes)[0]); // 'F#maj7'
// First, need to remove 'F' and '#' from chordName
chordName = chordName.slice(1).replace(/^(b+|#+)/, '');
// then, pass 'maj7' to TeoriaNote.chord()
var chord = degreeInScale.chord(chordName);
Ideally, I'd just pass the inferred chord name straight to teoria.chord and everything is done.
I haven't tested it against the existing tests, but why not have the .slice(1) be part of the regex like
chordName = chordName.replace(/^([A-G]|b|#)+/, '');
Sure, that's prettier. Done!
I was really hoping there might be a way to construct a TeoriaChord from the inferred chord name, without needing the regex. I can't do it from TeoriaNote.chord. I guess it's not important though... this works.
Any other pointers before I publish? I'll also add...
Hej @JasonStorey - beautiful work! We'll need a feature to just get the chord without the note name!
I personally prefer .interval
instead of .transpose
(returning a new object instead of mutating the existing one) - but that's just me.
I believe this is resolved with your teoria-chord-progression module?
TeoriaProgression
A new feature for constructing and managing diatonic chord progressions.
I needed this for a project, so I added it!
First PR, so apologies if this is no good. Let me know if it's not worthy... or if you don't think this belongs in Teoria.
I've only implemented
TeoriaProgression.simple()
&TeoriaProgression.getChord(n)
so far.Because of the way the chords are built, this will only work for the standard modes. God knows what kind of exotic harmony would be generated from the pentatonics or blues scales. This needs sorting... I wanted to get your opinion before I continue though.
It'd be cool to add...
Feedback welcome!