saebekassebil / teoria

Javascript taught Music Theory
http://saebekassebil.github.io/teoria
MIT License
1.32k stars 114 forks source link

Possible bug: TeoriaNote#solfege is not octave-independent? #6

Closed lukehorvat closed 12 years ago

lukehorvat commented 12 years ago

Firstly, this is how I expected TeoriaNote#solfege to work:

s = teoria.scale("c", "major");
teoria.note("a#3").solfege(s); //returns "li"
teoria.note("a#4").solfege(s); //returns "li"
teoria.note("a#5").solfege(s); //returns "li"
s = teoria.scale("c4", "major");
teoria.note("a#3").solfege(s); //returns "li"
teoria.note("a#4").solfege(s); //returns "li"
teoria.note("a#5").solfege(s); //returns "li"

However, this is how it actually works:

s = teoria.scale("c", "major");
teoria.note("a#3").solfege(s); //returns "li"
teoria.note("a#4").solfege(s); //returns undefined
teoria.note("a#5").solfege(s); //error: 'Interval is bigger than an augmented fifteenth'
s = teoria.scale("c4", "major");
teoria.note("a#3").solfege(s); //returns undefined
teoria.note("a#4").solfege(s); //returns "li"
teoria.note("a#5").solfege(s); //returns undefined

Just wondering if this behaviour is by design. If so, care to clarify your rationale? It's not a huge deal, but it does force the developer to be extra conscious of the scale tonic's octave.

lukehorvat commented 12 years ago

I also noticed this:

teoria.note("c#4").solfege(teoria.scale("c#4", "major")); //returns "do", as expected
teoria.note("c#4").solfege(teoria.scale("db4", "major")); //error: Cannot read property 'quality' of undefined

This error occurs because the two notes are identical, and so an interval cannot be determined. I've detailed this in issue 7.

My expectation was that C# would still be "do" in a Db major scale.

saebekassebil commented 12 years ago

Thanks for reporting this, I'll look into it right away (as I feel this might have something to do with the new Interval class).

To not make the solfege method octave agnostic was actually a deliberate decision, because that is (to my belief) what is "theoretically" correct. But now you've made my not so sure of it, so I'll dust off some old theory books (and websites) and see what's actually correct.

Actually teoria.note('c#4').solfege(teoria.scale('db4', 'major')) ought to return 'raw' as it's a diminished second. But since it doesn't do that, there's something wrong.. :)

saebekassebil commented 12 years ago

@LukeHorvat: The "extreme" intervals is fixed per 533bacc4af7c99d0f26ab91839ecdfc3342d337a.

teoria.note('c#4').solfege(teoria.scale('db4', 'major')); // 'raw'
teoria.note('c4').solfege(teoria.scale('b#4', 'major')); // 'tai'

etc. We're now using the "Shearer" syllables which are about the only system that provides for all intervals (including double-diminished, which isn't supported pt. in teoria).

lukehorvat commented 12 years ago

Thanks for fixing that.

As for the octave stuff, let me know what you decide. I'm new to music theory, so you would know better than I what is "theoretically" correct.

I've been doing a little reading, and it looks like octaves above and below middle C should be annotated with superscript and subscript strokes respectively. Example:

s = teoria.scale("c", "major");
teoria.note("c2").solfege(s); //do,,
teoria.note("c3").solfege(s); //do,
teoria.note("c4").solfege(s); //do
teoria.note("c5").solfege(s); //do'

But that's just what I've gleaned from a cursory reading, so take that with a grain of salt...

saebekassebil commented 12 years ago

That looks like a reasonable approach. Much like the helmholtz notation system, though they would probably prefix , and suffix ' - Do you remember where you read that?

var cmajor = teoria.scale('c4', 'major');
teoria.note('c2').solfege(cmajor); // ,,do
teoria.note('c3').solfege(cmajor); // ,do
teoria.note('c4').solfege(cmajor); // do
teoria.note('c5').solfege(cmajor); // do'

I've asked a question at Music @ StackExchange, so I'll wait and see if I get any answers there - If not, your approach seems like the best to me. Maybe one should hide it behind an optional argument to the solfege method like this: teoria.note.solfege(scale, octaves = false)

lukehorvat commented 12 years ago

Here are a few sites where I read about solfege strokes: Link 1 Link 2 Link 3 (see Tonic Sol-fa diagram)

Your optional argument idea sounds good. If the developer does not specify octaves, what what were you thinking of returning for notes outside of the scale tonic's octave? undefined (i.e. the current behaviour) or just solfege syllables without strokes? The latter seems better to me.

saebekassebil commented 12 years ago

@LukeHorvat per 950beefd69e8abdade2e5c605bfbf947897f86b7 This should be "somewhat" fixed. It does not use the solfege strokes, but as a biproduct of improving the TeoriaInterval class, this issue got partially fixed.

Status right now:

var cmajor = teoria.scale('c4', 'major');
teoria.note('c2').solfege(cmajor); // do
teoria.note('c3').solfege(cmajor); // do
teoria.note('c4').solfege(cmajor); // do
teoria.note('c5').solfege(cmajor); // do

I'll look into adding the optional parameter - It shouldn't be that hard

saebekassebil commented 12 years ago

Fixed by above commit 63738930b528e93fa8ad109c70659320d2181e61

Implemented optional showOctave parameter in TeoriaNote#solfege and TeoriaScale#solfege

lukehorvat commented 12 years ago

Nice work.

