AtlatestRepositorycrash-the-stack
1
;;; Shared finite-score operations, derived from the first album pilot composer.2
(define-library (crash soundtrack album arrangement)3
(import (sigil shell prelude)4
(sigil list)5
(crash soundtrack tools))6
(export CHANNELS off attack? pattern put-cell empty-pattern add-notes phrase7
transform level unchanged write-album phrase-rows)8
(begin9
(define CHANNELS '(1 2 3 4 5 6 7 8))10
(define (off ch) (list ch "===" 0 0 "000"))11
(define (attack? cell)12
(not (member (cadr cell) '("..." "==="))))13
(define (pattern source ident)14
(or (list-find (lambda (p) (= (field p id:) ident))15
(cdr (child source 'patterns)))16
(error "Missing pattern" ident)))18
(define (put-cell p row cell)19
(check (< row (field p rows:)) "Cell exceeds pattern" row)20
(let* ((rows (skip p 5))21
(old (or (list-find (lambda (r) (= (cadr r) row)) rows)22
(list 'row row)))23
(cells (cons cell24
(filter (lambda (c) (not (= (car c) (car cell))))25
(cddr old))))26
(replacement27
(append (take old 2)28
(list-sort (lambda (a b) (< (car a) (car b))) cells))))29
(append (take p 5)30
(list-sort (lambda (a b) (< (cadr a) (cadr b)))31
(cons replacement32
(filter (lambda (r) (not (= (cadr r) row))) rows))))))34
(define (empty-pattern bars)35
(list 'pattern id: 0 rows: (* bars 32)36
(append '(row 0) (map off CHANNELS))))38
;;; A note spec is (row channel pitch instrument level held-rows).39
;;; End gates are explicit, including melodic responses in otherwise empty bars.40
(define (phrase bars notes)41
(fold-left42
(lambda (p n)43
(let* ((row (list-ref n 0))44
(ch (list-ref n 1))45
(cell (list ch46
(list-ref n 2)47
(list-ref n 3)48
(list-ref n 4)49
"000")))50
(put-cell (put-cell p row cell)51
(+ row (list-ref n 5))52
(off ch))))53
(empty-pattern bars)54
notes))56
;;; A transform returns a cell or #f. Missing channels are released at entry,57
;;; so removing an instrument cannot accidentally sustain it across a section.58
(define (transform p fn)59
(let* ((rows60
(filter (lambda (r) (pair? (cddr r)))61
(map (lambda (r)62
(append (take r 2)63
(filter (lambda (c) c)64
(map (lambda (c)65
(let ((clean66
(if (starts-with? (list-ref c 4) "B")67
(append (take c 4) '("000"))68
c)))69
(fn (cadr r) clean)))70
(cddr r)))))71
(skip p 5))))72
(result (append (take p 5) rows))73
(entry (or (list-find (lambda (r) (= (cadr r) 0)) rows)74
'(row 0))))75
(each ch CHANNELS76
(unless (assoc ch (cddr entry))77
(set! result (put-cell result 0 (off ch)))))78
result))80
(define (level cell factor)81
(if (attack? cell)82
(append (take cell 3)83
(list (max 1 (round (* (list-ref cell 3) factor)))84
(list-ref cell 4)))85
cell))87
(define (unchanged row cell) cell)89
(define (add-notes p notes)90
(fold-left91
(lambda (result note)92
(let* ((row (car note))93
(ch (cadr note))94
(cell (append (list ch)95
(take (skip note 2) 3)96
'("000"))))97
(put-cell (put-cell result row cell)98
(+ row (list-ref note 5))99
(off ch))))100
p101
notes))102
(define (phrase-rows rows notes)103
(add-notes (set-field (empty-pattern 1) rows: rows) notes))105
(define (write-album motif output slug title base sections sources . provenance)106
(let ((bar-rows (if (member meter: base) (apply * (field base meter:)) 16))107
(patterns '())108
(timeline '())109
(offset 0)110
(ident 0))111
(each spec sections112
(let* ((p (set-field (transform (cadr spec) unchanged) id: ident))113
(rows (field p rows:)))114
(set! patterns (append patterns (list p)))115
(set! timeline116
(append timeline117
(list (dict pattern: ident118
label: (car spec)119
start_bar: (+ 1 (quotient offset bar-rows))120
bars: (/ rows bar-rows)121
start_seconds: (/ (* offset 2.5 (field base speed:)) (field base tempo:))))))122
(set! offset (+ offset rows))123
(set! ident (+ ident 1))))124
(let* ((score (set-field base name: title))125
(destination (path output (str slug ".cts"))))126
(set! score (replace-child score 'patterns (cons 'patterns patterns)))127
(set! score128
(replace-child score 'order129
(cons 'order (map (lambda (p) (field p id:)) patterns))))130
(set! score131
(replace-child score 'history132
(list 'history133
(list "codex" (if (> (length provenance) 2) (caddr provenance) "2026-09-21")134
(if (pair? provenance) (car provenance)135
"Album opening pair I: finite arrangement, composed entrance, bridge, reprise and ending. Game sources remain unchanged; provenance and section map accompany this score."))136
(list "codex" (if (> (length provenance) 2) (caddr provenance) "2026-09-21")137
(if (and (pair? provenance) (pair? (cdr provenance))) (cadr provenance)138
"Accepted instruments and onsets retained. Explicit final gates and two bars for natural room decay; no playback jumps. Arranged with album-compose-opening.sgl.")))))139
(write-canonical motif destination score)140
($ ,motif tune check ,destination)141
(write-json-file (path output (str slug "-map.json"))142
(dict title: title143
duration_seconds: (/ (* offset 2.5 (field base speed:)) (field base tempo:))144
sources: (list->array sources)145
sections: (list->array timeline)))146
(println "~a: ~a bars, ~a seconds" title (/ offset bar-rows)147
(/ (* offset 2.5 (field base speed:)) (field base tempo:))))))148
))