-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtidal.txt
1773 lines (1279 loc) · 57.4 KB
/
tidal.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: 'Learning Tidal Fundamentals'
author: 'Mark Zadel'
date: 'December 2021'
fontsize: 12pt
monobackgroundcolor: 'LightGrey'
header-includes: |
<style>
body {
max-width: 60em;
}
img {
max-width: none;
}
</style>
---
## Introduction
These are the notes I made when trying to first understand how [Tidal
Cycles](https://tidalcycles.org/) works.
This isn't about making sound! It's about trying to fundamentally understand
the expressions and values to have a good basis going forward, and about
getting started writing your own Tidal libraries. If you're a musician and you
just want to make music with Tidal, you don't need this information.
This document ended up being more elaborate than I'd originally intended. It
gives an overview of Tidal's types and core APIs, shows how to run it as a
library from ghci, and explores some of its concepts. I think the main
contributions here are a relatively thorough survey of the core code, the
working code examples to try and to modify, and the diagrams. Hopefully it
will be helpful for developers trying to understand Tidal.
## Installing the Tidal library in Haskell
MacOS | Tidal Cycles<br/>
<https://tidalcycles.org/docs/getting-started/macos_install>
### Install via cabal
I tried installing a few different times in a few different ways, and
the method I preferred was the following:
- Use `ghcup` (<https://www.haskell.org/ghcup/>)
- Install `ghc` with `ghcup`
- Add `$HOME/.ghcup/bin:$HOME/.ghcup/ghc/8.10.7/bin` to `$PATH` in `.bashrc`
- Install `cabal` with `ghcup`
- `cabal update`
- `cabal install tidal --lib`
NB: THIS IS NOT SUFFICIENT TO MAKE SOUND!! I'm only interested in
executing expressions and inspecting types at this stage. Look at
the installation instructions on
[tidalcycles.org](https://tidalcycles.org) for installing
SuperCollider, SuperDirt and a compatible text editor, which are all
required for a typical install.
I initially had linker errors when compiling Haskell code. What I
had to do was uninstall ghc and cabal, uninstall all of my homebrew
programs, reinstall ghc and cabal, and then it worked. (It was fine
to reinstall homebrew for me since there was a lot of cruft left
around that I'd just kept upgrading over several years and OS
versions.)
The `--lib` part is important. That makes it possible to run `import
Sound.Tidal.Context` in `ghci`.
You can delete `.ghc`, `.ghcup` and `.cabal` to start over from
scratch if you're trying to debug install problems.
### Alternative: install via stack
You can also use stack:
- Run `stack setup`
- This will download the latest LTS version of GHC
- The LTS resolver version can be checked in `~/.stack/global-project/stack.yaml`: `resolver: lts-18.13`
- Go to <https://www.stackage.org> and click on LTS at the top to check the most recent stable version
- It ends up in `~/.stack/programs/x86_64-osx/ghc-8.10.7/bin/ghc`
- See `stack path`
- Invoke as `stack ghc` or `stack ghci`
- `stack install --no-library-stripping tidal`
You can blow away `~/.stack` to reset your install for
fixing installation problems.
`--no-library-stripping` is important, since it's needed to import
Tidal inside a `ghci` session.
### Installing diagrams (optional, not needed for Tidal)
I also installed `diagrams` for making the diagrams. (You don't need
to do this.)
- With cabal
- `cabal install diagrams-core --lib`
- `cabal install diagrams-lib --lib`
- `cabal install diagrams-svg --lib`
- `cabal install palette --lib`
- With stack
- change your stack install to a nightly instead of LTS. The
LTS one doesn't include diagrams for some reason.
- `stack install --no-library-stripping diagrams-core`
- `stack install --no-library-stripping diagrams-lib`
- `stack install --no-library-stripping diagrams-svg`
- `stack install --no-library-stripping palette`
I couldn't install the top-level `diagrams` library since I don't
think it worked with the `--lib` flag. (I would have to confirm that
detail, though.)
## Sanity checking the basic Tidal install
$ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help
Loaded package environment from /.../.ghc/x86_64-darwin-8.10.7/environments/default
Prelude> import Sound.Tidal.Context
Prelude Sound.Tidal.Context> tidal_version
"1.7.8"
## Starting to look at some expressions
This seems to work in the online examples but not in ghci:
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
s $ "bd bd bd"
~~~
It needs `parseBP_E`, which is automatically, silently added when running from the text editor. In a normal `ghci`:
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
s $ parseBP_E "bd bd bd"
~~~
You can turn on the implicit string handling with `{-# language OverloadedStrings #-}`, or the equivalent `:set -XOverloadedStrings` in `ghci` (see Waldmann article below).
~~~{.ghcisession}
import Sound.Tidal.Context
:set -XOverloadedStrings
s $ "bd bd bd"
~~~
`OverloadedStrings` is described at <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_strings.html> .
I'd prefer not to use it right now since I'm explicitly trying to figure out the types and to understand the core definitions.
## `Core.hs`
The basic functions for making patterns are in `Pattern.hs`, `Core.hs` and
`UI.hs` (as far as I can tell).
Almost all the examples you see start from Tidal's pattern mini-language (like
`s $ "a b c"`), but that's not necessary for making patterns and it's outside
the core functionality. I'm going to start by surveying some of the basic API
in `Core.hs`.
There is API documentation online starting at
<https://tidalcycles.org/docs/patternlib/tour/concatenation>. (See "Small
Reference" at the left.)
A pattern is something you can query to get a list of events. Events have
extents in time and can carry an arbitrary kind of data. From `Pattern.hs`:
data Pattern a = Pattern {query :: State -> [Event a]}
type Event a = EventF (ArcF Time) a
The most basic pattern is made via `pure`:
~~~{.ghcisession #purecycle tidalexpression='pure "eventcontents" :: Pattern String' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
So this is a pattern that has one event per cycle, and the event's contents are
the string `"eventcontents"`.
A cycle is some amount of time, like a bar, or say four bars of music. You can
set it to whatever you want, but it's the basic sync period for a loop.
--------------------------------------------------
You can make an empty pattern with `silence` (`Core.hs`), which is just an
alias for `empty` (`Pattern.hs`).
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
silence
:t silence
~~~
(There are no events to print.)
--------------------------------------------------
You can use `fromList` (`Core.hs`) to make a pattern where each list item
corresponds to an event with length one cycle:
~~~{.ghcisession #fromListExample tidalexpression='fromList ["phi", "psi" , "tau"]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
pat
putStrLn $ showAll (Arc 0 3) pat
:t pat
~~~
`showAll` from `Show.hs` takes an `Arc` argument, so we can print the pattern
over more than one cycle.
--------------------------------------------------
Use `fastFromList` (`Core.hs`) to make a pattern that squeezes each list item
into one cycle:
~~~{.ghcisession #fastFromListExample tidalexpression='fastFromList ["phi", "psi" , "tau"]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
pat
:t pat
~~~
We can also draw the pattern on a circle to reflect its cyclical nature:
~~~{#fastFromListExampleCircular tidalexpression='fastFromList ["phi", "psi" , "tau"]' .diagram .insertdiagram}
~~~
`listToPat` is a synonym from `fastFromList`.
--------------------------------------------------
`fromMaybes` allows you to put gaps in the pattern:
~~~{.ghcisession #fromMaybesExample tidalexpression='fromMaybes [Just "phi", Nothing, Just "tau"]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
pat
:t pat
~~~
--------------------------------------------------
`append` alternates between cycles of two patterns.
~~~{.ghcisession #appendExample tidalexpression='append (fromList [\'a\', \'j\']) (fromList [\'c\', \'k\'])' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
putStrLn $ showAll (Arc 0 8) pat
:t pat
~~~
--------------------------------------------------
`cat` alternates between cycles of several patterns.
~~~{.ghcisession #catExample tidalexpression='cat [fromList [\'a\', \'j\'], fromList [\'c\', \'k\'], fromList [\'e\', \'l\']]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
putStrLn $ showAll (Arc 0 8) pat
:t pat
~~~
`slowCat` and `slowcat` (lowercase) are aliases for `cat`.
--------------------------------------------------
`fastCat` (also `fastcat`), works like `cat` but squeezes all the patterns into one cycle
~~~{.ghcisession #fastCatExample tidalexpression='fastCat [fromList [\'a\', \'j\'], fromList [\'c\', \'k\'], fromList [\'e\', \'l\']]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
putStrLn $ showAll (Arc 0 3) pat
:t pat
~~~
--------------------------------------------------
`timeCat` takes patterns and squeezes them into parts of a cycle. The time
argument for each pattern is its relative duration.
~~~{.ghcisession #timeCatExample tidalexpression='timeCat [(1, fastFromList [\'a\', \'b\']), (2, fastFromList [\'c\', \'d\', \'e\'])]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
You could imagine using this for creating events of arbitrary lengths.
--------------------------------------------------
`overlay` superimposes two patterns, playing them in parallel.
~~~{.ghcisession #overlayExample tidalexpression='overlay (fastFromList [\'a\', \'b\', \'c\', \'d\']) (fastFromList [\'j\', \'k\', \'l\'])' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
`stack` superimposes a list of patterns.
~~~{.ghcisession #stackExample tidalexpression='stack [pure \'a\', fastFromList [\'j\', \'k\', \'l\'], fromList [\'m\', \'n\']]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
`fast` speeds things up
~~~{.ghcisession #fastfunction tidalexpression='fast 3 $ fastFromList [\'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
`drawLine` is a utility function from `Show.hs` that renders out a `Pattern
Char` as ascii art. It only works on char patterns!
--------------------------------------------------
`slow` elongates (i.e., slows down) a pattern
~~~{.ghcisession #slowfunction tidalexpression='slow 2 $ fastFromList [\'a\', \'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
--------------------------------------------------
`fastGap` speeds things up but aligns to the cycle
~~~{.ghcisession #fastgapfunction tidalexpression='fastGap 3 $ fastFromList [\'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
--------------------------------------------------
`compress` squeezes a pattern into a given arc of time
~~~{.ghcisession #compressfunction tidalexpression='compress (1/4,1/2) $ fastFromList [\'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
--------------------------------------------------
`zoom` zooms in on a portion of a pattern. It maps the zoomed portion to the
duration of the input pattern.
~~~{.ghcisession #zoomfunction tidalexpression='zoom (1/4,3/4) $ fastFromList [\'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
(I don't know why the "b" event isn't shown in the `drawLine` output above.)
--------------------------------------------------
`rev` reverses each cycle of a pattern
~~~{.ghcisession tidalexpression='rev $ slow 2 $ fromMaybes [Just \'a\', Nothing, Just \'b\', Nothing, Nothing, Just \'c\']'}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
Input (`slow 2 $ fromMaybes [Just 'a', Nothing, Just 'b', Nothing, Nothing, Just 'c']`):
~~~{#revfunctioninput tidalexpression='slow 2 $ fromMaybes [Just \'a\', Nothing, Just \'b\', Nothing, Nothing, Just \'c\']' .diagram .insertdiagram}
~~~
Output (`rev $ slow 2 $ fromMaybes [Just 'a', Nothing, Just 'b', Nothing, Nothing, Just 'c']`):
~~~{#revfunctionoutput tidalexpression='rev $ slow 2 $ fromMaybes [Just \'a\', Nothing, Just \'b\', Nothing, Nothing, Just \'c\']' .diagram .insertdiagram}
~~~
--------------------------------------------------
`every` applies a function to the input pattern, but only every `n` cycles.
~~~{.ghcisession #everyfunction tidalexpression='every 3 rev $ fastFromList [\'a\', \'b\', \'c\']' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
drawLine $ {{tidalexpression}}
~~~
The '`n` cycles' argument is actually of type `Pattern Int`, so you can vary
the argument over time like this:
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
pat = fastFromList ['a', 'b', 'c']
ints = slow 6 $ cat [2, 3]
drawLine $ every ints rev pat
~~~
For the first six cycles the pattern is reversed every two repetitions; for the
second six cycles the pattern is reversed every three.
--------------------------------------------------
`when` applies a function when the given predicate function returns true.
The predicate is fed the current cycle number.
~~~{.ghcisession #whenfunction tidalexpression='when (\x -> (x+2) `mod` 3 == 0) rev (fastFromList [\'a\',\'b\',\'c\'])' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
drawLine $ {{tidalexpression}}
~~~
--------------------------------------------------
`rotR` shifts a pattern forward in time by a certain number of cycles.
~~~{.ghcisession #rotRexample tidalexpression='rotR (1%3) (fastFromList [\'a\', \'b\', \'c\'])' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
drawLine $ {{tidalexpression}}
~~~
You can use `rotR` and `fastGap` to manually put an event at a particular point
in time with a particular duration.
~~~{.ghcisession #rotRwithfastGap tidalexpression='rotR (3%8) (fastGap 4 (pure \'a\'))' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
## Queries
Patterns can be queried over a time range, returning a list of events. The
scheduler does this repeatedly over small time slices to decide when to send
OSC ([Open Sound Control](https://en.wikipedia.org/wiki/Open_Sound_Control))
messages to SuperCollider. We can use this to check how our pattern will be
rendered.
Each `Pattern` implements `query`:
data Pattern a = Pattern {query :: State -> [Event a]}
data State = State {arc :: Arc,
controls :: ValueMap
}
You pass in a `State`, but for our purposes we'll always leave `controls` empty.
(I understand it to be the current values of any external MIDI controllers,
which we don't need.) That means `query` essentially goes from an `Arc` of
time to a list of `Event`s; that's exactly what `queryArc` does:
queryArc :: Pattern a -> Arc -> [Event a]
queryArc p a = query p $ State a Map.empty
The events that are returned have a `whole` and a `part`, which we'll get into
later.
--------------------------------------------------
~~~{.ghcisession #queryFullCycle .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0 1)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
In this example, each of the output events has a `whole`, and that `whole` is the same as its `part`.
--------------------------------------------------
~~~{.ghcisession #queryHalfCycle .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0 0.5)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
~~~{.ghcisession #queryQuarterCycle .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0 0.25)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
So we see here that the output `Event`'s `whole` is the entire extent of the
input event that intersects with the query `Arc`; the `part` is the
intersection of the query `Arc` and the overlapping input event.
We see `(⅕>¼)-⅖` in the ghci output. The way to read this is
- `(⅕>¼)` is the part, where the query window overlaps with the input event window,
- `⅕` to `⅖` is the whole, and
- `(⅕>¼)-⅖` is a representation that combines both the part and the whole
--------------------------------------------------
~~~{.ghcisession #queryEmptySpace .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0 0.2)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Querying an arc that doesn't overlap any pattern events returns an empty list.
Note here that the right edge of the query arc touches the first pattern event.
--------------------------------------------------
~~~{.ghcisession #queryOverlapOneItemExactly .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.6 0.8)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
~~~{.ghcisession #queryOverlapOneItemExactly2 .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.8 1.0)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
~~~{.ghcisession #queryOverlapTwoItemsExactly .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.6 1.0)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
~~~{.ghcisession #queryOverlapTwoItems .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.7 0.9)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
~~~{.ghcisession #queryTwoItemsZeroWidth .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.8 0.8)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
So if you query a zero-width arc right between two events, it returns only the
second event (with a zero-width part).
--------------------------------------------------
~~~{.ghcisession #queryOneItemZeroWidth .queryexample tidalexpression='queryArc (fromMaybes [Nothing,Just \'a\', Nothing, Just \'c\', Just \'d\']) (Arc 0.75 0.75)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Note here how the event's extents are notated: `⅗-(¾>¾)-⅘`.
### Querying continuous patterns
Tidal also has a concept of a continuous pattern.
~~~{#simplesine tidalexpression='sine :: Pattern Double' .diagram .insertdiagram}
~~~
(This is `sine` from `Core.hs`.)
These patterns accept `query` like any other pattern.
~~~{.ghcisession #querycontinuouspattern .queryexamplecontinuous tidalexpression='queryArc sine (Arc 0.5 0.75)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Let's look into the details to see what the query is returning and where the
floating point value is coming from.
~~~{.ghcisession #querycontinuouspatternshowpoint .queryexamplecontinuous tidalexpression='queryArc sine (Arc 0.5 0.75)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
results = {{tidalexpression}}
results
:t results
length results
event : _ = results
:t event
whole event
part event
value event
~~~
So we see that querying a continuous pattern returns events with parts and
values, but no wholes.
The notation for continuous results includes tildes: `~½>¾~`.
Note too that the query samples the continuous function in the middle of the
query arc.
~~~{.ghcisession #querycontinuouspatternsamplesatmidpoint .queryexamplecontinuous tidalexpression='queryArc sine (Arc 0.3 0.5)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
queryArc sine (0.4 :: Arc)
~~~
If you want to sample a continuous pattern at exactly one point, use a zero-width arc.
~~~{.ghcisession #querycontinuouspatternzwarc .queryexamplecontinuous tidalexpression='queryArc sine (Arc 0.22 0.22)' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Tidal calls continuous patterns "analog" and discrete patterns "digital":
isAnalog :: Event a -> Bool
isAnalog (Event {whole = Nothing}) = True
isAnalog _ = False
isDigital :: Event a -> Bool
isDigital = not . isAnalog
(from `Pattern.hs`)
## Basic types (`Pattern.hs`)
We've seen several examples of patterns, events and queries. Hopefully that
will have built up enough intuition to make sense of the basic types.
`Pattern` is defined in `Sound/Tidal/Pattern.hs`.
-- | A datatype representing events taking place over time
data Pattern a = Pattern {query :: State -> [Event a]}
deriving (Generic, Functor)
A pattern is something that can be queried, returning a list of events.
An `Arc` is an interval of time. Time is expressed as a rational (i.e., a fraction) (defined in `Sound/Tidal/Time.hs`)
-- | Time is rational
type Time = Rational
-- | An arc of time, with a start time (or onset) and a stop time (or offset)
data ArcF a = Arc
{ start :: a
, stop :: a
} deriving (Eq, Ord, Functor, Show, Generic)
type Arc = ArcF Time
I think the reason it's called an arc is that a cycle is imagined to lie on a circle. An arc is a portion of that circle.
`Event`s are defined thusly (see `Sound/Tidal/Pattern.hs`):
-- | An event is a value that's active during a timespan. If a whole
-- is present, the part should be equal to or fit inside it.
data EventF a b = Event
{ context :: Context
, whole :: Maybe a
, part :: a
, value :: b
} deriving (Eq, Ord, Functor, Generic)
type Event a = EventF (ArcF Time) a
So events are understood to be boxes on the timeline with a beginning and an
end, which contain a value. We also have the notion of looking at a subsection
of an event, hence the `whole` and `part` distinction. `whole` is `Maybe a`,
so it might be `Nothing`, which means that it's from an analog (continuous)
pattern.
`value` is, of course, the value that the event contains.
The context is apparently the position within the source code. I guess this is
used when interpreting expressions in a live coding context.
There are some utility functions to access an event's fields in `Pattern.hs`:
`isAnalog`, `isDigital`, `wholeStart`, `wholeStop`, `eventPartStart`,
`eventPartStop`, etc.
## Patterns as numbers
You can perform many operations on patterns as though they were numbers:
~~~{.ghcisession #numberpatternmin .intpatternexample tidalexpression='min 27 $ fastFromList [11,22,33,44]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession #numberpatternmax .intpatternexample tidalexpression='max 27 $ fastFromList [11,22,33,44]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession #numberpatternplus .intpatternexample tidalexpression='fastFromList [11,22,33,44] + 3' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession #numberpatterntimes .intpatternexample tidalexpression='2 * fastFromList [11,22,33,44]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession #numberpatternmod .intpatternexample tidalexpression='mod (fastFromList [11,22,33,44]) 4' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession #numberpatternsqrt .doublepatternexample tidalexpression='sqrt $ fastFromList [2,9,16,100]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Neat.
## Pattern Algebra
What happens when we straight up add two patterns together?
~~~{.ghcisession #additionexample .patternalgebraexample tidalexpression='fastFromList [1, 2, 3] + fastFromList [20, 40] :: Pattern Int' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Tidal adds the patterns together by combining the events and values from both
input patterns. Internally, Tidal does the addition via `applyPatToPatBoth` in `Pattern.hs`.
There are other addition operators that combine the events in alternate ways
that call `applyPatToPatLeft` and `applyPatToPatRight` instead.
The `(|+)` operator is addition, taking structure (i.e., the wholes) from the left (`src/Sound/Tidal/Core.hs`).
~~~{.ghcisession .patternalgebraexample #leftPlusExample1 tidalexpression='fastFromList [1, 2, 3] |+ fastFromList [20, 40] :: Pattern Int' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
(In the text output, notice that the "22" and the "42" entries correspond to
the same whole event. I understand this to mean that the value changes midway
though the event, but the onset still happens at the "22" in this case. I
later confirmed with `oscdump` and a live Tidal REPL that that's what's
happening -- the value that is lined up when the onset happens is the one that
gets sent. The change midway through the event does nothing.)
~~~{.ghcisession .patternalgebraexample #leftPlusExample2 tidalexpression='fastFromList [1, 2, 3] |+ fastFromList [20, 40, 60] :: Pattern Int' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
~~~{.ghcisession .patternalgebraexample #leftPlusExample3 tidalexpression='fastFromList [1, 2, 3] |+ fastFromList [20, 40, 60, 80] :: Pattern Int' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
--------------------------------------------------
The `+|` operator does addition, but takes the events from the right argument:
~~~{.patternalgebraexample #rightPlusExample1 tidalexpression='fastFromList [1, 2, 3] +| fastFromList [20, 40] :: Pattern Int' .diagram .insertdiagram}
~~~
~~~{.patternalgebraexample #rightPlusExample2 tidalexpression='fastFromList [1, 2, 3] +| fastFromList [20, 40, 60] :: Pattern Int' .diagram .insertdiagram}
~~~
~~~{.patternalgebraexample #rightPlusExample3 tidalexpression='fastFromList [1, 2, 3] +| fastFromList [20, 40, 60, 80] :: Pattern Int' .diagram .insertdiagram}
~~~
--------------------------------------------------
The `|+|` operator does addition, but takes the events from both arguments:
~~~{.patternalgebraexample #bothPlusExample1 tidalexpression='fastFromList [1, 2, 3] |+| fastFromList [20, 40] :: Pattern Int' .diagram .insertdiagram}
~~~
~~~{.patternalgebraexample #bothPlusExample2 tidalexpression='fastFromList [1, 2, 3] |+| fastFromList [20, 40, 60] :: Pattern Int' .diagram .insertdiagram}
~~~
~~~{.patternalgebraexample #bothPlusExample3 tidalexpression='fastFromList [1, 2, 3] |+| fastFromList [20, 40, 60, 80] :: Pattern Int' .diagram .insertdiagram}
~~~
--------------------------------------------------
If you use `+`, it's like `|+|`:
~~~{.patternalgebraexample #justPlusExample1 tidalexpression='fastFromList [1, 2, 3] + fastFromList [20, 40, 60, 80] :: Pattern Int' .diagram .insertdiagram}
~~~
--------------------------------------------------
There are a bunch of operators that work in the same way documented
at <https://tidalcycles.org/docs/patternlib/tutorials/pattern_structure>.
## Applying a pattern of functions
The above are examples of `Pattern`'s `Applicative` instance, which is a
Haskell thing. It allows you to apply a pattern of unary functions to a
pattern of arguments.
instance Applicative Pattern where
-- | Repeat the given value once per cycle, forever
pure v = Pattern $ \(State a _) ->
map (\a' -> Event (Context []) (Just a') (sect a a') v) $ cycleArcsInArc a
(<*>) = applyPatToPatBoth
~~~{.ghcisession #applicativeboth .applicativeexample tidalexpression='fastFromList [ (2.0*), exp, (max 7) ] <*> fastFromList [1, 2] :: Pattern Double' .diagram .insertdiagram}
import Prelude hiding ((<*), (*>))
import Sound.Tidal.Context
--cut>>
a = {{left}}
:t a
b = {{right}}
a {{operator}} b
~~~
There are also the operators `<*` and `*>`, which take the structure from the left and right, respectively.
~~~{.ghcisession #applicativeleft .applicativeexample tidalexpression='fastFromList [ (2.0*), exp, (max 7) ] <* fastFromList [1, 2] :: Pattern Double' .diagram .insertdiagram}
--cut>>
import Prelude hiding ((<*), (*>))
import Sound.Tidal.Context
a = {{left}}
b = {{right}}
a {{operator}} b
~~~
~~~{.ghcisession #applicativeright .applicativeexample tidalexpression='fastFromList [ (2.0*), exp, (max 7) ] *> fastFromList [1, 2] :: Pattern Double' .diagram .insertdiagram}
import Prelude hiding ((<*), (*>))
import Sound.Tidal.Context
a = {{left}}
b = {{right}}
--cut>>
a {{operator}} b
~~~
(Only the first event contains the whole's onset, so that's the one that'll get
sent out via OSC.)
These operators are the more general ones that are called into by `|+`, `+|`, etc.
## Mapping over a pattern
You can map a function over a `Pattern`. It'll apply the function to that
pattern's events.
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
fmap abs fastFromList [ 2, -3, 1, -1, 4 ]
fmap ((+3) . (*2)) fastFromList [ 1, 2, 3, 4 ]
~~~
There are also various filter functions defined in `Pattern.hs`.
~~~{.ghcisession #filtervaluesexample .intpatternexample tidalexpression='filterValues (> 0) $ fastFromList [ 2, -3, 1, -1, 4 ]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
See `filterJust`, `filterWhen`, `filterOnsets`, `filterEvents`, etc.
## Patterns of patterns
You can of course define patterns of patterns as well.
~~~{.ghcisession}
import Sound.Tidal.Context
--cut>>
pat = fastFromList [ fastFromList ['a','b','c'], fastFromList ['d','e'] ]
pat
:t pat
~~~
The way to read this is that there's one event on `(0>½)` that contains the first pattern,
and there's a second event on `(½>1)` that contains the second pattern.
--------------------------------------------------
You can use `unwrap` if you want to flatten the patterns:
~~~{.ghcisession #unwrapexample tidalexpression='unwrap $ fastFromList [ fastFromList [\'a\',\'b\',\'c\'], fastFromList [\'j\',\'k\',\'l\',\'m\'] ]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
pat = {{tidalexpression}}
pat
:t pat
~~~
So this is allowing the inner patterns to shine through the windows defined by
the events they're contained in. The wholes and the parts are cleaned up so
that they're coincident for every event.
--------------------------------------------------
`innerJoin` flattens the pattern but takes the structure from the inner pattern.
~~~{.ghcisession #innerjoinexample tidalexpression='innerJoin $ fastFromList [ fastFromList [\'a\',\'b\',\'c\'], fastFromList [\'j\',\'k\',\'l\',\'m\'] ]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
The events are defined by the inner patterns over the entire cycle, and those
windowed by the outer events they're contained in. This looks like the
`unwrap` case, but the wholes from the inner pattern are preserved in the
output events.
We can emulate this with `queryArc` to help understand what `innerJoin` is doing:
~~~{.ghcisession #innerjoinasqueryarc}
import Sound.Tidal.Context
--cut>>
queryArc (fastFromList ['a','b','c']) (Arc 0 0.5) ++ queryArc (fastFromList ['j','k','l','m']) (Arc 0.5 1)
~~~
--------------------------------------------------
`outerJoin` flattens the pattern but takes the structure from the outer pattern.
~~~{.ghcisession #outerjoinexample tidalexpression='outerJoin $ fastFromList [ fastFromList [\'a\',\'b\',\'c\'], fastFromList [\'j\',\'k\',\'l\',\'m\'] ]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
Note that the wholes come from the outer pattern.
--------------------------------------------------
Finally, there's `squeezeJoin`, which compresses each of the inner patterns
into the event they're contained in.
~~~{.ghcisession #squeezejoinexample tidalexpression='squeezeJoin $ fastFromList [ fastFromList [\'a\',\'b\',\'c\'], fastFromList [\'j\',\'k\',\'l\',\'m\'] ]' .diagram .insertdiagram}
import Sound.Tidal.Context
--cut>>
{{tidalexpression}}
~~~
## Manually defining the query function