AtlatestRepositorycrash-the-stack
crash-the-stack / tree / docs / music / toolsbasement-expression.sgl
1
#!/usr/bin/env sigil2
;;; Separate patch and phrase auditions; both full forms share one candidate pool.3
(import (sigil list))4
(load (path (script-dir) "lib/core.sgl"))5
(load (path (script-dir) "lib/album-arrangement.sgl"))6
(load (path (script-dir) "lib/song-source.sgl"))7
(import (crash soundtrack tools)8
(crash soundtrack album arrangement)9
(crash soundtrack songs))10
(initialize (path (script-dir) "../../.."))12
(define PHRASES '(3 12 14))13
(define PATCHES '(7 9 10 11))15
(define (setting form key value)16
(if (member key form)17
(set-field form key value)18
(append form (list key value))))20
(define (patches parts pluck? acid?)21
(replace-child parts 'instruments22
(cons 'instruments23
(map24
(lambda (instrument)25
(let ((id (field instrument id:))26
(patch (field instrument patch:)))27
(cond28
((and pluck? (= id 7))29
(each pair '((index: 0.95) (cutoff: 1650) (resonance: 0.15)30
(decay: 0.22) (release: 0.075))31
(set! patch (setting patch (car pair) (cadr pair))))32
(set-field instrument patch: patch))33
((and acid? (member id '(9 10 11)))34
(each pair '((filter-decay: 0.09) (decay: 0.12) (sustain: 0.48))35
(set! patch (setting patch (car pair) (cadr pair))))36
(set-field instrument patch: patch))37
(else instrument))))38
(cdr (child parts 'instruments))))))40
(define (line p channel start instrument specs)41
(add-notes42
(append (take p 5)43
(filter44
(lambda (row) (pair? (cddr row)))45
(map46
(lambda (row)47
(append (take row 2)48
(filter49
(lambda (cell)50
(not (and (= (car cell) channel) (>= (cadr row) start))))51
(cddr row))))52
(skip p 5))))53
(map54
(lambda (spec)55
(list (car spec) channel (cadr spec) instrument56
(caddr spec) (list-ref spec 3)))57
specs)))59
(define (develop p)60
(case (field p id:)61
;; Recall the opening's rising gesture over D minor, then answer over F.62
((3)63
(line64
(line p 6 128 765
'((136 "D-4" 20 8) (144 "F-4" 18 8) (152 "A-4" 19 12)66
(184 "E-4" 16 7)67
(200 "F-4" 20 8) (208 "A-4" 18 8) (216 "G-4" 18 12)68
(240 "E-4" 17 8) (248 "C-4" 18 7)))69
7 128 870
'((176 "F-4" 13 12) (232 "C-5" 12 12))))71
;; The quiet A-pedal bridge states a connected question, then resolves.72
((12)73
(line74
(line p 6 0 775
'((16 "E-4" 18 8) (24 "G-4" 16 8) (32 "A-4" 19 12)76
(64 "G-4" 17 8) (72 "E-4" 18 12)77
(104 "B-3" 16 8) (112 "A-3" 18 12)))78
7 0 879
'((48 "C-5" 11 12) (88 "E-4" 12 12))))80
;; Descending recall over C and G prepares the existing A-minor ending.81
((14)82
(line83
(line p 6 128 784
'((144 "G-4" 18 8) (152 "E-4" 17 8) (160 "C-4" 19 12)85
(200 "D-4" 18 8) (208 "B-3" 17 8) (216 "G-3" 18 12)86
(240 "E-4" 16 8) (248 "A-3" 17 7)))87
7 128 888
'((176 "E-4" 12 12) (232 "D-4" 11 12))))89
(else p)))91
(define (phrases parts)92
(replace-child parts 'patterns93
(cons 'patterns (map develop (cdr (child parts 'patterns))))))95
(define (channel-events p channel)96
(filter97
(lambda (row) (pair? (cddr row)))98
(map99
(lambda (row)100
(append (take row 2)101
(filter (lambda (cell) (= (car cell) channel)) (cddr row))))102
(skip p 5))))104
(define (protect before after patch? phrase?)105
(each key '(name: tempo: speed: channels: meter:)106
(check (equal? (field before key) (field after key)) "Header changed" key))107
(each section '(bus order history)108
(check (equal? (child before section) (child after section))109
"Protected section changed" section))110
(each instrument (cdr (child before 'instruments))111
(let* ((id (field instrument id:))112
(new (list-find (lambda (i) (= (field i id:) id))113
(cdr (child after 'instruments)))))114
(check115
(equal? instrument116
(if (and patch? (member id PATCHES))117
(set-field new patch: (field instrument patch:))118
new))119
"Protected instrument field changed" id)))120
(each p (cdr (child before 'patterns))121
(let ((new (pattern after (field p id:))))122
(check (equal? (take p 5) (take new 5)) "Pattern clock changed")123
(each channel CHANNELS124
(unless (and phrase? (member (field p id:) PHRASES)125
(member channel '(6 7)))126
(check (equal? (channel-events p channel) (channel-events new channel))127
"Protected performance changed" (field p id:) channel))))))129
(cli "basement-expression" (script-args) '()130
(lambda (options)131
(let* ((motif (require-tool (option-ref options 'motif)))132
(output (option-ref options 'output))133
(reference (path (root) "docs/music/alternates/basement-expression-i"))134
(frozen? (file? (path reference "parts-before.cts")))135
(source136
(if frozen? (path reference "parts-before.cts")137
(path (root) "docs/music/songs/basement-circuit/parts.cts")))138
(layout-file139
(if frozen? (path reference "arrangements.sgl")140
(path (root) "docs/music/songs/basement-circuit/arrangements.sgl")))141
(parts (read-score source))142
(layout (read-score layout-file))143
(arrangements (child layout 'arrangements))144
(album (child arrangements 'album))145
(pluck-parts (patches parts #t #f))146
(acid-parts (patches parts #f #t))147
(phrase-parts (phrases parts))148
(combined (patches phrase-parts #t #t))149
(base (build-song-score parts layout album))150
(clips '()))151
(ensure-directory output)152
(unless frozen?153
(apply-song-plans (list (song-plan "basement-circuit" motif)) "check" #f))154
(protect parts pluck-parts #t #f)155
(protect parts acid-parts #t #f)156
(protect parts phrase-parts #f #t)157
(protect parts combined #t #t)158
(each spec (list (list "before" parts) (list "pluck" pluck-parts)159
(list "acid" acid-parts) (list "phrases" phrase-parts)160
(list "after" combined))161
(write-canonical motif (path output (str "parts-" (car spec) ".cts"))162
(cadr spec)))163
(let ((destination (path output "arrangements.sgl")))164
(if (file? destination)165
(check (equal? (read-text layout-file) (read-text destination))166
"Refusing to overwrite differing layout")167
(copy-file layout-file destination)))168
(each spec '((album "basement-circuit") (game "basement-circuit-game"))169
(let* ((arrangement (child arrangements (car spec)))170
(before-file (path output (str (cadr spec) "-before.cts"))))171
(write-canonical motif before-file (build-song-score parts layout arrangement))172
(unless frozen?173
(check (equal? (read-text before-file)174
(read-text (path (root) (field arrangement destination:))))175
"Accepted score differs from shared reference" (car spec)))176
(write-canonical motif (path output (str (cadr spec) "-after.cts"))177
(build-song-score combined layout arrangement))))178
(each spec179
(list (list "exposed" "Exposed pluck / equal +18 dB monitoring" '(2) pluck-parts)180
(list "pluck" "Calm groove / pluck patch only" '(2) pluck-parts)181
(list "acid" "Tense groove / acid contour only" '(9) acid-parts)182
(list "phrases" "Bridge and return / phrases only" '(4 5) phrase-parts))183
(each variant '("before" "after")184
(let* ((score185
(if (equal? variant "before") base186
(build-song-score (list-ref spec 3) layout album)))187
(stem (str (car spec) "-" variant))188
(sections189
(append190
(map191
(lambda (id)192
(let* ((p (pattern score id))193
(p194
(if (equal? (car spec) "exposed")195
(transform196
(set-field197
(append (take p 5)198
(filter (lambda (row) (< (cadr row) 128))199
(skip p 5)))200
rows: 128)201
(lambda (row cell)202
(and (= (car cell) 6) cell)))203
p)))204
(list (str "Album pattern " id) p)))205
(caddr spec))206
(list (list "Two-bar release / comparison gap" (phrase-rows 64 '())))))207
(rows (apply + (map (lambda (s) (field (cadr s) rows:)) sections))))208
(write-album motif output stem209
(str "Basement Circuit / " (cadr spec) " / " variant)210
score sections211
'("parts-before.cts" "parts-after.cts" "arrangements.sgl")212
"Expression I: isolated patch and phrasing auditions; no maintained score replaced."213
"Each pair changes only its named feature; drum, sub, chord, swell and bus identity retained."214
"2026-09-23")215
(set! clips216
(append clips217
(list (dict stem: stem218
label: (str (cadr spec) " / " variant)219
duration_seconds: (/ (* rows 7.5) 124)220
audition_gain_db: (if (equal? (car spec) "exposed") 18 0))))))))221
(write-json-file (path output "comparison.json")222
(dict slug: "basement-circuit"223
title: "Basement Circuit"224
audition: "expression-i"225
comment: "Four pairs, each BEFORE then AFTER: exposed pluck (+18 dB both), pluck in the calm mix, acid contour, melodic replies. Phrase pair retains original patches. Full candidate combines all three ideas."226
full_duration_seconds: (/ (* 3648 7.5) 124)227
clips: (list->array clips)))228
(write-json-file (path output "source-checks.json")229
(dict source_sha256: (song-hash source)230
layout_sha256: (song-hash layout-file)231
shared_patch_ids: PATCHES232
shared_phrase_ids: PHRASES233
changed_phrase_channels: '(6 7)234
patch_comparisons_identical_performance: #t235
phrase_comparison_original_patches: #t236
drums_sub_chords_swell_bus_protected: #t237
acid_notes_accents_slides_and_output_gain_preserved: #t238
game_and_album_from_same_candidate_parts: #t))239
(display "Basement Circuit isolated comparisons and synchronized full candidates ready.\n"))))