aboutsummaryrefslogtreecommitdiff
path: root/tracker.bqn
blob: d530da94353ef8b095469c3804b3f07f445595c7 (plain)
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
# Sequencer/tracker based on a string pattern

⟨
  SetOpts      # Set global options
  MakeTrack    # Build entire track
  Sequence     # Build a single instrument
  AdvanceState # Advance state object 𝕩 past pattern 𝕨
  ReadPattern  # Read pattern for a track from lf-separated string
βŸ©β‡

"tracker.bqn takes a single option namespace, or no arguments" ! 1β‰₯β‰ β€’args
op ← β‰ β—ΆβŸ¨β€’Import∘"options.bqn", βŠ‘βŸ© β€’args
⟨AddβŸ©β€ΏβŸ¨Lp2,Hp2⟩ ← op β€’ImportΒ¨ "mix.bqn"β€Ώ"filter.bqn"

# Sequencing options
Opts ← {
  shifts  ← {𝕩.shifts}⎊("><]["≍÷8β€ΏΒ―8β€Ώ3β€ΏΒ―3) 𝕩
  gains   ← {𝕩.gains} ⎊("-+"≍1.2⋆¯1β€Ώ1) 𝕩
  noop    ← {𝕩.noop}  ⎊"." 𝕩
  control ⇐ noop∾shiftsβˆΎβ—‹βŠgains # Non-sample characters
  GetShiftβ€ΏGetVol ⇐ 0β€Ώ1 {{π•©βŠΛœπ•¨βŠΈβŠ}⟜(βˆΎβŸœπ•¨)˝𝕩}Β¨ shiftsβ€Ώgains

  beat  ⇐ {𝕩.beat} ⎊(!Λ™) 𝕩 # Starting samples/beat (optional with :BEAT:)
  swing ⇐ {𝕩.swing}⎊⟨1⟩ 𝕩  # Modifier for length of each beat

  empty ⇐ {𝕩.empty}⎊(2β€Ώ0β₯Š0) 𝕩
  pink  ⇐ {𝕩.pink} ⎊2   𝕩  # Level of pink noise "humanization"
  end   ⇐ {𝕩.end}  ⎊0   𝕩  # Number of additional beats at the end
}
o ← Opts{⇐}
SetOpts ⇐ { o ↩ Opts 𝕩 }

# Build a multi-track pattern
MakeTrack ← { π•Šπ•©:
  patternβ€Ώsample ← 𝕩 β‹„ pattern<˘⍟(1<=)↩   # Template, and sample function
  state     ← pattern⊒¨{⟨v⇐state⟩:v;{⇐}}𝕩 # Starting state for each track
  overlap   ← {⟨v⇐overlap⟩:v;0Β¨ pattern}𝕩 # Whether adjacent samples overlap
  post      ← {⟨v⇐post   ⟩:v;βŠ’Λ™Β¨pattern}𝕩 # Post-processing for each channel
  postgroup ← {⟨v⇐postgroup⟩:v; ↕≠post}𝕩  # Group equal values: add before post-processing
  arg ← ⍉[pattern,sample,state,overlap]
  _sum ← { 𝔽_π•£βŸ¨x⟩: 𝔽x ; o.empty π”½βŠΈAddΒ΄ 𝕩 }
  { (postβŠ‘ΛœβŠ‘π•©){π•Žπ•©} (Sequence ⊏⟜arg)_sum 𝕩 } _sum βŠ”postgroup
}

# String handling
Ex ← +`βŠΈΓ—βŸœΒ¬-⊒
Cut ← Ex˜∘=βŠ”βŠ’
ReadPattern ← { βˆΎΛ˜β‰> 1⊸»⊸<⊸Ex∘(0=β‰ Β¨)βŠΈβŠ” (@+10) Cut 𝕩 }

# :BEAT: indication handling
Getb ← β€’BQN
Avgb ← (+´÷≠)∘β₯Š

# Sound utilities
Con ← βˆΎβ‰
# Pink noise with no normalization
Rand ← -⟜¬ {op.RandFloats𝕩}
PinkDiff ← {𝕩 ↑ β₯Šβˆ˜β‰βˆ˜β‰Β΄βŒ½ -⟜«∘RandΒ¨ (1βŒˆβ†•βˆ˜βŒˆ)⌾(2β‹†βΌβŠ’)2βŒˆπ•©}

# Output is ⟨list of lengths, list of characters, average lengths⟩
ParseBeats ← {
  m ← "Initial beat length unknown"
  s ← ':' Cut ' 'βŠΈβ‰ βŠΈ/ 𝕩
  g ← (βŠ‘1↑s) {𝕩.beat}βˆ˜π•¨βŠΈβ‹ˆβŠΈβˆΎβŸ(0<β‰ βˆ˜βŠ£) Getb¨⌾(βŠβ‰) βŒŠβ€Ώ2β₯Š1↓s
  <∘∾˘ ⍉ (β‰ βˆ˜βŠ’ β₯ŠΒ¨ β‹ˆβˆΎAvgb∘⊣)´˘ g
}
AdvanceState ← { b π•Š st:
  offset ⇐ ({𝕩.offset}⎊0𝕨) + ⌊0.5 +Β΄ βŠ‘ st ParseBeats b
  beat ⇐ {βŠ‘':'∊b ? Avgb Getb 1↓ (1-˜⊒´)⊸=∘(+`':'⊸=)⊸/ b ; st.beat}
}

Sequence ← { π•Špβ€Ώsβ€Ώt:π•Šπ•©βˆΎ0 ; π•Š patternβ€ΏGetSamplesβ€Ώstateβ€Ώoverlap:
  # Beat length, character, average length
  bβ€Ώcβ€Ώa ← state ParseBeats pattern

  # Compute lengths
  b +↩ (β₯ŠβŸœ(o.swing-1) + (o.pinkΓ·100){(0≠𝕗)β—ΆβŸ¨0,𝕗×PinkDiff⟩})βˆ˜β‰ βŠΈΓ— a
  ge ← (m∾0) (Β¬βŠΈΓ—-⊣) g ← +`0∾˜ m ← Β¬ c∊o.control
  sh ← +´¨ ge βŠ” 0∾˜b Γ— o.GetShift c
  len ← ⌊0.5+ (-⟜»sh) + +´¨ g βŠ” b∾o.endΓ—Β―1⊏b

  # Samples
  vol ← ×´¨ Β―1↓ ge βŠ” 1∾˜o.GetVol c
  d ← ⟨o.empty⟩ ∾ vol Γ— GetSamples m/c

  # Construct output from samples and lengths
  { overlap ?
    Lβ€ΏH ← Lp2β€ΏHp2 {π•©βŠΈπ•ŽβŸ2}Β¨ 1000
    Overlap ← {
      tail ← o.empty
      Next ← {lπ•Šπ•©:
        xt ← 𝕩‿tail
        f ← lβ‰₯Β―1βŠ‘β‰’π•©
        tail ↩ fβ—ΆAddβ€ΏβŠ‘ l β†“βŽ‰1Β¨ f↓xt
        +Β΄ l β†‘βŽ‰1Β¨ xt
      }
      Con 𝕨 NextΒ¨ 𝕩
    }
    len (Overlap⟜(HΒ¨) Add Β·Con(Lβ†‘βŽ‰1)Β¨) d
  ;
    Con len β†‘βŽ‰1Β¨ d
  }
}