Repository navigation
Expand file tree
/
Copy pathStream.tish
More file actions
131 lines (120 loc) · 4.05 KB
/
Copy pathStream.tish
File metadata and controls
131 lines (120 loc) · 4.05 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
// Incremental, per-actor deck line decode → progressive apply (the streaming counterpart to
// the atomic deck.block path). A non-indented known statement opens a block; indented lines
// extend it. Each line that extends the open block re-applies the growing block, which is
// idempotent per channel/target via applyCoDjTplSource — so you HEAR a remote actor's track
// appear the instant `track …` arrives, then the pattern fills in as `steps …` streams in.
//
// This is what makes "decode a song into live stems one token-line at a time" real, rather
// than waiting for a whole block. Ownership and skill-gating are enforced on every line.
import { applyCoDjTplSource } from '../codj/Merge.tish'
import { coDjLineAllowedForSkills } from '../codj/Skills.tish'
export fn createTplLineStream(actorId, authorId, skillIds) {
return {
actorId: actorId,
authorId: authorId,
skillIds: skillIds,
openLines: [],
hasOpen: false
}
}
fn lineIndented(line) {
if (!line || line.length === 0) {
return false
}
let c = line.charAt(0)
return c === " " || c === "\t"
}
fn topLevelHead(trimmed) {
let sp = trimmed.indexOf(" ")
if (sp < 0) {
return trimmed
}
return trimmed.substring(0, sp)
}
// Statements that have an indented body — they stay "open" and re-apply as the body streams.
fn isBlockOpener(head) {
return head === "track" || head === "clip" || head === "auto" || head === "song" || head === "follow"
}
// Statements that are complete on one line — apply immediately, no open block left dangling.
fn isStandaloneTopLevel(head) {
return (
head === "bpm" ||
head === "launch_quant" ||
head === "song_seed" ||
head === "master_mix" ||
head === "actor_mix" ||
head === "session_scenes" ||
head === "session_slot" ||
head === "xfade" ||
head === "main_deck" ||
head === "swing" ||
head === "scale" ||
head === "remove_track"
)
}
fn stripEol(line) {
let s = line
while (s.length > 0) {
let c = s.charAt(s.length - 1)
if (c === "\n" || c === "\r") {
s = s.substring(0, s.length - 1)
} else {
break
}
}
return s
}
/**
* Feed one streamed deck line into the actor's stream and apply progressively.
* Returns { applied: bool, denied: bool, opened: bool }.
*/
export fn tplLineStreamPush(stream, rawLine, project, perfStep) {
if ((rawLine === null)) {
return { applied: false, denied: false, opened: false }
}
let line = stripEol(String(rawLine))
let trimmed = line.trim()
if (trimmed.length === 0 || trimmed.charAt(0) === "#") {
return { applied: false, denied: false, opened: false }
}
if (topLevelHead(trimmed) === "deck" || topLevelHead(trimmed) === "tpl") {
return { applied: false, denied: false, opened: false }
}
if (!coDjLineAllowedForSkills(trimmed, stream.skillIds)) {
return { applied: false, denied: true, opened: false }
}
let opened = false
if (lineIndented(line)) {
if (!stream.hasOpen) {
// body line with no header yet — not committable on its own
return { applied: false, denied: false, opened: false }
}
stream.openLines.push(line)
} else {
let head = topLevelHead(trimmed)
if (isBlockOpener(head)) {
stream.openLines = [line]
stream.hasOpen = true
opened = true
} else if (isStandaloneTopLevel(head)) {
let frag1 = "deck 1\n" + line + "\n"
applyCoDjTplSource(project, frag1, stream.actorId, stream.authorId, perfStep, stream.skillIds)
stream.openLines = []
stream.hasOpen = false
return { applied: true, denied: false, opened: false }
} else {
// unknown / unsupported top-level keyword — ignore (stays out of the open block)
return { applied: false, denied: false, opened: false }
}
}
let frag = "deck 1\n" + stream.openLines.join("\n") + "\n"
applyCoDjTplSource(project, frag, stream.actorId, stream.authorId, perfStep, stream.skillIds)
return { applied: true, denied: false, opened: opened }
}
fn tplLineStreamReset(stream) {
if (!stream) {
return
}
stream.openLines = []
stream.hasOpen = false
}