AtlatestRepositorycrash-the-stack
crash-the-stack / tree / docs / music / toolsfault-line-rock-form.sgl
1
#!/usr/bin/env sigil2
;;; Composition II: rock-led form, two solos and a soft secondary melody.3
(import (sigil list))4
(load (path (script-dir) "lib/core.sgl"))5
(load (path (script-dir) "lib/album-arrangement.sgl"))6
(import (crash soundtrack tools)7
(crash soundtrack album arrangement))8
(initialize (path (script-dir) "../../.."))10
(define BASE "docs/music/alternates/fault-line-composition-i")11
;;; Guitar root, bass root, third, fourth.12
(define CHORDS13
'(("D-3" "D-2" "F-3" "G-3")14
("A#2" "A#1" "D-3" "D#3")15
("G-2" "G-1" "A#2" "C-3")16
("A-2" "A-1" "C-3" "D-3")))17
;;; Row, guitar pitch role, gate, open chord?, bass/kick anchor?.18
(define DRIVE-CALL19
'((0 0 3 #f #t) (6 0 1 #f #f) (12 0 3 #f #t)20
(18 0 3 #t #t) (24 2 1 #f #f) (26 3 1 #f #f) (30 0 1 #f #t)))21
(define DRIVE-REPLY22
'((0 0 3 #f #t) (8 0 2 #t #t) (14 2 1 #f #f)23
(18 0 3 #f #t) (24 0 5 #t #t) (30 0 1 #f #f)))24
(define HALF-CALL25
'((0 0 6 #f #t) (10 0 2 #f #t) (16 0 6 #t #t) (26 0 3 #f #t)))26
(define HALF-REPLY27
'((0 0 3 #f #t) (12 2 2 #f #f) (16 0 10 #t #t) (28 0 3 #f #t)))29
(define (first-bars p bars)30
(transform31
(append (list 'pattern id: 0 rows: (* bars 32))32
(filter (lambda (r) (< (cadr r) (* bars 32))) (skip p 5)))33
unchanged))35
(define (new-riff base half?)36
(let ((p (transform base37
(lambda (row cell) (and (= (car cell) 8) cell)))))38
(each bar '(0 1 2 3 4 5 6 7)39
(let* ((chord (list-ref CHORDS (quotient bar 2)))40
(offset (* bar 32))41
(specs (if half?42
(if (even? bar) HALF-CALL HALF-REPLY)43
(if (even? bar) DRIVE-CALL DRIVE-REPLY))))44
(each spec specs45
(let ((row (+ offset (car spec)))46
(pitch (list-ref chord (cadr spec)))47
(gate (caddr spec))48
(open? (list-ref spec 3)))49
(set! p50
(add-notes p51
(list (list row 5 pitch (if open? 6 5) (if open? 31 28) gate)52
(list row 6 pitch (if open? 8 7) (if open? 21 17) gate))))53
(when (list-ref spec 4)54
(set! p55
(add-notes p56
(list (list row 4 (cadr chord) 4 (if open? 33 32) gate)57
(list row 1 "C-2" 1 (if (= (car spec) 0) 40 35)58
(min gate 3))))))))59
(each row (if half? '(24) '(8 24))60
(set! p61
(add-notes p (list (list (+ offset row) 2 "F#3" 2 31 3)))))62
(each row (if half? '(0 8 16 24) '(0 4 8 12 16 20 24 28))63
(set! p64
(add-notes p65
(list (list (+ offset row) 3 "C-8" 366
(if (= (modulo row 8) 0) 17 11) 2)))))67
;; A restrained ghost gives alternate replies a small lift.68
(when (and (not half?) (= (modulo bar 4) 3))69
(set! p70
(add-notes p (list (list (+ offset 20) 2 "F#3" 2 12 2)))))))71
p))73
(define (copy-melody backing melody source-channel instrument factor start)74
(let ((p (transform backing75
(lambda (row cell) (and (not (= (car cell) 7)) cell)))))76
(each row (skip melody 5)77
(when (>= (cadr row) start)78
(let ((cell (assoc source-channel (cddr row))))79
(when cell80
(set! p81
(put-cell p (cadr row)82
(if (attack? cell)83
(list 7 (cadr cell) instrument84
(round (* (list-ref cell 3) factor)) (list-ref cell 4))85
(append '(7) (cdr cell)))))))))86
p))88
(define (harmonic-turn p)89
(let ((result (empty-pattern 8)) (index 0))90
(each source-pair '(2 1 0 3)91
(each row (skip p 5)92
(when (<= (* source-pair 64) (cadr row) (+ (* source-pair 64) 63))93
(each cell (cddr row)94
(set! result95
(put-cell result (+ (* index 64) (- (cadr row) (* source-pair 64))) cell)))))96
(set! index (+ index 1)))97
(transform result unchanged)))99
(define ALT-SOLO100
'((0 "D-4" 27 8) (10 "F-4" 27 4) (16 "G-4" 29 10)101
(28 "A-4" 30 8) (38 "G-4" 27 4) (44 "F-4" 26 10) (56 "D-4" 25 6)102
(64 "F-4" 28 12) (78 "A-4" 29 6) (88 "C-5" 30 10)103
(102 "A-4" 27 4) (108 "G-4" 26 4) (116 "F-4" 27 10)104
(128 "G-4" 28 8) (138 "A-4" 26 4) (144 "A#4" 30 12)105
(160 "D-5" 30 10) (174 "C-5" 27 6) (184 "A#4" 28 6)106
(192 "A-4" 29 12) (208 "G-4" 27 6) (218 "E-4" 26 4)107
(224 "G-4" 28 8) (236 "A-4" 29 6) (248 "D-5" 28 6)))109
(define (alternate-solo p)110
(let ((result (transform p (lambda (row cell) (and (not (= (car cell) 7)) cell)))))111
(set! result112
(add-notes result113
(map (lambda (n) (list (car n) 7 (cadr n) 9 (caddr n) (list-ref n 3))) ALT-SOLO)))114
;; Connected whole-tone rise in the first phrase, with an explicit stop.115
(set! result (put-cell result 18 '(7 "..." 0 0 "304")))116
(set! result (put-cell result 22 '(7 "..." 0 0 "300")))117
result))119
(define (strip-entry p)120
(append (take p 5)121
(filter (lambda (r) (pair? (cddr r)))122
(map (lambda (r)123
(append (take r 2)124
(filter (lambda (c)125
(not (and (= (cadr r) 0) (equal? (cadr c) "==="))))126
(cddr r))))127
(skip p 5)))))129
(define (flat form) (trim (score-text form)))130
(define (layout-text names arrangements)131
(str "(song version: 1\n (phrases\n"132
(string-join (map (lambda (p) (str " " (flat p))) names) "\n")133
")\n (arrangements\n"134
(string-join135
(map (lambda (a)136
(str " (" (symbol->string (car a)) " destination: "137
(flat (field a destination:)) "\n"138
(string-join (map (lambda (s) (str " " (flat s))) (skip a 3)) "\n")139
")"))140
arrangements)141
"\n")142
"))\n"))144
(cli "fault-line-rock-form" (script-args) '()145
(lambda (options)146
(let* ((motif (require-tool (option-ref options 'motif)))147
(output (option-ref options 'output))148
(parts (read-score (path (root) BASE "parts.cts")))149
(layout (read-score (path (root) BASE "arrangements.sgl")))150
(riff (transform (pattern parts 3) unchanged))151
(drive (new-riff riff #f))152
(half (new-riff riff #t))153
(new154
(list155
(list "Short clean invitation"156
(transform (first-bars (pattern parts 1) 4)157
(lambda (row cell)158
(and (or (member (car cell) '(5 6 7 8))159
(and (= (car cell) 4) (>= row 64))) cell))))160
(list "Driving variation"161
(copy-melody drive (pattern parts 4) 7 13 1.05 128))162
(list "Lower-register alternate solo" (alternate-solo drive))163
(list "Rock harmonic turn"164
(copy-melody (harmonic-turn riff) (pattern parts 5) 5 13 0.9 0))165
(list "Half-time weight"166
(copy-melody half (pattern parts 4) 7 13 0.95 0))167
(list "Final driving theme"168
(copy-melody drive (pattern parts 10) 7 9 1 0))169
(list "Rock coda" (first-bars drive 4))170
(list "Final band chord"171
(pattern (read-score172
(path (root) "docs/music/alternates/fault-line-bass-lock-vii/fault-line-after.cts")) 4))))173
(names (cdr (child layout 'phrases)))174
(pool (cdr (child parts 'patterns)))175
(ident 15))176
(each spec new177
(set! names (append names (list (list (car spec) ident))))178
(set! pool (append pool (list (strip-entry (set-field (cadr spec) id: ident)))))179
(set! ident (+ ident 1)))180
(set! parts (replace-child parts 'patterns (cons 'patterns pool)))181
(set! parts (replace-child parts 'order (cons 'order (map cadr names))))182
(define (arrangement role ids)183
(let* ((old (child (child layout 'arrangements) role))184
(template (cadr (child old 'template)))185
(refs '()) (labels '()) (index 0))186
(each id ids187
(let* ((p (transform (pattern parts id) unchanged))188
(name (car (list-find (lambda (n) (= (cadr n) id)) names)))189
(row0 (list-find (lambda (r) (= (cadr r) 0)) (skip p 5)))190
(offs (filter (lambda (c) (equal? (cadr c) "===")) (cddr row0)))191
(effects192
(cond ((and (eq? role 'game) (= index 1)) '((255 8 "B00")))193
((and (eq? role 'game) (= index (- (length ids) 1))) '((255 8 "B03")))194
(else '()))))195
(set! refs196
(append refs (list (list index name (cons 'entry-offs offs) (cons 'effects effects)))))197
(set! labels (append labels (list (list index name))))198
(set! index (+ index 1))))199
(set! template (replace-child template 'order (cons 'order (map car labels))))200
(set! template201
(replace-child template 'history202
(append (child template 'history)203
'(("codex" "2026-09-23"204
"Composition II: short clean intro and one interlude frame an extended rock form, contrasting solos, driving and half-time variations, retained soft secondary melody.")))))205
(append (list role destination: (field old destination:)206
(list 'template template) (child old 'bank) (cons 'patterns refs))207
(if (eq? role 'album) (list (cons 'labels labels)) '()))))208
(let ((arrangements209
(list (arrangement 'game '(1 2 8 3 16 4 18 9 19 20))210
(arrangement 'album '(15 3 16 4 17 18 5 8 9 10 19 20 21 22 14)))))211
(write-canonical motif (path output "parts.cts") parts)212
(write-text (path output "arrangements.sgl") (layout-text names arrangements))213
(println "Rock-led shared arrangement ready: ~a" output)))))