-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEdits.tish
More file actions
192 lines (174 loc) · 4.66 KB
/
Copy pathEdits.tish
File metadata and controls
192 lines (174 loc) · 4.66 KB
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
// Pure edit helpers — safe transforms for agents.
import { makeStep, clonePianoNote } from './Project.tish'
/// Grow a channel to `bars`-bar storage so each bar can be edited independently (tiling the existing
/// single-bar pattern across the new bars so it keeps sounding the same until you change a bar).
export fn promoteChannelToBars(ch, bars) {
let want = Math.max(1, Math.floor(Number(bars)))
let cur = (ch.patternBars !== null && Math.floor(ch.patternBars) >= 1) ? Math.floor(ch.patternBars) : 1
let target = cur > want ? cur : want
let targetSteps = target * 16
// Tile piano notes from the current span across the new bars (if it grows by a whole multiple).
if (target > cur && ch.pianoNotes && ch.pianoNotes.length > 0) {
let reps = Math.floor(target / cur)
if (reps * cur === target && reps > 1) {
let orig = ch.pianoNotes
let origLen = orig.length
let span = cur * 4
let rep = 1
while (rep < reps) {
let k = 0
while (k < origLen) {
let n = orig[k]
let cn = clonePianoNote(n)
cn.startBeat = n.startBeat + rep * span
ch.pianoNotes.push(cn)
k = k + 1
}
rep = rep + 1
}
}
}
// Ensure the step array spans the target bars (tile the current single bar).
if (ch.steps.length < targetSteps) {
let curLen = ch.steps.length < 1 ? 1 : ch.steps.length
let out = []
let i = 0
while (i < targetSteps) {
let st = ch.steps[i % curLen]
out.push(makeStep(st.on, st.vel !== null ? st.vel : 100))
i = i + 1
}
ch.steps = out
}
ch.patternBars = target
}
export fn clearAllSteps(ch) {
let si = 0
while (si < 16) {
ch.steps[si].on = false
si = si + 1
}
}
export fn toggleStep(ch, stepIdx) {
let st = ch.steps[stepIdx]
st.on = !st.on
}
export fn clearStep(ch, stepIdx) {
ch.steps[stepIdx].on = false
}
fn setStepVel(ch, stepIdx, vel) {
ch.steps[stepIdx].vel = vel
}
export fn setChannelGain(ch, g) {
ch.gain = g
}
export fn setChannelPan(ch, p) {
ch.pan = p
}
// 3-band track EQ (dB). band = "eqLo" | "eqMid" | "eqHi".
export fn setChannelEq(ch, band, v) {
if (band === "eqLo") {
ch.eqLo = v
} else if (band === "eqMid") {
ch.eqMid = v
} else if (band === "eqHi") {
ch.eqHi = v
}
}
export fn setChannelMute(ch, on) {
ch.mute = on
}
export fn setChannelSolo(ch, on) {
ch.solo = on
}
// ADSR lives on generatorParams (basicOsc / fmTone / noiseBurst attack).
fn setGeneratorAdsr(ch, a, d, s, r) {
if (!ch.generatorParams) {
return
}
ch.generatorParams.attack = a
ch.generatorParams.decay = d
ch.generatorParams.sustain = s
ch.generatorParams.release = r
}
export fn clampPianoNoteToFirstBar(n, minDur) {
let bar = 4
let md = minDur
if ((md === null) || md <= 0) {
md = 0.0078125
}
if (n.startBeat < 0) {
n.startBeat = 0
}
if (n.startBeat >= bar) {
n.startBeat = bar - md
}
if (n.startBeat < 0) {
n.startBeat = 0
}
if (n.startBeat + n.durBeats > bar) {
n.durBeats = bar - n.startBeat
}
if (n.durBeats < md) {
n.durBeats = md
if (n.startBeat + n.durBeats > bar) {
n.startBeat = bar - md
if (n.startBeat < 0) {
n.startBeat = 0
}
}
}
}
export fn clampChannelPianoNotesToFirstBar(ch, minDur) {
if (!ch.pianoNotes) {
return
}
let i = 0
while (i < ch.pianoNotes.length) {
clampPianoNoteToFirstBar(ch.pianoNotes[i], minDur)
i = i + 1
}
}
/// Clamp a note into the channel's full pattern (bars*4 beats), not just the first bar — so notes can
/// live on any bar of a multi-bar pattern.
export fn clampPianoNoteToPattern(ch, n, minDur) {
let bars = (ch.patternBars !== null && Math.floor(ch.patternBars) >= 1) ? Math.floor(ch.patternBars) : 1
let lim = bars * 4
let md = minDur
if ((md === null) || md <= 0) {
md = 0.0078125
}
if (n.startBeat < 0) {
n.startBeat = 0
}
if (n.startBeat >= lim) {
n.startBeat = lim - md
}
if (n.startBeat < 0) {
n.startBeat = 0
}
if (n.startBeat + n.durBeats > lim) {
n.durBeats = lim - n.startBeat
}
if (n.durBeats < md) {
n.durBeats = md
if (n.startBeat + n.durBeats > lim) {
n.startBeat = lim - md
if (n.startBeat < 0) {
n.startBeat = 0
}
}
}
}
export fn addPianoNote(ch, startBeat, durBeats, pitch, vel) {
let n = { startBeat: startBeat, durBeats: durBeats, pitch: pitch, vel: vel, prob: 1, ratchet: 1, nudge: 0 }
clampPianoNoteToPattern(ch, n, 0.0078125)
ch.pianoNotes.push(n)
}
export fn removePianoNoteAt(ch, idx) {
ch.pianoNotes.splice(idx, 1)
}
fn quantizeNoteToStep(note, stepFrac) {
let q = Math.round(note.startBeat / stepFrac) * stepFrac
note.startBeat = q
}