Week 6, lesson 2 - musical scales, including navigating them with waveforms

Time to look at musical scales. Moving around scales can be especially fun with waveforms, so there's a lot of focus on that. It's a bit fiddly because as I explain in the video, you have to convert between decimal and whole numbers with e.g. floor <$>.. I'm going to have to look at making that easier in Tidal. Anyway, here's the video:

  • 0:00 - intro
  • 0:48 - Using sets of samples that are ready-tuned in scales (e.g. arpy)
  • 02:59 - Random notes in a scale
  • 03:30 - Random variation around a melody
  • 03:57 - Motivating the 'scale' function
  • 04:24 - Using the 'scale' function
  • 04:55 - Shifting time to get different random streams
  • 05:48 - Listing the available scales with scaleList
  • 06:48 - Using waveforms to move around a scale
  • 10:35 - Working outside of the scope of a 'scale'
  • 12:40 - Patterning the range of a waveform
  • 13:47 - Patterning the scale that's used
  • 14:10 - Adding rhythmic structure with 'struct'
  • 15:50 - Adding / multiplying waveforms together to make new waveforms
  • 17:50 - Using 'off' to add some canon structure

And the worksheet:

-- The 'arpy' folder contains sounds sampled using a pentatonic
-- 'ritusen' scale, starting with 'c'. In this scale there are five
-- notes per octave.  So these are the same notes:
d1 $ n "0 5" # sound "arpy"

d2 $ n "0 12" # sound "superpiano"

-- Pentatonic scales like this are nice to work with because they all
-- sound good together. So if we add a random note to a melody, it
-- always sounds 'good':

d1 $ n ("0 [7 2] 3 2" |+ irand 3) # sound "arpy"

-- This isn't really the case on the usual twelve-tone "equal
-- temperament" (12-TET) scale:
d1 $ n ("0 [7 2] 3 2" |+ (irand 3)) # sound "superpiano"

-- 12-TET is the scale that pianos etc are normally tuned to in the west.

-- To use a different scale, we can use the "scale" function for converting
-- numbers from a different scale to 12-TET.
d1 $ n (scale "ritusen" $ "0 [7 2] 3 2" |+ (irand 3))
  # sound "superpiano"

-- There's quite a few available:
scaleList

-- It's fun to use waveforms to pick notes from a scale. For example,
-- use a smooth sinewave to select notes from a minor scale:
d1 $ segment 16 $ n (scale "minor"
                     $ floor <$> (range 0 14 sine)
                    )
  # sound "supersaw"
  # legato 0.5
  # lpf 1000 # lpq 0.1

-- Remember that waveforms don't have structure, so don't produce
-- events until you use something like 'segment', which in the example
-- above picks 16 notes per cycle.

-- There's also a complication that waveforms produce 'floating point'
-- decimal numbers, but scale only accepts 'integers' - whole numbers.
-- The 'floor <$>' bit converts from decimal to whole numbers.  The
-- "range 0 14" bit converts from the usual range of 0 to 1 to the
-- given range of 0 to 14.

-- We can make this more exciting by patterning the range:
d1 $ segment 16 $ n (scale "minor"
                     $ floor <$> (range "<0 4 -8>" "<12 14 13 -13>" sine)
                    )
  # sound "supersaw"
  # legato 0.5
  # lpf 1000 # lpq 0.1

-- And maybe even more exciting by using 'struct' to pattern the
-- rhythm using Euclidean syntax.. Taking the opportunity to pattern
-- the lpf (low pass filter) as well:
d1 $ struct "t(<9 7>,16)"
  $ n (scale "minor"
        $ floor <$> (range "<0 4 -8>" "<12 14 13 -13>" sine)
      )
  # sound "supersaw"
  # legato 0.5
  # lpf (range 400 5000 saw) # lpq 0.1


-- Using scales in this way allows us to play with movement while
-- still making tunes that make 'sense'. Here I add together
-- waveforms to create some longer-form movement:
d1 $ segment 16 $
  n (scale "minor"
      $ floor <$> (slow 2 $ (slow 2 sine + slow 3 cosine) * "<6 -3>"
                  )
    )
  # sound "supersaw"
  # legato 0.5
  # lpf (range 400 5000 saw) # lpq 0.1

