;;; with-sound for a sndlib-only context (no Snd editor) (provide 'sndlib-ws.scm) (set! *clm-srate* 44100) (define *clm-file-name* "test.snd") (define *clm-channels* 1) (define *clm-sample-type* mus-lfloat) (define *clm-header-type* mus-next) (define *clm-verbose* #f) (define *clm-play* #f) (define *clm-statistics* #f) (define *clm-reverb* #f) (define *clm-reverb-channels* 1) (define *clm-reverb-data* ()) (define *clm-locsig-type* mus-interp-linear) (define *clm-clipped* #t) (define *clm-array-print-length* 12) (define *clm-player* #f) (define *clm-notehook* #f) (define *clm-with-sound-depth* 0) ; for CM, not otherwise used (define *clm-delete-reverb* #f) ; should with-sound clean up reverb stream (set! *clm-file-buffer-size* 65536) (define (times->samples beg dur) "(times->samples beg dur) converts beg and (+ beg dur) to samples, returning both in a list" (list (seconds->samples beg) (seconds->samples (+ beg dur)))) ;;; -------- definstrument -------- ;(define definstrument define*) -- old form 2-Nov-05 (define *definstrument-hook* #f) ; for CM (define-macro (definstrument args . body) (let* ((name (car args)) (targs (cdr args)) (utargs (let ((arg-names ())) (for-each (lambda (a) (if (not (keyword? a)) (if (symbol? a) (set! arg-names (cons a arg-names)) (set! arg-names (cons (car a) arg-names))))) targs) (reverse arg-names)))) `(begin (define* (,name ,@targs) (if *clm-notehook* (*clm-notehook* (symbol->string ',name) ,@utargs)) ,@body) ,@(if *definstrument-hook* (list (*definstrument-hook* name targs)) (list))))) ;;; -------- with-sound -------- (define* (with-sound-helper thunk (output *clm-file-name*) (channels *clm-channels*) (srate *clm-srate*) (sample-type *clm-sample-type*) (header-type *clm-header-type*) (comment #f) (verbose *clm-verbose*) (reverb *clm-reverb*) (revfile "test.rev") (reverb-data *clm-reverb-data*) (reverb-channels *clm-reverb-channels*) (continue-old-file #f) (statistics *clm-statistics*) (scaled-to #f) (scaled-by #f) (play *clm-play*) (clipped 'unset) (notehook *clm-notehook*) ; (with-sound (:notehook (lambda args (display args))) (fm-violin 0 1 440 .1)) (ignore-output #f)) "with-sound-helper is the business portion of the with-sound macro" (let* ((old-srate *clm-srate*) (old-*output* *output*) (old-*reverb* *reverb*) (old-notehook *clm-notehook*) (old-verbose *clm-verbose*) (output-to-file (string? output)) (output-1 (if (and output-to-file (or scaled-to scaled-by)) (string-append output ".temp") output)) ; protect during nesting (reverb-1 revfile) (reverb-to-file (and reverb (string? revfile)))) (if ignore-output (begin (set! output-1 *clm-file-name*) (set! output-to-file (string? output-1)))) (dynamic-wind (lambda () (set! *clm-verbose* verbose) (set! *clm-notehook* notehook) (set! (locsig-type) *clm-locsig-type*) (set! (mus-array-print-length) *clm-array-print-length*) (if (equal? clipped 'unset) (if (and (or scaled-by scaled-to) (member sample-type (list mus-bfloat mus-lfloat mus-bdouble mus-ldouble))) (set! (mus-clipping) #f) (set! (mus-clipping) *clm-clipped*)) (set! (mus-clipping) clipped)) (set! *clm-srate* srate)) (lambda () (if output-to-file (begin (if continue-old-file (begin (set! *output* (continue-sample->file output-1)) (set! *clm-srate* (mus-sound-srate output-1))) (begin (if (file-exists? output-1) (delete-file output-1)) (set! *output* (make-sample->file output-1 channels sample-type header-type comment))))) (begin (if (and (not continue-old-file) (vector? output-1)) (fill! output-1 0.0)) (set! *output* output-1))) (if reverb (if reverb-to-file (begin (if continue-old-file (set! *reverb* (continue-sample->file reverb-1)) (begin (if (file-exists? reverb-1) (delete-file reverb-1)) (set! *reverb* (make-sample->file reverb-1 reverb-channels sample-type header-type))))) (begin (if (and (not continue-old-file) (vector? reverb-1)) (fill! reverb-1 0.0)) (set! *reverb* reverb-1)))) (let ((start (if statistics (get-internal-real-time))) (flush-reverb #f) (cycles 0) (revmax #f)) (catch 'mus-error thunk (lambda args (format () ";~%with-sound mus-error: ~{~A~^ ~}~%" (cdr args)) (set! flush-reverb #t))) (if (and reverb (not flush-reverb)) ; i.e. not interrupted by error and trying to jump out (begin (if reverb-to-file (mus-close *reverb*)) (if statistics (if reverb-to-file (set! revmax (cadr (mus-sound-maxamp reverb-1))) (if (float-vector? reverb-1) (set! revmax (float-vector-peak reverb-1))))) (if reverb-to-file (set! *reverb* (make-file->sample reverb-1))) (apply reverb reverb-data) ; here is the reverb call(!) (if reverb-to-file (mus-close *reverb*)) )) (if output-to-file (mus-close *output*)) (if statistics (begin (set! cycles (- (get-internal-real-time) start)) (format () "~%;~A:~% maxamp~A:~{ ~,4F~}~%~A compute time: ~,3F~%" (if output-to-file (if (or scaled-to scaled-by) (substring output-1 0 (- (length output-1) 5)) output-1) (if (vector? output-1) "vector" "flush")) (if (or scaled-to scaled-by) " (before scaling)" "") (if output-to-file (let ((lst (mus-sound-maxamp output-1))) (do ((i 0 (+ i 2))) ((>= i (length lst))) (list-set! lst i (/ (list-ref lst i) *clm-srate*))) lst) (if (float-vector? output-1) (list (float-vector-peak output-1)) '(0.0))) (if revmax (format #f " rev max: ~,4F~%" revmax) "") cycles))) (if (or scaled-to scaled-by) (if output-to-file (let ((scaling (or scaled-by (let* ((mx-lst (mus-sound-maxamp output-1)) (mx (if (not (null? mx-lst)) (cadr mx-lst) 1.0))) (do ((i 1 (+ i 2))) ((>= i (length mx-lst)) (/ scaled-to mx)) (set! mx (max mx (list-ref mx-lst i))))))) (out-file (substring output-1 0 (- (length output-1) 5)))) (let ((g (make-sample->file out-file channels sample-type header-type #f))) (mus-close g)) (mus-file-mix out-file output-1 0 (mus-sound-framples output-1) 0 (let ((mx (make-float-vector (list channels channels) 0.0))) (do ((i 0 (+ i 1))) ((= i channels) mx) (set! (mx i i) scaling)))) (delete-file output-1) (set! output-1 (substring output-1 0 (- (length output-1) 5)))) (if (float-vector? output-1) (if scaled-to (let ((pk (float-vector-peak output-1))) (if (> pk 0.0) (float-vector-scale! output-1 (/ scaled-to pk)))) (float-vector-scale! output-1 scaled-by))))) (if (and *clm-player* play output-to-file) (*clm-player* output-1))) output-1) (lambda () (set! *clm-verbose* old-verbose) (set! *clm-notehook* old-notehook) (if *reverb* (begin (mus-close *reverb*) (set! *reverb* old-*reverb*))) (if *output* (begin (if (mus-output? *output*) (mus-close *output*)) (set! *output* old-*output*))) (set! *clm-srate* old-srate))))) (define-macro (with-sound args . body) `(with-sound-helper (lambda () ,@body) ,@args)) ;;; -------- with-temp-sound -------- (define-macro (with-temp-sound args . body) `(let ((old-file-name *clm-file-name*)) ;; with-sound but using tempnam for output (can be over-ridden by explicit :output) (dynamic-wind (lambda () (set! *clm-file-name* (tmpnam))) (lambda () (with-sound-helper (lambda () ,@body) ,@args)) ; dynamic-wind returns this as its result (lambda () (set! *clm-file-name* old-file-name))))) ;;; -------- clm-load -------- (define (clm-load file . args) "(clm-load file . args) loads 'file' in the context of with-sound" (apply with-sound-helper (lambda () (load file)) args)) ;;; -------- sound-let -------- ;;; ;;; (with-sound () (sound-let ((a () (fm-violin 0 .1 440 .1))) (mus-file-mix "test.snd" a))) (define-macro (sound-let snds . body) `(let ((temp-files ())) (begin (let ((val (let ,(map (lambda (arg) (if (> (length arg) 2) `(,(car arg) (with-temp-sound ,(cadr arg) ,@(cddr arg))) arg)) snds) ,@body))) ; sound-let body (for-each (lambda (file) ; clean up all local temps (if (and (string? file) ; is it a file? (file-exists? file)) (delete-file file))) temp-files) val)))) ; return body result ;;; -------- Common Music -------- (define* (init-with-sound (srate *clm-srate*) (output *clm-file-name*) (channels *clm-channels*) (header-type *clm-header-type*) data-format (sample-type *clm-sample-type*) (comment #f) ;(verbose *clm-verbose*) ; why is this commented out? (reverb *clm-reverb*) (revfile "test.rev") (reverb-data *clm-reverb-data*) (reverb-channels *clm-reverb-channels*) (continue-old-file #f) (statistics *clm-statistics*) (scaled-to #f) (play *clm-play*) (scaled-by #f)) "(init-with-sound . args) is the first half of with-sound; it sets up the CLM output choices, reverb, etc. Use \ finish-with-sound to complete the process." (let ((old-srate *clm-srate*) (start (if statistics (get-internal-real-time))) (output-to-file (string? output)) (reverb-to-file (and reverb (string? revfile)))) (set! *clm-srate* srate) (if output-to-file (if continue-old-file (begin (set! *output* (continue-sample->file output)) (set! *clm-srate* (mus-sound-srate output))) (begin (if (file-exists? output) (delete-file output)) (set! *output* (make-sample->file output channels (or data-format sample-type) header-type comment)))) (begin (if (and (not continue-old-file) (vector output)) (fill! output 0.0)) (set! *output* output))) (if reverb (if reverb-to-file (if continue-old-file (set! *reverb* (continue-sample->file revfile)) (begin (if (file-exists? revfile) (delete-file revfile)) (set! *reverb* (make-sample->file revfile reverb-channels (or data-format sample-type) header-type)))) (begin (if (and (not continue-old-file) (vector? revfile)) (fill! revfile 0.0)) (set! *reverb* revfile)))) (list 'with-sound-data output reverb revfile old-srate statistics #f ;to-snd scaled-to scaled-by play reverb-data start))) (define (finish-with-sound wsd) "(finish-with-sound wsd) closes the notelist process started by init-with-sound" (if (eq? (car wsd) 'with-sound-data) (let ((output (list-ref wsd 1)) (reverb (list-ref wsd 2)) (revfile (list-ref wsd 3)) (old-srate (list-ref wsd 4)) ;(statistics (list-ref wsd 5)) ;(to-snd (list-ref wsd 6)) ;(scaled-to (list-ref wsd 7)) ;(scaled-by (list-ref wsd 8)) ;(play (list-ref wsd 9)) (reverb-data (list-ref wsd 10)) ;(start (list-ref wsd 11)) ) (if reverb (begin (mus-close *reverb*) (if (string? revfile) (set! *reverb* (make-file->sample revfile)) (set! *reverb* revfile)) (apply reverb reverb-data) (mus-close *reverb*))) (if (mus-output? *output*) (mus-close *output*)) (set! *clm-srate* old-srate) output) (throw 'wrong-type-arg (list "finish-with-sound" wsd)))) (define wsdat-play ; for cm (dilambda (lambda (w) "accessor for play field of init-with-sound struct" (list-ref w 9)) (lambda (w val) (list-set! w 9 val)))) (define ->frequency (let ((main-pitch (/ 440.0 (expt 2.0 (/ 57 12)))) ; a4 = 440Hz is pitch 57 in our numbering (last-octave 0) ; octave number can be omitted (ratios (vector 1.0 256/243 9/8 32/27 81/64 4/3 1024/729 3/2 128/81 27/16 16/9 243/128 2.0))) (lambda* (pitch pythagorean) ; pitch can be pitch name or actual frequency "(->frequency pitch pythagorean) returns the frequency (Hz) of the 'pitch', a CLM/CM style note name as a \ symbol: 'e4 for example. If 'pythagorean', the frequency calculation uses small-integer ratios, rather than equal-tempered tuning." (if (symbol? pitch) (let* ((name (string-downcase (symbol->string pitch))) (base-char (name 0)) (sign-char (and (> (length name) 1) (not (char-numeric? (name 1))) (not (char=? (name 1) #\n)) (name 1))) (octave-char (if (and (> (length name) 1) (char-numeric? (name 1))) (name 1) (if (and (> (length name) 2) (char-numeric? (name 2))) (name 2) #f))) (base (modulo (+ 5 (- (char->integer base-char) (char->integer #\a))) 7)) ; c-based (diatonic) octaves (sign (if (not sign-char) 0 (if (char=? sign-char #\f) -1 1))) (octave (if octave-char (- (char->integer octave-char) (char->integer #\0)) last-octave)) (base-pitch (+ sign (case base ((0) 0) ((1) 2) ((2) 4) ((3) 5) ((4) 7) ((5) 9) ((6) 11)))) (et-pitch (+ base-pitch (* 12 octave)))) (set! last-octave octave) (if pythagorean (* main-pitch (expt 2 octave) (ratios base-pitch)) (* main-pitch (expt 2.0 (/ et-pitch 12))))) pitch)))) (define (->sample beg) "(->sample time-in-seconds) -> time-in-samples" (round (* (if (not (null? (sounds))) (srate) *clm-srate*) beg))) ;;; -------- defgenerator -------- ;;; (defgenerator osc a b) ;;; (defgenerator (osc :methods (list (cons 'mus-frequency (lambda (obj) 100.0)))) a b) (define-macro (defgenerator struct-name . fields) (define (list->bindings lst) (let ((len (length lst))) (let ((nlst (make-list (* len 2)))) (do ((old lst (cdr old)) (nsym nlst (cddr nsym))) ((null? old) nlst) (if (pair? (car old)) (begin (set-car! (cdr nsym) (caar old)) (set-car! nsym (list 'quote (caar old)))) (begin (set-car! (cdr nsym) (car old)) (set-car! nsym (list 'quote (car old))))))))) (let* ((name (if (pair? struct-name) (car struct-name) struct-name)) (sname (if (string? name) name (symbol->string name))) (wrapper (or (and (pair? struct-name) (or (and (> (length struct-name) 2) (equal? (struct-name 1) :make-wrapper) (struct-name 2)) (and (= (length struct-name) 5) (equal? (struct-name 3) :make-wrapper) (struct-name 4)))) (lambda (gen) gen))) (methods (and (pair? struct-name) (or (and (> (length struct-name) 2) (equal? (struct-name 1) :methods) (struct-name 2)) (and (= (length struct-name) 5) (equal? (struct-name 3) :methods) (struct-name 4)))))) `(begin (define ,(string->symbol (string-append sname "?")) #f) (define ,(string->symbol (string-append "make-" sname)) #f) (let ((gen-type ',(string->symbol (string-append "+" sname "+"))) (gen-methods (and ,methods (apply inlet ,methods)))) (set! ,(string->symbol (string-append sname "?")) (lambda (obj) (and (let? obj) (eq? (obj 'mus-generator-type) gen-type)))) (set! ,(string->symbol (string-append "make-" sname)) (lambda* ,(map (lambda (n) (if (pair? n) n (list n 0.0))) fields) (,wrapper (openlet ,(if methods `(sublet gen-methods ,@(list->bindings (reverse fields)) 'mus-generator-type gen-type) `(inlet 'mus-generator-type gen-type ,@(list->bindings fields))))))))))) ;;; -------------------------------------------------------------------------------- ;;; ;;; functions from Snd that are used in some instruments ;;; these replacements assume that the Snd functions are not present (define* (file-name name) (if (string? name) (mus-expand-filename name) (mus-file-name name))) (define srate mus-sound-srate) (define (channels . args) (let ((obj (car args))) (if (string? obj) (mus-sound-chans obj) (mus-channels obj)))) ;;; I think length is handled by s7 for all types (define (framples . args) (let ((obj (car args))) (if (string? obj) (mus-sound-framples obj) (length obj)))) (define snd-print display) (define snd-warning display) (define snd-display (lambda args (apply format (append (list #t) (cdr args))))) (define (snd-error str) (error 'mus-error str)) (define snd-tempnam tmpnam)