AtlatestRepositorycrash-the-stack
crash-the-stack / tree / docs / music / toolsshort-ab-render.sgl
1
#!/usr/bin/env sigil2
;;; 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))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->number13
(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 stems58
(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! levels70
(append levels71
(list (dict stem: stem72
source_sha256: (out sha256sum ,snapshot)73
levels: measurement)))))))74
(let* ((peak (apply max75
(map (lambda (entry) (dict-ref (dict-ref entry levels:) true_peak_dbtp:))76
levels)))77
(gain (min -0.5 (- -2 peak))))78
(write-text sequence79
(str (string-join (map (lambda (stem) (str "file '" stem ".wav'")) stems) "\n") "\n"))80
(run81
(list "ffmpeg" "-v" "error" "-nostdin" "-n"82
"-f" "concat" "-safe" "1" "-i" sequence83
"-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: gain100
after_starts_seconds: half101
duration_seconds: (* 2 half)102
encoded: encoded103
ending_peak_dbfs: ending104
ogg_sha256: (out sha256sum ,ogg)))))105
(display "Short A/B passed duration, format, peak and ending checks.\n")))))