How to Programmatically Generate Melodies and Maintain a Fixed Notes-Per-Cycle Rate?

I began working with a very simple pattern structure in TidalCycles. Using a string literal as a melody, I defined a helper function to keep a fixed notes-per-cycle rate:

let
  notesPerCycle :: Rational -> String -> Pattern Time
  notesPerCycle eventFrequency melody = pure $ fromRational $ realToFrac (length (words melody)) / eventFrequency
  melody1 = "7 4 0 2 ~ 0 4  9 5 4 2 4 5 0 4 2 0 6 8 0" -- delete or add notes as desired
  satellitePattern = slow (notesPerCycle 4 melody1) $ note melody1 # s "supersaw"
    # sustain 0.3
    # speed 0.3
    # cut 1
    # gain 0.8 
in do
  hush
  d1 $ satellitePattern
  d2 $ s "kick*4" # gain 0.8

This works perfectly. The melody plays at exactly four notes per cycle, regardless of how many notes the string contains. However, I then wondered whether this melody could instead be generated programmatically, rather than hardcoding magic numbers into a string. Conceptually, something like this:

let
  notesPerCycle :: Rational -> String -> Pattern Time
  notesPerCycle eventFrequency melody = pure $ fromRational $ realToFrac (length (words melody)) / eventFrequency
  melody1 :: String -- imagine this is a function that outputs a melody as String
  melody1 = "7 4 0 2 ~ 0 4  9 5 4 2 4 5 0 4 2 0 6 8 0" -- delete or add notes as desired
  satellitePattern = slow (notesPerCycle 4 melody1) $ note melody1 # s "supersaw"
    # sustain 0.3
    # speed 0.3
    # cut 1
    # gain 0.8 
in do
  hush
  d1 $ satellitePattern
  d2 $ s "kick*4" # gain 0.8

But this version fails with:

2025-10-10 13:30:20.216 [error] t>  
 [GHC-83865]
    * Couldn't match type: [Char]
                     with: Pattern Note
      Expected: Pattern Note
        Actual: String
    * In the first argument of `note', namely `melody1'
      In the first argument of `(#)', namely `note melody1'
      In the first argument of `(#)', namely
        `note melody1 # s "supersaw"'

From a user’s perspective, a string literal and a string returned by a function should be interchangeable, yet Tidal treats note "7 4 0" differently from note melody1 when melody1 is user-defined. Clearly this is intentional, but it's not immediately intuitive.

My Actual Question

What is the idiomatic way in TidalCycles to:

Generate melodies programmatically (e.g. as a Pattern Note, not a static string)?

Play them at a fixed rate. For example, exactly 4 notes per cycle, regardless of whether the melody contains 8, 16, or 32 steps?

In other words, how can we dynamically adjust slow (or some equivalent) based on the actual density or length of a generated melody pattern, without resorting to manual strings or magic numbers?

on the type error, not the (interesting!) "actual question"

Tidal uses -XOverloadedStrings (https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_strings.html), and then each string literal is polymorphic, and has (can have) type Pattern Note. When you write melody1 :: String, you remove that polymorphism. You can get it with fromString melody1which, again, has type Pattern _. Inserting that function is exactly what the compiler does automatically for overoaded string literals, cf. https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-String.html#t:IsString

This is what I got so far:

let
  density = 4    -- number of notes to be played per cycle
  noteCount = 14 -- number of notes in the melody
  fitToCycle :: Rational -> Rational -> Pattern Time
  fitToCycle events beats = pure $ fromRational $ realToFrac events / beats
  melody :: Pattern Note
  scaleNotes = map fromIntegral [0,2,4,5,7,9,11]        -- C major scale
  ascendSeq = fastcat (map pure scaleNotes)             -- 7 ascending notes
  descendSeq = fastcat $ map pure (reverse scaleNotes)  -- 7 descending notes
  melody = segment 14 $ fastcat [descendSeq, ascendSeq] -- 14 notes total   
in do
  hush
  d1 $ s "kick*4" # gain 0.7
  d2 $ slow (fitToCycle noteCount density) $ n melody # s "superpiano"
   # speed 1 # gain 0.9 # legato 1 # cut 1 # room 0.2 # size 0.4