AtlatestRepositorycrash-the-stack

crash-the-stack / tree / docs / music / toolsshort-ab-render.sgl

1#!/usr/bin/env sigil
2;;; Render one contextual A/B without also exporting full arrangements.
3(import (sigil args) (sigil json))
4(load (path (script-dir) "lib/core.sgl"))
5(import (crash soundtrack tools))
6(initialize (path (script-dir) "../../.."))
7(requires (tools ffmpeg ffprobe sha256sum))
8
9(define (last-number text marker)
10 (let ((matches (filter (lambda (line) (contains? line marker)) (split text "\n"))))
11 (check (pair? matches) "Measurement missing" marker)
12 (string->number
13 (car (filter (lambda (s) (not (equal? s "")))
14 (split (trim (cadr (split (car (reverse matches)) marker))) " "))))))
16(define (measure file log)
17 ($ ffmpeg -hide_banner -nostats -i ,file -af "ebur128=peak=true" -f null - err: ,log)
18 (let ((text (read-text log)))
19 (dict integrated_lufs: (last-number text " I:")
20 true_peak_dbtp: (last-number text " Peak:"))))
22(define (check-audio file expected)
23 (let* ((probe (json ffprobe -v error -show_format -show_streams -of json ,file))
24 (stream (array-ref (dict-ref probe streams:) 0))
25 (duration (string->number (dict-ref (dict-ref probe format:) duration:))))
26 (check (and (= (dict-ref stream channels:) 2)
27 (equal? (dict-ref stream sample_rate:) "44100")
28 (< (abs (- duration expected)) 0.001))
29 "Unexpected audio duration or format" file)))
31(cli "short-ab-render" (script-args)
32 (list (option name: 'input long: "input" value: "DIR"))
33 (lambda (options)
34 (let* ((input (option-ref options 'input))
35 (output (option-ref options 'output))
36 (motif (require-tool (option-ref options 'motif)))
37 (manifest (json-decode (read-text (path input "comparison.json"))))
38 (half (dict-ref manifest half_seconds:))
39 (stems (list (dict-ref manifest before_stem:)
40 (dict-ref manifest after_stem:)))
41 (levels '())
42 (commands '())
43 (sequence (path output "sequence.txt"))
44 (ogg (path output "ogg/comparison.ogg")))
45 (check (and (starts-with? output "/") (not (file-exists? output)))
46 "Use a fresh absolute external output directory")
47 (ensure-directory output)
48 (check (not (starts-with? (str (realpath output) "/") (str (root) "/")))
49 "Keep audio outside the repository")
50 (ensure-directory (path output "ogg"))
51 (define (run argv)
52 (set! commands (append commands (list (list->array argv))))
53 (write-json-file (path output "commands.json") (list->array commands))
54 (run-tool argv))
55 (write-text (path output "renderer.sha256") (str (out sha256sum ,motif) "\n"))
56 (copy-file (path input "comparison.json") (path output "comparison.json"))
57 (each stem stems
58 (let* ((source (path input (str stem ".cts")))
59 (snapshot (path output (str stem ".cts")))
60 (wav (path output (str stem ".wav"))))
61 (check (and (not (contains? stem "/")) (not (contains? stem "'")))
62 "Use a plain score stem" stem)
63 ($ ,motif tune check ,source)
64 (copy-file source snapshot)
65 (run (list motif "tune" "render" snapshot "-r" "44100" "-o" wav))
66 (check-audio wav half)
67 (let ((measurement (measure wav (path output (str stem "-levels.txt")))))
68 (check (< (dict-ref measurement true_peak_dbtp:) 0) "Native render clips" stem)
69 (set! levels
70 (append levels
71 (list (dict stem: stem
72 source_sha256: (out sha256sum ,snapshot)
73 levels: measurement)))))))
74 (let* ((peak (apply max
75 (map (lambda (entry) (dict-ref (dict-ref entry levels:) true_peak_dbtp:))
76 levels)))
77 (gain (min -0.5 (- -2 peak))))
78 (write-text sequence
79 (str (string-join (map (lambda (stem) (str "file '" stem ".wav'")) stems) "\n") "\n"))
80 (run
81 (list "ffmpeg" "-v" "error" "-nostdin" "-n"
82 "-f" "concat" "-safe" "1" "-i" sequence
83 "-af" (str "volume=" (number->string gain) "dB")
84 "-c:a" "libvorbis" "-q:a" "7"
85 "-metadata" (str "title=" (dict-ref manifest title:) " A/B")
86 "-metadata" "artist=David Wilson"
87 "-metadata" "album=Crash The Stack - Auditions"
88 "-metadata" "comment=Before then after; fresh renderer state and equal monitoring gain."
89 ogg))
90 (check-audio ogg (* 2 half))
91 (let ((encoded (measure ogg (path output "encoded-levels.txt"))))
92 (check (<= (dict-ref encoded true_peak_dbtp:) -1) "Encoded peak exceeds ceiling")
93 ($ ffmpeg -hide_banner -nostats -sseof -1 -i ,ogg -af volumedetect -f null -
94 err: ,(path output "ending.txt"))
95 (let ((ending (last-number (read-text (path output "ending.txt")) "max_volume:")))
96 (check (<= ending -70) "Final tail has not decayed")
97 (write-json-file (path output "delivery-checks.json")
98 (dict native: (list->array levels)
99 common_gain_db: gain
100 after_starts_seconds: half
101 duration_seconds: (* 2 half)
102 encoded: encoded
103 ending_peak_dbfs: ending
104 ogg_sha256: (out sha256sum ,ogg)))))
105 (display "Short A/B passed duration, format, peak and ending checks.\n")))))