You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1224 lines
43KB

  1. \ clm.fs -- clm related base words, with-sound and friends -*- snd-forth -*-
  2. \ Author: Michael Scholz <mi-scholz@users.sourceforge.net>
  3. \ Created: Mon Mar 15 19:25:58 CET 2004
  4. \ Changed: Fri Jun 26 23:04:01 CEST 2009
  5. \ Commentary:
  6. \
  7. \ clm-print ( fmt args -- )
  8. \ clm-message ( fmt args -- )
  9. \
  10. \ now@ ( -- secs )
  11. \ now! ( secs -- )
  12. \ step ( secs -- )
  13. \ tempo@ ( -- secs )
  14. \ tempo! ( secs -- )
  15. \ interval->hertz ( n -- r )
  16. \ keynum->hertz ( n -- r )
  17. \ hertz->keynum ( n -- r )
  18. \ bpm->seconds ( bpm -- secs )
  19. \ rhythm->seconds ( rhy -- secs )
  20. \
  21. \ tempnam ( -- name )
  22. \ fth-tempnam ( -- name )
  23. \ make-default-comment ( -- str )
  24. \ times->samples ( start dur -- len beg )
  25. \ normalize-partials ( parts1 -- parts2 )
  26. \
  27. \ ws-local-variables ( -- )
  28. \ ws-interrupt? ( -- )
  29. \ ws-info ( start dur -- )
  30. \ run ( start dur -- )
  31. \ run-instrument ( start dur args -- )
  32. \ end-run ( sample -- )
  33. \ reverb-info ( caller in-chans out-chans -- )
  34. \ instrument: ( -- )
  35. \ ;instrument ( -- )
  36. \ event: ( -- )
  37. \ ;event ( -- )
  38. \
  39. \ find-file ( file -- fname|#f )
  40. \ snd-info ( output :key reverb-file-name scaled? timer -- )
  41. \ play-sound ( input :key verbose dac-size audio-format -- )
  42. \ record-sound ( output keyword-args -- )
  43. \
  44. \ clm-mix ( infile :key output output-frame frames input-frame scaler -- )
  45. \ with-sound ( body-xt keyword-args -- ws )
  46. \ clm-load ( fname keyword-args -- ws )
  47. \ with-current-sound ( :key offset scaled-to scaled-by -- )
  48. \ scaled-to ( body-xt scl -- )
  49. \ scaled-by ( body-xt scl -- )
  50. \ with-offset ( body-xt secs -- )
  51. \ with-mix ( body-str args fname beg -- )
  52. \ sound-let ( ws-xt-lst body-xt -- )
  53. $" fth 26-Jun-2009" value *clm-version*
  54. \ defined in snd/snd-xen.c
  55. [ifundef] snd-print : snd-print ( str -- str ) dup .string ; [then]
  56. [ifundef] clm-print : clm-print ( fmt args -- ) format snd-print drop ; [then]
  57. [ifundef] clm-message : clm-message ( fmt args -- str ) ." \ " fth-print cr ; [then]
  58. [ifundef] flog10
  59. <'> flog alias flog10
  60. <'> fln alias flog
  61. <'> flnp1 alias flogp1
  62. [then]
  63. dl-load sndlib Init_sndlib
  64. 'snd provided? [unless]
  65. <'> noop alias main-widgets
  66. <'> noop alias sounds
  67. <'> noop alias set-selected-sound
  68. <'> noop alias sound?
  69. <'> noop alias open-sound
  70. <'> noop alias find-sound
  71. <'> noop alias update-sound
  72. <'> noop alias save-sound
  73. <'> noop alias close-sound
  74. <'> noop alias close-sound-extend
  75. <'> noop alias channels
  76. <'> noop alias play
  77. <'> noop alias play-and-wait
  78. <'> noop alias maxamp
  79. <'> noop alias frames
  80. <'> noop alias scale-channel
  81. <'> noop alias snd-tempnam
  82. <'> noop alias snd-version
  83. : c-g? ( -- f ) #f ;
  84. [then]
  85. \ Also defined in examp.fs.
  86. [ifundef] close-sound-extend
  87. \ 5 == notebook widget
  88. : close-sound-extend <{ snd -- }>
  89. main-widgets 5 array-ref false? unless
  90. 0 { idx }
  91. sounds empty? unless sounds snd array-index to idx then
  92. snd close-sound drop
  93. sounds empty? unless
  94. sounds length 1 = if
  95. sounds 0 array-ref
  96. else
  97. idx sounds length < if
  98. sounds idx array-ref
  99. else
  100. sounds -1 array-ref
  101. then
  102. then set-selected-sound drop
  103. then
  104. else
  105. snd close-sound drop
  106. then
  107. ;
  108. [then]
  109. \ === Notelist ===
  110. hide
  111. 0.00 value *clm-current-time*
  112. 60.0 value *clm-tempo*
  113. 0.25 value *clm-beat*
  114. set-current
  115. : now@ ( -- secs ) *clm-current-time* ;
  116. : now! ( secs -- ) to *clm-current-time* ;
  117. : step ( secs -- ) now@ f+ now! ;
  118. : tempo@ ( -- secs ) *clm-tempo* ;
  119. : tempo! ( secs -- ) to *clm-tempo* ;
  120. previous
  121. \ --- Pitches ---
  122. 6.875 constant lowest-freq
  123. : interval->hertz ( n -- r ) { n } 2.0 12.0 n 3.0 f+ f+ 12.0 f/ f** lowest-freq f* ;
  124. : keynum->hertz ( n -- r ) { n } 2.0 n 3.0 f+ 12.0 f/ f** lowest-freq f* ;
  125. : hertz->keynum ( r -- n ) lowest-freq f/ 2.0 flogn 12.0 f* 3.0 f- f>s ;
  126. hide
  127. : pitch ( interval octave "name" --; self -- freq )
  128. { interval octave }
  129. 2.0 octave 1.0 f+ 12.0 f* interval 3.0 f+ f+ 12.0 f/ f** lowest-freq f*
  130. create ,
  131. does> ( self -- freq )
  132. @
  133. ;
  134. set-current
  135. 0 0 pitch |C0 1 0 pitch |Cs0 1 0 pitch |Df0
  136. 2 0 pitch |D0 3 0 pitch |Ds0 3 0 pitch |Ef0
  137. 4 0 pitch |E0 4 0 pitch |Ff0 5 0 pitch |Es0
  138. 5 0 pitch |F0 6 0 pitch |Fs0 6 0 pitch |Gf0
  139. 7 0 pitch |G0 8 0 pitch |Gs0 8 0 pitch |Af0
  140. 9 0 pitch |A0 10 0 pitch |As0 10 0 pitch |Bf0
  141. 11 0 pitch |B0 11 0 pitch |Cf0 12 0 pitch |Bs0
  142. 0 1 pitch |C1 1 1 pitch |Cs1 1 1 pitch |Df1
  143. 2 1 pitch |D1 3 1 pitch |Ds1 3 1 pitch |Ef1
  144. 4 1 pitch |E1 4 1 pitch |Ff1 5 1 pitch |Es1
  145. 5 1 pitch |F1 6 1 pitch |Fs1 6 1 pitch |Gf1
  146. 7 1 pitch |G1 8 1 pitch |Gs1 8 1 pitch |Af1
  147. 9 1 pitch |A1 10 1 pitch |As1 10 1 pitch |Bf1
  148. 11 1 pitch |B1 11 1 pitch |Cf1 12 1 pitch |Bs1
  149. 0 2 pitch |C2 1 2 pitch |Cs2 1 2 pitch |Df2
  150. 2 2 pitch |D2 3 2 pitch |Ds2 3 2 pitch |Ef2
  151. 4 2 pitch |E2 4 2 pitch |Ff2 5 2 pitch |Es2
  152. 5 2 pitch |F2 6 2 pitch |Fs2 6 2 pitch |Gf2
  153. 7 2 pitch |G2 8 2 pitch |Gs2 8 2 pitch |Af2
  154. 9 2 pitch |A2 10 2 pitch |As2 10 2 pitch |Bf2
  155. 11 2 pitch |B2 11 2 pitch |Cf2 12 2 pitch |Bs2
  156. 0 3 pitch |C3 1 3 pitch |Cs3 1 3 pitch |Df3
  157. 2 3 pitch |D3 3 3 pitch |Ds3 3 3 pitch |Ef3
  158. 4 3 pitch |E3 4 3 pitch |Ff3 5 3 pitch |Es3
  159. 5 3 pitch |F3 6 3 pitch |Fs3 6 3 pitch |Gf3
  160. 7 3 pitch |G3 8 3 pitch |Gs3 8 3 pitch |Af3
  161. 9 3 pitch |A3 10 3 pitch |As3 10 3 pitch |Bf3
  162. 11 3 pitch |B3 11 3 pitch |Cf3 12 3 pitch |Bs3
  163. 0 4 pitch |C4 1 4 pitch |Cs4 1 4 pitch |Df4
  164. 2 4 pitch |D4 3 4 pitch |Ds4 3 4 pitch |Ef4
  165. 4 4 pitch |E4 4 4 pitch |Ff4 5 4 pitch |Es4
  166. 5 4 pitch |F4 6 4 pitch |Fs4 6 4 pitch |Gf4
  167. 7 4 pitch |G4 8 4 pitch |Gs4 8 4 pitch |Af4
  168. 9 4 pitch |A4 10 4 pitch |As4 10 4 pitch |Bf4
  169. 11 4 pitch |B4 11 4 pitch |Cf4 12 4 pitch |Bs4
  170. 0 5 pitch |C5 1 5 pitch |Cs5 1 5 pitch |Df5
  171. 2 5 pitch |D5 3 5 pitch |Ds5 3 5 pitch |Ef5
  172. 4 5 pitch |E5 4 5 pitch |Ff5 5 5 pitch |Es5
  173. 5 5 pitch |F5 6 5 pitch |Fs5 6 5 pitch |Gf5
  174. 7 5 pitch |G5 8 5 pitch |Gs5 8 5 pitch |Af5
  175. 9 5 pitch |A5 10 5 pitch |As5 10 5 pitch |Bf5
  176. 11 5 pitch |B5 11 5 pitch |Cf5 12 5 pitch |Bs5
  177. 0 6 pitch |C6 1 6 pitch |Cs6 1 6 pitch |Df6
  178. 2 6 pitch |D6 3 6 pitch |Ds6 3 6 pitch |Ef6
  179. 4 6 pitch |E6 4 6 pitch |Ff6 5 6 pitch |Es6
  180. 5 6 pitch |F6 6 6 pitch |Fs6 6 6 pitch |Gf6
  181. 7 6 pitch |G6 8 6 pitch |Gs6 8 6 pitch |Af6
  182. 9 6 pitch |A6 10 6 pitch |As6 10 6 pitch |Bf6
  183. 11 6 pitch |B6 11 6 pitch |Cf6 12 6 pitch |Bs6
  184. 0 7 pitch |C7 1 7 pitch |Cs7 1 7 pitch |Df7
  185. 2 7 pitch |D7 3 7 pitch |Ds7 3 7 pitch |Ef7
  186. 4 7 pitch |E7 4 7 pitch |Ff7 5 7 pitch |Es7
  187. 5 7 pitch |F7 6 7 pitch |Fs7 6 7 pitch |Gf7
  188. 7 7 pitch |G7 8 7 pitch |Gs7 8 7 pitch |Af7
  189. 9 7 pitch |A7 10 7 pitch |As7 10 7 pitch |Bf7
  190. 11 7 pitch |B7 11 7 pitch |Cf7 12 7 pitch |Bs7
  191. 0 8 pitch |C8 1 8 pitch |Cs8 1 8 pitch |Df8
  192. 2 8 pitch |D8 3 8 pitch |Ds8 3 8 pitch |Ef8
  193. 4 8 pitch |E8 4 8 pitch |Ff8 5 8 pitch |Es8
  194. 5 8 pitch |F8 6 8 pitch |Fs8 6 8 pitch |Gf8
  195. 7 8 pitch |G8 8 8 pitch |Gs8 8 8 pitch |Af8
  196. 9 8 pitch |A8 10 8 pitch |As8 10 8 pitch |Bf8
  197. 11 8 pitch |B8 11 8 pitch |Cf8 12 8 pitch |Bs8
  198. previous
  199. \ --- Note length ---
  200. : bpm->seconds ( bpm -- secs ) 60.0 swap f/ ;
  201. : rhythm->seconds ( rhy -- secs ) 4.0 tempo@ bpm->seconds f* f* ;
  202. : notelength ( scale "name" --; self -- r )
  203. rhythm->seconds create ,
  204. does> ( self -- r )
  205. @
  206. ;
  207. 1.0 notelength |W \ whole
  208. 2.0 1/f notelength |H \ half
  209. 4.0 1/f notelength |Q \ quarter
  210. 8.0 1/f notelength |A \ eighth
  211. 16.0 1/f notelength |S \ sixteenth
  212. 32.0 1/f notelength |T \ thirty-second
  213. 1.0 2.0 1/f f+ notelength |W.
  214. 2.0 1/f 4.0 1/f f+ notelength |H.
  215. 4.0 1/f 8.0 1/f f+ notelength |Q.
  216. 8.0 1/f 16.0 1/f f+ notelength |A.
  217. 16.0 1/f 32.0 1/f f+ notelength |S.
  218. \ === Global User Variables (settable in ~/.snd_forth or ~/.fthrc) ===
  219. #f value *output*
  220. #f value *reverb*
  221. #f value *locsig*
  222. mus-lshort value *clm-audio-format*
  223. #f value *clm-comment*
  224. 1.0 value *clm-decay-time*
  225. #f value *clm-delete-reverb*
  226. "test.snd" value *clm-file-name*
  227. #f value *clm-notehook*
  228. #f value *clm-play*
  229. #f value *clm-player*
  230. #f value *clm-reverb*
  231. 1 value *clm-reverb-channels*
  232. #() value *clm-reverb-data*
  233. "test.reverb" value *clm-reverb-file-name*
  234. #f value *clm-statistics*
  235. #f value *clm-verbose*
  236. #f value *clm-debug*
  237. #() value *clm-search-list* \ array of sound directories
  238. #() value *clm-instruments* \ array of arrays #( ins-name start dur local-vars )
  239. 'snd provided? [unless]
  240. 1 constant default-output-chans
  241. 44100 constant default-output-srate
  242. mus-next constant default-output-header-type
  243. mus-lfloat constant default-output-data-format
  244. mus-audio-default constant audio-output-device
  245. 512 constant dac-size
  246. 0.0 constant clm-default-frequency
  247. [then]
  248. default-output-chans value *clm-channels*
  249. default-output-srate value *clm-srate*
  250. locsig-type value *clm-locsig-type*
  251. default-output-header-type value *clm-header-type*
  252. default-output-data-format value *clm-data-format*
  253. audio-output-device value *clm-output-device*
  254. dac-size value *clm-rt-bufsize*
  255. mus-file-buffer-size value *clm-file-buffer-size*
  256. mus-clipping value *clm-clipped*
  257. mus-array-print-length value *clm-array-print-length*
  258. clm-table-size value *clm-table-size*
  259. clm-default-frequency value *clm-default-frequency*
  260. \ internal global variables
  261. *clm-channels* value *channels*
  262. *clm-verbose* value *verbose*
  263. *clm-notehook* value *notehook*
  264. hide
  265. user *fth-file-number*
  266. set-current
  267. : tempnam ( -- name )
  268. doc" Looks for environment variables TMP, TEMP, or TMPDIR, otherwise \
  269. uses /tmp as temporary path and produces something like:\n\
  270. /tmp/fth-12345-1.snd\n\
  271. /tmp/fth-12345-2.snd\n\
  272. /tmp/fth-12345-3.snd\n\
  273. [...]"
  274. 1 *fth-file-number* +!
  275. "%s/fth-%d-%d.snd"
  276. environ "TMP" array-assoc-ref ?dup-if
  277. 1 object-ref
  278. else
  279. environ "TEMP" array-assoc-ref ?dup-if
  280. 1 object-ref
  281. else
  282. environ "TMPDIR" array-assoc-ref ?dup-if
  283. 1 object-ref
  284. else
  285. "/tmp"
  286. then
  287. then
  288. then ( tmp ) getpid *fth-file-number* @ 3 >array string-format
  289. ;
  290. previous
  291. : fth-tempnam ( -- fname )
  292. 'snd provided? if
  293. snd-tempnam
  294. else
  295. tempnam
  296. then
  297. ;
  298. : make-default-comment ( -- str )
  299. $" Written %s by %s at %s using clm (%s)" _
  300. #( $" %a %d-%b-%y %H:%M %Z" current-time strftime
  301. getlogin
  302. gethostname
  303. *clm-version* ) string-format
  304. ;
  305. : times->samples ( start dur -- limit begin )
  306. { start dur }
  307. start seconds->samples { beg }
  308. dur seconds->samples { len }
  309. beg len b+ beg
  310. ;
  311. : normalize-partials ( parts1 -- parts2 )
  312. { parts1 }
  313. 0.0 ( sum ) parts1 object-length 1 ?do parts1 i object-ref fabs f+ 2 +loop
  314. dup f0= if
  315. $" all parts have 0.0 amplitude: %s" #( parts1 ) string-format warning
  316. ( sum ) drop parts1
  317. else
  318. ( sum ) 1/f { scl }
  319. parts1 map i 2 mod if *key* scl f* else *key* then end-map ( parts2 )
  320. then
  321. ;
  322. \ === With-Sound Run-Instrument ===
  323. $" with-sound error" create-exception with-sound-error
  324. $" with-sound interrupt" create-exception with-sound-interrupt
  325. #() value *ws-args* \ array for recursive with-sound calls
  326. #f value *clm-current-instrument* \ current instrument set in INSTRUMENT:
  327. : ws-local-variables ( -- )
  328. *clm-instruments* empty? if
  329. $" *clm-instruments* is empty" #() clm-message
  330. else
  331. nil { vals }
  332. "" #() clm-message
  333. *clm-instruments* each to vals
  334. $" === %s [%.3f-%.3f] ===" #( vals 0 array-ref vals 1 array-ref vals 2 array-ref ) clm-message
  335. vals 3 array-ref each ( var ) $" %s = %s" swap clm-message end-each
  336. "" #() clm-message
  337. end-each
  338. then
  339. ;
  340. : ws-interrupt? ( -- )
  341. c-g? if
  342. 'with-sound-interrupt #( "interrupted" ) fth-throw
  343. then
  344. ;
  345. : ws-info ( start dur vars -- start dur )
  346. { start dur vars }
  347. *clm-instruments* #( *clm-current-instrument* start dur vars ) array-push to *clm-instruments*
  348. *notehook* dup xt? swap proc? || if
  349. *clm-current-instrument* start dur *notehook* dup proc? if proc->xt then execute stack-reset
  350. then
  351. ws-interrupt?
  352. start dur
  353. ;
  354. hide
  355. : (run) ( start dur vars -- limit begin ) ws-info times->samples ;
  356. : (run-instrument) ( start dur args vars -- limit begin )
  357. { start dur args vars }
  358. args hash? unless #{} to args then
  359. :degree args :degree hash-ref 0.0 ||
  360. :distance args :distance hash-ref 1.0 ||
  361. :reverb args :reverb hash-ref 0.05 ||
  362. :channels args :channels hash-ref *channels* ||
  363. :output args :output hash-ref *output* ||
  364. :revout args :revout hash-ref *reverb* ||
  365. :type args :type hash-ref locsig-type || make-locsig to *locsig*
  366. \ we set channel 3/4 if any to 0.5 * channel 1/2
  367. *output* mus-output? if
  368. *output* mus-channels 2 > if
  369. *locsig* 2 *locsig* 0 locsig-ref f2/ locsig-set! drop
  370. *output* mus-channels 3 > if
  371. *locsig* 3 *locsig* 1 locsig-ref f2/ locsig-set! drop
  372. then
  373. then
  374. then
  375. *reverb* mus-output? if
  376. *reverb* mus-channels 2 > if
  377. *locsig* 2 *locsig* 0 locsig-reverb-ref f2/ locsig-reverb-set! drop
  378. *reverb* mus-channels 3 > if
  379. *locsig* 3 *locsig* 1 locsig-reverb-ref f2/ locsig-reverb-set! drop
  380. then
  381. then
  382. then
  383. start dur vars (run)
  384. ;
  385. : (end-run) { val idx -- } *locsig* idx val locsig drop ;
  386. set-current
  387. \ RUN/LOOP is only a simple replacement of
  388. \ start dur TIMES->SAMPLES ?DO ... LOOP
  389. \
  390. \ RUN-INSTRUMENT/END-RUN requires at least an opened *output*
  391. \ generator (file->sample), optional an opened *reverb* generator. It
  392. \ uses LOCSIG to set samples in output file. At the end of the loop a
  393. \ sample value must remain on top of stack!
  394. \
  395. \ instrument: foo
  396. \ 0 0.1 nil run-instrument 0.2 end-run
  397. \ ;instrument
  398. \ <'> foo with-sound
  399. \
  400. \ fills a sound file of length 0.1 seconds with 2205 samples (srate
  401. \ 22050) with 0.2.
  402. \
  403. \ 0.0 1.0 RUN ... LOOP
  404. \ 0.0 1.0 #{ :degree 45.0 } RUN-INSTRUMENT ... END-RUN
  405. : run ( start dur -- )
  406. postpone local-variables postpone (run) postpone ?do
  407. ; immediate compile-only
  408. : run-instrument ( start dur locsig-args -- )
  409. postpone local-variables postpone (run-instrument) postpone ?do
  410. ; immediate compile-only
  411. : end-run ( sample -- )
  412. postpone r@ postpone (end-run) postpone loop
  413. ; immediate compile-only
  414. previous
  415. : reverb-info ( caller in-chans out-chans -- )
  416. { caller in-chans out-chans }
  417. $" %s on %d in and %d out channels" #( caller in-chans out-chans ) clm-message
  418. ;
  419. \ === Helper functions for instruments ===
  420. hide
  421. : ins-info ( ins-name -- ) to *clm-current-instrument* ;
  422. : event-info ( ev-name -- ) *clm-verbose* if #() clm-message else drop then ;
  423. set-current
  424. : instrument: ( -- )
  425. >in @ parse-word $>string { ins-name } >in !
  426. :
  427. ins-name postpone literal <'> ins-info compile,
  428. ;
  429. : event: ( -- )
  430. >in @ parse-word $>string { ev-name } >in !
  431. :
  432. ev-name postpone literal <'> event-info compile,
  433. ;
  434. : ;instrument ( -- ) postpone ; ; immediate
  435. <'> ;instrument alias ;event immediate
  436. previous
  437. \ === Playing and Recording Sound Files ===
  438. : find-file ( file -- fname|#f )
  439. doc" Returns the possibly full path name of FILE if FILE exists or \
  440. if FILE was found in *CLM-SEARCH-LIST*, otherwise returns #f."
  441. { file }
  442. file file-exists? if
  443. file
  444. else
  445. #f { fname }
  446. file string? *clm-search-list* array? && if
  447. *clm-search-list* each ( dir )
  448. "/" $+ file $+ dup file-exists? if to fname leave else drop then
  449. end-each
  450. then
  451. fname
  452. then
  453. ;
  454. hide
  455. : .maxamps ( fname name sr scl? -- )
  456. { fname name sr scl? }
  457. fname file-exists? if
  458. fname mus-sound-maxamp { vals }
  459. vals length 0 ?do
  460. $" %6s %c: %.3f (near %.3f secs)%s"
  461. #( name
  462. [char] A i 2/ +
  463. vals i 1+ array-ref
  464. vals i array-ref sr f/
  465. scl? if $" (before scaling)" else "" then ) clm-message
  466. 2 +loop
  467. then
  468. ;
  469. : .timer ( obj -- )
  470. { obj }
  471. $" %*s: %.3f (utime %.3f, stime %.3f)"
  472. #( 8 $" real" obj real-time@ obj user-time@ obj system-time@ ) clm-message
  473. ;
  474. : .timer-ratio ( srate frames obj -- )
  475. { sr frms obj }
  476. frms 0> if
  477. sr frms f/ { m }
  478. $" %*s: %.2f (uratio %.2f)"
  479. #( 8 $" ratio" obj real-time@ m f* obj user-time@ m f* ) clm-message
  480. else
  481. $" %*s: no ratio" #( 8 $" ratio" ) clm-message
  482. then
  483. ;
  484. set-current
  485. : snd-info <{ output :key reverb-file-name #f scaled? #f timer #f -- }>
  486. output mus-sound-duration { dur }
  487. output mus-sound-frames { frames }
  488. output mus-sound-chans { channels }
  489. output mus-sound-srate { srate }
  490. $" filename: %S" #( output ) clm-message
  491. $" chans: %d, srate: %d" #( channels srate f>s ) clm-message
  492. $" format: %s [%s]"
  493. #( output mus-sound-data-format mus-data-format-name
  494. output mus-sound-header-type mus-header-type-name ) clm-message
  495. $" length: %.3f (%d frames)" #( dur frames ) clm-message
  496. timer timer? if
  497. timer .timer
  498. srate frames timer .timer-ratio
  499. then
  500. output $" maxamp" srate scaled? .maxamps
  501. reverb-file-name ?dup-if $" revamp" srate #f .maxamps then
  502. output mus-sound-comment { comm }
  503. comm empty? unless $" comment: %S" #( comm ) clm-message then
  504. ;
  505. previous
  506. \ === Playing and Recording one or two Channel Sounds ===
  507. : play-sound <{ input
  508. :key
  509. verbose *clm-verbose*
  510. dac-size *clm-rt-bufsize*
  511. audio-format *clm-audio-format* -- }>
  512. doc" Plays sound file INPUT.\n\
  513. \"bell.snd\" :verbose #t play-sound"
  514. input find-file to input
  515. input false? if 'no-such-file #( get-func-name input ) fth-throw then
  516. input mus-sound-frames { frames }
  517. input mus-sound-srate { srate }
  518. input mus-sound-chans { chans }
  519. chans 2 > if
  520. $" %s: we can only handle 2 chans, not %d" _ #( get-func-name chans ) string-format warning
  521. 2 to chans
  522. then
  523. verbose if input snd-info then
  524. dac-size frames min { bufsize }
  525. bufsize 0> if
  526. chans bufsize make-sound-data { data }
  527. input mus-sound-open-input { snd-fd }
  528. snd-fd 0< if 'forth-error #( get-func-name $" cannot open %s" _ input ) fth-throw then
  529. mus-audio-default srate chans 2 min audio-format bufsize mus-audio-open-output { dac-fd }
  530. dac-fd 0< if 'forth-error #( get-func-name $" cannot open dac" _ ) fth-throw then
  531. frames 0 ?do
  532. i bufsize + frames > if frames i - to bufsize then
  533. snd-fd 0 bufsize 1- chans data mus-sound-read drop
  534. dac-fd data bufsize mus-audio-write drop
  535. bufsize +loop
  536. snd-fd mus-sound-close-input drop
  537. dac-fd mus-audio-close drop
  538. else
  539. $" nothing to play for %S (%d frames)" #( input bufsize ) string-format warning
  540. then
  541. ;
  542. : record-sound ( output keyword-args -- )
  543. <{ output :key
  544. duration 10.0
  545. verbose *clm-verbose*
  546. output-device *clm-output-device*
  547. dac-size *clm-rt-bufsize*
  548. srate *clm-srate*
  549. channels *clm-channels*
  550. audio-format *clm-audio-format*
  551. data-format *clm-data-format*
  552. header-type *clm-header-type*
  553. comment *clm-comment* -- }>
  554. doc" Records from dac output device to the specified OUTPUT file."
  555. duration seconds->samples { frames }
  556. dac-size frames min { bufsize }
  557. channels 2 min { chans }
  558. mus-srate { old-srate }
  559. srate set-mus-srate drop
  560. comment empty? if $" written %s by %s" _ #( date get-func-name ) string-format to comment then
  561. chans bufsize make-sound-data { data }
  562. chans 0.25 make-vct { vals }
  563. vals each drop mus-audio-mixer mus-audio-reclev i vals mus-audio-mixer-write drop end-each
  564. vals 0.75 vct-fill! drop
  565. vals each drop output-device mus-audio-amp i vals mus-audio-mixer-write drop end-each
  566. output srate chans data-format header-type comment mus-sound-open-output { snd-fd }
  567. snd-fd 0< if 'forth-error #( get-func-name $" cannot open %S" _ output ) fth-throw then
  568. output-device srate chans audio-format bufsize mus-audio-open-input { dac-fd }
  569. dac-fd 0< if 'forth-error #( get-func-name $" cannot open dac" _ ) fth-throw then
  570. verbose if
  571. $" filename: %s" #( output ) clm-message
  572. $" device: %d" #( output-device ) clm-message
  573. $" chans: %d, srate: %d" #( chans srate ) clm-message
  574. $" r format: %s [Dac]" #( audio-format mus-data-format-name ) clm-message
  575. $" w format: %s [%s]"
  576. #( data-format mus-data-format-name header-type mus-header-type-name ) clm-message
  577. $" length: %.3f (%d frames)" #( duration frames ) clm-message
  578. $" comment: %S" #( comment ) clm-message
  579. then
  580. frames 0 ?do
  581. i bufsize + frames > if frames i - to bufsize then
  582. dac-fd data bufsize mus-audio-read drop
  583. snd-fd 0 bufsize 1- chans data mus-sound-write drop
  584. bufsize +loop
  585. dac-fd mus-audio-close drop
  586. snd-fd frames chans * data-format mus-bytes-per-sample * mus-sound-close-output drop
  587. old-srate set-mus-srate drop
  588. ;
  589. : clm-mix <{ infile :key output #f output-frame 0 frames #f input-frame 0 scaler 1.0 -- }>
  590. doc" Mixes files in with-sound's *output* generator.\n\
  591. \"oboe.snd\" clm-mix\n\
  592. Mixes oboe.snd in *output* at *output*'s location 0 from oboe.snd's location 0 on. \
  593. The whole oboe.snd file will be mixed in because :frames is not specified."
  594. 0 { chans }
  595. #f { mx }
  596. *output* mus-output? { outgen }
  597. output unless
  598. outgen if
  599. *output* mus-channels to chans
  600. *output* mus-file-name to output
  601. else
  602. 'with-sound-error $" %s: *output* gen or :output required" #( get-func-name ) fth-raise
  603. then
  604. then
  605. infile find-file to infile
  606. infile false? if 'file-not-found $" %s: cannot find %S" #( get-func-name infile ) fth-raise then
  607. frames infile mus-sound-frames || dup unless drop undef then to frames
  608. outgen if *output* mus-close drop then
  609. chans 0>
  610. scaler f0<> &&
  611. scaler 1.0 f<> && if
  612. save-stack { s }
  613. chans chans dup * 0 ?do scaler loop make-mixer to mx
  614. s restore-stack
  615. then
  616. output ( outfile )
  617. infile ( infile )
  618. output-frame ( outloc )
  619. frames ( frames )
  620. input-frame ( inloc )
  621. mx ( mixer )
  622. #f ( envs ) mus-mix drop
  623. outgen if output continue-sample->file to *output* then
  624. ;
  625. hide
  626. : ws-get-snd ( ws -- snd )
  627. { ws }
  628. ws :output array-assoc-ref find-file { fname }
  629. fname 0 find-sound dup sound? if ( snd ) save-sound then drop
  630. fname open-sound ( snd )
  631. ;
  632. : ws-scaled-to ( ws -- )
  633. { ws }
  634. ws :scaled-to array-assoc-ref { scale }
  635. 'snd provided? if
  636. ws ws-get-snd { snd }
  637. 0.0 snd #t #f maxamp each fmax end-each { mx }
  638. mx f0<> if
  639. scale mx f/ to scale
  640. snd #f #f frames { len }
  641. ws :channels array-assoc-ref 0 ?do scale 0 len snd i ( chn ) #f scale-channel drop loop
  642. then
  643. snd save-sound drop
  644. else
  645. ws :output array-assoc-ref mus-sound-maxamp { smax }
  646. 0.0 smax length 1 ?do smax i array-ref fabs fmax 2 +loop { mx }
  647. mx f0<> if
  648. ws :output array-assoc-ref :scaler scale mx f/ clm-mix
  649. then
  650. then
  651. ;
  652. : ws-scaled-by ( ws -- )
  653. { ws }
  654. ws :scaled-by array-assoc-ref { scale }
  655. 'snd provided? if
  656. ws ws-get-snd { snd }
  657. snd #f #f frames { len }
  658. ws :channels array-assoc-ref 0 ?do scale 0 len snd i ( chn ) #f scale-channel drop loop
  659. snd save-sound drop
  660. else
  661. ws :output array-assoc-ref :scaler scale clm-mix
  662. then
  663. ;
  664. : ws-before-output ( ws -- )
  665. { ws }
  666. ws :old-table-size clm-table-size array-assoc-set!
  667. ( ws ) :old-file-buffer-size mus-file-buffer-size array-assoc-set!
  668. ( ws ) :old-array-print-length mus-array-print-length array-assoc-set!
  669. ( ws ) :old-clipping mus-clipping array-assoc-set!
  670. ( ws ) :old-srate mus-srate array-assoc-set!
  671. ( ws ) :old-locsig-type locsig-type array-assoc-set!
  672. ( ws ) :old-*output* *output* array-assoc-set!
  673. ( ws ) :old-*reverb* *reverb* array-assoc-set!
  674. ( ws ) :old-verbose *verbose* array-assoc-set!
  675. ( ws ) :old-debug *clm-debug* array-assoc-set!
  676. ( ws ) :old-channels *channels* array-assoc-set!
  677. ( ws ) :old-notehook *notehook* array-assoc-set!
  678. ( ws ) :old-decay-time *clm-decay-time* array-assoc-set! to ws
  679. ws :verbose array-assoc-ref to *verbose*
  680. ws :debug array-assoc-ref to *clm-debug*
  681. ws :channels array-assoc-ref to *channels*
  682. ws :notehook array-assoc-ref to *notehook*
  683. ws :decay-time array-assoc-ref to *clm-decay-time*
  684. *clm-table-size* set-clm-table-size drop
  685. *clm-file-buffer-size* set-mus-file-buffer-size drop
  686. *clm-array-print-length* set-mus-array-print-length drop
  687. *clm-clipped* boolean? if *clm-clipped* else #f then set-mus-clipping drop
  688. ws :srate array-assoc-ref set-mus-srate drop
  689. ws :locsig-type array-assoc-ref set-locsig-type drop
  690. ;
  691. : ws-after-output ( ws -- ws )
  692. { ws }
  693. ws :old-table-size array-assoc-ref set-clm-table-size drop
  694. ws :old-file-buffer-size array-assoc-ref set-mus-file-buffer-size drop
  695. ws :old-array-print-length array-assoc-ref set-mus-array-print-length drop
  696. ws :old-clipping array-assoc-ref set-mus-clipping drop
  697. ws :old-srate array-assoc-ref set-mus-srate drop
  698. ws :old-locsig-type array-assoc-ref set-locsig-type drop
  699. ws :old-*output* array-assoc-ref to *output*
  700. ws :old-*reverb* array-assoc-ref to *reverb*
  701. ws :old-verbose array-assoc-ref to *verbose*
  702. ws :old-debug array-assoc-ref to *clm-debug*
  703. ws :old-channels array-assoc-ref to *channels*
  704. ws :old-notehook array-assoc-ref to *notehook*
  705. ws :old-decay-time array-assoc-ref to *clm-decay-time*
  706. *ws-args* array-pop
  707. ;
  708. : ws-statistics ( ws -- )
  709. { ws }
  710. ws :output array-assoc-ref
  711. :reverb-file-name ws :reverb-file-name array-assoc-ref
  712. :scaled? ws :scaled-to array-assoc-ref ws :scaled-by array-assoc-ref ||
  713. :timer ws :timer array-assoc-ref
  714. snd-info
  715. ;
  716. \ player can be one of xt, proc, string, or #f.
  717. \
  718. \ xt: output player execute
  719. \ proc: player #( output ) run-proc
  720. \ string: "player output" system
  721. \ else snd: output play-and-wait
  722. \ clm: output play-sound
  723. \
  724. \ A player may look like this:
  725. \
  726. \ : play-3-times ( output -- )
  727. \ { output }
  728. \ 3 0 ?do output play-and-wait drop loop
  729. \ ;
  730. \ <'> play-3-times to *clm-player*
  731. : ws-play-it ( ws -- )
  732. { ws }
  733. ws :output array-assoc-ref { output }
  734. ws :player array-assoc-ref { player }
  735. player proc? if
  736. player #( output ) run-proc drop
  737. else
  738. player string? if
  739. $" %s %s" #( player output ) string-format file-shell drop
  740. else
  741. 'snd provided? if
  742. output find-file play-and-wait drop
  743. else
  744. output :verbose #f play-sound
  745. then
  746. then
  747. then
  748. ;
  749. : set-args ( key def ws -- )
  750. { key def ws }
  751. key def get-optkey ws key rot array-assoc-set! to ws
  752. ;
  753. set-current
  754. : with-sound-default-args ( keyword-args -- ws )
  755. #() to *clm-instruments*
  756. #() { ws }
  757. *ws-args* ws array-push to *ws-args*
  758. :play *clm-play* ws set-args
  759. :statistics *clm-statistics* ws set-args
  760. :verbose *clm-verbose* ws set-args
  761. :debug *clm-debug* ws set-args
  762. :continue-old-file #f ws set-args
  763. :output *clm-file-name* ws set-args
  764. :channels *clm-channels* ws set-args
  765. :srate *clm-srate* ws set-args
  766. :locsig-type *clm-locsig-type* ws set-args
  767. :header-type *clm-header-type* ws set-args
  768. :data-format *clm-data-format* ws set-args
  769. :comment *clm-comment* ws set-args
  770. :notehook *clm-notehook* ws set-args
  771. :scaled-to #f ws set-args
  772. :scaled-by #f ws set-args
  773. :delete-reverb *clm-delete-reverb* ws set-args
  774. :reverb *clm-reverb* ws set-args
  775. :reverb-data *clm-reverb-data* ws set-args
  776. :reverb-channels *clm-reverb-channels* ws set-args
  777. :reverb-file-name *clm-reverb-file-name* ws set-args
  778. :player *clm-player* ws set-args
  779. :decay-time *clm-decay-time* ws set-args
  780. ws
  781. ;
  782. : with-sound-args ( keyword-args -- ws )
  783. #() { ws }
  784. *ws-args* -1 array-ref { ws1 }
  785. *ws-args* ws array-push to *ws-args*
  786. :play #f ws set-args
  787. :player #f ws set-args
  788. :statistics #f ws set-args
  789. :continue-old-file #f ws set-args
  790. :verbose ws1 :verbose array-assoc-ref ws set-args
  791. :debug ws1 :debug array-assoc-ref ws set-args
  792. :output ws1 :output array-assoc-ref ws set-args
  793. :channels ws1 :channels array-assoc-ref ws set-args
  794. :srate ws1 :srate array-assoc-ref ws set-args
  795. :locsig-type ws1 :locsig-type array-assoc-ref ws set-args
  796. :header-type ws1 :header-type array-assoc-ref ws set-args
  797. :data-format ws1 :data-format array-assoc-ref ws set-args
  798. :comment $" with-sound level %d" #( *ws-args* length ) string-format ws set-args
  799. :notehook ws1 :notehook array-assoc-ref ws set-args
  800. :scaled-to ws1 :scaled-to array-assoc-ref ws set-args
  801. :scaled-by ws1 :scaled-by array-assoc-ref ws set-args
  802. :delete-reverb ws1 :delete-reverb array-assoc-ref ws set-args
  803. :reverb ws1 :reverb array-assoc-ref ws set-args
  804. :reverb-data ws1 :reverb-data array-assoc-ref ws set-args
  805. :reverb-channels ws1 :reverb-channels array-assoc-ref ws set-args
  806. :reverb-file-name ws1 :reverb-file-name array-assoc-ref ws set-args
  807. :decay-time ws1 :decay-time array-assoc-ref ws set-args
  808. ws
  809. ;
  810. : with-sound-main ( body-xt ws -- ws )
  811. { body-xt ws }
  812. body-xt xt? body-xt proc? || body-xt 1 $" a proc or xt" assert-type
  813. ws array? ws 2 $" an associative array" assert-type
  814. ws ws-before-output
  815. ws :reverb array-assoc-ref { reverb-xt }
  816. reverb-xt if
  817. reverb-xt xt? reverb-xt proc? || reverb-xt 3 $" a proc or xt" assert-type
  818. #t
  819. else
  820. #f
  821. then { rev? }
  822. ws :output array-assoc-ref { output }
  823. ws :reverb-file-name array-assoc-ref { revput }
  824. ws :continue-old-file array-assoc-ref { cont? }
  825. cont? if
  826. output continue-sample->file
  827. else
  828. output file-delete
  829. output
  830. ws :channels array-assoc-ref
  831. ws :data-format array-assoc-ref
  832. ws :header-type array-assoc-ref
  833. ws :comment array-assoc-ref dup empty? if drop make-default-comment then
  834. make-sample->file
  835. then to *output*
  836. *output* sample->file? unless
  837. 'with-sound-error #( get-func-name $" cannot open sample->file" _ ) fth-throw
  838. then
  839. cont? if
  840. output mus-sound-srate set-mus-srate drop
  841. 'snd provided? if output 0 find-sound dup sound? if close-sound-extend else drop then then
  842. then
  843. rev? if
  844. cont? if
  845. revput continue-sample->file
  846. else
  847. revput file-delete
  848. revput
  849. ws :reverb-channels array-assoc-ref
  850. ws :data-format array-assoc-ref
  851. ws :header-type array-assoc-ref
  852. $" with-sound temporary reverb file" make-sample->file
  853. then to *reverb*
  854. *reverb* sample->file? unless
  855. 'with-sound-error #( get-func-name $" cannot open reverb sample->file" _ ) fth-throw
  856. then
  857. then
  858. ws :timer make-timer array-assoc-set! to ws
  859. \ compute ws body
  860. *clm-debug* if
  861. \ EXECUTE provides probably a more precise backtrace than FTH-CATCH.
  862. body-xt dup proc? if proc->xt then execute
  863. else
  864. body-xt 'with-sound-interrupt #t fth-catch if
  865. stack-reset
  866. *output* mus-close drop
  867. *reverb* if *reverb* mus-close drop then
  868. $" body-xt interrupted by C-g" #() clm-message
  869. ws ws-after-output ( ws )
  870. exit
  871. then
  872. then
  873. reverb-xt if
  874. *reverb* mus-close drop
  875. ws :reverb-file-name array-assoc-ref undef make-file->sample to *reverb*
  876. *reverb* file->sample? unless
  877. 'with-sound-error #( get-func-name $" cannot open file->sample" _ ) fth-throw
  878. then
  879. \ compute ws reverb
  880. *clm-debug* if
  881. \ push reverb arguments on stack
  882. ws :reverb-data array-assoc-ref each end-each reverb-xt dup proc? if proc->xt then execute
  883. else
  884. reverb-xt 'with-sound-interrupt #t fth-catch if
  885. stack-reset
  886. *output* mus-close drop
  887. *reverb* mus-close drop
  888. $" reverb-xt interrupted by C-g" #() clm-message
  889. ws ws-after-output ( ws )
  890. exit
  891. then
  892. then
  893. *reverb* mus-close drop
  894. then
  895. *output* mus-close drop
  896. ws :timer array-assoc-ref stop-timer
  897. ws ws-get-snd drop
  898. ws :statistics array-assoc-ref if ws ws-statistics then
  899. ws :delete-reverb array-assoc-ref reverb-xt && if ws :reverb-file-name array-assoc-ref file-delete then
  900. ws :scaled-to array-assoc-ref if ws ws-scaled-to then
  901. ws :scaled-by array-assoc-ref if ws ws-scaled-by then
  902. ws :play array-assoc-ref if ws ws-play-it then
  903. ws ws-after-output ( ws )
  904. ;
  905. previous
  906. \ Usage: <'> resflt-test with-sound drop
  907. \ <'> resflt-test :play #f :channels 2 with-sound .g
  908. \ lambda: resflt-test ; :output "resflt.snd" with-sound drop
  909. : with-sound ( body-xt keyword-args -- ws )
  910. doc" \\ keywords and default values:\n\
  911. :play *clm-play* (#f)\n\
  912. :statistics *clm-statistics* (#f)\n\
  913. :verbose *clm-verbose* (#f)\n\
  914. :debug *clm-debug* (#f)\n\
  915. :continue-old-file (#f)\n\
  916. :output *clm-file-name* (\"test.snd\")\n\
  917. :channels *clm-channels* (1)\n\
  918. :srate *clm-srate* (44100)\n\
  919. :locsig-type *clm-locsig-type* (mus-interp-linear)\n\
  920. :header-type *clm-header-type* (mus-next)\n\
  921. :data-format *clm-data-format* (mus-lfloat)\n\
  922. :comment *clm-comment* (#f)\n\
  923. :notehook *clm-notehook* (#f)\n\
  924. :scaled-to (#f)\n\
  925. :scaled-by (#f)\n\
  926. :delete-reverb *clm-delete-reverb* (#f)\n\
  927. :reverb *clm-reverb* (#f)\n\
  928. :reverb-data *clm-reverb-data* (#())\n\
  929. :reverb-channels *clm-reverb-channels* (1)\n\
  930. :reverb-file-name *clm-reverb-file-name* (\"test.reverb\")\n\
  931. :player *clm-player* (#f)\n\
  932. :decay-time *clm-decay-time* (1.0)\n\
  933. Executes BODY-XT, a proc object or an xt, and returns an assoc array with with-sound arguments.\n\
  934. <'> resflt-test with-sound .g cr\n\
  935. <'> resflt-test :play #t :channels 2 :srate 44100 with-sound drop"
  936. *ws-args* empty? if
  937. with-sound-default-args
  938. else
  939. with-sound-args
  940. then ( ws )
  941. with-sound-main ( ws )
  942. ;
  943. : clm-load ( fname keyword-args -- ws )
  944. doc" Loads and evals the CLM instrument call file FNAME. \
  945. See with-sound for a full keyword list.\n\
  946. \"test.fsm\" :play #t :player \"sndplay\" clm-load drop"
  947. *ws-args* empty? if
  948. with-sound-default-args
  949. else
  950. with-sound-args
  951. then
  952. { fname ws }
  953. fname file-exists? if
  954. ws :verbose array-assoc-ref if $" loading %S" _ #( fname ) clm-message then
  955. fname <'> file-eval ws with-sound-main ( ws )
  956. else
  957. 'no-such-file $" %s: %S not found" #( get-func-name fname ) fth-raise
  958. then
  959. ;
  960. : with-current-sound <{ body-xt :key offset 0.0 scaled-to #f scaled-by #f -- }>
  961. doc" Must be called within with-sound body. \
  962. Takes all arguments from current with-sound except :output, :scaled-to, :scaled-by and :comment."
  963. *output* mus-output? false? if
  964. 'with-sound-error $" %s can only be called within with-sound" #( get-func-name ) fth-raise
  965. then
  966. with-sound-args { ws }
  967. fth-tempnam { output }
  968. ws :output output array-assoc-set!
  969. ( ws ) :scaled-to scaled-to array-assoc-set!
  970. ( ws ) :scaled-by scaled-by array-assoc-set! to ws
  971. body-xt ws with-sound-main drop
  972. output :output-frame offset seconds->samples clm-mix
  973. output file-delete
  974. ;
  975. : scaled-to <{ body-xt scl -- }>
  976. doc" Must be called within with-sound body. \
  977. Scales BODY-XT's resulting sound file to SCL.\n\
  978. lambda: ( -- )\n\
  979. 0.0 0.1 660.0 0.5 fm-violin\n\
  980. 0.5 0.1 550.0 0.1 <'> fm-violin 0.8 scaled-to ( scaled to 0.8 )\n\
  981. ; with-sound"
  982. body-xt :scaled-to scl with-current-sound
  983. ;
  984. : scaled-by <{ body-xt scl -- }>
  985. doc" Must be called within with-sound body. \
  986. Scales BODY-XT's resulting sound file by SCL.\n\
  987. lambda: ( -- )\n\
  988. 0.0 0.1 660.0 0.5 fm-violin\n\
  989. 0.5 0.1 550.0 0.1 <'> fm-violin 2.0 scaled-by ( scaled to 0.2 )\n\
  990. ; with-sound"
  991. body-xt :scaled-by scl with-current-sound
  992. ;
  993. : with-offset <{ body-xt sec -- }>
  994. doc" Must be called within with-sound body. \
  995. Mixes BODY-XT's resulting sound file into main sound file at SEC seconds.\n\
  996. lambda: ( -- )\n\
  997. 0.0 0.1 660.0 0.5 fm-violin\n\
  998. 0.5 0.1 550.0 0.1 <'> fm-violin 1.0 with-offset ( its actual begin time is 1.5 )\n\
  999. ; with-sound"
  1000. body-xt :offset sec with-current-sound
  1001. ;
  1002. : with-mix <{ body-str args fname start -- }>
  1003. doc" BODY-STR is a string with with-sound commands, \
  1004. ARGS is an array of with-sound arguments, \
  1005. FNAME is the temporary mix file name without extension, \
  1006. and START is the begin time for mix in.\n\
  1007. lambda: ( -- )\n\
  1008. 0.0 0.1 440 0.1 fm-violin\n\
  1009. \"\n\
  1010. 0.0 0.1 550 0.1 fm-violin\n\
  1011. 0.1 0.1 660 0.1 fm-violin\n\
  1012. \" #() \"sec1\" 0.5 with-mix\n\
  1013. \"\n\
  1014. 0.0 0.1 880 0.1 :reverb-amount 0.2 fm-violin\n\
  1015. 0.1 0.1 1320 0.1 :reverb-amount 0.2 fm-violin\n\
  1016. \" #( :reverb <'> jc-reverb ) \"sec2\" 1.0 with-mix\n\
  1017. 2.0 0.1 220 0.1 fm-violin\n\
  1018. ; with-sound drop"
  1019. body-str string? body-str 1 $" a string" assert-type
  1020. args array? args 2 $" an array" assert-type
  1021. fname string? fname 3 $" a string" assert-type
  1022. start number? start 4 $" a number" assert-type
  1023. *output* mus-output? false? if
  1024. 'with-sound-error $" %s can only be called within with-sound" #( get-func-name ) fth-raise
  1025. then
  1026. fname ".snd" $+ { snd-file }
  1027. fname ".fsm" $+ { mix-file }
  1028. snd-file file-exists? if
  1029. snd-file file-mtime
  1030. else
  1031. #f
  1032. then { snd-time }
  1033. mix-file file-exists? if
  1034. mix-file readlines "" array-join
  1035. else
  1036. ""
  1037. then ( old-body ) body-str string= if
  1038. mix-file file-mtime
  1039. else
  1040. mix-file #( body-str ) writelines
  1041. #f
  1042. then { mix-time }
  1043. snd-time false?
  1044. mix-time false? ||
  1045. snd-time mix-time b< || if
  1046. mix-file args each end-each :output snd-file clm-load drop
  1047. then
  1048. snd-file :output-frame start seconds->samples clm-mix
  1049. ;
  1050. : sound-let ( ws-xt-lst body-xt -- )
  1051. doc" Requires an array of arrays WS-XT-LST with with-sound args and xts, and a BODY-XT. \
  1052. The BODY-XT must take WS-XT-LST length arguments which are tempfile names. \
  1053. with-sound will be feed with ws-args und ws-xts from WS-XT-LST. \
  1054. :output is set to tempnam which will be on stack before executing BODY-XT. \
  1055. These temporary files will be deleted after execution of BODY-XT.\n\
  1056. #( #( #( :reverb <'> jc-reverb ) 0.0 1 220 0.2 <'> fm-violin )\n\
  1057. #( #() 0.5 1 440 0.3 <'> fm-violin ) ) ( the ws-xt-lst )\n\
  1058. lambda: { tmp1 tmp2 }\n\
  1059. tmp1 :output tmp2 clm-mix\n\
  1060. tmp1 clm-mix\n\
  1061. ; ( the body-xt ) <'> sound-let with-sound drop"
  1062. { ws-xt-lst body-xt }
  1063. ws-xt-lst array? ws-xt-lst 1 $" an array" assert-type
  1064. body-xt xt? body-xt proc? || body-xt 2 $" a proc or xt" assert-type
  1065. *output* mus-output? false? if
  1066. 'with-sound-error $" %s can only be called within with-sound" #( get-func-name ) fth-raise
  1067. then
  1068. ws-xt-lst map
  1069. *key* 0 array-ref ( args ) each end-each with-sound-args
  1070. ( ws ) :output fth-tempnam array-assoc-set! { ws }
  1071. *key* 1 array-ref ( xt ) each end-each ws with-sound-main :output array-assoc-ref ( outfile )
  1072. end-map { outfiles }
  1073. body-xt xt? if
  1074. outfiles each end-each body-xt execute
  1075. else
  1076. body-xt outfiles run-proc drop
  1077. then
  1078. outfiles each file-delete end-each
  1079. ;
  1080. \ === example instruments, more in clm-ins.fs ===
  1081. instrument: simp ( start dur freq amp -- )
  1082. { start dur freq amp }
  1083. :frequency freq make-oscil { os }
  1084. :envelope #( 0e 0e 25e 1e 75e 1e 100e 0e ) :duration dur :scaler amp make-env { en }
  1085. start dur run
  1086. i os 0.0 0.0 oscil en env f* *output* outa drop
  1087. loop
  1088. ;instrument
  1089. : run-test ( -- ) 0.0 1.0 330.0 0.5 simp ;
  1090. : input-fn ( gen -- proc; dir self -- r )
  1091. 1 proc-create swap ,
  1092. does> ( dir self -- r )
  1093. nip @ readin
  1094. ;
  1095. instrument: src-simp ( start dur amp sr sr-env fname -- )
  1096. { start dur amp sr sr-env fname }
  1097. :file fname find-file make-readin { f }
  1098. :input f input-fn :srate sr make-src { sc }
  1099. :envelope sr-env :duration dur make-env { en }
  1100. start dur run
  1101. i sc en env #f src amp f* *output* outa drop
  1102. loop
  1103. f mus-close drop
  1104. ;instrument
  1105. instrument: conv-simp ( start dur filt fname amp -- )
  1106. { start dur filt fname amp }
  1107. :file fname find-file make-readin { f }
  1108. filt string? if
  1109. 8192 0.0 make-vct { v }
  1110. filt find-file 0 0 v length v file->array
  1111. else
  1112. filt
  1113. then { data }
  1114. :input f input-fn :filter data make-convolve { cv }
  1115. start dur run
  1116. i cv #f convolve amp f* *output* outa drop
  1117. loop
  1118. f mus-close drop
  1119. ;instrument
  1120. \ <'> src-test with-sound drop
  1121. event: src-test ( -- )
  1122. 0.0 1.0 1.0 0.2 #( 0e 0e 50e 1e 100e 0e ) $" oboe.snd" src-simp
  1123. ;event
  1124. \ <'> conv1-test with-sound drop
  1125. event: conv1-test ( -- )
  1126. 0.0 1.0 vct( 0.5 0.2 0.1 0.05 0e 0e 0e 0e ) $" fyow.snd" 1.0 conv-simp
  1127. ;event
  1128. \ <'> conc2-test with-sound drop
  1129. event: conv2-test ( -- )
  1130. 0.0 1.0 $" pistol.snd" $" fyow.snd" 0.2 conv-simp
  1131. ;event
  1132. \ <'> inst-test with-sound drop
  1133. event: inst-test ( -- )
  1134. 0.0 1.0 1.0 0.2 #( 0 0 50 1 100 0 ) $" oboe.snd" src-simp
  1135. 1.2 1.0 vct( 0.5 0.2 0.1 0.05 0 0 0 0 ) $" fyow.snd" 1.0 conv-simp
  1136. 2.4 1.0 $" pistol.snd" $" fyow.snd" 0.2 conv-simp
  1137. ;event
  1138. \ waveshape removed from clm.c
  1139. #f 'snd provided? && [if]
  1140. instrument: arpeggio <{ start dur freq amp :key ampenv #( 0 0 0.5 1 1 0 ) offset 1.0 -- }>
  1141. start dur times->samples { end beg }
  1142. 12 make-array map!
  1143. :frequency freq offset i 6 - 0.03 f* f* f+
  1144. :partials #( 1 1 5 0.7 6 0.7 7 0.7 8 0.7 9 0.7 10 0.7 ) make-waveshape
  1145. end-map { waveshbank }
  1146. :envelope ampenv :scaler amp 0.25 f* :length end make-env { amp-env }
  1147. end 0.0 make-vct map!
  1148. 0.0 ( wvsum ) waveshbank each ( wv ) 1.0 0.0 waveshape f+ end-each ( wvsum ) amp-env env f*
  1149. end-map ( output )
  1150. #f channels 0 ?do ( output ) beg end #f i #f undef vct->channel ( output ) loop ( output ) drop
  1151. ;instrument
  1152. event: arpeggio-test ( -- )
  1153. :file "arpeggio.snd"
  1154. :header-type mus-next
  1155. :data-format mus-lfloat
  1156. :channels 2
  1157. :srate 22050 new-sound { snd }
  1158. 0 10 65 0.5 arpeggio
  1159. snd save-sound drop
  1160. 0 snd play drop
  1161. snd close-sound drop
  1162. ;event
  1163. [then]
  1164. \ clm.fs ends here