{-# LANGUAGE OverloadedStrings #-}
{-|
Mapping and Abstractions for the analog four-voice synthesizer, TETR4 by Dave Smith Instruments.

Rather than being a tutorial on how to write your own mappings for your MIDI devices, this is more like the kitchen sink: it shows what is possible.

The DSI TETR4 has four separate voices that can (when set to multi-mode) be controlled via four different MIDI channels. Almost everything you can control directly on the device can be automated via MIDI messages.
-}
module Sound.Tidal.MIDI.Tetra where

import Sound.Tidal.MIDI.Control

import Sound.Tidal.Params
       hiding (cutoff_p, attack_p, decay_p, sustain_p, release_p)
import Sound.Tidal.Pattern (Pattern(..), atom)
import Sound.Tidal.Stream
       ((|+|), (|=|), ( # ), merge, ParamPattern)

import Control.Applicative

{-|
The controller mapping for TETR4

To get the most out of this device with tidal, I chose to use the non-registered parameter (NRPN) variant to control it. This allows to use the full spectrum for controls like @cutoff@ that allow more granular stepping than standard MIDI (164 instead of 128 steps).

Many parameters diverge from the default ranges of 0 to 127 and therefore this mapping uses the @mapRange@ functionality for controls to allow you to always use double params, e.g.:

>>> t1 $ n (run 4) # famt "0.5" # cutoff "0.3" # resonance "0.8"

while @famt@, @cutoff@ and @resonance@ all have different ranges of operation (0..254, 0..14 and 0..127 respectively)

Some parameters are essentially mode switches and I chose to pass the values you enter into patterns directly through to the resulting control change. The method @passThru@ on control specification simply skips the scaling of values (you can write your own scaler if you want).

For very generic control I reused Tidal's own params, like @cutoff@, @attack@, @gain@, @pan@ and @release@. Sometimes the defaults for Tidal do not make sense for MIDI. See @(_, cutoff_p)@ below the actual controller shape for examples on how to deal with this.
-}
tetraController :: ControllerShape
tetraController =
  ControllerShape
  { controls =
      [ mrNRPN osc1freq_p 0 (0, 120)
      , mrNRPN osc1detune_p 1 (0, 100)
      , NRPN osc1shape_p 2 (0, 103) passThru
      , mNRPN osc1glide_p 3
      , NRPN osc1kbd_p 4 (0, 1) passThru
      , mrNRPN osc2freq_p 5 (0, 120)
      , mrNRPN osc2detune_p 6 (0, 100)
      , NRPN osc2shape_p 7 (0, 103) passThru
      , mNRPN osc2glide_p 8
      , NRPN osc2kbd_p 9 (0, 1) passThru
      , NRPN oscsync_p 10 (0, 1) passThru
      , NRPN glidemode_p 11 (0, 3) passThru
      , NRPN oscslop_p 12 (0, 5) passThru
      , mrNRPN oscmix_p 13 (0, 127)
      , mNRPN noise_p 14
      , mrNRPN cutoff_p 15 (0, 164)
      , mNRPN resonance_p 16
      , mNRPN kamt_p 17
      , mNRPN audiomod_p 18
      , NRPN fpoles_p 19 (0, 1) passThru
                                  -- filter envelope
      , mrNRPN famt_p 20 (0, 254)
      , mNRPN fvel_p 21
      , mNRPN fdel_p 22
      , mNRPN fatk_p 23
      , mNRPN fdcy_p 24
      , mNRPN fsus_p 25
      , mNRPN frel_p 26
      , mNRPN vcavol_p 27
      , mNRPN pan_p 28
      , mNRPN gain_p 29 -- max volume by default
      , mrNRPN vamt_p 30 (0, 127) -- max vca envelope amount
      , mNRPN vvel_p 31
      , mNRPN vdel_p 32
      , mNRPN attack_p 33
      , mrNRPN decay_p 34 (0, 127)
      , mrNRPN sustain_p 35 (0, 127)
      , mNRPN release_p 36
      , NRPN lfo1rate_p 37 (0, 166) passThru
      , NRPN lfo1shape_p 38 (0, 4) passThru
      , mNRPN lfo1amt_p 39
      , NRPN lfo1dest_p 40 (0, 43) passThru
      , NRPN lfo1sync_p 41 (0, 1) passThru
      , NRPN lfo2rate_p 42 (0, 166) passThru -- unsynced
      , NRPN lfo2shape_p 43 (0, 4) passThru
      , mNRPN lfo2amt_p 44
      , NRPN lfo2dest_p 45 (0, 43) passThru
      , NRPN lfo2sync_p 46 (0, 1) passThru
      , NRPN lfo3rate_p 47 (0, 166) passThru -- unsynced
      , NRPN lfo3shape_p 48 (0, 4) passThru
      , mNRPN lfo3amt_p 49
      , NRPN lfo3dest_p 50 (0, 43) passThru
      , NRPN lfo3sync_p 51 (0, 1) passThru
      , NRPN lfo4rate_p 52 (0, 166) passThru -- unsynced
      , NRPN lfo4shape_p 53 (0, 4) passThru
      , mNRPN lfo4amt_p 54
      , NRPN lfo4dest_p 55 (0, 43) passThru
      , NRPN lfo4sync_p 56 (0, 1) passThru
      , NRPN emod_p 57 (0, 43) passThru
      , mrNRPN eamt_p 58 (0, 254)
      , mNRPN evel_p 59
      , mNRPN edel_p 60
      , mNRPN eatk_p 61
      , mNRPN edcy_p 62
      , mNRPN esus_p 63
      , mNRPN erel_p 64
      , NRPN mod1src_p 65 (0, 20) passThru
      , mrNRPN mod1amt_p 66 (0, 254)
      , NRPN mod1dst_p 67 (0, 47) passThru
      , NRPN mod2src_p 68 (0, 20) passThru
      , mrNRPN mod2amt_p 69 (0, 254)
      , NRPN mod2dst_p 70 (0, 47) passThru
      , NRPN mod3src_p 71 (0, 20) passThru
      , mrNRPN mod3amt_p 72 (0, 254)
      , NRPN mod3dst_p 73 (0, 47) passThru
      , NRPN mod4src_p 74 (0, 20) passThru
      , mrNRPN mod4amt_p 75 (0, 254)
      , NRPN mod4dst_p 76 (0, 47) passThru
      , NRPN seq1dst_p 77 (0, 47) passThru
      , NRPN seq2dst_p 78 (0, 47) passThru
      , NRPN seq3dst_p 79 (0, 47) passThru
      , NRPN seq4dst_p 80 (0, 47) passThru
      , mrNRPN mwhl_p 81 (0, 254)
      , NRPN mwhldst_p 82 (0, 47) passThru
      , mrNRPN aftt_p 83 (0, 254)
      , NRPN afttdst_p 84 (0, 47) passThru
      , mrNRPN breath_p 85 (0, 254)
      , NRPN breathdst_p 86 (0, 47) passThru
      , mrNRPN mvel_p 87 (0, 254)
      , NRPN mveldst_p 88 (0, 47) passThru
      , mrNRPN foot_p 89 (0, 254)
      , NRPN footdst_p 90 (0, 47) passThru
      , NRPN kbpm_p 91 (30, 250) passThru
      , NRPN clockdiv_p 92 (0, 12) passThru -- TODO: document values
      , NRPN bendrng_p 93 (0, 12) passThru
      , NRPN sqntrig_p 94 (0, 4) passThru -- TODO: document values
      , NRPN unisonkey_p 95 (0, 5) passThru -- TODO: document values
      , NRPN unisonmode_p 96 (0, 4) passThru -- TODO: document values
      , NRPN arpmode_p 97 (0, 14) passThru
      , NRPN erepeat_p 98 (0, 1) passThru
      , NRPN unison_p 99 (0, 1) passThru
      , NRPN arp_p 100 (0, 1) passThru
      , NRPN sqn_p 101 (0, 1) passThru
      , NRPN mcr1_p 105 (0, 183) passThru
      , NRPN mcr2_p 106 (0, 183) passThru
      , NRPN mcr3_p 107 (0, 183) passThru
      , NRPN mcr4_p 108 (0, 183) passThru
      , mNRPN shape_p 110
      , mrNRPN btnfreq_p 111 (0, 127)
      , mrNRPN btnvel_p 112 (0, 127)
      , NRPN btnmode_p 113 (0, 1) passThru
      , mNRPN pitch1_p 114
      , mNRPN pitch2_p 115
      , mNRPN fbvol_p 116
                                  -- left out: editor byte,
      , mrNRPN ksplitpoint_p 118 (0, 127)
                                  -- each patch has layer a/b
      , NRPN kmode_p 119 (0, 2) passThru -- 0: normal, 1: stack, both layers respond to full key range, 2: split at ksplitpoint
      , mCC notesoff_p 123
      , mCC ccreset_p 121
      , mCC damp_p 64
      ]
  , latency = 0.1
  }

tetra = toShape tetraController

(notesoff, notesoff_p) = pF "notesoff" (Just 0)

(ccreset, ccreset_p) = pF "ccreset" (Just 0)

(damp, damp_p) = pF "damp" (Just (-1))

(osc1freq, osc1freq_p) = pF "osc1freq" (Just 0.2)

(osc1detune, osc1detune_p) = pF "osc1detune" (Just 0.5)

(osc1shape, osc1shape_p) = pI "osc1shape" (Just 1)

(osc1glide, osc1glide_p) = pF "osc1glide" (Just 0)

(osc1kbd, osc1kbd_p) = pI "osc1kbd" (Just 1)

(osc2freq, osc2freq_p) = pF "osc2freq" (Just 0.2)

(osc2detune, osc2detune_p) = pF "osc2detune" (Just 0.5)

(osc2shape, osc2shape_p) = pI "osc2shape" (Just 1)

(osc2glide, osc2glide_p) = pF "osc2glide" (Just 0)

(osc2kbd, osc2kbd_p) = pI "osc2kbd" (Just 1)

(oscsync, oscsync_p) = pF "oscsync" (Just 0)

(glidemode, glidemode_p) = pI "glidemode" (Just 0)

(oscslop, oscslop_p) = pF "oscslop" (Just 0)

(oscmix, oscmix_p) = pF "oscmix" (Just 0.5)

(noise, noise_p) = pF "noise" (Just 0)

(kamt, kamt_p) = pF "kamt" (Just 0.5)

-- overridden defaults for MIDI
(_, cutoff_p) = pF "cutoff" (Just 1)

(_, attack_p) = pF "attack" (Just 0.12)

(_, release_p) = pF "release" (Just 0.01)

(_, decay_p) = pF "decay" (Just 0.5)

(_, sustain_p) = pF "sustain" (Just 0.5)

(audiomod, audiomod_p) = pF "audiomod" (Just 0)

(fpoles, fpoles_p) = pI "fpoles" (Just 0)

twopole = fpoles "0"

fourpole = fpoles "1"

-- filter envelope
(famt, famt_p) = pF "famt" (Just 0.5)

(fvel, fvel_p) = pF "fvel" (Just 0)

(fdel, fdel_p) = pF "fdel" (Just 0)

(fatk, fatk_p) = pF "fatk" (Just 0.01)

(fdcy, fdcy_p) = pF "fdcy" (Just 0)

(fsus, fsus_p) = pF "fsus" (Just 0)

(frel, frel_p) = pF "frel" (Just 0.01)

(vcavol, vcavol_p) = pF "vcavol" (Just 0)

(vamt, vamt_p) = pF "vamt" (Just 1)

(vvel, vvel_p) = pF "vvel" (Just 0)

(vdel, vdel_p) = pF "vdel" (Just 0)

(lfo1rate, lfo1rate_p) = pF "lfo1rate" (Just 0)

(lfo1shape, lfo1shape_p) = pI "lfo1shape" (Just 0)

(lfo1amt, lfo1amt_p) = pF "lfo1amt" (Just 0)

(lfo1dest, lfo1dest_p) = pF "lfo1dest" (Just 0)

(lfo1sync, lfo1sync_p) = pI "lfo1sync" (Just 0)

(lfo2rate, lfo2rate_p) = pF "lfo2rate" (Just 0)

(lfo2shape, lfo2shape_p) = pI "lfo2shape" (Just 0)

(lfo2amt, lfo2amt_p) = pF "lfo2amt" (Just 0)

(lfo2dest, lfo2dest_p) = pF "lfo2dest" (Just 0)

(lfo2sync, lfo2sync_p) = pI "lfo2sync" (Just 0)

(lfo3rate, lfo3rate_p) = pF "lfo3rate" (Just 0)

(lfo3shape, lfo3shape_p) = pI "lfo3shape" (Just 0)

(lfo3amt, lfo3amt_p) = pF "lfo3amt" (Just 0)

(lfo3dest, lfo3dest_p) = pF "lfo3dest" (Just 0)

(lfo3sync, lfo3sync_p) = pI "lfo3sync" (Just 0)

(lfo4rate, lfo4rate_p) = pF "lfo4rate" (Just 0)

(lfo4shape, lfo4shape_p) = pI "lfo4shape" (Just 0)

(lfo4amt, lfo4amt_p) = pF "lfo4amt" (Just 0)

(lfo4dest, lfo4dest_p) = pF "lfo4dest" (Just 0)

(lfo4sync, lfo4sync_p) = pI "lfo4sync" (Just 0)

(emod, emod_p) = pF "emod" (Just 0)

(eamt, eamt_p) = pF "eamt" (Just 0.5)

(evel, evel_p) = pF "evel" (Just 0)

(edel, edel_p) = pF "edel" (Just 0)

(eatk, eatk_p) = pF "eatk" (Just 0.01)

(edcy, edcy_p) = pF "edcy" (Just 0)

(esus, esus_p) = pF "esus" (Just 0)

(erel, erel_p) = pF "erel" (Just 0.01)

(mod1src, mod1src_p) = pF "mod1src" (Just 0)

(mod1amt, mod1amt_p) = pF "mod1amt" (Just 0.5)

(mod1dst, mod1dst_p) = pF "mod1dst" (Just 0)

(mod2src, mod2src_p) = pF "mod2src" (Just 0)

(mod2amt, mod2amt_p) = pF "mod2amt" (Just 0.5)

(mod2dst, mod2dst_p) = pF "mod2dst" (Just 0)

(mod3src, mod3src_p) = pF "mod3src" (Just 0)

(mod3amt, mod3amt_p) = pF "mod3amt" (Just 0.5)

(mod3dst, mod3dst_p) = pF "mod3dst" (Just 0)

(mod4src, mod4src_p) = pF "mod4src" (Just 0)

(mod4amt, mod4amt_p) = pF "mod4amt" (Just 0.5)

(mod4dst, mod4dst_p) = pF "mod4dst" (Just 0)

(seq1dst, seq1dst_p) = pF "seq1dst" (Just 0)

(seq2dst, seq2dst_p) = pF "seq2dst" (Just 0)

(seq3dst, seq3dst_p) = pF "seq3dst" (Just 0)

(seq4dst, seq4dst_p) = pF "seq4dst" (Just 0)

(mwhl, mwhl_p) = pF "mwhl" (Just 0.5)

(mwhldst, mwhldst_p) = pI "mwhldst" (Just 0)

(aftt, aftt_p) = pF "aftt" (Just 0.5)

(afttdst, afttdst_p) = pI "afttdst" (Just 0)

(breath, breath_p) = pF "breath" (Just 0.5)

(breathdst, breathdst_p) = pI "breathdst" (Just 0)

(mvel, mvel_p) = pF "mvel" (Just 0.5)

(mveldst, mveldst_p) = pI "mveldst" (Just 0)

(foot, foot_p) = pF "foot" (Just 0.5)

(footdst, footdst_p) = pI "footdst" (Just 0)

(bendrng, bendrng_p) = pI "bendrng" (Just 0)

-- left out: modwheel, breath, footctrl, pressure, velocity
(kbpm, kbpm_p) = pI "kbpm" (Just 0)

(clockdiv, clockdiv_p) = pI "clockdiv" (Just 0)

-- left out: pitchbend range
(sqntrig, sqntrig_p) = pF "sqntrig" (Just 0)

(unisonkey, unisonkey_p) = pF "unisonkey" (Just 0)

(unisonmode, unisonmode_p) = pF "unisonmode" (Just 0)

(arpmode, arpmode_p) = pF "arpmode" (Just 0)

(erepeat, erepeat_p) = pF "erepeat" (Just 0)

(unison, unison_p) = pF "unison" (Just 0)

(arp, arp_p) = pF "arp" (Just 0)

(sqn, sqn_p) = pF "sqn" (Just 0)

(mcr1, mcr1_p) = pI "mcr1" (Just 0)

(mcr2, mcr2_p) = pI "mcr2" (Just 0)

(mcr3, mcr3_p) = pI "mcr3" (Just 0)

(mcr4, mcr4_p) = pI "mcr4" (Just 0)

(btnfreq, btnfreq_p) = pF "btnfreq" (Just 0.25)

(btnvel, btnvel_p) = pF "btnvel" (Just 1)

(btnmode, btnmode_p) = pF "btnmode" (Just 0)

(sub1vol, sub1vol_p) = pF "sub1vol" (Just 0)

(sub2vol, sub2vol_p) = pF "sub2vol" (Just 0)

(fbvol, fbvol_p) = pF "fbvol" (Just 0)

(ksplitpoint, ksplitpoint_p) = pF "ksplitpoint" (Just 0.5)

(kmode, kmode_p) = pF "kmode" (Just 0)

knormal = kmode "0"

kstack = kmode "0.5"

ksplit = kmode "1"

{-| Abstractions

Though not yet finished, these are some examples on how to combine multiple parameters into one single function you can use within your patterns.

Since Tidal 0.7 you can use the @grp@ method to allow things like:

>>> t1 $ n (run 4) # adsr "0.1:0.6:0.3:0.9 0.8:0.3:0.9:0.1"

which alternates between two filter envelope shapes, the first one with a sharp attack and a long decay/release and the latter with a long attack, high sustain and a short release.

-}
-- adsr
adsr = grp [attack_p, decay_p, sustain_p, release_p]

atk3 = grp [attack_p, fatk_p, eatk_p]

dcy3 = grp [decay_p, fdcy_p, edcy_p]

sus3 = grp [sustain_p, fsus_p, esus_p]

rel3' = grp [release_p, frel_p, erel_p]

-- lfo
lfotri = doublePattern 0

lforsaw = doublePattern 1

lfosaw = doublePattern 2

lfopulse = doublePattern 3

lforand = doublePattern 4

{-|
A hack to handle a param with completely different behavior for certain parts of the range:

@lrate@ is limited from 0 to 150 and specifies values that will produce an lfo frequency

@lstep@ however is essentially limited from 0 to 16 and specifies values that will produce rhythmic lfo based on the _sequence speed_. as taken from the manual:

0: 32 steps
1: 16 steps
2: 8 steps
3: 6 steps
4: 4 steps
5: 3 steps
6: 2 steps
7: 1.5 steps
8: 1 step
9: 2/3 steps
10: 1/2 step
11: 1/3 step
12: 1/4 step
13: 1/6 step
14: 1/8 step
15: 1/16 step

-}
lrate r = min 150 . max 0 <$> r

lstep s = min 166 . max 151 . (+ 150) <$> s

lfo1 s d r a = lfo1shape s |+| lfo1dest d |+| lfo1rate r |+| lfo1amt a

lfo2 s d r a = lfo2shape s |+| lfo2dest d |+| lfo2rate r |+| lfo2amt a

lfo3 s d r a = lfo3shape s |+| lfo3dest d |+| lfo3rate r |+| lfo3amt a

lfo4 s d r a = lfo4shape s |+| lfo4dest d |+| lfo4rate r |+| lfo4amt a

-- mod
mod1 s d a = mod1src s |+| mod1dst d |+| mod1amt a

mod2 s d a = mod2src s |+| mod2dst d |+| mod2amt a

mod3 s d a = mod3src s |+| mod3dst d |+| mod3amt a

mod4 s d a = mod4src s |+| mod4dst d |+| mod4amt a

{-| Modulation

named sources and destinations to able to refer them for lfos, mods and also sequences.
-}
doublePattern d = atom d :: Pattern Double

dosc1 = doublePattern 1

dosc2 = doublePattern 2

dosc = doublePattern 3

dmix = doublePattern 4

dnoise = doublePattern 5

dpw1 = doublePattern 6

dpw2 = doublePattern 7

dpw = doublePattern 8

dcut = doublePattern 9

dres = doublePattern 10

damod = doublePattern 11

dvca = doublePattern 12

dspread = doublePattern 13

dlfo1f = doublePattern 14

dlfo2f = doublePattern 15

dlfo3f = doublePattern 16

dlfo4f = doublePattern 17

dlfof = doublePattern 18

dlfo1a = doublePattern 19

dlfo2a = doublePattern 20

dlfo3a = doublePattern 21

dlfo4a = doublePattern 22

dlfoa = doublePattern 23

dfamt = doublePattern 24

dvamt = doublePattern 25

deamt = doublePattern 26

damt = doublePattern 27

dfatk = doublePattern 28

dvatk = doublePattern 29

deatk = doublePattern 30

datk = doublePattern 31

dfdcy = doublePattern 32

dvdcy = doublePattern 33

dedcy = doublePattern 34

ddcy = doublePattern 35

dfrel = doublePattern 36

dvrel = doublePattern 37

derel = doublePattern 38

drel = doublePattern 39

dmod1 = doublePattern 40

dmod2 = doublePattern 41

dmod3 = doublePattern 42

dmod4 = doublePattern 43

dfb = doublePattern 44

dsub1 = doublePattern 45

dsub2 = doublePattern 46

dshape = doublePattern 47

dslew = doublePattern 48

-- mod sources
sseq1 = doublePattern 1

sseq2 = doublePattern 2

sseq3 = doublePattern 3

sseq4 = doublePattern 4

slfo1 = doublePattern 5

slfo2 = doublePattern 6

slfo3 = doublePattern 7

slfo4 = doublePattern 8

sfenv = doublePattern 9

svenv = doublePattern 10

seenv = doublePattern 11

spitchb = doublePattern 12

smodwh = doublePattern 13

saftert = doublePattern 14

sbreath = doublePattern 15

sfoot = doublePattern 16

sexpr = doublePattern 17

svel = doublePattern 18

snote = doublePattern 19

snoise = doublePattern 20

{-|
Presets suck, but I constantly forget how to make drums
with the TETR4, so here are some defaults (that can mostly be changed when using them)
to make a snare and a kick.

Use the snare like this:

>>> t1 $ n "0(3,8)" # snare

To make it shorter:

>>> t1 $ n "0(3,8)" # snare |-| release "0.1" |-| decay "0.1"
-}
snare =
  fourpole |=| osc1shape "0" |+| osc1kbd "0" |+| osc2shape "0" |+| osc2kbd "0" |+|
  noise "1" |+|
  shape "0.7" |+|
  fbvol "0.3" |=|
  resonance "0.2" |+|
  release "0.3" |+|
  sustain "0" |+|
  decay "0.3" |+|
  cutoff "1" |+|
  dur "0.02"

{-|
usage would be:

>>> t1 $ n "0 [[0 1] 1]" # kick

you can change the defaults by applying merge operations for certain params:

>>> t1 $ n "0(3,8)" # kick |+| edcy "0.05"

will give the kick more resonance

>>> t1 $ n "0(3,8)" # kick |-| edcy "0.15"

will make it really dry
-}
kick =
  fourpole |+| osc1shape "0" |+| osc1kbd "0" |+| osc2shape "0" |+| osc2kbd "0" |+|
  sustain "0" |+|
  decay "0.95" |+|
  release "0.5" |+|
  resonance "0.99" |+|
  cutoff "0.001" |+|
  eamt "0.8" |+|
  emod "9" |+|
  edcy "0.2"

{-|
Since the TETR4 lacks a high-pass filter, things like snares and especially cymbals are somewhat limited to filtered noise which sounds okish.

Therefore some presets for the different types of sounds you can get from the TETR4 can come in handy and can also be combined and modified with and through others:
-}
{-|
A chip tune like sound, reminds me of gameboys
-}
chip =
  osc1shape "2" # osc2shape "2" |+| osc2freq "0.23" # osc1glide "0.05" #
  osc2glide "0.05" #
  glidemode "3" #
  release "0.78"