Looking for help in making a custom 'force to scale' function

I am looking to make a function that is similar to toScale but that takes a pattern of chords as its input instead of a list.

The reason I want to build this is so that I can write chord progressions and strum them with a series of numbers in a more precise way than arpeggiate.

for example:

$ note ( 
    fingerpick "<c'maj7 d'min7 g'dom7 c'maj7>"
    $ (run 8)
) # s "midi"

Where fingerpick is my custom function. The result in this case would be 8 notes of each chord ascending but could be patterned to be the notes in any order.

I started by reading over some similar posts on the forum and building up from the implementation of toScale

toScale :: Num a => [a] -> Pattern Int -> Pattern a
toScale [] = const silence
toScale s = fmap noteInScale
  where octave x = x `div` length s
        noteInScale x = (s !!! x) + fromIntegral (12 * octave x)

The type of fingerpick would be: fingerpick :: Num a => Pattern a -> Pattern Int -> Pattern a but I'm lost as to how to write it. My gut is that the answer will be similar to this [thread](thread Working with groups of co-occuring events) in that it will involve making the main function (in this case noteInScale) to operator on [Event a] rather than [a] but I'm getting lost in the wiring.

I've gotten as far as attempting to define the following 'force to scale' function for lists of events in the hopes that this will bring me closer to being able to define it for patterns of chords

eventFts :: Num a => [Event a] -> [Event Int] -> [Event a]
eventFts s =  fmap ( (\x -> (s !!! x) + fromIntegral (12 * (octave x) )) =<<)
  where octave x = x `div` length s

I'm not at all sure that is a step in the right direction.

Does anyone have an idea for what I might try next? Thanks!


1 Like

I've been thinking about this more and trying things out. I think I need something of the following shape:
createMappingEvents :: [Event a] -> [Event (Int -> a)]
Where [Event a] are the simultaneous held notes from the chord and [Event (Int -> a)] are the scale functions that will constrain the pattern.

With somthing like that I think you could write

fingerpick :: Num a => Pattern a -> Pattern Int -> Pattern a 
fingerpick ch p = withEvents munge ch <* p
  where munge es = concatMap (createMappingEvents) (groupBy (\a b -> whole a == whole b) $ sortOn whole es)

I can't figure out the first part though...

@left_adjoint was working on something somewhat similar on discord iirc

Is this of any help?

let rolled = withEvents aux
             where aux es = concatMap (steppityIn) (groupBy (\a b -> whole a == whole b) $ sortOn whole es)
                   steppityIn xs = mapMaybe (\(n, x) -> shiftIt n (length xs) x) $ enumerate xs
                   shiftIt n d (Event c (Just (Arc s e)) a' v) = do 
                             a'' <- subArc (Arc newS e) a'
                             return (Event c (Just $ Arc newS e) a'' v)
                          where newS = s + (dur * fromIntegral n)
                                dur = (e - s) / (4*fromIntegral d)

The discussion:

Thanks @cleary! Yeah the idea of a "rolled" or strummed chord isn't miles away from where I'm headed.

I also found inspiration in the definition of arpWith. The primary difference is that I want the picking pattern to determine the event structure instead of using a function to move around the events in the chord pattern.

If tidal were a guitar and the two inputs to fingerpick were the left and right hand, the structure of the events would come from the right "picking hand" and the available notes would come from the pattern in the left hand.

EDIT: I've made it a little further but the solution still feels hacky.

createMappingEvents :: Num a => [Event a] -> [Event (Int -> a)]
createMappingEvents [] = []
createMappingEvents ch = [
  Event { context = context$head ch,  
          whole = whole$head ch,
          part = part$head ch,
          value = noteInScale (fmap eventValue ch)
  where octave s x = x `div` length s
        noteInScale s x = (s !!! x) + fromIntegral (12 * octave s x)

fingerpick :: Num a => Pattern a -> Pattern Int -> Pattern a 
fingerpick ch p = withEvents munge ch *> p
  where munge es = concatMap (createMappingEvents) (groupBy (\a b -> whole a == whole b) $ sortOn whole es)

run :: Microspec ()
run =
  describe "Sound.Tidal.DavidTest" $ do
    describe "fingerpick" $ do
      it "works, yes?" $ do
        compareP (Arc 0 1)
          (fingerpick "[0,5] [1,8]" (fast 2$Sound.Tidal.Core.run 4))
          ("0 5 12 17 1 8 13 20"::Pattern Rational)

Like I said, a bit hacky. But... the unit test passes!

A triumphant fake guitar ditty:

$ note (
    fingerpick (cat [ "g'm11'ii", "d'maj7'iii", "[e'min7'ii b'min7]", "d'maj7'i"])
    $ someCycles (overlay "5 6 7 . 9")
    $ "[<0 1> -1,3 . 2 1 , 5]"
) #s "gtr" #gain 0.79 #legato 1

Thanks for helping me think through this!