tonal icon indicating copy to clipboard operation
tonal copied to clipboard

Scale detection

Open Mdashdotdashn opened this issue 6 years ago • 15 comments

I'm replacing some of my half-baked code by your awesome tonal but converting my tests I'm getting puzzled by the output of scale detect. Here's what I'm doing:

`tonal = require("tonal");

processChordList = function(chordArray) { var notes = []; chordArray.forEach(function(chordName) { var chordNotes = tonal.chord.notes(chordName); notes = notes.concat(chordNotes); })

result = tonal.scale.detect(notes);

console.log("-----------------------------------------------------"); console.log("Input chords: " + chordArray.join()); console.log("Notes: " + notes.join()); console.log(""); console.log("Resulting scales"); console.log(result); }

processChordList(["cm"]); processChordList(["cm", "a#"]); processChordList(["cm", "a#", "fm"]); processChordList(["cm", "a#", "fm", "g#"]); processChordList(["cm", "bb", "fm", "ab"]); `

That leads to

`$ node test/test-scale-detection.js

Input chords: cm Notes: C,Eb,G

Resulting scales []

Input chords: cm,a# Notes: C,Eb,G,A#,C##,E#

Resulting scales [ 'E# piongio' ]

Input chords: cm,a#,fm Notes: C,Eb,G,A#,C##,E#,F,Ab,C

Resulting scales [ 'C aeolian', 'C locrian', 'C## major', 'Eb dorian', 'E# phrygian', 'F lydian', 'G mixolydian' ]

Input chords: cm,a#,fm,g# Notes: C,Eb,G,A#,C##,E#,F,Ab,C,G#,B#,D#

Resulting scales [ 'C aeolian', 'B# locrian', 'C major', 'C## dorian', 'D# phrygian', 'Eb lydian', 'E# mixolydian' ]

Input chords: cm,bb,fm,ab Notes: C,Eb,G,Bb,D,F,F,Ab,C,Ab,C,Eb

Resulting scales [ 'C aeolian', 'C locrian', 'C major', 'D dorian', 'Eb phrygian', 'Eb lydian', 'F mixolydian' ] ` I'm expecting c minor to show everytime and certainly not C Major to show up.

Am I doing something wrong ?

Mdashdotdashn avatar Aug 29 '17 06:08 Mdashdotdashn

Yes, this is related to #33. I hope I can solve it soon. Thanks for reporting.

danigb avatar Aug 29 '17 19:08 danigb

Hi @Mdashdotdashn,

Now I have time to look to your issue more carefully, I think you're misundestanding the tonal.scale.detect function. What it does: given a collection of notes, it tells you what scales have exactly that notes. For example "C Eb G" is not a scale (is a chord) so results are []. Notes "C,Eb,G,A#,C##,E#,F,Ab,C" are exactly A minor and all its modes.

I think the results of your test are ok, but that's not what you are looking for.

If I understand you, you want all the scales that a chord fits in. Currently, this function is not implemented, but it should not very hard to do using tonal-pcset and tonal-dictionary. Maybe I'll include it in 1.0 (see #26)

danigb avatar Sep 05 '17 17:09 danigb

What happened to the scale.detect function? Am I missing something? I can't find it!

I'm trying to take a collection of chromas and transform them into a scale name. Right now I'm using the Scale.toMidi function like so:

let chroma = [0,2,4,5,7,9,11].map(Note.fromMidi) // => [ 'C-1', 'D-1', 'E-1', 'F-1', 'G-1', 'A-1', 'B-1' ]
Scale.toScale(chroma) // => [ 'C', 'D', 'E', 'F', 'G', 'A', 'B' ]

But where do I go from here? How can I get tonal.js to tell me that this is a C major scale?

micahscopes avatar Oct 18 '17 03:10 micahscopes

Hi @micahscopes

Sorry, I've removed scale.detect from the core to release the 1.0.0. I will release it as a module soon. I'll keep you informed.

danigb avatar Oct 19 '17 14:10 danigb

Okay, cool. I figured, since it was nowhere to be found. Thanks for this nice library!

On Thu, Oct 19, 2017 at 9:08 AM, danigb [email protected] wrote:

Hi @micahscopes https://github.com/micahscopes

Sorry, I've removed scale.detect from the core to release the 1.0.0. I will release it as a module soon. I'll keep you informed.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/danigb/tonal/issues/36#issuecomment-337919282, or mute the thread https://github.com/notifications/unsubscribe-auth/AAXylug1hqCoToEJGasWZDM7NRw0WWu6ks5st1fEgaJpZM4PFc5- .

micahscopes avatar Oct 19 '17 14:10 micahscopes

By the way, If you have the exact chroma, you can use Dictionary.scale.names to get the names:

const Tonal = require('tonal');
Tonal.Dictionary.scale.names('101011010101') // => [ 'major', 'ionian' ]

danigb avatar Oct 19 '17 14:10 danigb

Ooh!!!! I do have the exact chroma :) So this gives the scale and the mode?

micahscopes avatar Oct 19 '17 14:10 micahscopes

No, it gives all the possible names (name and aliases). Most of the time, an array with one element is returned. major and ionian are considered aliases in tonal.

danigb avatar Oct 19 '17 19:10 danigb