-- Back with the struct:
d1 $ struct "t(<9 7>,16)" $
  n (scale "minor"
      $ floor <$> (slow 2 $ (slow 2 sine + slow 3 cosine) * "<6 -3>"
                  )
    )
  # sound "supersaw"
  # legato 0.5
  # lpf (range 400 5000 saw) # lpq 0.1

-- And with an 'off' going up an octave:
d1 $ off 0.25 (|+ n 12) $ struct "t(<9 7>,16)" $ segment 16 $
  n (scale "minor"
      $ floor <$> (slow 2 $ (slow 2 sine + slow 3 cosine) * "<6 -3>"
                  )
    )
  # sound "supersaw"
  # legato 0.5
  # lpf (range 400 5000 saw) # lpq 0.1

-- Note that in the above the 'off' is outside of the 'scale'
-- function, So we're back in 12-TET land, so add '12' to go up an
-- octave, rather than the number of notes in the minor scale (7)
6 Likes

Thanks @yaxu ! I've ran into strange issues running the code in your worksheet :

d1 $ segment 16 $ n (scale "minor"
                     $ floor <$> (range 0 14 sine)
                    )
  # sound "supersaw"
  # legato 0.5
  # lpf 1000 # lpq 0.1
<interactive>:60:9: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘floor’
      prevents the constraint ‘(RealFrac a0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Integral a => RealFrac (Ratio a) -- Defined in ‘GHC.Real’
        instance [safe] RealFrac a => RealFrac (Pattern a)
          -- Defined in ‘Sound.Tidal.Pattern’
        instance RealFrac Double -- Defined in ‘GHC.Float’

On Tidal 1.5.2, GHCi 8.0.2. I do wonder, should I update GHCi at some point ?

Ah interesting. ghc 8.0.2 is a few years old, but it's the first time I've seen a problem with it - tidal is tested against it before release. Is this is a version that came with a linux distribution?

Does this work?

d1 $ segment 16 $ n (scale "minor"
                     $ floor <$> (range 0 14 sine :: Pattern Double)
                    )
  # sound "supersaw"
  # legato 0.5
  # lpf 1000 # lpq 0.1

1 Like

Thanks, it does work indeed ! Does it confirm I should somehow update my GHCi ? That old version corresponds to my very very first installation of Tidal a few years ago already, version 0.8.0, I guess, on osx.This setup has worked flawlessly for me through these years until this issue.

Up to you - I think it would solve this issue, but if you're fine with adding this type signature then you probably won't see other problems.

Thanks for your kind support, I can live with it for now.

Is this the best way to select a key:

d1
  $ slow 2
  $ n (scale "dorian" "0 1 . 2 3 4 . 5 6 7"+ "a4")
  # s "superpiano"

Or is there a better/different way?

I'd use |+ to make sure that the structure of the a4 (one event per cycle) has bearing on the rhythmic structure of the result (it doesn't in this case, because it aligns perfectly with an event on the left).

d1
  $ slow 2
  $ n (scale "dorian" "0 1 . 2 3 4 . 5 6 7" |+ "a4")
  # s "superpiano"

I like to do this as a separate control, but that's down to taste:

d1
  $ slow 2
  $ n (scale "dorian" "0 1 . 2 3 4 . 5 6 7")
  # s "superpiano"
  |+ n "a4"
3 Likes

ah, yes - makes sense - thx!

Been having lots of fun going through these lessons!

I'm curious about the best way to approach pitch collections outside the pre-defined scales, such as from a pitch class set perspective. I'm actually curious about this apropos chords also, but I figured I'd ask here as I imagine it would be something similar? I tend to work with pitches in a sense that is often removed from strict adherence to a scale-based concept.

I think I probably already have some notions regarding moving groups notes around and manipulating them, but I was curious if there was anyone who already had similar thoughts in this area who was working this way, and if so, how?

thanks!

1 Like

I found getScale, which is very cool. It allowed me to make "scales" greater than one octave and 7 notes, which is right in line with what I was hoping to do.

2 Likes

Great! Sorry I have been super busy the last few days, please shout if you have more questions.

No problem, thanks!

I guess one thing that would be great would be if there was a corresponding type of function for chords, like “getChord”, where one could define user chords. I didn’t seem to see one, though. Maybe this is straying from the topic a little...