Help with a fudgeToScale function

I've been playing around with a function that will take a list of non-scale tones and use fix to lower/raise their up value one semitone so that they conform with a diatonic scale, e.g.

fix (|- up 1)  "[1,3,6,8,10]" -- notes in range conform to [0,2,4,5,7,9,11]

Of course, I want this function to work across other octaves, for which I can provide it with a giant list of values to avoid (this works well and is tons of fun):

fix (|- up 1) "[-35,-33,-30,-28,-26,-23,-21,-18,-16,-14,-11,-9,-6,-4,-2,1,3,6,8,10,13,15,18,20,22,25,27,30,32,34,37,39,42,44,46]"

And I can programatically generate this list from a list of scale tones:

let showableAvoidList scaleIntervals =  show completeAvoidList where
               completeAvoidList = concat $ transposeScale avoidIntervals <$> [(-3)..3] 
               transposeScale avoidIntervals oct = (+ (oct * 12)) <$> avoidIntervals
               avoidIntervals  = filter (\n -> notElem n scaleIntervals) [0..11]

So that

showableAvoidList [0,2,4,5,7,9,11] creates the giant list above.

But I thought that with parseBP_E, I’d be able to use this result directly with fix, e.g.

let fudgeToScale scaleIntervals = fix (|- up 1) (parseBP_E $ showableAvoidList scaleIntervals) where
               showableAvoidList scaleIntervals =  show completeAvoidList
               completeAvoidList = concat $ transposeScale avoidIntervals <$> [(-3)..3] 
               transposeScale avoidIntervals oct = (+ (oct * 12)) <$> avoidIntervals
               avoidIntervals  = filter (\n -> notElem n scaleIntervals) [0..11]

but I don’t seem to have all the pieces there yet. Could someone help me with this?

Also, I have no idea how inefficient this might be. I would love to hear if there is a more efficient/idiomatic way to do this (e.g., can this be done with modulo? That was also a dead end for me)

Finally figured this out, adding the solution here for completeness' sake:

let fudgeToScale scaleIntervals = fix (|- up 1) (up $ parseBP_E $ completeAvoidList scaleIntervals) where
                completeAvoidList scaleIntervals = show $ concat $ transposeScale avoidIntervals <$> [(-3)..3]
                transposeScale avoidIntervals oct = (+ (oct * 12)) <$> avoidIntervals
                avoidIntervals  = filter (\n -> notElem n scaleIntervals) [0..11]

It's pretty handy. With it you can do transformations on pitch (outside of the Pattern Int argument to scale where functions like jux won't work) and still confine notes to a set of diatonic intervals, e.g., using jux to harmonize with diatonic triads:

let dorian = [0,2,3,5,7,9,10] 

d1  $ fudgeToScale dorian
  $ juxBy 0.5 ((|+ up "[0,4,-5]") . (# s  "superpiano"))
  $ up "<[0 [2 3] 5 7] [4 <2 -2>]>" # s "superhammond"

NB: It only works with diatonic scales, i.e., where any wrong note is at most one semitone above a correct note. Applying the function twice should work if there are bigger gaps in the pitch set.

2 Likes

This is a very cool idea - I need to have a play this week, thanks for posting the solution :slight_smile:

Side note, I'm going to go with the name fixToScale instead I think -

Next thing I'm thinking about, is how can you make use of the pre-defined scales in scaleList?

ie dorian is already defined as a scale in scaleList so it should be possible to not have to redefine it... I think I need to learn more about types if I had a snowball chance in hell of implementing it :frowning:

fixToScale does make so much more sense!

About using predefined scales here, crucially the missing piece for me is being able to extract the scale intervals from a named scale. My original idea posted here was to undo the scale function (rounding down or something to any values that didn't fit), applying some transformation, then re-applying the scale. If I could figure out how to get access to the scale intervals, I probably could have done that. But my Haskell skills are worse now than they were a year ago.

The problem with using named scales in fixToScale is that while it works nicely with diatonic scales (where there is at most a major second between intervals), if there were a minor third between adjacent steps, the function would need to be applied twice; for a major third, three times etc.

I still think undo scale -> apply function -> redo scale is the best way to go for this kind of patterning - if we can figure out how to get the interval list from a scale.

1 Like