I've been thinking a little about the use of Shearer syllables, though. Shearer is fixed-do, but Teoria is employing movable-do (i.e. "do" is whatever the tonic of the scale is). I think they might not be compatible...

For instance, Shearer assumes that in C major, the note one semitone above the tonic (C) can be either "di" (C#) or "ra" (Db). But if the scale is Db major, the note one semitone above the tonic (Db) can only be D, which does not have raised or lowered variants. So which syllable does D use - "di" or "ra"?

From what I can see, Teoria only ever returns "di":

teoria.note('d').solfege(teoria.scale("db", "major")); //returns "di"

EDIT: actually, I think this might be the case whether you use Shearer or not. So it might not be a problem.

saebekassebil commented 12 years ago

@LukeHorvat Thanks for pointing that out! I wasn't aware that the Shearer syllables were fixed-do. The only reason I choose those, was because they support augmented and diminished intervals. I had no experience whatsoever with the syllables otherwise :)

Teoria.js' solfege implementation is movable-do, so it might be confusing that we're using the same syllables as a fixed-do system. I think that ought to be made clear(er) in the README and in the code. Do you know any other system with the same support for augmented and diminished intervals?

lukehorvat commented 12 years ago

Nope. :(

Just a minor update to this issue. Currently, octave 3 returns solfege syllables with no strokes, but it should be octave 4 (middle C). This is detailed in the first link, and I've been looking at scores with solfege annotations that also show this to be true. It differs from Helmholtz notation, which has no strokes on octaves 2 and 3.

In other words:

s = teoria.scale("c", "major");
teoria.note("c3").solfege(s, true); //do,
teoria.note("c4").solfege(s, true); //do
teoria.note("c5").solfege(s, true); //do'
saebekassebil commented 12 years ago

Ah, but it's relative to the scale root, not C4, confer the movable-do system, not fixable do. When you create a scale:

teoria.scale('c', 'major');

You're instantiating a TeoriaNote('c') and since you've provided no octave number, the note parser will think of it as Helmholtz notation, where c = C3 and C = C2, and C, = C1 etc.

So the behavior is expected. Both teoria.note("c4").solfege(teoria.scale('c4', 'major'), true); and teoria.note("Eb6").solfege(teoria.scale('Eb6', 'major'), true); will return do.

lukehorvat commented 12 years ago

Okay, that sounds reasonable. :)

Sorry to drag this issue out, but I ran some tests to check the relativity to the scale root and got the following results:

s = teoria.scale(teoria.note("a4"), "major");
teoria.note("a4").solfege(s, true); //do,
teoria.note("a4").solfege(s, true); //do
teoria.note("a5").solfege(s, true); //do'

teoria.note("c3").solfege(s, true); //me, <-- should be me,,
teoria.note("c4").solfege(s, true); //me  <-- should be me,
teoria.note("c5").solfege(s, true); //me
teoria.note("c6").solfege(s, true); //me'

teoria.note("b2").solfege(s, true); //re, <-- should be re,,
teoria.note("b3").solfege(s, true); //re  <-- should be re,
teoria.note("b4").solfege(s, true); //re
teoria.note("b5").solfege(s, true); //re'

teoria.note("g3").solfege(s, true); //te, <-- should be te,,
teoria.note("g4").solfege(s, true); //te  <-- should be te,
teoria.note("g5").solfege(s, true); //te
teoria.note("g6").solfege(s, true); //te'
saebekassebil commented 12 years ago

Hey Luke,

I think we may be differ in opinion how this is to work. I've implemented this, so that notes which are an octave away from the tonic of the scale is being assigned strokes and commas.

I've made a JSFiddle to illustrate it: Solfege syllables and octaves

lukehorvat commented 12 years ago

Oh, I see. An octave away in both directions... Hmm, that is interesting. I have not seen it work like that. The first two links I provided both seem to go against your implementation, and it seems odd that you could potentially encounter two notes without strokes and not know which octave they come from. From what I can see, you're trying to make it direction-agnostic so the developer can do descending scales and not have to specify the scale root one octave lower to get solfege syllables without strokes, but it prevents you from being able to move above and below the scale root within the same scale. I think it is more intuitive for developers to make a conscious effort to specify the scale root an octave lower if they have a descending scale.

Today I briefly spoke with a professional who teaches movable-do for a living, Mark O'Leary of Young Voices of Melbourne. I may be building a sight singing app/website for him (hence I am scoping out Teoria for potential use in this project). He provided me with a few scores; here's one. As you can see, it seems to be at odds with Teoria's current implementation of solfege strokes. So I asked him this question via email:

Am I correct in assuming that if a scale is Eb major, Eb4 is do, Eb5 is do', D4 is ti| (that's a subscript stroke), and D5 is ti? In other words, only notes between Eb4 and D5 are without solfa strokes, and notes such as C4 and D4 each have a stroke because they are before Eb4 (despite being in the same octave as Eb4)?

He said that was correct.

Now, that is not to say there aren't multiple ways to do this. Maybe his is just one particular way of doing it? But seeing as he teaches solfa professionally, perhaps it's worth sending him an email yourself? His contact information is here. Tell him I referred you and he should be willing to answer your questions. He might be able to offer some insight on the double augmented/diminished stuff too.

saebekassebil commented 12 years ago

@LukeHorvat That makes much better sense! How would you else differentiate between D5 and D4's solfege step in a C5 major scale?

I'll open an issue on this subject.