Haskell 🔄 tidal

recently i've been working with sonification of some time series data and getting into the non-live aspects of tidal and haskell. what started as adhoc experiments in tidal and supercollider has been getting a bit more structured, so thinking now about how to write decent haskell and tidal longer than a few lines at a time...

so wondering if anyone has suggestions, or ideas about the benefits/complications of using non pattern related code in .tidal or using tidal patterns in .hs?


perhaps as a concrete example, using data from a spreadsheet as source of patterns and sample maps...

what would be an idiomatic way of using sequences as pattern sources?

seems like there would some way of mixing or filtering based on type (e.g. _CellText or _CellDouble) but where or when to?


slonkCell :: Int -> Int -> IO String
slonkCell row col = do
  bs <- L.readFile dataFile
  let value = toXlsx bs ^? ixSheet  "Taul1" .
              cellValueAt (row, col) . _Just  -- . _CellText -- . _CellDouble
  case value of
    Nothing   -> return "Nothing"
    Just val  -> return $ show val

main :: IO [(String)]
main = do
  let range2 = [(1, 1), (2, 1), (1, 3)]
      seq = mapM (\(x, y) -> slonkCell x y) range2

Well the usual tidal commands are IO actions so you could do something like

import Sound.Tidal.Context

main :: IO [(String)]
main = do
  tidal <- startTidal (superdirtTarget {oLatency = 0.05, oAddress = "", oPort = 57120}) (defaultConfig {cVerbose = True, cFrameTimespan = 1/20})
  -- let range = [1..5]
  let range2 = [(1, 1), (2, 1), (1, 3)]
  let seq = mapM (\(x, y) -> slonkCell x y) range2
  streamReplace tidal "thingie" $ sound $ fromList seq
  -- would need to put a sleep action or something so the program doesn't terminate..

You'd want to run / compile with -XOverloadedStrings for mininotation to work.

Thanks. seems to make sense, still building my intuitions around haskell...

Perhaps a more basic question re. typing, in the example CellValue could have constructors CellText or CellDouble is there a way to assemble a list based on the type? or is there another approach?

Which library are you using?


I don’t know the library in question, but from a quick glance at CellValue in hackage, and from your code up at top of thread, I’d imagine you’ll find use in pattern matching ala

case value of
  Just (CellDouble foo) -> ...
  Just (CellText bar) -> ...
  _ -> ...

Depending on exactly what value is, and what you mean by “assemble a list based on type”.

It’s probably also worth taking a look at functions along the line of catMaybes:

Thanks @mvdirty the typecase style pattern matching does work, for example returning a list like ["CellText \"Text1\"","CellText \"Text2\"","CellDouble 1001.1"] (or without the prefix from show) but it feels like I'm missing the point of a static type system by doing string manipulation to get typed values...

Perhaps to rephrase, for now I'm reading from a spreadsheet, which produces values like "name" of type CellText or a number of type CellDouble I'd like to use an intermediate representation like ["name",1,0,1,0,0] (or ["name", 331, 403, 398 .. ]) which could be transformed to a tidal pattern like s "name ~ name ~ ~" , or use the tail of the list as control values, but it's not obvious to me what sort of type signatures make sense, or where it's easiest to do the required type casting.

thanks for the catMaybes tip. looks useful

Just to put it out there, you shouldn’t need string manipulation unless you need to parse inside the content of text cell values themselves.

(Below I have to make at least some assumptions about where you’re trying to take your code, but hopefully I’ll be able to cover some generally helpful material.)

If you step back from that list structure a bit and consider exactly what you are trying to represent conceptually, is it really a list you’re looking for? [NB: I say this because carrying arbitrary types of values in lists is somewhat antithetical to Haskell and suggests that deeper type design is needed. More on that as we go…]

Is ”name” somehow conceptually distinct from the set of numbers which follow? Such that the list could instead be a structure containing a field where ”name” would go and a list where the numbers would appear? Are those numbers just a list? Or is there positional value to the numbers within it, such that they could become their own fields within the structure? and so on.

Or is it really just a list, meant to carry strings and numbers interchangeably, perhaps with some semantic meaning to strings versus numbers? If so then you could create a new sum type with data constructors to distinguish the “kinds” of values. (CellValue itself is like this, for example, encoding various kinds of cell values into a sum type.) Your new sum type could then be carried in other structures like lists, and then pattern-matched on in order to apply different behaviours to different kinds of items.

Or perhaps both combined would provide the strongest model of what you’re going for, ala a structure carrying fields of other structures (like lists) of sum types, and so on.

Haskell leans pretty hard into encoding concepts, semantics, etc. into the type system. In fact, I dare say its use is predicated on it. With a bit of time spent defining a model of what you want, I’m sure you’ll be able to come up with a very safe and productive encoding.

I spent some time playing around with sum types and records as suggested, and can certainly see the appeal. Some of the more useful reading...

Since most of my exploratory programming tends to rely on the lisp family (for better or worse) "carrying arbitrary types of values in lists" is usually where it begins. I'll probably return to type system wrangling at some point, but for now, it's back to dynamic typing and strings of patterns...

(define (row->pattern name)
  (string-append "d1 $ s \""
                 (let ((seq (get-row-by-name name)))
                   (replace seq 1 name))
                 "\"" ))

Maybe the "Write Yourself a Scheme in 48 Hours" book is another possible entry point...

The last time I used Lisp was nearly thirty years ago, but I definitely get ya. :+1: Even spent some time on a Lisp machine. Parens all the way down. :wink:

With that background in mind, lists of a sum type with simple data constructors would be a good next step when the time comes. It’ll add a pile of safe and easy pattern-matching power without dragging in too many concepts/constructs at once.