How hard would it be to make it return the modes in order? Would you be opposed to a pull request if I made that change? It'd be useful to me!

On Thu, Oct 19, 2017 at 2:02 PM, danigb [email protected] wrote:

No, it gives all the possible names (name and aliases). Most of the time, an array with one element is returned. major and ionian are considered aliases in tonal.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/danigb/tonal/issues/36#issuecomment-338005618, or mute the thread https://github.com/notifications/unsubscribe-auth/AAXylmWNhf_f5Mxyu9CKMSmovxsit6dRks5st5zHgaJpZM4PFc5- .

micahscopes avatar Oct 19 '17 20:10 micahscopes

I don't understand what you mean with "return the modes in order". Which function are you talking about?

danigb avatar Oct 19 '17 21:10 danigb

ahh, I think was getting confused at how the scale dictionary works compared with the old scale detect function. the old scale detect function gave back a seemingly unordered list of possible modes/scales that fit the notes you give it, whereas the new one will give you the exact mode of a chroma.

If you wanted to find other modes with those notes, you'd have to rotate the chroma 11 times and look each one up in the dictionary. That is much more concrete and serves my purposes very well!

Thanks again!

micahscopes avatar Oct 19 '17 21:10 micahscopes

Hi! I'm new to this library but it seems to be very useful thus far! Good work.
It seems there's been a lot of refactoring since 2017 and I'm trying to find a simple scale detection function that seems to have existed at some point. It seems there is a ChordDetect.detect(notes:string[]) method, but I can't seem to find the similar method for Scales. Does this exist anymore, or will I have to first get the chroma for my notes, then search to find the scale that matches the chroma?

Let me know if I missed something in the documentation. Thanks!

manuelnelson avatar Sep 21 '20 19:09 manuelnelson

Hi Manuel,

I think you're right. There's no Scale.detect because, as you said, I think scale detection is just a matter of find the chroma of the notes and get the scale that matches.

On the other hand, chord detection is more complex, because different chords can have the same notes (C6 and Am for example) and there are inversions. That's why Chord.detect exists.

danigb avatar Sep 23 '20 16:09 danigb

I don't think it would be a bad idea to have this as a helper function in the library.

glend1 avatar Jan 12 '22 14:01 glend1

Definitely appreciated if a scale detect would be available :F

katerlouis avatar May 11 '22 11:05 katerlouis

@katerlouis I agree. There should at least be documentation explaining how to get the Scale detect, but really for usability purposes it feels necessary. If for no other reason than it just seems a little silly that I can just do Chord.detect() but have to jump through extra hoops to do the same thing for scales. If this issue wasn't submitted then I never would've figured it out.

jordanforbes avatar Nov 09 '22 19:11 jordanforbes

So for a partial scale lookup how would I do it? I don't have the full chroma. I have a chord and want to know what scales that chord fits into. Using Pcset and the dictionary? Hmmm....an insection between the chord chroma and the chromas of each scale? What about the transposition though?

robclouth avatar Dec 07 '22 17:12 robclouth

the way i do it is loop through the elements in a scale and then check to make sure it contains each chord key

glend1 avatar Dec 07 '22 18:12 glend1

For those who may have gotten caught up on the transposition requirement for partial scale recognition, here is one way to do it. (Please let me know how to simplify!)

const scaleNotes = [ 'Ab', 'Bb', 'C', 'Db', 'Eb', 'G' ] // partial Ab scale

// Transpose to 'C' in order to match scales
const interval = Interval.distance(scaleNotes[0], 'C')
const transposed = scaleNotes.map(Note.transposeBy(interval))
const pcset = Pcset.get(transposed)

// Filter scales that include every interval - Better way below! 
// const scales = ScaleType.all().filter(scale => {
//   let intervalsFound = 0
//   pcset.intervals.forEach(interval => {
//     if (scale.intervals.includes(interval)) intervalsFound++
//   })
//
//   if (intervalsFound === pcset.intervals.length) return true
// })

// Found this beauty in the source
// https://github.com/tonaljs/tonal/blob/main/packages/chord/index.ts#L197
const areNotesIncluded = Pcset.isSupersetOf(pcset.chroma)
const scales = Tonal.ScaleType.all().filter(scale =>
  areNotesIncluded(scale.chroma),
)

This yields the following scales:

harmonic major
major
bebop
bebop major
ichikosucho
chromatic

You can then get the full scales in the original key like so:

const keyScales = scales.map(scale => {
  return Scale.get(`${scaleNotes[0]} ${scale.name}`)
})

alindsay55661 avatar Jan 10 '23 23:01 alindsay55661

Thanks for the code @alindsay55661 I've (finally!) implemented Scale.detect. Is like yours, but it returns the scale name with tonic and accept some options. See https://github.com/tonaljs/tonal/tree/main/packages/scale#scaledetectnotes-string-options--tonic-string-match-fit--exact---string

      Scale.detect(["Ab", "Bb", "C", "Db", "Eb", "G"]); 
//      [
//        "Ab major",
//        "Ab bebop",
//        "Ab harmonic major",
//        "Ab bebop major",
//        "Ab ichikosucho",
//        "Ab chromatic",
//      ]);

danigb avatar Jan 19 '23 03:01 danigb