A way to control grouped elements parameters individually with mininotation?

Hello,

While playing with Tidal I wanted to control grouped chords gains individually e.g every event to have different gain (or any control parameter) dynamics. But it outputs whole chords:

d1 $ n "[0,12,24]" # s "superreese" # gain "<[1,1,1] [1,0,0] [0,1,0] [0,0,1] [1,1,0] [1,0,1] [0,1,1]>"

I tried above because I know SC can do it similary:

Pbindef(\a,
  \instrument, \superreese,
  \note, [0,12,24],
  \amp, Pseq([[1,1,1], [1,0,0], [0,1,0], [0,0,1], [1,1,0], [1,0,1], [0,1,1]], inf).trace,
  // \legato, Pseq([[1,1,1], [1,0,0], [0,1,0], [0,0,1], [1,1,0], [1,0,1], [0,1,1]], inf) * [0.9,0.7,0.1]
).play

Ofc I can rewrite like below to change individual chords gains, but it becomes too complicated if I want to control more parameters individually:

d1 $ n "<[0,12,24] [0] [12] [24] [0,12] [0,24] [12,24]>" # s "superreese" -- # legato ? # lpf ???

Maybe someone know clean way to achieve above in Tidal? Or is it even possible?

The fix function is usually the way to do it.

fix fn prop p applies function fn only to the events in p that match prop. You could use it like this:

