;;; playing-related examples previously scattered around at random ;;; ;;; play-sound func -- play current sound, calling (func data) on each buffer if func is passed ;;; play-often, play-until-c-g -- play sound n times or until c-g ;;; play-region-forever -- play region over and over until C-g typed ;;; loop-between-marks -- play while looping continuously between two movable marks ;;; start-dac -- hold DAC open and play sounds via keyboard ;;; "vector synthesis" ;;; play-with-amps -- play channels with individually settable amps ;;; play-sine and play-sines -- produce tones direct to DAC ;;; see also play-syncd-marks and play-between-marks in marks.scm (provide 'snd-play.scm) (define* (samples->sound-data (beg 0) num snd chn obj pos (sd-chan 0)) (vct->sound-data (channel->vct beg num snd chn pos) (or obj (make-sound-data 1 (or num (frames snd chn)))) sd-chan)) (define* (open-play-output out-chans out-srate out-format out-bufsize) ;; returns (list audio-fd chans frames) (let* ((outchans (or out-chans 1)) (cur-srate (or out-srate (and (not (null? (sounds))) (srate)) 22050)) (pframes (or out-bufsize 256)) (frm (or out-format (if (little-endian?) mus-lshort mus-bshort))) (outbytes (* pframes 2)) ; 2 here since we'll first try to send short (16-bit) data to the DAC (audio-fd ;; ALSA throws an error where the rest of the audio cases simply report failure ;; so we turn off the "error" printout, catch the error itself, and toss it (let ((no-error (hook-empty? mus-error-hook))) (if no-error (add-hook! mus-error-hook (lambda (typ msg) #t))) (let ((val (catch #t (lambda () (mus-audio-open-output 0 cur-srate outchans frm outbytes)) (lambda args -1)))) ; -1 returned in case of error (if no-error (reset-hook! mus-error-hook)) val)))) (if (not (= audio-fd -1)) (set! (dac-size) outbytes)) (list audio-fd outchans pframes))) (define* (play-sound func) "(play-sound func) plays the currently selected sound, calling func on each data buffer, if func exists" (if (not (null? (sounds))) (let* ((filechans (chans)) (audio-info (open-play-output filechans (srate) #f 256)) (audio-fd (car audio-info)) (outchans (cadr audio-info)) (pframes (caddr audio-info))) (if (not (= audio-fd -1)) (let ((len (frames)) (data (make-sound-data outchans pframes))) ; the data buffer passed to the function (func above), then to mus-audio-write (do ((beg 0 (+ beg pframes))) ((or (c-g?) ; C-g to stop in mid-stream (> beg len))) (if (and (> outchans 1) (> filechans 1)) (do ((k 0 (+ 1 k))) ((= k (min outchans filechans))) (samples->sound-data beg pframes #f k data current-edit-position k)) (samples->sound-data beg pframes #f 0 data)) (if func (func data)) (mus-audio-write audio-fd data pframes)) (mus-audio-close audio-fd)) (snd-print ";could not open dac"))) (snd-print ";no sounds open"))) #| (play-sound (lambda (data) (let ((len (length data))) (do ((i 0 (+ 1 i))) ((= i len)) (sound-data-set! data 0 i (* 2.0 (sound-data-ref data 0 i))))))) |# ;;; this could also be done with a function argument to the play function -- get a ;;; sampler for the sound, call it on each invocation of the argument function etc ;;; -------- play sound n times -- (play-often 3) for example. (define (play-often n) "(play-often n) plays the selected sound 'n' times (interruptible via C-g)" (let ((plays (- n 1))) (define (play-once reason) (if (and (> plays 0) (not (c-g?)) (= reason 0)) (begin (set! plays (- plays 1)) (play (selected-sound) :start 0 :stop play-once)))) (play (selected-sound) :start 0 :stop play-once))) ;(bind-key #\p 0 (lambda (n) "play often" (play-often (max 1 n)))) ;;; -------- play sound until c-g (define (play-until-c-g) "(play-until-c-g) plays the selected sound until you interrupt it via C-g" (define (play-once reason) (if (and (not (c-g?)) (= reason 0)) (play (selected-sound) :start 0 :stop play-once))) (play (selected-sound) :start 0 :stop play-once)) ;;; -------- play region over and over until C-g typed (define (play-region-forever reg1) "(play-region-forever reg) plays region 'reg' until you interrupt it via C-g" (let ((reg (if (integer? reg1) (integer->region reg1) reg1))) (define (play-region-again reason) (if (and (not (c-g?)) ; be extra careful (probably superfluous) (= reason 0)) ; 0=play completed normally (play reg :wait #f :stop play-region-again))) (play reg :wait #f :stop play-region-again))) ;(bind-key #\p 0 (lambda (n) "play region forever" (play-region-forever (list-ref (regions) (max 0 n))))) ;;; -------- play while looping continuously between two movable marks (define (loop-between-marks m1 m2 bufsize) "(loop-between-marks mark1 mark2 buffersize) plays while looping between two marks. C-g exits the loop." (let* ((pos1 (mark-sample m1)) (pos2 (mark-sample m2)) (beg (min pos1 pos2)) (end (max pos1 pos2)) (all-data (samples->sound-data)) ; for simplicity, just grab all the data (audio-data (make-sound-data 1 bufsize)) (bytes (* bufsize 2)) ; mus-audio-write handles the translation to short (and takes frames, not bytes as 3rd arg) (audio-fd (mus-audio-open-output 0 (srate) 1 mus-lshort bytes))) (if (not (= audio-fd -1)) (do () ((c-g?) (mus-audio-close audio-fd)) (do ((i 0 (+ 1 i))) ((= i bufsize) (begin (set! i 0) (mus-audio-write audio-fd audio-data bufsize))) (sound-data-set! audio-data 0 i (sound-data-ref all-data 0 beg)) (set! beg (+ 1 beg)) (if (= beg end) (begin (set! pos1 (mark-sample m1)) ; get current mark positions (can change while looping) (set! pos2 (mark-sample m2)) (set! beg (min pos1 pos2)) (set! end (max pos1 pos2))))))))) ;;; m1 and m2 are mark (id) numbers ;;; (loop-between-marks 0 1 512) ;;; -------- hold DAC open and play sounds via keyboard (define* (start-dac (sr 44100) (chans 1)) "(start-dac) starts the DAC running continuously in the background" (play #f :srate sr :channels chans)) (define stop-dac stop-playing) ;;; -------- "vector synthesis" ;;; ;;; this idea (and the weird name) from linux-audio-development mailing list discussion ;;; apparently some commercial synths (or software?) provide this (define (vector-synthesis driver files read-even-when-not-playing) "(vector-synthesis driver files read-even-when-not-playing) uses 'driver', a function of two args (the number of files, and the number of samples between calls) to decide which file to play. If 'read-even-when-not-playing' is #t (default is #f), the input files are constantly read, even if not playing. 'files' is a list of files to be played." (let ((files-len (length files))) (if (> files-len 0) (let* ((bufsize 256) (srate (srate (car files))) (chans (apply max (map channels files))) (data (make-sound-data chans bufsize)) (readers (map make-file->frame files)) (locs (make-vector files-len 0)) (pframes (make-vector files-len 0)) (current-file 0) (reading #t) (out-port (mus-audio-open-output 0 srate chans mus-lshort (* bufsize 2)))) (if (< out-port 0) (format #t "can't open audio port! ~A" out-port) (begin (do ((i 0 (+ 1 i))) ((= i files-len)) (set! (pframes i) (frames (list-ref files i)))) (catch #t (lambda () (while reading (let ((next-file (driver files-len bufsize))) (if (not (= next-file current-file)) (let* ((ramp-down 1.0) (ramp (/ 1.0 bufsize)) (current (list-ref readers current-file)) (current-loc (locs current-file)) (next (list-ref readers next-file)) (next-loc (locs next-file)) (downs (channels current)) (ups (channels next)) (up (make-frame ups)) (down (make-frame downs))) (do ((i 0 (+ 1 i))) ((= i bufsize)) (file->frame next (+ next-loc i) up) (file->frame current (+ current-loc i) down) (do ((j 0 (+ 1 j))) ((= j chans)) (sound-data-set! data j i (+ (if (< j downs) (* ramp-down (frame-ref down j)) 0.0) (if (< j ups) (* (- 1.0 ramp-down) (frame-ref up j)) 0.0)))) (set! ramp-down (- ramp-down ramp))) (if read-even-when-not-playing (do ((i 0 (+ 1 i))) ((= i files-len)) (set! (locs i) (+ (locs i) bufsize))) (begin (set! (locs current-file) (+ (locs current-file) bufsize)) (set! (locs next-file) (+ (locs next-file) bufsize)))) (set! current-file next-file)) (let* ((current (list-ref readers current-file)) (current-loc (locs current-file)) (ons (channels current)) (on (make-frame ons))) (do ((i 0 (+ 1 i))) ((= i bufsize)) (file->frame current (+ current-loc i) on) (do ((k 0 (+ 1 k))) ((= k chans)) (sound-data-set! data k i (if (< k ons) (frame-ref on k) 0.0)))) (if read-even-when-not-playing (do ((i 0 (+ 1 i))) ((= i files-len)) (set! (locs i) (+ (locs i) bufsize))) (set! (locs current-file) (+ (locs current-file) bufsize))))) (mus-audio-write out-port data bufsize) (set! reading (and (not (c-g?)) (letrec ((any-data-left (lambda (f) (if (= f files-len) #f (or (< (locs f) (pframes f)) (any-data-left (+ 1 f))))))) (any-data-left 0))))))) (lambda args (begin (snd-print (format #f "error ~A" args)) (car args)))) (mus-audio-close out-port))))))) #| (vector-synthesis (let ((ctr 0) (file 0)) (lambda (files bufsize) (if (> ctr 4) (begin (set! file (+ 1 file)) (set! ctr 0) (if (>= file files) (set! file 0))) (set! ctr (+ 1 ctr))) file)) (list "oboe.snd" "pistol.snd") #t) |# ;;; play-with-amps -- play channels with individually settable amps (define play-with-amps (lambda (sound . amps) "(play-with-amps snd :rest amps) plays snd with each channel scaled by the corresponding amp: (play-with-amps 0 1.0 0.5) plays channel 2 of stereo sound at half amplitude" (let ((chans (channels sound))) (do ((chan 0 (+ 1 chan))) ((= chan chans)) (let ((player (make-player sound chan))) (set! (amp-control player) (list-ref amps chan)) (add-player player))) (start-playing chans (srate sound))))) ;;; play-sine and play-sines (define (play-sine freq amp) "(play-sine freq amp) plays a 1 second sinewave at freq and amp" (let* ((len 22050) (osc (make-oscil freq))) (play (lambda () (set! len (- len 1)) (if (<= len 0) #f (* amp (oscil osc))))))) (define (play-sines freqs-and-amps) "(play-sines freqs-and-amps) produces a tone given its spectrum: (play-sines '((440 .4) (660 .3)))" (let* ((len 22050) (num-oscs (length freqs-and-amps)) (oscs (make-vector num-oscs)) (amps (make-vector num-oscs))) (do ((i 0 (+ 1 i))) ((= i num-oscs)) (set! (oscs i) (make-oscil (car (list-ref freqs-and-amps i)))) (set! (amps i) (cadr (list-ref freqs-and-amps i)))) (play (lambda () (set! len (- len 1)) (if (<= len 0) #f (let ((sum 0.0)) (do ((i 0 (+ 1 i))) ((= i num-oscs)) (set! sum (+ sum (* (amps i) (oscil (oscs i)))))) sum)))))) ;(play-sines '((425 .05) (450 .01) (470 .01) (546 .02) (667 .01) (789 .034) (910 .032)))