d1
  $ fix (# gain "1 1 0 0 1 1 0") (n 0)
  $ fix (# gain "1 0 1 0 1 0 1") (n 12)
  $ fix (# gain "1 0 0 1 0 1 1") (n 24)
  $ n "[0,12,24]" # s "superreese"

The problem with your approach is that Pattern does not really have a notion of chords. It knows only events that may or may not occur at the same time. If you're willing to dig deep into Haskell and Tidal, you might be able to write a function that applies the gain as you wish. Modelling chords as Pattern [Note] might do the trick and you can have a look at this thread (How to apply functions to number Patterns?) to get an idea how it might be implemented.

1 Like

Hey,
I've been wanting the same functionality for a while, so I went ahead and tried to implement it. For my implementation to work, the events in the patterns always have to have the same order ( some functions might mess with this ) . So I don't think it is a general solution to the problem, but it allows to express what you need, let me know if you find any unexpected behaviour.

let (##) :: Unionable a => Pattern a -> Pattern a -> Pattern a
	(##) a b = applyOneByOneFromLeft (union <$> a) b
    
let
applyOneByOneFromLeft :: Pattern (a -> b) -> Pattern a -> Pattern b
applyOneByOneFromLeft pf px = Pattern q
    where q st = catMaybes $ concat $ zipWith match [0..length fQuery - 1] fQuery
            where
              fQuery = query pf st
              match n ef = case length xQuery > n of
                                              True -> [withFX ef (xQuery!!n)]
                                              False -> []
                         where xQuery = query px $ st {arc = wholeOrPart ef}
              withFX ef ex = do let whole' = whole ef
                                part' <- subArc (part ef) (part ex)
                                return (Event (combineContexts [context ef, context ex]) whole' part' (value ef $ value ex))

after evaluating the above you can get your example to work like follows:

d1 $ n "[0,12,24]" # s "superreese" ## gain "<[1,1,1] [1,0,0] [0,1,0] [0,0,1] [1,1,0] [1,0,1] [0,1,1]>"

1 Like

is catMaybes another custom function you implemented elsewhere? i am getting errors when evaluating the above let blocks

what kind of errors?

I just modified the definition of applyPatToPatLeft and catMaybes was in there, you might have to import some library though (I'm using my custom gui which has all libraries that tidal uses imported by default, so I can't really tell where it's from)

catMaybes can be found like this: catMaybes package:base - Hoogle
The result is Data.Maybe which also contains examples.

NB: This search also works for names for functions and types in Tidal (since it is a package on hackage and stackage).

... and it also works the other way (know the type, find the function), e.g., Pattern a -> Pattern a package:tidal - Hoogle

   Variable not in scope: catMaybes :: [Maybe (EventF Arc b)] -> t

is the error i am getting

mhm, like @jwaldmann above said, you just need to import Data.Maybe by evaluating the following:

import Data.Maybe

1 Like

I didn't exactly say it, sorry for leaving that implicit. Some names (of Haskell library functions) are visible (because they are brought into scope by Sound.Tidal.Context, which is loaded by BootTidal.hs), and some others (like Data.Maybe) are not.

1 Like

Hey,
thanks for implementation. I evaluated a function and tried to run example, however cannot hear or see any synths created with no error messages.

got it. thank you! yes, no errors on your let blocks now :slight_smile:

1 Like

yes you're right, something seems to be not working. I coded without testing it with sounds again (if you apply show to the pattern, instead of d1 you should see that the output seems to be correct..)

I guess I'll have to think it through again

i am getting sound, albeit slowly. speeding things up, i am getting a more syncopated feel from the gain patterns whereas i would have expected a uniform rhythm coming from having the angled brackets only contain one event per cycle.

d1 $ fast 8 $ n "[0,4,7]" # s "supersaw" ## gain "<[1,1,1] [1,0,0] [0,1,0] [0,0,1] [1,1,0] [1,0,1] [0,1,1]>"

Intresting. It works for me as expected, if I put fast 1 in front like this:

d1 $ fast 1 $ n "[0,12,24]" # s "superreese" ## gain "<[1,1,1] [1,0,0] [0,1,0] [0,0,1] [1,1,0] [1,0,1] [0,1,1]>"

don't know what you mean by more syncopated feel though..

I think @yaxu needs to help us with this one (if he has time! :slight_smile: )

1 Like

ok yes, fast 1 gives me the expected result. but why, i wonder does removing the fast 1 make it much slower, if fast 1 shouldn't in fact change anything at all?

yes - it is indeed very strange

Interesting! Using your definition of (##), I get

 flip queryArc (Arc 0 2)                                                                
  $ fast 1                                                                               
  $ s "superreese"                                                                       
  ## gain "1" 

[[((1,1),(11,1)),((1,1),(2,1))](0>1)|gain: 1.0f, s: "superreese"
,[((1,1),(11,1)),((1,1),(2,1))](1>2)|gain: 1.0f, s: "superreese"]

but when I remove the fast 1

 flip queryArc (Arc 0 2)                                                                
  --  $ fast 1                                                                           
  $ s "superreese"                                                                       
  ## gain "1"  

[[((1,1),(11,1)),((1,1),(2,1))](0>1)|gain: 1.0f, s: "superreese"]

the event for (1>2) is missing.

I checked that _fast 1 is fine, so the extra event must be produced by innerJoin (called via tParam).

I've thought about it some more and I'm pretty sure it's more difficult to implement than how it is currently. Although we might get it to work with these simple examples, it will break instantly with a little more complex patterns that involve rythms (like s "[bd*2,sn] ## gain "[0.5,1]")

I don't really see a way to make it work, but if somebody has an idea I'm happy to discuss it !

@jwaldmann question for you...i am getting an error on the union function here because it looks like there are conflicting libraries containing it.

Ambiguous occurrence ‘union’
    It could refer to either ‘Data.List.union’,
                             imported from ‘Data.List’
                             (and originally defined in ‘base-4.11.1.0:Data.OldList’)
                          or ‘Sound.Tidal.Context.union’,
                             imported from ‘Sound.Tidal.Context’
                             (and originally defined in ‘Sound.Tidal.Core’)

in my boottidal.hs file i am bringing in the following...

import Sound.Tidal.Context

import Sound.Tidal.Bjorklund (bjorklund)

import System.IO (hSetEncoding, stdout, utf8)
hSetEncoding stdout utf8

:m +  Data.List

:m +  Data.Maybe

:m + Sound.Tidal.Utils

is there a redundancy somewhere i can eliminate?

Hi. When writing import declarations, these conflicts can be solved by qualifying names, or hiding unqualified names.

  • import qualified Data.List as L, then write L.inits, L.tails, etc.
  • import Data.List hiding (union)

I don't think these mechanisms are available with :m. (But 3. Using GHCi — Glasgow Haskell Compiler 9.2.1 User's Guide)