1 #include "snd.h"
2 #include "sndlib-strings.h"
3 #include "clm-strings.h"
4 
5 static const char **snd_xrefs(const char *topic);
6 static const char **snd_xref_urls(const char *topic);
7 
8 static char **snd_itoa_strs = NULL;
9 static int snd_itoa_ctr = 0, snd_itoa_size = 0;
10 
snd_itoa(int n)11 static char *snd_itoa(int n)
12 {
13   char *str;
14   if (!snd_itoa_strs)
15     {
16       snd_itoa_size = 32;
17       snd_itoa_strs = (char **)calloc(snd_itoa_size, sizeof(char *));
18     }
19   else
20     {
21       if (snd_itoa_ctr >= snd_itoa_size)
22 	{
23 	  int i;
24 	  snd_itoa_size += 32;
25 	  snd_itoa_strs = (char **)realloc(snd_itoa_strs, snd_itoa_size * sizeof(char *));
26 	  for (i = snd_itoa_ctr; i < snd_itoa_size; i++) snd_itoa_strs[i] = NULL;
27 	}
28     }
29   str = (char *)calloc(LABEL_BUFFER_SIZE, sizeof(char));
30   snprintf(str, LABEL_BUFFER_SIZE, "%d", n);
31   snd_itoa_strs[snd_itoa_ctr++] = str;
32   return(str);
33 }
34 
35 
free_snd_itoa(void)36 static void free_snd_itoa(void)
37 {
38   int i;
39   for (i = 0; i < snd_itoa_ctr; i++)
40     if (snd_itoa_strs[i])
41       {
42 	free(snd_itoa_strs[i]);
43 	snd_itoa_strs[i] = NULL;
44       }
45   snd_itoa_ctr = 0;
46 }
47 
48 
vstrcat(const char * arg1,...)49 static char *vstrcat(const char *arg1, ...)
50 {
51   char *buf;
52   va_list ap;
53   int len = 0;
54   char *str;
55   len = strlen(arg1);
56   va_start(ap, arg1);
57   while ((str = va_arg(ap, char *)))
58     len += strlen(str);
59   va_end(ap);
60   buf = (char *)calloc(len + 32, sizeof(char));
61   strcat(buf, arg1);
62   va_start(ap, arg1);
63   while ((str = va_arg(ap, char *)))
64     strcat(buf, str);
65   va_end(ap);
66   return(buf);
67 }
68 
69 
70 static const char *main_snd_xrefs[16] = {
71   "{CLM}: sound synthesis",
72   "{CM}: algorithmic composition",
73   "{CMN}: music notation",
74   "{Ruby}: extension language",
75   "{Forth}: extension language",
76   "{s7}: extension language",
77   "{Emacs}: Snd as Emacs subjob",
78   "{Sndlib}: underlying sound support library",
79   "{Scripting}: Snd with no GUI",
80   "{Motif}: Motif extensions",
81   "{Ladspa}: plugins",
82   "{Multiprecision arithmetic}: libgmp and friends",
83   NULL
84 };
85 
86 static const char *main_snd_xref_urls[16] = {
87   "grfsnd.html#sndwithclm",
88   "grfsnd.html#sndwithcm",
89   "sndscm.html#musglyphs",
90   "grfsnd.html#sndandruby",
91   "grfsnd.html#sndandforth",
92   "grfsnd.html#sndands7",
93   "grfsnd.html#emacssnd",
94   "sndlib.html#introduction",
95   "grfsnd.html#sndwithnogui",
96   "grfsnd.html#sndwithmotif",
97   "grfsnd.html#sndandladspa",
98   "grfsnd.html#sndandgmp",
99   NULL,
100 };
101 
102 
main_snd_help(const char * subject,...)103 static void main_snd_help(const char *subject, ...)
104 {
105   va_list ap;
106   char *helpstr;
107   snd_help_with_xrefs(subject, "", WITHOUT_WORD_WRAP, main_snd_xrefs, main_snd_xref_urls);
108   va_start(ap, subject);
109   while ((helpstr = va_arg(ap, char *))) snd_help_append(helpstr);
110   va_end(ap);
111   snd_help_back_to_top();
112 }
113 
114 
115 #if USE_MOTIF
116   #include <X11/IntrinsicP.h>
117   #include <X11/xpm.h>
118 #endif
119 
120 #if HAVE_LADSPA
121   #include <ladspa.h>
122 #endif
123 
124 #if HAVE_FFTW3
125   #include <fftw3.h>
126 #endif
127 
128 #if USE_MOTIF
129   #define XM_VERSION_NAME "xm-version"
130 #else
131   #define XM_VERSION_NAME "xg-version"
132 #endif
133 
134 #if WITH_GMP
135   #include <gmp.h>
136   #include <mpfr.h>
137   #include <mpc.h>
138 #endif
139 
xm_version(void)140 static char *xm_version(void)
141 {
142   Xen xm_val = Xen_false;
143 
144 #if HAVE_SCHEME
145   #if USE_MOTIF
146     xm_val = Xen_eval_C_string("(and (defined? 'xm-version) xm-version)");
147   #endif
148 #endif
149 
150 #if HAVE_FORTH
151       xm_val = Xen_variable_ref(XM_VERSION_NAME);
152 #endif
153 
154 #if HAVE_RUBY
155   #if USE_MOTIF
156       if (rb_const_defined(rb_cObject, rb_intern("Xm_Version")))
157 	xm_val = Xen_eval_C_string("Xm_Version");
158   #endif
159 #endif
160 
161   if (Xen_is_string(xm_val))
162     {
163       char *version;
164       version = (char *)calloc(32, sizeof(char));
165       snprintf(version, 32, "\n    %s: %s",
166 #if USE_MOTIF
167 		   "xm",
168 #else
169 		   "xg",
170 #endif
171 		   Xen_string_to_C_string(xm_val));
172       if (snd_itoa_ctr < snd_itoa_size) snd_itoa_strs[snd_itoa_ctr++] = version;
173       return(version);
174     }
175   return(mus_strdup(" ")); /* not null because that breaks the sequence for --version (xm-version not defined by that point) */
176 }
177 
178 
179 #if HAVE_GL
180 void Init_libgl(void);
181 
gl_version(void)182 static char *gl_version(void)
183 {
184   Xen gl_val = Xen_false;
185   Init_libgl(); /* define the version string, if ./snd --version */
186 
187 #if HAVE_SCHEME
188   gl_val = Xen_eval_C_string("(and (provided? 'gl) gl-version)"); /* this refers to gl.c, not the GL library */
189 #endif
190 
191 #if HAVE_RUBY
192   if (rb_const_defined(rb_cObject, rb_intern("Gl_Version")))
193     gl_val = Xen_eval_C_string("Gl_Version");
194 #endif
195 
196 #if HAVE_FORTH
197   if (fth_provided_p("gl"))
198     gl_val = Xen_variable_ref("gl-version");
199 #endif
200 
201   if (Xen_is_string(gl_val))
202     {
203       char *version = NULL;
204       version = (char *)calloc(32, sizeof(char));
205       snprintf(version, 32, " (snd gl: %s)", Xen_string_to_C_string(gl_val));
206       if (snd_itoa_ctr < snd_itoa_size) snd_itoa_strs[snd_itoa_ctr++] = version;
207       return(version);
208     }
209   return(mus_strdup(" "));
210 }
211 
212 
213 #if WITH_GL2PS
214   char *gl2ps_version(void); /* snd-print.c */
215 #endif
216 
glx_version(void)217 static char *glx_version(void)
218 {
219 #if USE_MOTIF
220   #define VERSION_SIZE 128
221   char *version = NULL;
222 
223   if ((!ss->dialogs) || /* snd --help for example */
224       (!(main_display(ss))))
225     return(mus_strdup(" "));
226 
227   version = (char *)calloc(VERSION_SIZE, sizeof(char));
228   if (ss->cx)
229     {
230       glXMakeCurrent(main_display(ss), XtWindow(ss->mainshell), ss->cx);
231       snprintf(version, VERSION_SIZE, " %s", glGetString(GL_VERSION));
232     }
233   else
234     {
235       int major = 0, minor = 0;
236       glXQueryVersion(main_display(ss), &major, &minor);
237       snprintf(version, VERSION_SIZE, " %d.%d", major, minor);
238     }
239   if (snd_itoa_ctr < snd_itoa_size) snd_itoa_strs[snd_itoa_ctr++] = version;
240   return(version);
241 #else
242   return(mus_strdup(" "));
243 #endif
244 }
245 #endif
246 
247 #if HAVE_GSL
248   #include <gsl/gsl_version.h>
249 #endif
250 #ifndef _MSC_VER
251   #include <sys/utsname.h>
252 #endif
253 
254 #if USE_NOTCURSES
255   /* I don't want to include notcurses.h */
256   const char* notcurses_version(void);
257 #endif
258 
version_info(void)259 char *version_info(void)
260 {
261   char *result, *xversion, *consistent = NULL;
262 #if HAVE_GL && WITH_GL2PS
263   char *gl2ps_name = NULL;
264 #endif
265 #ifndef _MSC_VER
266   int uname_ok;
267   struct utsname uts;
268   uname_ok = uname(&uts); /* 0=success */
269 #endif
270   snd_itoa_ctr = 0;
271   xversion = xen_version();
272   result = vstrcat(
273 	  "This is Snd version ",
274 	  SND_VERSION,
275 	  " of ",
276 	  SND_DATE,
277 	  ":\n    ", xversion,
278 	  "\n    ",
279 	  mus_audio_moniker(),
280 	  "\n    Sndlib ", snd_itoa(SNDLIB_VERSION), ".",
281                            snd_itoa(SNDLIB_REVISION),
282                            " (", SNDLIB_DATE,
283 	  ")",
284 	  "\n    CLM ", snd_itoa(MUS_VERSION), ".",
285 	                snd_itoa(MUS_REVISION), " (",
286                         MUS_DATE, ")",
287 #if HAVE_GSL
288 	  "\n    GSL",
289   #ifdef GSL_VERSION
290           " ", GSL_VERSION,
291   #endif
292 #endif
293 #if HAVE_FFTW3
294 	  "\n    ", fftw_version,
295 #endif
296 #if USE_MOTIF
297 	  "\n    Motif ", snd_itoa(XmVERSION), ".",
298                           snd_itoa(XmREVISION), ".",
299                           snd_itoa(XmUPDATE_LEVEL),
300 	  " X", snd_itoa(X_PROTOCOL), "R",
301                 snd_itoa(XT_REVISION),
302 #endif
303 	  xm_version(), /* omitted if --version/--help because the init procs haven't run at that point */
304 #if HAVE_GL
305 	  "\n    OpenGL", glx_version(),
306 	  gl_version(),
307   #if WITH_GL2PS
308           ", ", gl2ps_name = gl2ps_version(),
309   #endif
310 #endif
311 #if (!USE_MOTIF)
312 	  "\n    no graphics",
313 #endif
314 #if USE_MOTIF
315 	  "\n    Xpm ", snd_itoa(XpmFormat), ".",
316                         snd_itoa(XpmVersion), ".",
317                         snd_itoa(XpmRevision),
318 #endif
319 #if USE_NOTCURSES
320 	  "\n    Notcurses ", notcurses_version(),
321 #endif
322 #if HAVE_LADSPA
323 	  "\n    Ladspa: ",
324   #ifdef LADSPA_VERSION
325 	  LADSPA_VERSION,
326   #else
327 	  "1.0",
328   #endif
329 #endif
330 #if WITH_GMP
331 	  "\n    gmp: ",  gmp_version,
332 	      ", mpfr: ", mpfr_get_version(),
333 	      ", mpc: ",  mpc_get_version(),
334 #endif
335 #if (defined(__DATE__)) && (!(defined(REPRODUCIBLE_BUILD)))
336 	  "\n    Compiled ", __DATE__, " ", __TIME__,
337 #endif
338 #ifndef __cplusplus
339 	  "\n    C: ",
340 #else
341 	  "\n    C++: ",
342 #endif
343 /* the __VERSION__ macro is useless */
344 #if defined(__clang__)
345 	  "clang ", snd_itoa(__clang_major__), ".", snd_itoa(__clang_minor__),
346 #elif defined(__ICC) || defined(__INTEL_COMPILER)
347 	  "intel ",
348 #elif defined(__GNUC__) || defined(__GNUG__)
349 #ifndef __cplusplus
350 	  "gcc ",
351 #else
352 	  "g++ ",
353 #endif
354 	  snd_itoa(__GNUC__), ".", snd_itoa(__GNUC_MINOR__),
355 #elif defined(_MSC_VER)
356 	  "MS ",
357 #elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
358 	  "Sun ",
359 #endif
360 	  "\n",
361 #ifndef _MSC_VER
362 	  (uname_ok == 0) ? "    " : "",
363 	  (uname_ok == 0) ? uts.sysname : "",
364 	  (uname_ok == 0) ? " " : "",
365 	  (uname_ok == 0) ? uts.release : "",
366 	  (uname_ok == 0) ? " " : "",
367 	  (uname_ok == 0) ? uts.machine : "",
368 	  (uname_ok == 0) ? "\n" : "",
369 #endif
370 	  NULL);
371   free_snd_itoa();
372   if (xversion) free(xversion); /* calloc in xen.c */
373   if (consistent) free(consistent);
374 #if HAVE_GL && WITH_GL2PS
375   if (gl2ps_name) free(gl2ps_name);
376 #endif
377   return(result);
378 }
379 
380 
about_snd_help(void)381 void about_snd_help(void)
382 {
383   char *info = NULL, *features = NULL;
384 
385 #if HAVE_RUBY
386   features = word_wrap(Xen_object_to_C_string(Xen_eval_C_string((char *)"$\".join(' ')")), 400);
387 #endif
388 
389 #if HAVE_FORTH
390   features = word_wrap(Xen_object_to_C_string(Xen_eval_C_string("*features*")), 400);
391 #endif
392 
393 #if HAVE_SCHEME
394   {
395     char *temp = NULL;
396     features = word_wrap(temp = Xen_object_to_C_string(Xen_eval_C_string("*features*")), 400);
397     if (temp) free(temp);
398   }
399 #endif
400   info = version_info();
401 
402   /* -------------------------------------------------------------------------------- */
403   main_snd_help("Snd is a sound editor.",
404 		info,
405 #if HAVE_RUBY
406 	    "\n    $LOADED_FEATURES: \n", features, "\n\n",
407 #endif
408 #if HAVE_FORTH
409 	    "\n    *features*:\n    ", features, "\n\n",
410 #endif
411 #if HAVE_SCHEME
412   	    "\n    *features*:\n    '", features, "\n\n",
413 #endif
414 #if (!HAVE_EXTENSION_LANGUAGE)
415 	    "\n",
416 #endif
417 	    "Please send bug reports or suggestions to bil@ccrma.stanford.edu.",
418 NULL);
419 
420   if (info) free(info);
421   if (features) free(features);
422 }
423 
424 
425 /* ---------------- help menu help texts ---------------- */
426 
append_key_help(const char * name,int key,int state,bool cx_extended,bool first_time)427 static bool append_key_help(const char *name, int key, int state, bool cx_extended, bool first_time)
428 {
429   int pos;
430   pos = in_keymap(key, state, cx_extended);
431   if (pos != -1)
432     {
433       char *msg, *tmp = NULL;
434       msg = mus_format("%s%s is bound to %s",
435 		       (first_time) ? "\n\ncurrently " : "\n          ",
436 		       name,
437 		       tmp = key_description(key, state, cx_extended));
438       snd_help_append(msg);
439       free(msg);
440       if (tmp) free(tmp);
441       return(false);
442     }
443   return(first_time);
444 }
445 
446 
447 
448 /* ---------------- Find ---------------- */
449 
find_help(void)450 void find_help(void)
451 {
452   #if HAVE_SCHEME
453     #define basic_example "(lambda (y) (> y 0.1))"
454     #define find_channel_example "    >(find-channel (lambda (y) (> y .1)))\n    (#t 4423)"
455     #define count_matches_example "    >(count-matches (lambda (y) (> y .1)))\n    2851"
456     #define search_procedure_example "    >(set! (search-procedure) (lambda (y) (> y .1)))"
457   #endif
458   #if HAVE_RUBY
459     #define basic_example "lambda do |y| y > 0.1 end"
460     #define find_channel_example "    >find_channel(lambda do |y| y > 0.1 end)\n    [true, 4423]"
461     #define count_matches_example "    >count_matches(lambda do |y| y > 0.1 end)\n    2851"
462     #define search_procedure_example "    >set_search_procedure(lambda do |y| y > 0.1 end)"
463   #endif
464   #if HAVE_FORTH
465     #define basic_example "lambda: <{ y }> y 0.1 f> ;"
466     #define find_channel_example "    >lambda: <{ y }> 0.1 y f< ; find-channel\n    '( #t 4423 )"
467     #define count_matches_example "     >lambda: <{ y }> 0.1 y f< ; count-matches\n     2851"
468     #define search_procedure_example "   >lambda: <{ y }> 0.1 y f< ; set-search-procedure"
469   #endif
470 
471   snd_help_with_xrefs(I_FIND,
472 
473 #if HAVE_EXTENSION_LANGUAGE
474 "Searches in Snd refer to the sound data.  When you type \
475 C-s, the find dialog is activated and you are asked for the search expression. \
476 The expression is a function that takes one argument, the current sample value, and returns " PROC_TRUE " when it finds a match. \
477 To look for the next sample that is greater than .1, " basic_example ".  The cursor then moves \
478 to that sample, if any. Successive C-s's continue the search starting from the next sample.\
479 \n\n\
480 The primary searching function is:\n\
481 \n\
482  " S_find_channel " (proc :optional (sample 0) snd chn edpos)\n\
483     This function finds the sample that satisfies the function 'proc'. \n\
484     'sample' determines where to start the search. \n\
485 " find_channel_example "\n\
486 \n\
487 Closely related is:\n\
488 \n\
489   " S_count_matches " (proc :optional (sample 0) snd chn edpos)\n\
490     This returns how many samples satisfy the function 'proc'.\n\
491 " count_matches_example "\n\
492 \n\
493 To find whether a given sound is currently open in Snd, use:\n\
494 \n\
495   " S_find_sound " (filename :optional (nth 0))\n\
496     " S_find_sound " returns the index of 'filename' or " PROC_FALSE ".\n\
497 \n\
498 The current search procedure (for the Edit:Find dialog) is:\n\
499 \n\
500   " S_search_procedure " (:optional snd)\n\
501 " search_procedure_example "\n\
502 ",
503 
504 #else
505 "Searches in Snd depend completely on the extension language.  Since none is loaded,\
506 the searching mechanisms are disabled.",
507 #endif
508 		      WITH_WORD_WRAP,
509 		      snd_xrefs("Search"),
510 		      snd_xref_urls("Search"));
511 
512   append_key_help("C-s", snd_K_s, ControlMask, false, true);
513 }
514 
515 
516 /* ---------------- Undo ---------------- */
517 
undo_help(void)518 void undo_help(void)
519 {
520   #if HAVE_SCHEME
521     #define H_undo S_undo
522     #define H_redo S_redo
523     #define edit_position_example "(set! (edit-position) 0) ; revert channel"
524   #endif
525   #if HAVE_RUBY
526     #define H_undo "undo_edit"
527     #define H_redo "redo_edit"
528     #define edit_position_example "set_edit_position(0) # revert channel"
529   #endif
530   #if HAVE_FORTH
531     #define H_undo S_undo
532     #define H_redo S_redo
533     #define edit_position_example "0 set-edit-position \\ revert channel"
534   #endif
535 
536   snd_help_with_xrefs("Undo and Redo",
537 
538 #if HAVE_EXTENSION_LANGUAGE
539 "Snd supports 'unlimited undo' in the sense that you can move back and forth in the list of edits without any \
540 limit on how long that list can get.  Each editing operation \
541 extends the current edit list; each undo backs up in that list, and each redo moves forward in the list of previously \
542 un-done edits.  Besides the Edit and Popup menu options, and the " H_undo " and " H_redo " functions, \
543 there are these keyboard sequences: \
544 \n\n\
545   C-x r     redo last edit\n\
546   C-x u     undo last edit\n\
547   C-x C-r   redo last edit\n\
548   C-x C-u   undo last edit\n\
549   C-_       undo last edit\n\
550 \n\
551 File:Revert is the same as undo all edits.\
552 In the listener, C-M-g deletes all text, and C-_ deletes back to the previous command.\
553 In the sound display, the number at the lower left shows the current edit position and the channel number.\
554 The main functions that affect the edit position are:\n\
555 \n\
556   " H_undo " (:optional (edits 1) snd chn)\n\
557     This undoes 'edits' edits in snd's channel chn.\n\
558 \n\
559   " H_redo " (:optional (edits 1) snd chn)\n\
560     This re-activates 'edits' edits in snd's channel chn.\n\
561 \n\
562   " S_revert_sound " (:optional snd)\n\
563     This reverts 'snd' to its saved (unedited) state.\n\
564 \n\
565   " S_edit_position " (:optional snd chn)\n\
566     This is the current position in the edit history list.\n\
567     " edit_position_example "\n\
568 ",
569 
570 #else
571 "Snd supports 'unlimited undo' in the sense that you can move back and forth in the list of edits without any \
572 limit on how long that list can get.  Each editing operation \
573 extends the current edit list; each undo backs up in that list, and each redo moves forward in the list of previously \
574 un-done edits.  Besides the Edit and Popup menu options,\
575 there are these keyboard sequences: \
576 \n\n\
577   C-x r     redo last edit\n\
578   C-x u     undo last edit\n\
579   C-x C-r   redo last edit\n\
580   C-x C-u   undo last edit\n\
581   C-_       undo last edit\n\
582 \n\
583 File:Revert is the same as undo all edits.",
584 #endif
585 
586 		      WITH_WORD_WRAP,
587 		      snd_xrefs("Undo"),
588 		      snd_xref_urls("Undo"));
589 
590   append_key_help("C-M-g", snd_K_g, ControlMask | MetaMask, false,
591     append_key_help("C-_", snd_K_underscore, ControlMask, false,
592       append_key_help("C-x C-u", snd_K_u, ControlMask, true,
593         append_key_help("C-x C-r", snd_K_r, ControlMask, true,
594           append_key_help("C-x u", snd_K_u, 0, true,
595 	    append_key_help("C-x r", snd_K_r, 0, true, true))))));
596 }
597 
598 
599 /* ---------------- Sync and Unite ---------------- */
600 
601 static const char *sync_xrefs[5] = {
602   "sound sync field: {" S_sync "}, {" S_sync_max "}",
603   "mark sync field: {" S_mark_sync "}, {" S_mark_sync_max "}, {mark-sync-color}, {" S_syncd_marks "}",
604   "mix sync field: {" S_mix_sync "}",
605   "channel display choice: {" S_channel_style "}",
606   NULL};
607 
sync_help(void)608 void sync_help(void)
609 {
610   #if HAVE_SCHEME
611     #define channel_style_example "(set! (channel-style snd) channels-combined)"
612     #define H_channels_separate S_channels_separate
613     #define H_channels_combined S_channels_combined
614     #define H_channels_superimposed S_channels_superimposed
615   #endif
616   #if HAVE_RUBY
617     #define channel_style_example "set_channel_style(Channels_combined, snd)"
618     #define H_channels_separate "Channels_separate"
619     #define H_channels_combined "Channels_combined"
620     #define H_channels_superimposed "Channels_superimposed"
621   #endif
622   #if HAVE_FORTH
623     #define channel_style_example "channels-combined set-channel-style"
624     #define H_channels_separate S_channels_separate
625     #define H_channels_combined S_channels_combined
626     #define H_channels_superimposed S_channels_superimposed
627   #endif
628 
629   snd_help_with_xrefs("Sync",
630 
631 #if HAVE_EXTENSION_LANGUAGE
632 "The sync button causes certain operations to apply to all channels or multiple sounds simultaneously. \
633 For example, to get a multichannel selection, set the sync button, then define the selection (by dragging \
634 the mouse) in one channel, and the parallel portions of the other channels are also selected. \
635 The default sync setting is set by " S_sync_style ", which defaults to " S_sync_by_sound ". \
636 This ties together all the channels in a sound, but keeps sounds separate.  The other sync styles \
637 are " S_sync_none ", and " S_sync_all ".  " S_sync_none " means each channel is separate, and " S_sync_all " means \
638 everything is handled together.  You can set the sync style via " S_sync ". \
639 Marks and mixes can also be sync'd together.\n\
640 \n\
641 Similarly, the unite button combines channels of a \
642 multichannel sound into one display window.  control-click the unite button to have the channels \
643 superimposed on each other. The function associated with this is:\n\
644 \n\
645   " S_channel_style " (:optional snd)\n\
646     " S_channel_style " reflects the value of the 'unite' button in multichannel files.\n\
647     Possible values are " H_channels_separate ", " H_channels_combined ", and " H_channels_superimposed ".\n\
648     " channel_style_example "\n\
649 ",
650 
651 #else
652 "The sync button causes certain operations to apply to all channels or multiple sounds simultaneously. \
653 For example, to get a multichannel selection, set the sync button, then define the selection (by dragging \
654 the mouse) in one channel, and the parallel portions of the other channels are also selected. \
655 Marks and mixes can also be sync'd together.\n\
656 \n\
657 Similarly, the unite button combines channels of a \
658 multichannel sound into one display window.  control-click the unite button to have the channels \
659 superimposed on each other.",
660 #endif
661 		      WITH_WORD_WRAP,
662 		      sync_xrefs,
663 		      NULL);
664 }
665 
666 
667 /* ---------------- Debug ---------------- */
668 
669 static const char *debug_xrefs[5] = {
670   "C debugging: {gdb}",
671   "CLM Instrument debugging: {variable-display}",
672   "Error handling",
673   "Print statement: {" S_snd_print "}",
674   NULL};
675 
676 static const char *debug_urls[5] = {
677   "extsnd.html#cdebugging",
678   "sndscm.html#variabledisplay",
679   "extsnd.html#snderrors",
680   "extsnd.html#sndprint",
681   NULL};
682 
debug_help(void)683 void debug_help(void)
684 {
685   #if HAVE_SCHEME
686     #define vardpy_reference "variable-display in snd-motif.scm"
687   #endif
688   #if HAVE_RUBY
689     #define vardpy_reference "variable_display in snd-xm.rb"
690   #endif
691   #if HAVE_FORTH
692     #define vardpy_reference "variable-display, which isn't written yet"
693   #endif
694 
695   snd_help_with_xrefs("Debugging",
696 
697 #if HAVE_EXTENSION_LANGUAGE
698 "There are several sets of debugging aids, each aimed at a different level of code. \
699 C code is normally debugged with gdb.  If you hit a segfault in Snd, please tell me \
700 about it!  If possible, run Snd in gdb and send me the stack trace: \n\n\
701   gdb snd\n\
702   run\n\
703   <get error to happen>\n\
704   where\n\
705 \n\
706 See README.Snd for more about C-level troubles.  For CLM-based instruments, " vardpy_reference " might \
707 help.  For debugging your own Scheme/Ruby/Forth \
708 code (or Snd's for that matter), see the \"Errors and Debugging\" section of \
709 extsnd.html.  For notelist debugging, see ws-backtrace.",
710 #else
711 "If you hit a segfault in Snd, please tell me \
712 about it!  If possible, run Snd in gdb and send me the stack trace: \n\n\
713   gdb snd\n\
714   run\n\
715   <get error to happen>\n\
716   where\n\
717   <much info here that is useful to me>\n",
718 #endif
719 
720 		      WITH_WORD_WRAP,
721 		      debug_xrefs,
722 		      debug_urls);
723 }
724 
725 
726 /* ---------------- Envelope ---------------- */
727 
env_help(void)728 void env_help(void)
729 {
730   #if HAVE_SCHEME
731     #define envelope_example "'(0 0 1 1 2 0)"
732     #define env_sound_example "(env-sound '(0 0 1 1 2 0))"
733   #endif
734   #if HAVE_RUBY
735     #define envelope_example "[0.0, 0.0, 1.0, 1.0, 2.0, 0.0]"
736     #define env_sound_example "env_sound([0.0, 0.0, 1.0, 1.0, 2.0, 0.0])"
737   #endif
738   #if HAVE_FORTH
739     #define envelope_example "'( 0.0 0.0 1.0 1.0 2.0 0.0 )"
740     #define env_sound_example "'( 0.0 0.0 1.0 1.0 2.0 0.0 ) env-sound"
741   #endif
742 
743   snd_help_with_xrefs("Envelope",
744 
745 #if HAVE_EXTENSION_LANGUAGE
746 "An envelope in Snd is a list (an array in Ruby) of x y break-point pairs. The x axis range is arbitrary. \
747 To define a triangle curve: " envelope_example ".\
748 There is no preset limit on the number of breakpoints. Use the envelope editor to draw envelopes with the mouse. \
749 \n\n\
750 To apply an envelope to a sound, use " S_env_sound ". The primary enveloping functions are:\n\
751 \n\
752  " S_env_sound " (envelope :optional (samp 0) samps (env-base 1.0) snd chn edpos)\n\
753     " S_env_sound " applies the amplitude envelope to snd's channel chn.\n\
754     " env_sound_example "\n\
755 \n\
756  " S_env_channel " (clm-env-gen :optional beg dur snd chn edpos)\n\
757     This is the channel-specific version of " S_env_sound ".\n\
758 ",
759 
760 #else
761 "It is hard to define or use an envelope if there's no extension language.",
762 #endif
763 
764 		      WITH_WORD_WRAP,
765 		      snd_xrefs("Envelope"),           /* snd-xref.c */
766 		      snd_xref_urls("Envelope"));
767 }
768 
769 
770 /* ---------------- FFT ---------------- */
771 
fft_help(void)772 void fft_help(void)
773 {
774   #if HAVE_SCHEME
775     #define transform_normalization_example "(set! (transform-normalization) dont-normalize)"
776     #define transform_size_example "(set! (transform-size) 512)"
777     #define transform_type_example "(set! (transform-type) autocorrelation)"
778     #define fft_window_example "(set! (fft-window) rectangular-window)"
779     #define transform_graph_example "(set! (transform-graph?) #t)"
780     #define transform_graph_type_example "(set! (transform-graph-type) graph-as-sonogram)"
781     #define transform_log_magnitude_example "(set! (transform-log-magnitude) #f)"
782     #define transform_types "fourier-transform wavelet-transform  haar-transform\n      autocorrelation   walsh-transform    cepstrum"
783     #define transform_graph_types "graph-once  graph-as-sonogram  graph-as-spectrogram"
784     #define transform_normalizations "dont-normalize normalize-by-channel normalize-by-sound normalize-globally"
785     #define fft_windows "      bartlett-window blackman2-window blackman3-window blackman4-window\n      cauchy-window connes-window dolph-chebyshev-window exponential-window\n      gaussian-window hamming-window hann-poisson-window hann-window\n      kaiser-window parzen-window poisson-window rectangular-window\n      riemann-window samaraki-window tukey-window ultraspherical-window\n      welch-window bartlett-hann-window bohman-window flat-top-window  blackman5..10-window"
786   #endif
787   #if HAVE_RUBY
788     #define transform_normalization_example "set_transform_normalization(Dont_normalize)"
789     #define transform_size_example "set_transform_size(512)"
790     #define transform_type_example "set_transform_type(Autocorrelation)"
791     #define fft_window_example "set_fft_window(Rectangular_window)"
792     #define transform_graph_example "set_transform_graph?(#t)"
793     #define transform_graph_type_example "set_transform_graph_type(Graph_as_sonogram)"
794     #define transform_log_magnitude_example "set_transform_log_magnitude(#f)"
795     #define transform_types "Fourier_transform Wavelet_transform  Haar_transform\n      Autocorrelation   Walsh_transform    Cepstrum"
796     #define transform_graph_types "graph_once  graph_as_sonogram  graph_as_spectrogram"
797     #define transform_normalizations "Dont_normalize Normalize_by_channel Normalize_by_sound Normalize_globally"
798     #define fft_windows "      Bartlett_window Blackman2_window Blackman3_window Blackman4_window\n      Cauchy_window Connes_window Dolph_chebyshev_window Exponential_window\n      Gaussian_window Hamming_window Hann_poisson_window Hann_window\n      Kaiser_window Parzen_window Poisson_window Rectangular_window\n      Riemann_window Samaraki_window Tukey_window Ultraspherical_window\n      Welch_window Bartlett_hann_window Bohman_window Flat_top_window Blackman5..10_window"
799   #endif
800   #if HAVE_FORTH
801     #define transform_normalization_example "dont-normalize set-transform-normalization"
802     #define transform_size_example "512 set-transform-size"
803     #define transform_type_example "autocorrelation set-transform-type"
804     #define fft_window_example "rectangular-window set-fft-window"
805     #define transform_graph_example "#t set-transform-graph?"
806     #define transform_graph_type_example "graph-as-sonogram set-transform-graph-type"
807     #define transform_log_magnitude_example "#f set-transform-log-magnitude"
808     #define transform_types "fourier-transform wavelet-transform  haar-transform\n      autocorrelation   walsh-transform    cepstrum"
809     #define transform_graph_types "graph-once  graph-as-sonogram  graph-as-spectrogram"
810     #define transform_normalizations "dont-normalize normalize-by-channel normalize-by-sound normalize-globally"
811     #define fft_windows "      bartlett-window blackman2-window blackman3-window blackman4-window\n      cauchy-window connes-window dolph-chebyshev-window exponential-window\n      gaussian-window hamming-window hann-poisson-window hann-window\n      kaiser-window parzen-window poisson-window rectangular-window\n      riemann-window samaraki-window tukey-window ultraspherical-window\n      welch-window bartlett-hann-window bohman-window flat-top-window blackman5..10-window"
812   #endif
813 
814   snd_help_with_xrefs("FFT",
815 
816 #if HAVE_EXTENSION_LANGUAGE
817 "The FFT performs a projection of the time domain into the frequency domain. Good discussions of the Fourier Transform \
818 and the trick used in the FFT itself can be found in many DSP books; those I know of include 'A Digital Signal Processing \
819 Primer', Ken Steiglitz, or 'Numerical Recipes in C'. \
820 For the Fourier transform itself, good books abound: \
821 'Mathematics of the DFT', Julius Smith, or 'Understanding DSP', Lyons, etc.  For more sophistication, \
822 'Fourier Analysis', Korner, 'Fourier Analysis', Stein and Shakarchi, or 'Trigonometric Series', Zygmund. \
823 \n\n\
824 The FFT size can be any power of 2. The larger, the longer it takes to compute, and the larger the amount of the time domain \
825 that gets consumed.  Interpretation of the FFT results is not straightforward!  The window choices are taken primarily \
826 from Harris' article: Fredric J. Harris, 'On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform', Proceedings of the \
827 IEEE, Vol. 66, No. 1, January 1978, with updates from: Albert H. Nuttall, 'Some Windows with Very Good Sidelobe Behaviour', IEEE Transactions \
828 of Acoustics, Speech, and Signal Processing, Vol. ASSP-29, 1, February 1981. \
829 \n\n\
830 Nearly all the transform-related choices are set by the transform dialog launched from the Options \
831 Menu Transform item. Most of this dialog should be self-explanatory.  Some of the windows take an \
832 additional parameter sometimes known as alpha or beta.  This can be set by the scale(s) next to the fft window graph in the \
833 transform dialog, or via " S_fft_window_alpha " and " S_fft_window_beta ". \
834 \n\n\
835 To change the defaults, you can set the various values in your initialization file (see examples below), \
836 or use the Options:Preferences dialog's Transforms section.\
837 \n\n\
838 The FFT display is activated by setting the 'f' button on the channel's window.  It then updates \
839 itself each time the time domain waveform moves or changes.  \
840 The FFT is taken from the start (the left edge) of the current window and is updated as the window bounds change. \
841 The data is scaled to fit between 0.0 and 1.0 unless transform normalization is off. \
842 The full frequency axis is normally displayed, but the axis is draggable -- put the mouse on the axis \
843 and drag it either way to change the range (this is equivalent to changing the variable " S_spectrum_end "). \
844 You can also click on any point in the fft to get the associated fft value at that point displayed; \
845 if " S_with_verbose_cursor " is " PROC_TRUE ", you can drag the mouse through the fft display and \
846 the description in the status area is constantly updated.  To change the fft size by powers of two, \
847 you can use the keypad keys '*' and '/'.  \
848 \n\n\
849 The main FFT-related functions are:\
850 \n\n\
851   " S_transform_size " (:optional snd chn)\n\
852     the fft size (a power of 2): " transform_size_example "\n\
853 \n\
854   " S_transform_type " (:optional snd chn)\n\
855     which transform is performed: " transform_type_example "\n\
856     the built-in transform choices are:\n\
857       " transform_types "\n\
858 \n\
859   " S_transform_graph_type " (:optional snd chn)\n\
860     the display choice, one of: " transform_graph_types "\n\
861     " transform_graph_type_example "\n\
862 \n\
863   " S_fft_window " (:optional snd chn)\n\
864     the fft data window: " fft_window_example "\n\
865     the windows are:\n\
866 " fft_windows "\n\
867 \n\
868   " S_show_transform_peaks " and " S_max_transform_peaks "\n\
869     these control the peak info display\n\
870 \n\
871   " S_fft_log_magnitude " and " S_fft_log_frequency "\n\
872     these set whether the axes are linear or logarithmic\n\
873 \n\
874   " S_fft_with_phases " (:optional snd chn)\n\
875     sets whether the single fft display includes phase information\n\
876 \n\
877   " S_transform_graph_on " (:optional snd chn)\n\
878     if " PROC_TRUE ", the fft graph is displayed: " transform_graph_example "\n\
879 \n\
880   " S_transform_normalization " (:optional snd chn)\n\
881     how fft data is normalized.  " transform_normalization_example "\n\
882     the choices are:\n\
883     " transform_normalizations "\n\
884 \n\
885   " S_peaks " (:optional file snd chn) writes out current peak info as text\n\
886 \n\
887   Other related functions: " S_zero_pad ", " S_wavelet_type ", " S_add_transform "\n\
888     " S_min_dB ", " S_snd_spectrum ", " S_show_selection_transform ".",
889 
890 #else
891 "Nearly all the transform-related choices can be set in the transform dialog launched from the Options \
892 Menu Transform item. Most of this dialog should be self-explanatory.  Some of the windows take an \
893 additional parameter sometimes known as alpha or beta.  This can be set by the scale(s) next to the fft window graph in the \
894 transform dialog, or via the variables " S_fft_window_alpha " and " S_fft_window_beta ".\
895 \n\n\
896 The FFT display is activated by setting the 'f' button on the channel's window.  It then updates \
897 itself each time the time domain waveform moves or changes.  \
898 The FFT is taken from the start (the left edge) of the current window and is updated as the window bounds change. \
899 The data is scaled to fit between 0.0 and 1.0 unless transform normalization is off. \
900 The full frequency axis is normally displayed, but the axis is draggable -- put the mouse on the axis \
901 and drag it either way to change the range. \
902 You can also click on any point in the fft to get the associated fft value at that point displayed. \
903 To change the fft size by powers of two, \
904 you can use the keypad keys '*' and '/'.",
905 #endif
906 
907 		      WITH_WORD_WRAP,
908 		      snd_xrefs("FFT"),
909 		      snd_xref_urls("FFT"));
910 
911   global_fft_state();
912 }
913 
914 
915 /* ---------------- Control Panel ---------------- */
916 
917 static const char *control_xrefs[9] = {
918   "various control panel variables: {Control panel}",
919   "amplitude: {" S_scale_by "}",
920   "speed or srate: {" S_src_sound "}",
921   "expand: {granulate}",
922   "contrast: {" S_contrast_enhancement "}",
923   "filter: {" S_filter_sound "}",
924   "{" S_apply_controls "}",
925   "{" S_controls_to_channel "}",
926   NULL};
927 
controls_help(void)928 void controls_help(void)
929 {
930   #if HAVE_SCHEME
931     #define amp_control_example "(set! (amp-control) 0.5)"
932     #define amp_control_bounds_example "(set! (amp-control-bounds) (list 0.0 20.0))"
933     #define speed_control_styles "speed-control-as-float speed-control-as-ratio speed-control-as-semitone"
934   #endif
935   #if HAVE_RUBY
936     #define amp_control_example "set_amp_control(0.5)"
937     #define amp_control_bounds_example "set_amp_control_bounds([0.0, 20.0])"
938     #define speed_control_styles "Speed_control_as_float Speed_control_as_ratio Speed_control_as_semitone"
939   #endif
940   #if HAVE_FORTH
941     #define amp_control_example "0.5 set-amp-control"
942     #define amp_control_bounds_example "'( 0.0 20.0 ) set-amp-control-bounds"
943     #define speed_control_styles "speed-control-as-float speed-control-as-ratio speed-control-as-semitone"
944   #endif
945 
946   snd_help_with_xrefs("The Control Panel",
947 
948 #if HAVE_EXTENSION_LANGUAGE
949 "The control panel is the portion of each sound's pane beneath the channel graphs. \
950 The controls are: amp, speed, expand, contrast, reverb, and filter. \
951 \n\n\
952 'Speed' here refers to the rate at which the sound data is consumed during playback. \
953 Another term might be 'srate'.  The arrow button on the right determines \
954 the direction Snd moves through the data. The scroll bar position is normally interpreted \
955 as a float between .05 and 20. \
956 \n\n\
957 'Expand' refers to granular synthesis used to change the tempo of events \
958 in the sound without changing pitch.  Successive short slices of the file are overlapped with \
959 the difference in size between the input and output hops (between successive slices) giving \
960 the change in tempo.  This doesn't work in all files -- it sometimes sounds like execrable reverb \
961 or is too buzzy -- but it certainly is more robust than the phase vocoder approach to the \
962 same problem. The expander is on only if the expand button is set. \
963 \n\n\
964 The reverberator is a version of Michael McNabb's Nrev.  In addition to the controls \
965 in the control pane, you can set the reverb feedback gain and the coefficient of the lowpass \
966 filter in the allpass bank. The reverb is on only if the reverb button is set.  The reverb length \
967 field takes effect only when the reverb is set up (when the DAC is started by clicking \
968 'play' when nothing else is being played). \
969 \n\n\
970 'Contrast enhancement' is my name for a somewhat weird waveshaper or compander.  It \
971 phase-modulates a sound, which can in some cases make it sound sharper or brighter. \
972 For softer sounds, it causes only an amplitude change.  To scale a soft sound up before \
973 being 'contrasted', use the variable " S_contrast_control_amp ".  Contrast is on only if the contrast button is set. \
974 \n\n\
975 The filter is an arbitrary (even) order FIR filter specified by giving the frequency response \
976 envelope and filter order in the text windows provided. \
977 The envelope X axis goes from 0 to half the sampling rate. The actual frequency response (given the current filter order) \
978 is displayed in blue.  The filter is on only if the filter button is set. \
979 \n\n\
980 See the Options:controls menu item for even more controls.\
981 The keyboard commands associated with the control panel are: \
982 \n\n\
983   C-x C-o   show control panel\n\
984   C-x C-c   hide control panel\n\
985 \n\
986 There are functions to get and set every aspect of the control panel -- far too many to list! \
987 As an example of how they all work, to set the amplitude slider from a function: \n\
988 \n\
989   " amp_control_example "\n\
990 \n\
991 To set its overall bounds:\n\
992 \n\
993   " amp_control_bounds_example "\n\
994 \n\
995 The speed control can be interpreted as a float, a ratio, or a semitone. The choices are:\n\
996 \n\
997     " speed_control_styles "\n\
998 \n\
999 To apply the current settings as an edit, call:\n\
1000 \n\
1001   " S_apply_controls " (:optional snd (target 0) beg dur)\n\
1002     where 'target' selects what gets edited: \n\
1003       0 = sound, 1 = channel, 2 = selection.",
1004 
1005 #else
1006 "The control panel is the portion of each sound's pane beneath the channel graphs. \
1007 The controls are: amp, speed, expand, contrast, reverb, and filter. \
1008 \n\n\
1009 'Speed' here refers to the rate at which the sound data is consumed during playback. \
1010 Another term might be 'srate'.  The arrow button on the right determines \
1011 the direction Snd moves through the data. The scroll bar position is normally interpreted \
1012 as a float between .05 and 20. \
1013 \n\n\
1014 'Expand' refers to granular synthesis used to change the tempo of events \
1015 in the sound without changing pitch.  Successive short slices of the file are overlapped with \
1016 the difference in size between the input and output hops (between successive slices) giving \
1017 the change in tempo.  This doesn't work in all files -- it sometimes sounds like execrable reverb \
1018 or is too buzzy -- but it certainly is more robust than the phase vocoder approach to the \
1019 same problem. The expander is on only if the expand button is set. \
1020 \n\n\
1021 The reverberator is a version of Michael McNabb's Nrev.  In addition to the controls \
1022 in the control pane, you can set the reverb feedback gain and the coefficient of the lowpass \
1023 filter in the allpass bank. The reverb is on only if the reverb button is set.  The reverb length \
1024 field takes effect only when the reverb is set up (when the DAC is started by clicking \
1025 'play' when nothing else is being played). \
1026 \n\n\
1027 'Contrast enhancement' is my name for a somewhat weird waveshaper or compander.  It \
1028 phase-modulates a sound, which can in some cases make it sound sharper or brighter. \
1029 For softer sounds, it causes only an amplitude change. \
1030 Contrast is on only if the contrast button is set. \
1031 \n\n\
1032 The filter is an arbitrary (even) order FIR filter specified by giving the frequency response \
1033 envelope and filter order in the text windows provided. \
1034 The envelope X axis goes from 0 to half the sampling rate. The actual frequency response (given the current filter order) \
1035 is displayed in blue.  The filter is on only if the filter button is set.",
1036 #endif
1037 
1038 		      WITH_WORD_WRAP,
1039 		      control_xrefs,
1040 		      NULL);
1041 
1042   global_control_panel_state();
1043 
1044   append_key_help("C-x C-o", snd_K_o, ControlMask, true,
1045     append_key_help("C-x C-c", snd_K_c, ControlMask, true, true));
1046 }
1047 
1048 
1049 /* ---------------- Marks ---------------- */
1050 
marks_help(void)1051 void marks_help(void)
1052 {
1053   #if HAVE_SCHEME
1054     #define add_mark_example "(add-mark 1234)"
1055     #define mark_name_example "(set! (mark-name 0) \"mark-0\")"
1056   #endif
1057   #if HAVE_RUBY
1058     #define add_mark_example "add_mark(1234)"
1059     #define mark_name_example "set_mark_name(0, \"mark-0\")"
1060   #endif
1061   #if HAVE_FORTH
1062     #define add_mark_example "1234 add-mark"
1063     #define mark_name_example "0 \"mark-0\" set-mark-name"
1064   #endif
1065 
1066   snd_help_with_xrefs("Marks",
1067 
1068 #if HAVE_EXTENSION_LANGUAGE
1069 "A 'mark' marks a particular sample in a sound (not a position in that sound). \
1070 If we mark a sample, then delete 100 samples before it, the mark follows the sample, changing its current position \
1071 in the data.  If we delete the sample, the mark is also deleted; a subsequent undo that returns the sample also \
1072 returns its associated mark.  I'm not sure this is the right thing, but it's a lot less stupid than marking \
1073 a position. \
1074 \n\n\
1075 Once set, a mark can be moved by dragging the horizontal tab at the top.  Control-click of the tab followed by mouse \
1076 drag will drag the underlying data too, either inserting zeros or deleting data. \
1077 Click on the triangle at the bottom to play (or stop playing) from the mark. \
1078 \n\n\
1079 A mark can be named or unnamed.  It it has a name, it is displayed above the horizontal tab at the top of the window. As with \
1080 sounds and mixes, marks can be grouped together through the sync field; marks sharing the same sync value (other \
1081 than 0) will move together when one is moved, and so on.  The following keyboard commands relate to marks: \
1082 \n\n\
1083   C-m       place (or remove if argument negative) mark at cursor\n\
1084   C-M       place syncd marks at all currently syncd chan cursors\n\
1085   C-x /     place named mark at cursor\n\
1086   C-x C-m   add named mark\n\
1087   C-j       go to mark\n\
1088 \n\
1089 The main mark-related functions are:\n\
1090 \n\
1091   " S_add_mark " (sample :optional snd chn)\n\
1092    add a mark at sample 'sample': " add_mark_example "\n\
1093 \n\
1094   " S_delete_mark " (mark-id) \n\
1095     delete mark (the 'mark-id' is returned by " S_add_mark ")\n\
1096 \n\
1097   " S_mark_sample " (mark-id): sample marked by the mark\n\
1098   " S_mark_name " (mark-id): mark's name, if any\n\
1099     " mark_name_example "\n\
1100 \n\
1101 Other such functions: " S_find_mark ", " S_mark_color ", " S_mark_tag_width ",\n\
1102     " S_mark_tag_height ", " S_mark_sync ", " S_show_marks ", " S_save_marks ",\n\
1103     " S_marks "\n",
1104 
1105 #else
1106 "A 'mark' marks a particular sample in a sound (not a position in that sound). \
1107 If we mark a sample, then delete 100 samples before it, the mark follows the sample, changing its current position \
1108 in the data.  If we delete the sample, the mark is also deleted; a subsequent undo that returns the sample also \
1109 returns its associated mark.\
1110 \n\n\
1111 Once set, a mark can be moved by dragging the horizontal tab at the top.  Control-click of the tab followed by mouse \
1112 drag will drag the underlying data too, either inserting zeros or deleting data. \
1113 Click on the triangle at the bottom to play (or stop playing) from the mark. \
1114 The following keyboard commands relate to marks: \
1115 \n\n\
1116   C-m       place (or remove if argument negative) mark at cursor\n\
1117   C-M       place syncd marks at all currently syncd chan cursors\n\
1118   C-x /     place named mark at cursor\n\
1119   C-x C-m   add named mark\n\
1120   C-j       go to mark",
1121 #endif
1122 
1123 		      WITH_WORD_WRAP,
1124 		      snd_xrefs("Mark"),
1125 		      snd_xref_urls("Mark"));
1126 
1127   append_key_help("C-x C-m", snd_K_m, ControlMask, true,
1128     append_key_help("C-x /", snd_K_slash, 0, true,
1129       append_key_help("C-j", snd_K_j, ControlMask, false,
1130         append_key_help("C-m", snd_K_m, ControlMask, false, true))));
1131 }
1132 
1133 
1134 /* ---------------- Mixes ---------------- */
1135 
mix_help(void)1136 void mix_help(void)
1137 {
1138   #if HAVE_SCHEME
1139     #define mix_example "(mix \"oboe.snd\" 1234)"
1140     #define mix_vct_example "(mix-float-vector (float-vector 0 .1 .2) 1234)"
1141     #define mix_amp_example "(set! (mix-amp 0) .5)"
1142     #define mix_amp_env_example "(set! (mix-amp-env 0) '(0 0 1 1))"
1143   #endif
1144   #if HAVE_RUBY
1145     #define mix_example "mix(\"oboe.snd\", 1234)"
1146     #define mix_vct_example "mix_vct(vct(0.0, 0.1, 0.2), 1234)"
1147     #define mix_amp_example "set_mix_amp(0, .5)"
1148     #define mix_amp_env_example "set_mix_amp_env(0, [0.0, 0.0, 1.0, 1.0])"
1149   #endif
1150   #if HAVE_FORTH
1151     #define mix_example "\"oboe.snd\" 1234 mix"
1152     #define mix_vct_example "0.0 0.1 0.2 vct 1234 mix-vct"
1153     #define mix_amp_example "0 0.5 set-mix-amp"
1154     #define mix_amp_env_example "0 '( 0.0 0.0 1.0 1.0 ) set-mix-amp-env"
1155   #endif
1156 
1157   snd_help_with_xrefs("Mixing",
1158 
1159 #if HAVE_EXTENSION_LANGUAGE
1160 "Since mixing is the most common and most useful editing operation performed on \
1161 sounds, there is relatively elaborate support for it in Snd. To mix in a file, \
1162 use the File Mix menu option, the command C-x C-q, or one of the various \
1163 mixing functions. Currently the only difference between the first two is that \
1164 the Mix menu option tries to take the current sync state into account, whereas \
1165 the C-x C-q command does not. To mix a selection, use C-x q. The mix starts at \
1166 the current cursor location. It is displayed as a separate waveform above \
1167 the main waveform with a tag at the beginning.  You can drag the tag to \
1168 reposition the mix. \
1169 \n\n\
1170 The Mix dialog (under the View Menu) provides various \
1171 commonly-used controls on the currently chosen mix. At the top are the mix id, \
1172 name, begin and end times, and a play button. Beneath that are \
1173 various sliders controlling the speed (sampling rate) of the mix, amplitude of \
1174 each input channel, and the amplitude envelopes. \
1175 \n\n\
1176 The main mix-related functions are:\n\
1177 \n\
1178   " S_mix " (file :optional samp in-chan snd chn tags delete)\n\
1179     mix file's channel in-chan starting at samp\n\
1180     " mix_example "\n\
1181 \n\
1182   " S_mix_selection " (:optional beg snd chn selection-channel)\n\
1183     mix (add) selection starting at beg\n\
1184 \n\
1185   " S_mix_region " (:optional samp reg snd chn region-channel)\n\
1186     mix region reg at sample samp (default is cursor sample)\n\
1187 \n\
1188   " S_mix_vct " (v :optional beg snd chn with-mix-tags origin)\n\
1189     mix the contents of " S_vct " starting at beg:\n\
1190     " mix_vct_example "\n\
1191 \n\
1192   " S_mix_amp " (mix)\n\
1193     amplitude of mix:\n\
1194     " mix_amp_example "\n\
1195 \n\
1196   " S_mix_amp_env " (mix)\n\
1197     amplitude envelope of mix:\n\
1198     " mix_amp_env_example "\n\
1199 \n\
1200   " S_mix_speed " (mix)\n\
1201     speed (resampling ratio) of mix; 1.0 means no change;\n\
1202       2.0 reads the mix data twice as fast\n\
1203 \n\
1204   " S_mix_position " (mix)\n\
1205     position (a sample number) of mix\n\
1206 \n\
1207   " S_mix_length " (mix)\n\
1208     mix's length in samples\n\
1209 \n\
1210 Other such function include: " S_mix_waveform_height ", " S_with_mix_tags ", " S_mix_tag_width ",\n\
1211     " S_mix_tag_height ", " S_mix_tag_y ", " S_mixes ".",
1212 
1213 #else
1214 "Since mixing is the most common and most useful editing operation performed on \
1215 sounds, there is relatively elaborate support for it in Snd. To mix in a file, \
1216 use the File Mix menu option, the command C-x C-q, or one of the various \
1217 mixing functions. Currently the only difference between the first two is that \
1218 the Mix menu option tries to take the current sync state into account, whereas \
1219 the C-x C-q command does not. To mix a selection, use C-x q. The mix starts at \
1220 the current cursor location. It is displayed as a separate waveform above \
1221 the main waveform with a tag at the beginning.  You can drag the tag to \
1222 reposition the mix. \
1223 \n\n\
1224 The Mix dialog (under the View Menu) provides various \
1225 commonly-used controls on the currently chosen mix. At the top are the mix id, \
1226 name, begin and end times, and a play button. Beneath that are \
1227 various sliders controlling the speed (sampling rate), amplitude, \
1228 and amplitude envelope applied to the mix. \
1229 \n\n",
1230 #endif
1231 
1232 		      WITH_WORD_WRAP,
1233 		      snd_xrefs("Mix"),
1234 		      snd_xref_urls("Mix"));
1235 
1236   append_key_help("C-x q", snd_K_q, 0, true,
1237     append_key_help("C-x C-q", snd_K_q, ControlMask, true, true));
1238 }
1239 
1240 
1241 /* ---------------- Headers etc ---------------- */
1242 
1243 static const char *header_and_data_xrefs[10] = {
1244   "sample type discussion: {" S_sample_type "}",
1245   "sample type constants: {" S_mus_sample_type_name "}",
1246   "header type discussion: {" S_header_type "}",
1247   "header type constants: {" S_mus_header_type_name "}",
1248   "MPEG support: mpg in examp." Xen_file_extension,
1249   "OGG support: read-ogg in examp." Xen_file_extension,
1250   "Speex support: read-speex in examp." Xen_file_extension,
1251   "Flac support: read-flac in examp." Xen_file_extension,
1252   "{Sndlib}: underlying support",
1253   NULL};
1254 
1255 static const char *header_and_data_urls[10] = {
1256   "extsnd.html#sampletype",
1257   "extsnd.html#defaultoutputsampletype",
1258   "extsnd.html#headertype",
1259   "extsnd.html#defaultoutputheadertype",
1260   "sndscm.html#exmpg",
1261   "sndscm.html#exmpg",
1262   "sndscm.html#exmpg",
1263   "sndscm.html#exmpg",
1264   "sndlib.html#introduction",
1265   NULL};
1266 
sound_files_help(void)1267 void sound_files_help(void)
1268 {
1269   snd_help_with_xrefs("Headers and Data",
1270 "Snd can handle the following file and data types: \
1271 \n\n\
1272   read/write (many sample types):\n\
1273     NeXT/Sun/DEC/AFsp\n\
1274     AIFF/AIFC\n\
1275     RIFF (Microsoft wave)\n\
1276     RF64\n\
1277     IRCAM (old style)\n\
1278     NIST-sphere\n\
1279     CAFF\n\
1280     no header ('raw')\n\
1281 \n\n\
1282   read-only (in selected sample types):\n\
1283     8SVX (IFF), EBICSF, INRS, ESPS, SPPACK, ADC (OGI), AVR, VOC, PVF,\n\
1284     Sound Tools, Turtle Beach SMP, SoundFont 2.0, Sound Designer I, PSION, MAUD, Kurzweil 2000,\n\
1285     Gravis Ultrasound, ASF, PAF, CSL, Comdisco SPW, Goldwave sample, omf, quicktime, sox,\n\
1286     Sonic Foundry (w64), SBStudio II, Delusion digital, Digiplayer ST3, Farandole Composer WaveSample,\n\
1287     Ultratracker WaveSample, Sample Dump exchange, Yamaha SY85, SY99, and TX16, Covox v8, AVI, \n\
1288     Impulse tracker, Korg, Akai, Turtle Beach, Matlab-5\n\
1289 \n\n\
1290   automatically translated to a readable sample and header type:\n\
1291     IEEE text, Mus10, SAM 16-bit (modes 1 and 4), AVI, \n\
1292     NIST shortpack, HCOM, Intel, IBM, and Oki (Dialogic) ADPCM, \n\
1293     G721, G723_24, G723_40, MIDI sample dump, Ogg, Speex, \n\
1294     Flac, Midi, Mpeg, Shorten, Wavepack, tta (via external programs)\n\
1295 \n\n\
1296 The files can have any number of channels. Data can be either big or little endian. \
1297 The file types listed above as 'automatically translated' are \
1298 decoded upon being opened, translated to some type that Snd can read and write, \
1299 and rewritten as a new file with an added (possibly redundant) extension .snd, \
1300 and that file is the one the editor sees from then on.\
1301 \n\n\
1302 " S_sample_type " returns the current sound's sample type, and " S_header_type " returns \
1303 its header type.",
1304 
1305 		      WITH_WORD_WRAP,
1306 		      header_and_data_xrefs,
1307 		      header_and_data_urls);
1308 }
1309 
1310 
1311 /* ---------------- Initialization File ---------------- */
1312 
1313 static const char *init_file_xrefs[5] = {
1314   "{Invocation flags}",
1315   "~/.snd: {Initialization file}",
1316   "{Customization}",
1317   "examples of ~/.snd",
1318   NULL};
1319 
1320 static const char *init_file_urls[6] = {
1321   "grfsnd.html#sndswitches",
1322   "grfsnd.html#sndinitfile",
1323   "extsnd.html#lisplistener",
1324   "grfsnd.html#sndinitfile",
1325   NULL};
1326 
init_file_help(void)1327 void init_file_help(void)
1328 {
1329   snd_help_with_xrefs("Customization",
1330 
1331 #if HAVE_EXTENSION_LANGUAGE
1332 "Nearly everything in Snd can be set in an initialization file, loaded at any time from a saved-state file, specified \
1333 via inter-process communciation from any other program, or  \
1334 dealt with from the lisp listener panel. I've tried to bring out to lisp nearly every portion of Snd, \
1335 both the signal-processing functions, and much of the user interface. You can, for example, add your own menu choices, \
1336 editing operations, or graphing alternatives. These extensions can be loaded at any time.  One of the \
1337 easier ways to start an initialization file is to go to the Options:Preferences dialog, set the choices \
1338 you want, then save those choices.  The initialization file is just program text, so you can edit it and so on.",
1339 
1340 #else
1341 "Snd depends heavily on the extension language to provide much of its functionality.  Since none \
1342 is loaded, there's not much customization you can do.",
1343 #endif
1344 		      WITH_WORD_WRAP,
1345 		      init_file_xrefs,
1346 		      init_file_urls);
1347 }
1348 
1349 
1350 /* ---------------- Keys ---------------- */
1351 
1352 static const char *key_xrefs[4] = {
1353   "To change a key's action: {" S_bind_key "}",
1354   "To undefine a key: {" S_unbind_key "}",
1355   "Current key action: {" S_key_binding "}",
1356   NULL};
1357 
show_key_help(int key,int state,bool cx,char * help)1358 static void show_key_help(int key, int state, bool cx, char *help)
1359 {
1360   /* this frees the help string since it's always coming from key_description, and
1361    *   is a bother to handle in the loops that call us
1362    */
1363   if (help)
1364     {
1365       char buf[1024];
1366       char cbuf[256];
1367       make_key_name(cbuf, 256, key, state, cx);
1368       snprintf(buf, 1024, "\n%s: %s", cbuf, help);
1369       snd_help_append(buf);
1370       free(help);
1371     }
1372 }
1373 
1374 
find_unbuckified_keys(int key,int state,bool cx,Xen func)1375 static bool find_unbuckified_keys(int key, int state, bool cx, Xen func)
1376 {
1377   if ((key > 256) && (state == 0) && (!cx) && (Xen_is_bound(func)))
1378     show_key_help(key, state, cx, key_description(key, state, cx));
1379   return(false);
1380 }
1381 
1382 
find_buckified_keys(int key,int state,bool cx,Xen func)1383 static bool find_buckified_keys(int key, int state, bool cx, Xen func)
1384 {
1385   if ((key > 256) && (state == ControlMask) && (!cx) && (Xen_is_bound(func)))
1386     show_key_help(key, state, cx, key_description(key, state, cx));
1387   return(false);
1388 }
1389 
1390 
find_unbuckified_cx_keys(int key,int state,bool cx,Xen func)1391 static bool find_unbuckified_cx_keys(int key, int state, bool cx, Xen func)
1392 {
1393   if ((key > 256) && (state == 0) && (cx) && (Xen_is_bound(func)))
1394     show_key_help(key, state, cx, key_description(key, state, cx));
1395   return(false);
1396 }
1397 
1398 
find_buckified_cx_keys(int key,int state,bool cx,Xen func)1399 static bool find_buckified_cx_keys(int key, int state, bool cx, Xen func)
1400 {
1401   if ((key > 256) && (state == ControlMask) && (cx) && (Xen_is_bound(func)))
1402     show_key_help(key, state, cx, key_description(key, state, cx));
1403   return(false);
1404 }
1405 
1406 
find_leftover_keys(int key,int state,bool cx,Xen func)1407 static bool find_leftover_keys(int key, int state, bool cx, Xen func)
1408 {
1409   if ((key > 256) && (state & MetaMask))
1410     show_key_help(key, state, cx, key_description(key, state, cx));
1411   return(false);
1412 }
1413 
1414 
key_help(void)1415 void key_help(void)
1416 {
1417   #if HAVE_SCHEME
1418     #define bind_key_example "(bind-key \"End\" 0\n      (lambda () \"view full sound\"\n        (set! (x-bounds) (list 0.0 (/ (framples) (srate))))))"
1419   #endif
1420   #if HAVE_RUBY
1421     #define bind_key_example "bind_key(\"End\", 0,\n       lambda do ||\n         set_x_bounds([0.0, framples.to_f / srate.to_f])\n       end)"
1422   #endif
1423   #if HAVE_FORTH
1424     #define bind_key_example "\"End\" 0\n       lambda: <{ -- val }> doc\" view full sound\"\n         '( 0.0  #f #f #f framples  #f srate  f/ ) #f #f undef set-x-bounds ; bind-key"
1425   #endif
1426 
1427   int i;
1428 
1429   snd_help_with_xrefs("Keys",
1430 
1431 #if HAVE_EXTENSION_LANGUAGE
1432 "Although Snd has a number of built-in key actions (listed below), you can redefine \
1433 any key via:\n\
1434 \n\
1435   " S_bind_key " (key state func :optional extended origin)\n\
1436     this function causes 'key' (an integer or a key name) with \n\
1437     modifiers 'state' (and preceding C-x if 'extended') to evaluate\n\
1438     'func'.  For example, to set the End key to cause the full sound\n\
1439     to be displayed:\n\n\
1440     " bind_key_example "\n\n\nKeys:\n",
1441 
1442 #else
1443 "If there's no extension language, you're stuck with the built-in key actions:",
1444 #endif
1445 		      WITHOUT_WORD_WRAP,
1446 		      key_xrefs,
1447 		      NULL);
1448   /* run through bindings straight, C-key, C-x key, C-x C-key appending description in help dialog */
1449   for (i = 0; i < 256; i++)
1450     show_key_help(i, 0, false, key_description(i, 0, false));
1451   map_over_keys(find_unbuckified_keys);
1452   for (i = 0; i < 256; i++)
1453     show_key_help(i, ControlMask, false, key_description(i, ControlMask, false));
1454   map_over_keys(find_buckified_keys);
1455   for (i = 0; i < 256; i++)
1456     show_key_help(i, MetaMask, false, key_description(i, MetaMask, false));
1457   for (i = 0; i < 256; i++)
1458     show_key_help(i, ControlMask | MetaMask, false, key_description(i, ControlMask | MetaMask, false));
1459   for (i = 0; i < 256; i++)
1460     show_key_help(i, 0, true, key_description(i, 0, true));
1461   map_over_keys(find_unbuckified_cx_keys);
1462   for (i = 0; i < 256; i++)
1463     show_key_help(i, ControlMask, true, key_description(i, ControlMask, true));
1464 
1465   map_over_keys(find_buckified_cx_keys);
1466   map_over_keys(find_leftover_keys);
1467   snd_help_back_to_top();
1468 }
1469 
1470 
1471 /* ---------------- Play ---------------- */
1472 
play_help(void)1473 void play_help(void)
1474 {
1475 #if WITH_AUDIO
1476   #if HAVE_SCHEME
1477     #define play_cursor_example "(play (cursor))"
1478     #define play_file_example "(play \"oboe.snd\")"
1479     #define play_previous_version_example "(play 0 #f #f #f #f (1- (edit-position)))"
1480   #endif
1481   #if HAVE_RUBY
1482     #define play_cursor_example "play(cursor())"
1483     #define play_file_example "play(\"oboe.snd\")"
1484     #define play_previous_version_example "play(0, false, false, false, false, edit_position() - 1)"
1485   #endif
1486   #if HAVE_FORTH
1487     #define play_cursor_example "cursor play"
1488     #define play_file_example "\"oboe.snd\" play"
1489     #define play_previous_version_example "0 #f #f #f #f 0 0 edit-position 1- play"
1490   #endif
1491 
1492   snd_help_with_xrefs("Play",
1493 
1494 #if HAVE_EXTENSION_LANGUAGE
1495 "To play a sound, click the 'play' button.  If the sound has more channels than your DAC(s), Snd will (normally) try to mix the extra channels \
1496 into the available DAC outputs.  While it is playing, you can click the button again to stop it, or click some other \
1497 file's 'play' button to mix it into the current set of sounds being played. To play from a particular point, set the cursor \
1498 or a mark \
1499 there, then click its 'play triangle' (the triangular portion below the x axis).  (Use control-click here to play all channels \
1500 from the mark point). To play simultaneously from an arbitrary group of start points (possibly spread among many sounds), \
1501 set syncd marks at the start points, then click the play triangle of one of them. \
1502 \n\n\
1503 The Edit menu 'Play' option plays the current selection, if any.  The Popup menu's 'Play' option plays the \
1504 currently selected sound.  And the region and file browsers provide play buttons for each of the listed regions or files.  If you \
1505 hold down the control key when you click 'play', the cursor follows along as the sound is played.   \
1506 \n\n\
1507 In a multichannel file, C-q plays all channels from the current channel's \
1508 cursor if the sync button is on, and otherwise plays only the current channel. \
1509 Except in the browsers, what is actually played depends on the control panel.\
1510 \n\n\
1511 Use the play function to play any object.",
1512 
1513 #else
1514 "To play a sound, click the 'play' button.  If the sound has more channels than your DAC(s), Snd will (normally) try to mix the extra channels \
1515 into the available DAC outputs.  While it is playing, you can click the button again to stop it, or click some other \
1516 file's 'play' button to mix it into the current set of sounds being played. To play from a particular point, set a mark \
1517 there, then click its 'play triangle' (the triangular portion below the x axis).  (Use control-click here to play all channels \
1518 from the mark point). To play simultaneously from an arbitrary group of start points (possibly spread among many sounds), \
1519 set syncd marks at the start points, then click the play triangle of one of them. \
1520 \n\n\
1521 The Edit menu 'Play' option plays the current selection, if any.  The Popup menu's 'Play' option plays the \
1522 currently selected sound.  And the region and file browsers provide play buttons for each of the listed regions or files.  If you \
1523 hold down the control key when you click 'play', the cursor follows along as the sound is played.   \
1524 \n\n\
1525 In a multichannel file, C-q plays all channels from the current channel's \
1526 cursor if the sync button is on, and otherwise plays only the current channel. \
1527 Except in the browsers, what is actually played depends on the control panel.",
1528 #endif
1529 
1530 		      WITH_WORD_WRAP,
1531 		      snd_xrefs("Play"),
1532 		      snd_xref_urls("Play"));
1533 
1534   append_key_help("C-q", snd_K_q, ControlMask, true, true);
1535 #else
1536   snd_help("Play", "this version of Snd can't play!", WITH_WORD_WRAP);
1537 #endif
1538 }
1539 
1540 
1541 /* ---------------- Reverb ---------------- */
1542 
reverb_help(void)1543 void reverb_help(void)
1544 {
1545   #if HAVE_SCHEME
1546     #define reverb_control_length_bounds_example "(set! (reverb-control-length-bounds) (list 0.0 10.0))"
1547     #define reverb_control_p_example "(set! (reverb-control?) #t)"
1548     #define mention_hidden_controls "\nThe lowpass and feedback controls are accessible from the \"Option:Controls\" dialog."
1549   #endif
1550   #if HAVE_RUBY
1551     #define reverb_control_length_bounds_example "set_reverb_control_length_bounds([0.0, 10.0])"
1552     #define reverb_control_p_example "set_reverb_control?(true)"
1553     #define mention_hidden_controls ""
1554   #endif
1555   #if HAVE_FORTH
1556     #define reverb_control_length_bounds_example "'( 0.0 10.0 ) set-reverb-control-length-bounds"
1557     #define reverb_control_p_example "#t set-reverb-control?"
1558     #define mention_hidden_controls ""
1559   #endif
1560 
1561   snd_help_with_xrefs("Reverb",
1562 
1563 #if HAVE_EXTENSION_LANGUAGE
1564 "The reverb in the control panel is a version of Michael McNabb's Nrev (if it seems to be \
1565 non-functional, make sure the reverb button on the far right is clicked).  There are other \
1566 reverbs mentioned in the related topics list.  The control panel reverb functions are:\n\
1567 \n\
1568   " S_reverb_control_decay " (:optional snd):\n\
1569     length (in seconds) of the reverberation after the sound has finished\n\
1570 \n\
1571   " S_reverb_control_feedback " (:optional snd):\n\
1572     reverb feedback coefficient\n\
1573 \n\
1574   " S_reverb_control_length " (:optional snd):\n\
1575     reverb delay line length scaler. \n\
1576     Longer reverb simulates a bigger hall.\n\
1577 \n\
1578   " S_reverb_control_length_bounds " (:optional snd):\n\
1579     reverb-control-length min and max amounts as a list\n\
1580       " reverb_control_length_bounds_example "\n\
1581 \n\
1582   " S_reverb_control_lowpass " (:optional snd):\n\
1583     reverb low pass filter coefficient. (filter in feedback loop).\n\
1584 \n\
1585   " S_reverb_control_on " (:optional snd):\n\
1586     " PROC_TRUE " if the reverb button is set (i.e. the reverberator is active).\n\
1587       " reverb_control_p_example "\n\
1588 \n\
1589   " S_reverb_control_scale " (:optional snd):\n\
1590     reverb amount (the amount of the direct signal sent to the reverb).\n\
1591 \n\
1592   " S_reverb_control_scale_bounds " (:optional snd):\n\
1593     reverb-control-scale min and max amounts as a list.\n" mention_hidden_controls "\n",
1594 
1595 #else
1596 "The reverb in the control panel is a version of Michael McNabb's Nrev (if it seems to be \
1597 non-functional, make sure the reverb button on the far right is clicked).",
1598 #endif
1599 
1600 
1601 		      WITH_WORD_WRAP,
1602 		      snd_xrefs("Reverb"),
1603 		      snd_xref_urls("Reverb"));
1604 }
1605 
1606 
1607 /* ---------------- Save ---------------- */
save_help(void)1608 void save_help(void)
1609 {
1610   snd_help_with_xrefs("Save",
1611 "To save the current edited state of a file, use the File:Save option (to overwrite the old version of the \
1612 file), or File:Save as (to write to a new file, leaving the old file unchanged).  The equivalent keyboard \
1613 command is C-x C-s (save). ",
1614 
1615 		      WITH_WORD_WRAP,
1616 		      snd_xrefs("Save"),
1617 		      snd_xref_urls("Save"));
1618 
1619   append_key_help("C-x C-s", snd_K_s, ControlMask, true, true);
1620 }
1621 
1622 
1623 /* ---------------- Filter ---------------- */
1624 
filter_help(void)1625 void filter_help(void)
1626 {
1627   #if HAVE_SCHEME
1628     #define filter_sound_env_example "(filter-sound '(0 1 1 0) 1024)"
1629     #define filter_sound_vct_example "(filter-sound (float-vector .1 .2 .3 .3 .2 .1) 6)"
1630     #define filter_sound_clm_example "(filter-sound (make-filter 2 (float-vector 1 -1) (float-vector 0 -0.99)))"
1631   #endif
1632   #if HAVE_RUBY
1633     #define filter_sound_env_example "filter_sound([0.0, 1.0, 1.0, 0.0], 1024)"
1634     #define filter_sound_vct_example "filter_sound(vct(0.1, 0.2, 0.3, 0.3, 0.2, 0.1), 6)"
1635     #define filter_sound_clm_example "filter_sound(make_filter(2, vct(1.0, -1.0), vct(0.0, -0.99)))"
1636   #endif
1637   #if HAVE_FORTH
1638     #define filter_sound_env_example "'( 0.0 1.0 1.0 0.0 ) 1024 filter-sound"
1639     #define filter_sound_vct_example "'( .1 .2 .3 .3 .2 .1 ) list->vct 6 filter-sound"
1640     #define filter_sound_clm_example "2 '( 1 -1 ) list->vct '( 0.0 -0.99 ) list->vct make-filter filter-sound"
1641   #endif
1642 
1643   snd_help_with_xrefs("Filter",
1644 
1645 #if HAVE_EXTENSION_LANGUAGE
1646 "There is an FIR Filter in the control panel, a filtering option in the envelope editor dialog, and a variety of other filters scattered around; \
1647 see sndclm.html, dsp." Xen_file_extension " and analog-filters." Xen_file_extension " in particular. The \
1648 built-in filtering functions include: \n\
1649 \n\
1650   " S_filter_channel " (env :optional order beg dur snd chn edpos trunc origin)\n\
1651     apply FIR filter to channel; 'env' is frequency response.\n\
1652 \n\
1653   " S_filter_selection " (env :optional order truncate)\n\
1654     apply FIR filter with frequency response 'env' to the selection.\n\
1655 \n\
1656   " S_filter_sound " (env :optional order snd chn edpos origin)\n\
1657     apply FIR filter with freq response, clm gen, or coeffs 'env'\n\
1658 \n\
1659     " filter_sound_env_example "\n\
1660     " filter_sound_vct_example "\n\
1661     " filter_sound_clm_example "\n\
1662 \n\
1663 The control filter functions are:\n\
1664 \n\
1665   " S_filter_control_coeffs " (:optional snd)\n\
1666     filter coefficients (read-only currently)\n\
1667 \n\
1668   " S_filter_control_envelope " (:optional snd)\n\
1669     filter (frequency response) envelope\n\
1670 \n\
1671   " S_filter_control_in_dB " (:optional snd)\n\
1672     The filter dB button. If " PROC_TRUE ", the graph is displayed in dB.\n\
1673 \n\
1674   " S_filter_control_in_hz " (:optional snd)\n\
1675     If " PROC_TRUE ", the filter frequency response envelope x axis is in Hz,\n\
1676     otherwise 0 to 1.0 (where 1.0 corresponds to srate/2).\n\
1677 \n\
1678   " S_filter_control_order " (:optional snd)\n\
1679     The filter order\n\
1680 \n\
1681   " S_filter_control_on " (:optional snd)\n\
1682     " PROC_TRUE " if the filter is active.\n\
1683 \n\
1684   " S_filter_control_waveform_color "\n\
1685     The filter frequency response waveform color.\n",
1686 
1687 #else
1688 "There is an FIR Filter in the control panel, and a filtering option in the Edit envelope dialog.",
1689 #endif
1690 
1691 		      WITH_WORD_WRAP,
1692 		      snd_xrefs("Filter"),
1693 		      snd_xref_urls("Filter"));
1694 }
1695 
1696 
1697 /* ---------------- Resample ---------------- */
1698 
resample_help(void)1699 void resample_help(void)
1700 {
1701   #if HAVE_SCHEME
1702     #define src_number_example "(src-channel 2.0) ; go twice as fast"
1703     #define src_env_example "(src-channel '(0 1 1 2))"
1704     #define src_clm_example "(src-channel (make-env '(0 1 1 2) :end (framples)))"
1705     #define H_speed_control_as_float S_speed_control_as_float
1706     #define H_speed_control_as_ratio S_speed_control_as_ratio
1707     #define H_speed_control_as_semitone S_speed_control_as_semitone
1708     #define H_src_duration "src-duration"
1709   #endif
1710   #if HAVE_RUBY
1711     #define src_number_example "src_channel(2.0) # go twice as fast"
1712     #define src_env_example "src_channel([0.0, 1.0, 1.0, 2.0])"
1713     #define src_clm_example "src_channel(make_env([0.0, 1.0, 1.0, 2.0], :end, framples()))"
1714     #define H_speed_control_as_float "Speed_control_as_float"
1715     #define H_speed_control_as_ratio "Speed_control_as_ratio"
1716     #define H_speed_control_as_semitone "Speed_control_as_semitone"
1717     #define H_src_duration "src_duration"
1718   #endif
1719   #if HAVE_FORTH
1720     #define src_number_example "2.0 src-channel \\ go twice as fast"
1721     #define src_env_example "'( 0.0 1.0 1.0 2.0 ) src-channel"
1722     #define src_clm_example "'( 0.0 1.0 1.0 2.0 ) :end 0 0 -1 framples make-env src-channel"
1723     #define H_speed_control_as_float S_speed_control_as_float
1724     #define H_speed_control_as_ratio S_speed_control_as_ratio
1725     #define H_speed_control_as_semitone S_speed_control_as_semitone
1726     #define H_src_duration "src-duration"
1727   #endif
1728 
1729   snd_help_with_xrefs("Resample",
1730 
1731 #if HAVE_EXTENSION_LANGUAGE
1732 "There is a sampling rate changer in the control panel, and a resampling option in the envelope \
1733 editor dialog; see also sndclm.html and examp." Xen_file_extension ". \
1734 The basic resampling functions are:\n\
1735 \n\
1736   " S_src_channel " (num-or-env :optional beg dur snd chn edpos)\n\
1737     resample (change the speed/pitch of) a channel.\n\
1738     'num-or-env' can be the resampling ratio (higher=faster)\n\
1739     an envelope, or a CLM env generator:\n\
1740 \n\
1741       " src_number_example "\n\
1742       " src_env_example "\n\
1743       " src_clm_example "\n\
1744 \n\
1745   In the envelope case, the function " H_src_duration " can help \n\
1746   you predict the result's duration.\n\
1747 \n\
1748   " S_src_selection " (num-or-env :optional base)\n\
1749     apply sampling rate conversion to the selection\n\
1750 \n\
1751   " S_src_sound " (num-or-env :optional base snd chn edpos)\n\
1752     resample sound\n\
1753 \n\
1754 The control panel 'speed' functions are:\n\
1755 \n\
1756   " S_speed_control " (:optional snd)\n\
1757     current speed (sampling rate conversion factor)\n\
1758 \n\
1759   " S_speed_control_bounds " (:optional snd)\n\
1760     speed-control min and max amounts as a list.\n\
1761     The default is (list 0.05 20.0). If no 'snd' argument\n\
1762     is given, this affects Mix and View:Files dialogs.\n\
1763 \n\
1764   " S_speed_control_style " (:optional snd)\n\
1765     The speed control can be a float, (" H_speed_control_as_float "),\n\
1766     a ratio of integers (" H_speed_control_as_ratio "), or a step \n\
1767     in a (possibly microtonal) scale (" H_speed_control_as_semitone ")\n\
1768 \n\
1769   " S_speed_control_tones " (:optional snd)\n\
1770     number of tones per octave in the " H_speed_control_as_semitone "\n\
1771     speed style (default: 12).",
1772 
1773 #else
1774 "There is a sampling rate changer in the control panel, and a resampling option in the Edit envelope dialog.",
1775 #endif
1776 
1777 		      WITH_WORD_WRAP,
1778 		      snd_xrefs("Resample"),
1779 		      snd_xref_urls("Resample"));
1780 }
1781 
1782 
1783 /* ---------------- Insert ---------------- */
1784 
insert_help(void)1785 void insert_help(void)
1786 {
1787   snd_help_with_xrefs("Insert",
1788 
1789 #if HAVE_EXTENSION_LANGUAGE
1790 "C-o inserts a \
1791 zero sample at the cursor.  There are also the File:Insert and Edit:Insert Selection \
1792 dialogs. The insertion-related functions are:\n\
1793 \n\
1794   " S_pad_channel " (beg dur :optional snd chn edpos)\n\
1795     insert 'dur' zeros at 'beg'\n\
1796 \n\
1797   " S_insert_silence " (beg num :optional snd chn)\n\
1798     insert 'num' zeros at 'beg'\n\
1799 \n\
1800   " S_insert_region " (:optional beg reg snd chn)\n\
1801     insert region 'reg' at sample 'beg'\n\
1802 \n\
1803   " S_insert_selection " (:optional beg snd chn)\n\
1804     insert selection starting at 'beg'\n\
1805 \n\
1806   " S_insert_sample " (samp value :optional snd chn edpos)\n\
1807     insert sample 'value' at sample 'samp'\n\
1808 \n\
1809   " S_insert_samples " (samp samps data :optional snd chn pos del org)\n\
1810     insert 'samps' samples of 'data' (normally a " S_vct ") starting at\n\
1811     sample 'samp'.  'data' can also be a filename.\n\
1812 \n\
1813   " S_insert_sound " (file :optional beg in-chan snd chn pos del)\n\
1814     insert channel 'in-chan' of 'file' at sample 'beg' (cursor position\n\
1815     by default).  If 'in-chan' is not given (or is " PROC_FALSE "), all\n\
1816     channels are inserted.\n",
1817 
1818 #else
1819 "C-o inserts a \
1820 zero sample at the cursor.  There are also the File:Insert and Edit:Insert Selection dialogs.",
1821 #endif
1822 
1823 		      WITH_WORD_WRAP,
1824 		      snd_xrefs("Insert"),
1825 		      snd_xref_urls("Insert"));
1826 
1827   append_key_help("C-o", snd_K_o, ControlMask, false, true);
1828 }
1829 
1830 
1831 /* ---------------- Delete ---------------- */
1832 
delete_help(void)1833 void delete_help(void)
1834 {
1835   snd_help_with_xrefs("Delete",
1836 
1837 #if HAVE_EXTENSION_LANGUAGE
1838 "To delete a sample, use C-d; to delete the selection, C-w.  The main deletion-related \
1839 functions are:\n\
1840 \n\
1841   " S_delete_sample " (samp :optional snd chn edpos)\n\
1842     delete sample number 'samp'.\n\
1843 \n\
1844   " S_delete_samples " (samp samps :optional snd chn edpos)\n\
1845     delete 'samps' samples starting at 'samp'\n\
1846 \n\
1847   " S_delete_selection " (): delete selected portions.\n\
1848   " S_delete_mark " (id): delete mark 'id'",
1849 
1850 #else
1851 "To delete a sample, use C-d; to delete the selection, C-w, or the Edit menu Delete Selection option.",
1852 #endif
1853 
1854 		      WITH_WORD_WRAP,
1855 		      snd_xrefs("Delete"),
1856 		      snd_xref_urls("Delete"));
1857 
1858   append_key_help("C-w", snd_K_w, ControlMask, false,
1859     append_key_help("C-d", snd_K_d, ControlMask, false, true));
1860 }
1861 
1862 
1863 /* ---------------- Regions ---------------- */
1864 
region_help(void)1865 void region_help(void)
1866 {
1867   #if HAVE_SCHEME
1868     #define region_to_vct_example "(region->float-vector 0 0 reg) ; len=0 => entire region"
1869     #define save_region_example "(save-region reg :file \"reg0.snd\" :header-type mus-next)"
1870   #endif
1871   #if HAVE_RUBY
1872     #define region_to_vct_example "region2vct(0, 0, reg) # len=0 => entire region"
1873     #define save_region_example "save_region(reg, :file, \"reg0.snd\", :header_type, Mus_next)"
1874   #endif
1875   #if HAVE_FORTH
1876     #define region_to_vct_example "0 0 reg region->vct \\ len=0 => entire region"
1877     #define save_region_example "reg :file \"reg0.snd\" :header-type mus-next save-region"
1878   #endif
1879 
1880   snd_help_with_xrefs("Region",
1881 #if HAVE_EXTENSION_LANGUAGE
1882 "A region is a saved portion of the sound data. When a sound portion is selected, it is (by default) saved \
1883 as the new region; subsequent edits will not affect the region data. You can disable the region creation \
1884 by setting the variable " S_selection_creates_region " to " PROC_FALSE " (its default is " PROC_TRUE " which can slow down editing \
1885 of very large sounds). Regions can be defined by " S_make_region ", by dragging the mouse through a portion \
1886 of the data, or via the Select All menu option. If the mouse drags off the end of the graph, the x axis \
1887 moves, in a sense dragging the data along to try to keep up with the mouse; the further away the mouse \
1888 is from the display, the faster the axis moves. A region can also be defined with keyboard commands, \
1889 much as in Emacs. C-[space] starts the region definition and the various cursor moving commands \
1890 continue the definition.  As regions are defined, the new ones are pushed on a stack, and if enough regions already exist, \
1891 old ones are pushed off (and deleted) to make room. Each region has a unique id returned \
1892 by " S_make_region " and shown beside the region name in the Region Browser. \
1893 Most of the region arguments below default to the current region (the top of the regions stack). \
1894 The main region-related functions are:\n\
1895 \n\
1896   " S_view_regions_dialog ": start the Region browser\n\
1897   " S_save_region_dialog ": start the Save region dialog\n\
1898 \n\
1899   " S_insert_region " (:optional beg reg snd chn)\n\
1900     insert region 'reg' at sample 'beg'\n\
1901 \n\
1902   " S_mix_region " (:optional samp reg snd chn reg-chan)\n\
1903     mix in region 'reg' at sample 'samp' (defaults to the cursor sample)\n\
1904 \n\
1905   " S_save_region " (reg :file :header-type :sample-type :comment)\n\
1906     save region 'reg' in 'file' in sample-type (default is mus-bshort),\n\
1907     header type (default is mus-next), and comment. Return the output\n\
1908     filename.\n\
1909 \n\
1910       " save_region_example "\n\
1911 \n\
1912   " S_region_to_vct " (:optional samp samps reg chn v)\n\
1913     return a " S_vct " containing 'samps' samples starting at 'samp'\n\
1914     in region 'reg's channel 'chn'. If 'v' (a " S_vct ") is provided,\n\
1915     it is filled, rather than creating a new " S_vct ".\n\
1916 \n\
1917       " region_to_vct_example "\n\
1918 \n\
1919   " S_make_region " (:optional beg end snd chn)\n\
1920     create a new region spanning samples 'beg' to 'end';\n\
1921     return the new region's id.\n\
1922 \n\
1923   " S_forget_region " (:optional reg): remove region from region list\n\
1924 \n\
1925   " S_is_region " (reg): " PROC_TRUE " if 'reg' is a known region id\n\
1926 \n\
1927   " S_regions ": list of currently active regions\n\
1928 \n\
1929   " S_max_regions ": how big the region stack can get\n\
1930 \n\
1931   " S_selection_creates_region ": does making a selection create a region\n\
1932 \n\
1933   " S_region_chans " (:optional reg): region chans\n\
1934   " S_region_framples " (:optional reg chan): region length\n\
1935   " S_region_maxamp " (:optional reg): region maxamp\n\
1936   " S_region_maxamp_position " (:optional reg): maxamp location (sample number)\n\
1937   " S_region_position " (:optional reg chan): original begin time of region\n\
1938   " S_region_sample " (:optional samp reg chan): sample value\n\
1939   " S_region_srate " (:optional reg): original data's sampling rate\n",
1940 #else
1941 "A region is a portion of the sound data. When a sound portion is selected, it is (by default) saved \
1942 as the new region; subsequent edits will not affect the region data. \
1943 of very large sounds). Regions can be defined by dragging the mouse through a portion \
1944 of the data, or via the Select All menu option. If the mouse drags off the end of the graph, the x axis \
1945 moves, in a sense dragging the data along to try to keep up with the mouse; the further away the mouse \
1946 is from the display, the faster the axis moves. A region can also be defined with keyboard commands, \
1947 much as in Emacs. C-[space] starts the region definition and the various cursor moving commands \
1948 continue the definition.",
1949 #endif
1950 		      WITH_WORD_WRAP,
1951 		      snd_xrefs("Region"),
1952 		      snd_xref_urls("Region"));
1953 
1954   append_key_help("C-[space]", snd_K_space, ControlMask, false, true);
1955 }
1956 
1957 
1958 /* ---------------- Selections ---------------- */
1959 
selection_help(void)1960 void selection_help(void)
1961 {
1962   #if HAVE_SCHEME
1963     #define env_selection_example "(env-selection '(0 0 1 1 2 0))"
1964     #define save_selection_example "(save-selection \"sel.snd\" :channel 1)"
1965     #define scale_selection_list_example "(scale-selection-by '(0.0 2.0))"
1966     #define scale_selection_number_example "(scale-selection-by 2.0)"
1967   #endif
1968   #if HAVE_RUBY
1969     #define env_selection_example "env_selection([0.0, 0.0, 1.0, 1.0, 2.0, 0.0])"
1970     #define save_selection_example "save_selection(\"sel.snd\", :channel, 1)"
1971     #define scale_selection_list_example "scale_selection_by([0.0, 2.0])"
1972     #define scale_selection_number_example "scale_selection_by(2.0)"
1973   #endif
1974   #if HAVE_FORTH
1975     #define env_selection_example "'( 0.0 0.0 1.0 1.0 2.0 0.0 ) env-selection"
1976     #define save_selection_example "\"sel.snd\" :channel 1 save-selection"
1977     #define scale_selection_list_example "'( 0.0 2.0 ) scale-selection-by"
1978     #define scale_selection_number_example "2.0 scale-selection-by"
1979   #endif
1980 
1981   snd_help_with_xrefs("Selection",
1982 
1983 #if HAVE_EXTENSION_LANGUAGE
1984 "The selection is a high-lighted portion of the current sound. \
1985 You can create it by dragging the mouse through the data, or via various functions. \
1986 The primary selection-related functions are:\n\
1987 \n\
1988   " S_convolve_selection_with " (file :optional amp)\n\
1989     convolve the selected portion with 'file'\n\
1990 \n\
1991   " S_delete_selection " (): delete the selected portion\n\
1992 \n\
1993   " S_env_selection " (envelope :optional env-base)\n\
1994     apply amplitude envelope to selected portion\n\
1995 \n\
1996       " env_selection_example "\n\
1997 \n\
1998   " S_filter_selection " (env :optional order truncate)\n\
1999     apply an FIR filter with frequency response 'env' to the \n\
2000     selection. env can be the filter coefficients themselves in\n\
2001     a " S_vct " with at least 'order' elements, or a CLM filter\n\
2002 \n\
2003   " S_insert_selection " (:optional beg snd chn)\n\
2004     insert (a copy of) selection starting at 'beg'\n\
2005 \n\
2006   " S_mix_selection " (:optional beg snd chn selection-chan)\n\
2007     mix (add) selection starting at 'beg'\n\
2008 \n\
2009   " S_reverse_selection "(): reverse selected portion\n\
2010 \n\
2011   " S_save_selection " (:file (:header-type mus-next)\n\
2012                            :sample-type :srate :comment :channel)\n\
2013     save the selection in file. If channel is given, save\n\
2014     only that channel.\n\
2015 \n\
2016       " save_selection_example "\n\
2017 \n\
2018   " S_scale_selection_by " (scalers)\n\
2019     scale the selection by 'scalers' which can be either a float,\n\
2020     a list of floats, or a " S_vct ". In a multichannel selection, each\n\
2021     member of the " S_vct " or list is applied to the next channel in the\n\
2022     selection. " scale_selection_list_example " scales the first\n\
2023     channel by 0.0, the second (if any) by 2.0.\n\
2024     " scale_selection_number_example " scales all channels by 2.0.\n\
2025 \n\
2026   " S_scale_selection_to " (norms)\n\
2027     normalize the selection to norms which can be either a float,\n\
2028     a list of floats, or a " S_vct ".\n\
2029 \n\
2030   " S_select_all " (:optional snd chn)\n\
2031     select all samples\n\
2032 \n\
2033   " S_smooth_selection "()\n\
2034     apply a smoothing function to the selection. \n\
2035     This produces a sinusoid between the end points.\n\
2036 \n\
2037   " S_src_selection " (num-or-env :optional base)\n\
2038     apply sampling rate conversion to the selection\n\
2039 \n\
2040   " S_save_selection_dialog " (:optional managed)\n\
2041     start the Save Selection dialog.\n\
2042 \n\
2043   " S_selection_creates_region "\n\
2044     if " PROC_TRUE ", a region is created whenever a selection is made.\n\
2045 \n\
2046   " S_selection_chans ": chans in selection\n\
2047   " S_selection_color ": color to highlight selection\n\
2048   " S_selection_framples " (:optional snd chn): length of selection\n\
2049   " S_selection_maxamp " (:optional snd chn): maxamp of selection\n\
2050   " S_selection_maxamp_position " (:optional snd chn): sample number of maxamp\n\
2051   " S_selection_member " (:optional snd chn): \n\
2052     " PROC_TRUE " if snd's channel chn has selected data.\n\
2053   " S_is_selection ": " PROC_TRUE " if there's a selection\n\
2054   " S_selection_position " (:optional snd chn): \n\
2055     sample number where selection starts\n\
2056   " S_selection_srate ": nominal srate of selected portion.\n\
2057 \n\
2058 There are many more selection-oriented functions scattered around the various *." Xen_file_extension " files. \
2059 See the related topics list below.",
2060 
2061 #else
2062 "The selection is a high-lighted portion of the current sound. \
2063 You can create it by dragging the mouse.",
2064 #endif
2065 		      WITH_WORD_WRAP,
2066 		      snd_xrefs("Selection"),
2067 		      snd_xref_urls("Selection"));
2068 }
2069 
2070 
2071 /* ---------------- Colors ---------------- */
2072 
colors_help(void)2073 void colors_help(void)
2074 {
2075   #if HAVE_SCHEME
2076     #define make_color_example "(define blue (make-color 0.0 0.0 1.0))"
2077     #define set_basic_color_example "(set! (basic-color) blue)"
2078   #endif
2079   #if HAVE_RUBY
2080     #define make_color_example "Blue = make_color(0.0, 0.0, 1.0)"
2081     #define set_basic_color_example "set_basic_color(Blue)"
2082   #endif
2083   #if HAVE_FORTH
2084     #define make_color_example ": blue 0 0 1 make-color ;"
2085     #define set_basic_color_example "blue set-basic-color"
2086   #endif
2087 
2088   snd_help_with_xrefs("Colors",
2089 
2090 #if HAVE_EXTENSION_LANGUAGE
2091 "A color in Snd is an object with three fields representing the rgb (red green blue) settings \
2092 as numbers between 0.0 and 1.0. A color object is created via " S_make_color ":\n\
2093   " make_color_example "\n\
2094 \n\
2095 This declares the variable \"blue\" and gives it the value of the color whose rgb components \
2096 include only blue in full force. The X11 color names are defined in rgb." Xen_file_extension ". The overall widget background color is " S_basic_color ".\n\
2097 \n\
2098   " set_basic_color_example "\n\
2099 \n\
2100 The color variables are:\n\
2101 " S_basic_color ":  main Snd color.\n\
2102 " S_cursor_color ":  cursor color.\n\
2103 " S_data_color ":  color of data in unselected graph.\n\
2104 " S_enved_waveform_color ":  color of waveform displayed in envelope editor.\n\
2105 " S_filter_control_waveform_color ":  color of control panel filter waveform.\n\
2106 " S_graph_color ":  background color of unselected graph.\n\
2107 " S_highlight_color ":  highlighting color.\n\
2108 " S_listener_color ":  background color of lisp listener.\n\
2109 " S_listener_text_color ":  text color in lisp listener.\n\
2110 " S_mark_color ":  color of mark indicator.\n\
2111 " S_mix_color ":  color of mix waveforms.\n\
2112 " S_position_color ":  position slider color\n\
2113 " S_sash_color ":  color of paned window sashes.\n\
2114 " S_selected_data_color ":  color of data in currently selected graph.\n\
2115 " S_selected_graph_color ":  background color of currently selected graph.\n\
2116 " S_selection_color ":  color of selected portion of graph.\n\
2117 " S_text_focus_color ":  color of text field when it has focus.\n\
2118 " S_zoom_color ":  zoom slider color.\n\
2119 \n\
2120 The easiest way to try out other colors is to use the Options:Preferences dialog. \
2121 The sonogram colors can be set in the View:Colors dialog.",
2122 
2123 #else
2124 "To change the various Snd colors, use the Options:Preferences Dialog. The sonogram colors can be set in the View:Colors dialog.",
2125 #endif
2126 		      WITH_WORD_WRAP,
2127 		      snd_xrefs("Color"),
2128 		      snd_xref_urls("Color"));
2129 }
2130 
2131 
2132 
2133 /* -------- dialog help texts -------- */
2134 
2135 /* ---------------- Envelope Editor ---------------- */
2136 
envelope_editor_dialog_help(void)2137 void envelope_editor_dialog_help(void)
2138 {
2139   #if HAVE_SCHEME
2140     #define define_envelope_name "define, or define-envelope"
2141     #define ramp_envelope_example "'(0 0 1 1)"
2142     #define define_envelope_example "  (define-envelope pyramid '(0 0 1 1 2 0))"
2143   #endif
2144   #if HAVE_RUBY
2145     #define define_envelope_name "define_envelope"
2146     #define ramp_envelope_example "[0.0, 0.0, 1.0, 1.0]"
2147     #define define_envelope_example "  define_envelope(\"ramp\", [0.0, 0.0, 1.0, 1.0])\n  define_envelope(\"pyramid\", [0.0, 0.0, 1.0, 1.0, 2.0, 0.0])"
2148   #endif
2149   #if HAVE_FORTH
2150     #define define_envelope_name "define-envelope"
2151     #define ramp_envelope_example "'( 0.0 0.0 1.0 1.0 )"
2152     #define define_envelope_example "  \"ramp\" '( 0.0 0.0 1.0 1.0 ) define-envelope\n  \"pyramid\" '( 0.0 0.0 1.0 1.0 2.0 0.0 ) define-envelope"
2153   #endif
2154   #if (!HAVE_EXTENSION_LANGUAGE)
2155     #define define_envelope_name "<needs extension language>"
2156     #define ramp_envelope_example "'(0 0 1 1)"
2157     #define define_envelope_example "<needs extension language>"
2158   #endif
2159 
2160   snd_help_with_xrefs("Envelope Editor",
2161 "The Edit Envelope dialog (under the Edit menu) opens a window for viewing and editing envelopes. \
2162 The dialog has a display showing either the envelope currently being edited or \
2163 a panorama of all currently loaded envelopes.  The current envelope can be edited with the mouse: click at some spot in the graph to place a \
2164 new breakpoint, drag an existing breakpoint to change its position, and click an existing breakpoint to delete it. \
2165 The Undo and Redo buttons can be used to move around in the list of envelope edits; the current state \
2166 of the envelope can be saved with the 'save' button. Envelopes can be defined via " define_envelope_name ": \
2167 \n\n" define_envelope_example "\n\n\
2168 defines two envelopes that can be used in Snd wherever an envelope is needed.  You can also define \
2169 a new envelope in the dialog's text field; " ramp_envelope_example " creates a ramp as a new envelope. \
2170 \n\n\
2171 In the overall view of envelopes, click an envelope, or click its name in the scrolled \
2172 list on the left to select it; click the selected envelope to load it into the editor portion, clearing out whatever \
2173 was previously there.  To load an existing envelope into the editor, you can also type its name in the text field; to give a name to \
2174 the envelope as it is currently defined in the graph viewer, type its name in this field, then \
2175 either push return or the 'save' button. \
2176 \n\n\
2177 Once you have an envelope in the editor, you can apply it to the current sounds with the 'Apply' or 'Undo&Apply' buttons; the latter \
2178 first tries to undo the previous edit, then applies the envelope. The envelope can be applied to \
2179 the amplitude, the spectrum, or the sampling rate. The choice is made via the three buttons marked 'amp', \
2180 'flt', and 'src'. The filter order is the variable " S_enved_filter_order " which defaults to 40. To use fft-filtering (convolution) \
2181 instead, click the 'fir' button, changing its label to 'fft'. If you are displaying the fft graph of the current channel, \
2182 and the fft is large enough to include the entire sound, and the 'wave' button is set, \
2183 the spectrum is also displayed in the envelope editor, making it easier to perform accurate (fussy?) filtering operations. \
2184 \n\n\
2185 To apply the envelope to the current selection, rather than the current sound, set the 'selection' button. \
2186 To apply it to the currently selected mix, set the 'mix' button. \
2187 control-click 'mix' to load the current mix amplitude \
2188 envelope into the editor. \
2189 \n\n\
2190 The two toggle buttons at the lower right choose whether to show a light-colored version of \
2191 the currently active sound (the 'wave' button), and whether to clip mouse movement at the current y axis bounds (the \
2192 'clip' button).  The 'linear' and 'exp' buttons choose the type of connecting lines, and the 'exp base' slider at \
2193 the bottom sets the 'base' of the exponential curves, just as in CLM.  If the envelope is being treated as a spectrum ('flt' \
2194 is selected), the 'wave' button shows the actual frequency response of the filter that will be applied to the waveform \
2195 by the 'apply' buttons.  Increase the " S_enved_filter_order " to \
2196 improve the fit.  In this case, the X axis goes from 0 Hz to half the sampling rate, labelled as 1.0.",
2197 
2198 		      WITH_WORD_WRAP,
2199 		      snd_xrefs("Envelope"),
2200 		      snd_xref_urls("Envelope"));
2201 
2202 }
2203 
2204 
2205 /* ---------------- Transform Options ---------------- */
2206 
transform_dialog_help(void)2207 void transform_dialog_help(void)
2208 {
2209   snd_help_with_xrefs("Transform Options",
2210 
2211 "This dialog presents the various transform (fft) related choices. \
2212 \n\n\
2213 On the upper left is a list of available transform types; next on the right is a list of fft sizes;  \
2214 next is a panel of buttons that sets various display-oriented choices; the lower left panel \
2215 sets the current wavelet, when relevant; next is the fft data window choice; and next to it is a \
2216 graph of the current fft window; when the window has an associated parameter (sometimes known as \
2217 'alpha' or 'beta'), the slider beneath the window list is highlighted and can be used to choose the \
2218 desired member of that family of windows. The lower (second) scale sets the 'mu' parameter in the ultraspherical window. \
2219 \n\n\
2220 If the 'selection' button is not set, the FFT is taken from the start (the left edge) of the \
2221 current window and is updated as the window bounds change; otherwise the FFT is taken over the extent \
2222 of the selection, if any is active in the current channel.  The fft data is scaled to fit \
2223 between 0.0 and 1.0 unless the fft normalization is off. The full frequency axis is normally \
2224 displayed, but the axis is 'draggable' -- put the mouse on the axis and drag it either way to change \
2225 the range (this is equivalent to changing the variable " S_spectrum_end "). You can also click on \
2226 any point in the fft to get the associated fft data displayed; if " S_with_verbose_cursor " is on, you can \
2227 drag the mouse through the fft display and the description in the minibuffer will be constantly updated. \
2228 \n\n\
2229 The harmonic analysis function is normally the Fourier Transform, but others are available, \
2230 including about 20 wavelet choices. \
2231 \n\n\
2232 The top three buttons in the transform dialog choose between a normal fft, a sonogram, or a \
2233 spectrogram. The 'peaks' button affects whether peak info is displayed alongside the graph of the \
2234 spectrum. The 'dB' button selects between a linear and logarithmic Y (magnitude) axis. The 'log freq' \
2235 button makes a similar choice along the frequency axis. \
2236 \n\n\
2237 If you choose dB, the fft window graph (in the lower right) displays the window spectrum in dB, \
2238 but the y axis labelling is still 0.0 to 1.0 to reflect the fact that the fft window itself is still displayed \
2239 in linear terms.",
2240 		      WITH_WORD_WRAP,
2241 		      snd_xrefs("FFT"),
2242 		      snd_xref_urls("FFT"));
2243 }
2244 
2245 
2246 /* ---------------- Color/Orientation Dialog ---------------- */
2247 
2248 static const char *color_orientation_dialog_xrefs[11] = {
2249   "colormap variable: {colormap}",
2250   "orientation variables: {" S_spectro_x_scale "}, {" S_spectro_x_angle "}, etc",
2251   "colormap constants: rgb." Xen_file_extension,
2252   "colormap colors: {" S_colormap_ref "}",
2253   "color dialog variables: {" S_color_cutoff "}, {" S_color_inverted "}, {" S_color_scale "}",
2254   "start the color/orientation dialog: {" S_color_orientation_dialog "}",
2255   "add a new colormap: {" S_add_colormap "}",
2256   "remove a colormap: {" S_delete_colormap "}",
2257   "specialize orientation dialog actions: {" S_orientation_hook "}",
2258   "specialize color dialog actions: {" S_color_hook "}",
2259   NULL};
2260 
color_orientation_dialog_help(void)2261 void color_orientation_dialog_help(void)
2262 {
2263   snd_help_with_xrefs("View Color/Orientation",
2264 
2265 "This dialog sets the viewing projection (\"orientation\"), colormap and associated settings used during sonogram, spectrogram,  \
2266 and wavogram display. The cutoff scale refers to the minimum data value to be displayed. \
2267 You can add your own colormaps to the list via " S_add_colormap ", or delete one with " S_delete_colormap ".\
2268 The 'angle' scalers change the viewing angle, the 'scale' scalers change the 'stretch' amount \
2269 along a given axis, and 'hop' refers to the density of the traces (the jump in samples between successive \
2270 ffts or time domain scans).  If the 'use openGL' button is set, the \
2271 spectrogram is drawn by openGL.  In the spectrogram, 'x' refers to the time axis, \
2272 'y' to the amplitude axis, and 'z' to the frequency axis.",
2273 		      WITH_WORD_WRAP,
2274 		      color_orientation_dialog_xrefs,
2275 		      NULL);
2276 }
2277 
2278 
2279 
2280 
2281 /* ---------------- Region Dialog ---------------- */
2282 
region_dialog_help(void)2283 void region_dialog_help(void)
2284 {
2285   snd_help_with_xrefs("Region Browser",
2286 
2287 "This is the 'region browser'.  The scrolled window contains the list of current regions \
2288 with a brief title to indicate the source thereof, and a button to play the region. \
2289 One channel of the currently selected region is displayed in the graph window.  The up and \
2290 down arrows move up or down in the region's list of channels.  If you click a region's \
2291 title, that region is displayed in the graph area.  The 'print' button produces a Postscript \
2292 rendition of the current graph contents, using the default eps output name. 'play' plays the region.  \
2293 The 'edit' button loads the region into the main editor as a temporary file.  It can be edited or renamed, etc.  If you save \
2294 the file, the region is updated to reflect any edits you made.",
2295 		      WITH_WORD_WRAP,
2296 		      snd_xrefs("Region"),
2297 		      snd_xref_urls("Region"));
2298 }
2299 
2300 
2301 /* ---------------- Raw Sound Dialog ---------------- */
2302 
2303 static const char *raw_xrefs[7] = {
2304   "specialize handing of raw sounds: {" S_open_raw_sound_hook "}",
2305   "open a headerless sound: {" S_open_raw_sound "}",
2306   "header type constants: {" S_mus_header_type_name "}",
2307   "sample type constants: {" S_mus_sample_type_name "}",
2308   "what are these sample types?",
2309   "what are these headers?",
2310   NULL};
2311 
2312 static const char *raw_urls[7] = {
2313   NULL, NULL, NULL, NULL,
2314   "extsnd.html#sampletype",
2315   "extsnd.html#headertype",
2316   NULL
2317 };
2318 
2319 
raw_data_dialog_help(const char * info)2320 void raw_data_dialog_help(const char *info)
2321 {
2322   if (info)
2323     {
2324       snd_help_with_xrefs("Raw Data",
2325 "This file seems to have an invalid header.  I'll append what sense I can make of it. \
2326 To display and edit sound data, Snd needs to know how the data's sampling rate, number \
2327 of channels, and sample type.\n\n",
2328 			  WITH_WORD_WRAP,
2329 			  raw_xrefs,
2330 			  raw_urls);
2331       snd_help_append(info);
2332     }
2333   else
2334     {
2335       snd_help_with_xrefs("Raw Data",
2336 "To display and edit sound data, Snd needs to know how the data's sampling rate, number \
2337 of channels, and sample type.  This dialog gives you a chance to set those fields. \
2338 To use the defaults, click the 'Reset' button.",
2339 			  WITH_WORD_WRAP,
2340 			  raw_xrefs,
2341 			  NULL);
2342     }
2343 }
2344 
2345 
2346 /* ---------------- Save as Dialog ---------------- */
2347 
save_as_dialog_help(void)2348 void save_as_dialog_help(void)
2349 {
2350   snd_help_with_xrefs("Save As",
2351 
2352 "You can save the current state of a file with File:Save As, or the current selection with Edit:Save as. \
2353 The output header type, sample type, sampling rate, and comment can also be set.  If the 'src' button \
2354 is not set, setting the srate does not affect the data -- it is just a number placed in the sound file header. \
2355 Otherwise, if the output sampling rate differs from the current one, the data is converted to the new rate automatically. \
2356 The notation \"(be)\" in the sample type lists stands for big endian; similarly, \"(le)\" is little endian.\
2357 If a file by the chosen name already exists \
2358 it is overwritten, unless that file is already open in Snd and has edits.  In that case,  \
2359 you'll be asked what to do.  If you want to be warned whenever a file is about to be overwritten by this \
2360 option, set the variable " S_ask_before_overwrite " to " PROC_TRUE ". \
2361 If you give the current file name to Save As,  \
2362 any current edits will be saved and the current version in Snd will be updated (that is, in this \
2363 case, the edit tree is not preserved).  To save (extract) just one channel of a multichannel file, \
2364 put the (0-based) channel number in the 'extract channel' field, then click 'Extract', rather \
2365 than 'Save'.",
2366 		      WITH_WORD_WRAP,
2367 		      snd_xrefs("Save"),
2368 		      snd_xref_urls("Save"));
2369 }
2370 
2371 
2372 /* ---------------- Open File ---------------- */
2373 
2374 static const char *open_file_xrefs[] = {
2375   "open file: {" S_open_sound "}",
2376   "add to sound file extension list (for '" S_just_sounds "'): {" S_add_sound_file_extension "}",
2377   "specialize open: {" S_open_hook "}, {" S_after_open_hook "}, etc",
2378   "start the file dialog: {" S_open_file_dialog "}",
2379 #if HAVE_SCHEME
2380   "specialize file list: {install-searcher} in snd-motif.scm",
2381   "keep dialog active after opening: {keep-file-dialog-open-upon-ok} in snd-motif.scm",
2382 #endif
2383   NULL};
2384 
open_file_dialog_help(void)2385 void open_file_dialog_help(void)
2386 {
2387 #if USE_MOTIF
2388   snd_help_with_xrefs("Open File",
2389 
2390 "The file selection dialog is slightly different from the Motif default.  If you single click \
2391 in the directory list, that directory is immediately opened and displayed.  Also there are \
2392 two popup menus to set the \
2393 current sort routine (right click over the file list), and jump to a higher level directory (right click \
2394 in the directory list). \
2395 The 'sound files only' button filters out all non-sound files from the files list (using the \
2396 extension -- you can add to the list of sound file extensions via " S_add_sound_file_extension ". \
2397 When a sound file is selected, information about it is posted under the lists, and a 'play' \
2398 button is displayed.  The name field has <TAB> completion, of course, and also \
2399 watches as you type a new name, reflecting that partial name by moving the file list to \
2400 display possible matches.",
2401 		      WITH_WORD_WRAP,
2402 		      open_file_xrefs,
2403 		      NULL);
2404 #else
2405   snd_help_with_xrefs("Open File",
2406 		      "This dialog provides a clumsy way to open a file.",
2407 		      WITH_WORD_WRAP,
2408 		      open_file_xrefs,
2409 		      NULL);
2410 #endif
2411 }
2412 
2413 
2414 /* ---------------- Mix File ---------------- */
2415 
mix_file_dialog_help(void)2416 void mix_file_dialog_help(void)
2417 {
2418   snd_help_with_xrefs("Mix File",
2419 
2420 "The file will be mixed (added into the selected sound) at the cursor. If you click the 'Sound Files Only' button, \
2421 only those files in the current directory that look vaguely like sound files will be displayed.  For more control \
2422 of the initial mix, use the View:Files dialog.  To edit the mix, use the View:Mixes dialog.",
2423 
2424 		      WITH_WORD_WRAP,
2425 		      snd_xrefs("Mix"),
2426 		      snd_xref_urls("Mix"));
2427 }
2428 
2429 
2430 /* ---------------- Insert File ---------------- */
2431 
insert_file_dialog_help(void)2432 void insert_file_dialog_help(void)
2433 {
2434   snd_help_with_xrefs("Insert File",
2435 
2436 "The file will be inserted (pasted into the selected file) at the cursor. If you click the 'Sound Files Only' button, \
2437 only those files in the current directory that look vaguely like sound files will be displayed.  For more control \
2438 of the insertion, use the View:Files dialog.",
2439 
2440 		      WITH_WORD_WRAP,
2441 		      snd_xrefs("Insert"),
2442 		      snd_xref_urls("Insert"));
2443 }
2444 
2445 
2446 /* ---------------- Find Dialog ---------------- */
2447 
find_dialog_help(void)2448 void find_dialog_help(void)
2449 {
2450   #if HAVE_SCHEME
2451     #define find_example "(lambda (n) (> n .1))"
2452     #define zero_plus "zero+"
2453     #define closure_example "  (define (zero+)\n  (let ((lastn 0.0))\n      (lambda (n)\n        (let ((rtn (and (< lastn 0.0) (>= n 0.0) -1)))\n          (set! lastn n)\n          rtn)))"
2454   #endif
2455   #if HAVE_RUBY
2456     #define find_example "lambda do |y| y > 0.1 end"
2457     #define zero_plus "zero_plus"
2458     #define closure_example "def zero_plus\n  lastn = 0.0\n  lambda do |n|\n    rtn = lastn < 0.0 and n >= 0.0 and -1\n    lastn = n\n    rtn\n  end\nend"
2459   #endif
2460   #if HAVE_FORTH
2461     #define find_example "lambda: <{ y }> 0.1 y f< ;"
2462     #define zero_plus "zero+"
2463     #define closure_example ": zero+ ( -- prc; n self -- val )\n  lambda-create 0.0 ( lastn ) , latestxt 1 make-proc\n does> { n self -- val }\n  self @ ( lastn ) f0<  n f0>= &&  -1 && { rtn }\n  n self ! ( lastn = n )\n  rtn\n;"
2464   #endif
2465 
2466   snd_help_with_xrefs("Search all sounds",
2467 
2468 #if HAVE_EXTENSION_LANGUAGE
2469 "This search travels through all the current channels in parallel until a match is found.  The find \
2470 expression is a function of one argument, the current sample value.  It is evaluated on each sample, and should return " PROC_TRUE " when the \
2471 search is satisfied.  For example, \n\n  " find_example "\n\nlooks for the next sample that is greater than .1. \
2472 If you need to compare the current sample with a previous one, use a 'closure' as in " zero_plus " in \
2473 examp." Xen_file_extension ": \n\n" closure_example "\n\nThere are several other \
2474 search function examples in that file that search for peaks, clicks, or a particular pitch.",
2475 #else
2476 "This search mechanism is built on the extension language, which isn't available in this version of Snd.",
2477 #endif
2478 
2479 		      WITH_WORD_WRAP,
2480 		      snd_xrefs(I_FIND),
2481 		      snd_xref_urls(I_FIND));
2482 }
2483 
2484 
2485 /* ---------------- Mix Dialog ---------------- */
2486 
mix_dialog_help(void)2487 void mix_dialog_help(void)
2488 {
2489   #if HAVE_EXTENSION_LANGUAGE
2490     #define mix_dialog_mix_help "The function " S_mix_dialog_mix " gives (or sets) the currently displayed mix's id. "
2491   #else
2492     #define mix_dialog_mix_help ""
2493   #endif
2494   snd_help_with_xrefs("Mixes",
2495 
2496 "This dialog provides various commonly-used controls on the currently \
2497 chosen mix.  At the top are the mix id, begin and end times, \
2498 and a play button.  " mix_dialog_mix_help "Beneath that are various sliders \
2499 controlling the speed (sampling rate) and amplitude of the mix, \
2500 and finally, an envelope editor for the mix. \
2501 The current mix amp env is not actually changed until you click 'Apply Env'.\
2502 The editor envelope is drawn in black with dots whereas the current \
2503 mix amp env (if any) is drawn in blue.",
2504 
2505 		      WITH_WORD_WRAP,
2506 		      snd_xrefs("Mix"),
2507 		      snd_xref_urls("Mix"));
2508 }
2509 
2510 
2511 /* ---------------- New File ---------------- */
2512 
2513 static const char *new_file_xrefs[5] = {
2514   "open a new sound: {" S_new_sound "}",
2515   "specialize making a new sound: {" S_new_sound_hook "}",
2516   "header type constants: {" S_mus_header_type_name "}",
2517   "sample type constants: {" S_mus_sample_type_name "}",
2518   NULL};
2519 
new_file_dialog_help(void)2520 void new_file_dialog_help(void)
2521 {
2522   snd_help_with_xrefs("New File",
2523 
2524 #if HAVE_EXTENSION_LANGUAGE
2525 "This dialog sets the new file's output header type, sample type, srate, chans, and comment. \
2526 The 'srate:' and 'channels:' labels are actually drop-down menus providing quick access to common choices. \
2527 The default values for the fields can be set by clicking 'Reset'.  These values \
2528 are " S_default_output_chans ", " S_default_output_sample_type ", " S_default_output_srate ", and " S_default_output_header_type ".  \
2529 The notation \"(be)\" in the sample type lists stands for big endian; similarly, \"(le)\" is little endian.\
2530 Click 'Ok' to open the new sound. The actual new file representing the new sound is not written \
2531 until you save the new sound.",
2532 #else
2533 "This dialog sets the new file's output header type, sample type, srate, chans, and comment. \
2534 The 'srate:' and 'channels:' labels are actually drop-down menus providing quick access to common choices. \
2535 The default values for the fields can be set by clicking 'Reset'. Click 'Ok' to open the new sound. \
2536 The actual new file representing the new sound is not written \
2537 until you save the new sound.",
2538 #endif
2539 		      WITH_WORD_WRAP,
2540 		      new_file_xrefs,
2541 		      NULL);
2542 }
2543 
2544 
2545 /* ---------------- Edit Header ---------------- */
2546 
2547 static const char *edit_header_xrefs[11] = {
2548   "change srate: {" S_src_channel "}",
2549   "convert data to a new sample type: {" S_save_sound_as "}",
2550   "interpret current data in new sample type: {" S_sample_type "}",
2551   "convert header to a new type: {" S_save_sound_as "}",
2552   "interpret current header differently: {" S_header_type "}",
2553   "extract or combine chans: {mono->stereo}",
2554   "change data location: {" S_data_location "}",
2555   "change number of samples: {framples}",
2556   "what are these sample types?",
2557   "what are these headers?",
2558   NULL
2559 };
2560 
2561 static const char *edit_header_urls[11] = {
2562   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2563   "extsnd.html#sampletype",
2564   "extsnd.html#headertype",
2565   NULL
2566 };
2567 
2568 
edit_header_dialog_help(void)2569 void edit_header_dialog_help(void)
2570 {
2571   snd_help_with_xrefs("Edit Header",
2572 
2573 "This dialog edits the header of a sound file; no change is made to the actual sound data. \
2574 The notation \"(be)\" in the sample type lists stands for big endian; similarly, \"(le)\" is little endian.\
2575 If you specify 'raw' as the type, any existing header is removed.  This dialog is aimed at adding or removing an entire header,  \
2576 or editing the header comments; anything else is obviously dangerous.",
2577 
2578 		      WITH_WORD_WRAP,
2579 		      edit_header_xrefs,
2580 		      edit_header_urls);
2581 }
2582 
2583 
2584 /* ---------------- Print Dialog ---------------- */
2585 
2586 static const char *print_xrefs[4] = {
2587   "default eps file name: {" S_eps_file "}",
2588   "eps overall size: {" S_eps_size "}",
2589   "eps margins: {" S_eps_bottom_margin "}, {" S_eps_left_margin "}",
2590   NULL};
2591 
print_dialog_help(void)2592 void print_dialog_help(void)
2593 {
2594   snd_help_with_xrefs("File Print",
2595 
2596 #if HAVE_EXTENSION_LANGUAGE
2597 "Print causes the currently active graph(s) to be printed (via the lpr command) or saved as \
2598 a Postscript file.  In the latter case, the file name is set either by the dialog, or taken from the \
2599 variable " S_eps_file " (normally \"snd.eps\").  The functions that refer to this dialog are:\n\
2600 \n\
2601   " S_print_dialog " (:optional managed print): start the print dialog\n\
2602   " S_eps_file ": eps file name (\"snd.eps\")\n\
2603   " S_eps_bottom_margin ": bottom margin (0.0)\n\
2604   " S_eps_left_margin ": left margin (0.0)\n\
2605   " S_eps_size ": overall eps size scaler (1.0)\n\
2606   " S_graph_to_ps " (:optional file): write current graph to eps file\n\n\
2607 For openGL graphics, use " S_gl_graph_to_ps ".\n",
2608 #else
2609 "Print causes the currently active display to be either printed (via the lpr command) or saved as \
2610 an eps file.  Currently the openGL graphics can't be printed by Snd, \
2611 but you can use Gimp or some such program to get a screenshot, and print that.",
2612 #endif
2613 
2614 		      WITH_WORD_WRAP,
2615 		      print_xrefs,
2616 		      NULL);
2617 }
2618 
2619 /* ---------------- View Files ---------------- */
2620 
2621 static const char *view_files_xrefs[5] = {
2622   "place sound in view files list: {" S_add_file_to_view_files_list "}",
2623   "place all sounds from a directory in view files list: {" S_add_directory_to_view_files_list "}",
2624   "specialize view files selection: {" S_view_files_select_hook "}",
2625   "the sort choice: {" S_view_files_sort "}",
2626   NULL};
2627 
view_files_dialog_help(void)2628 void view_files_dialog_help(void)
2629 {
2630   snd_help_with_xrefs("File Browser",
2631 #if USE_MOTIF
2632 "The View:Files dialog provides a list of sounds and various things to do with them.\
2633 The play button plays the file. \
2634 Double click a file name, and that file is opened in Snd.  You can also mix or insert the \
2635 selected file with amplitude envelopes and so on. \
2636 \n\n\
2637 Files can be added to the list via the -p startup switch, and by the functions " S_add_file_to_view_files_list " \
2638 and " S_add_directory_to_view_files_list ". \
2639 \n\n\
2640 The 'sort' label on the right activates a menu of sorting choices; 'name' sorts the \
2641 files list alphabetically, 'date' sorts by date written, and 'size' sorts by the \
2642 number of samples in the sound. The variable " S_view_files_sort " refers to this menu. \
2643 The functions that refer to this dialog are: \n\
2644 \n\
2645   " S_view_files_dialog " (:optional managed): start this dialog\n\
2646   " S_add_directory_to_view_files_list "  (dir): add directory's files to list\n\
2647   " S_add_file_to_view_files_list " (file): add file to list\n\
2648   " S_view_files_files ": list of all files in dialog's file list\n\
2649   " S_view_files_selected_files ": list of currently selected files\n\
2650   " S_view_files_sort ": dialog's sort choice\n\
2651   " S_view_files_amp ": dialog's amp slider value\n\
2652   " S_view_files_amp_env ": dialog's amp env\n\
2653   " S_view_files_speed ": dialog's speed value\n\
2654   " S_view_files_speed_style ": dialog's speed style\n",
2655 #else
2656   "The View:Files dialog provides a list of files preloaded via the -p startup switch.",
2657 #endif
2658 		      WITH_WORD_WRAP,
2659 		      view_files_xrefs,
2660 		      NULL);
2661 }
2662 
2663 
2664 /* ---------------- help dialog special cases ---------------- */
2665 
copy_help(void)2666 static void copy_help(void)
2667 {
2668   snd_help_with_xrefs("Copy",
2669 		      "The copying functions are listed in the 'related topics' section. See also 'Save'",
2670 		      WITH_WORD_WRAP,
2671 		      snd_xrefs("Copy"),
2672 		      snd_xref_urls("Copy"));
2673 }
2674 
2675 
cursor_help(void)2676 static void cursor_help(void)
2677 {
2678   snd_help_with_xrefs("Cursor",
2679 "A big '+' marks the current sample.  This is Snd's cursor, and the \
2680 various cursor moving commands apply to it.  See also 'Tracking cursor'",
2681 		      WITH_WORD_WRAP,
2682 		      snd_xrefs("Cursor"),
2683 		      snd_xref_urls("Cursor"));
2684 }
2685 
2686 
tracking_cursor_help(void)2687 static void tracking_cursor_help(void)
2688 {
2689   snd_help_with_xrefs("Tracking cursor",
2690 "If you want the cursor to follow along more-or-less in time while \
2691 playing a sound, set " S_with_tracking_cursor " to " PROC_TRUE ". See also 'Cursor'",
2692 		      WITH_WORD_WRAP,
2693 		      snd_xrefs("Tracking cursor"),
2694 		      snd_xref_urls("Tracking cursor"));
2695 }
2696 
2697 
smooth_help(void)2698 static void smooth_help(void)
2699 {
2700   snd_help_with_xrefs("Smoothing",
2701 "Smoothing applies a sinusoidal curve to a portion of a sound to smooth \
2702 out any clicks.  See also 'Filter'",
2703 		      WITH_WORD_WRAP,
2704 		      snd_xrefs("Smooth"),
2705 		      snd_xref_urls("Smooth"));
2706 }
2707 
2708 
maxamp_help(void)2709 static void maxamp_help(void)
2710 {
2711   snd_help_with_xrefs("Maxamp",
2712 "Maxamp refers to the maximum amplitude in a sound",
2713 		      WITH_WORD_WRAP,
2714 		      snd_xrefs("Maxamp"),
2715 		      snd_xref_urls("Maxamp"));
2716 }
2717 
2718 
reverse_help(void)2719 static void reverse_help(void)
2720 {
2721   snd_help_with_xrefs("Reverse",
2722 "There are various things that can be reversed.  See the list below.",
2723 		      WITH_WORD_WRAP,
2724 		      snd_xrefs("Reverse"),
2725 		      snd_xref_urls("Reverse"));
2726 }
2727 
2728 
random_numbers_help(void)2729 static void random_numbers_help(void)
2730 {
2731   snd_help_with_xrefs("Random Numbers",
2732 		      "",
2733 		      WITH_WORD_WRAP,
2734 		      snd_xrefs("Random Numbers"),
2735 		      snd_xref_urls("Random Numbers"));
2736 }
2737 
2738 
2739 static const char *Wavogram_xrefs[] = {
2740   "wavogram picture",
2741   "{wavo-hop}",
2742   "{wavo-trace}",
2743   "time-graph-type: {graph-as-wavogram}",
2744   NULL};
2745 
2746 static const char *Wavogram_urls[] = {
2747   "snd.html#wavogram",
2748   "extsnd.html#wavohop",
2749   "extsnd.html#wavotrace",
2750   "extsnd#timegraphtype",
2751   NULL};
2752 
wavogram_help(void)2753 static void wavogram_help(void)
2754 {
2755   snd_help_with_xrefs("Wavogram",
2756 "The 'wavogram' is a 3-D view of the time-domain data.  If you set each trace length correctly, \
2757 you can often get periods to line up vertically, making a pretty picture.",
2758 		      WITH_WORD_WRAP,
2759 		      Wavogram_xrefs,
2760 		      Wavogram_urls);
2761 }
2762 
2763 
window_size_help(void)2764 static void window_size_help(void)
2765 {
2766   snd_help_with_xrefs("Window Size",
2767 		      "",
2768 		      WITH_WORD_WRAP,
2769 		      snd_xrefs("Window Size"),
2770 		      snd_xref_urls("Window Size"));
2771 }
2772 
2773 
2774 #if HAVE_SCHEME
2775   #define S_Vct "Float-vector"
2776 #else
2777   #define S_Vct "Vct"
2778 #endif
2779 
2780 #include "snd-xref.c"
2781 
2782 #define NUM_TOPICS 36
2783 static const char *topic_names[NUM_TOPICS] = {
2784   "Hook", S_Vct, "Sample reader", "Mark", "Mix", "Region", "Edit list", "Transform", "Error",
2785   "Color", "Font", "Graphic", "Widget", "Emacs",
2786   "CLM", "Instrument", "CM", "CMN", "Sndlib",
2787   "Motif", "Script", "Ruby", "s7", "LADSPA", "OpenGL", "Gdb", "Control panel",
2788   "X resources", "Invocation flags", "Initialization file", "Customization",
2789   "Window Size", "Color", "Random Number", "Wavogram",
2790   "Forth"
2791 };
2792 
2793 static const char *topic_urls[NUM_TOPICS] = {
2794   "extsnd.html#sndhooks", "extsnd.html#Vcts", "extsnd.html#samplers", "extsnd.html#sndmarks",
2795   "extsnd.html#sndmixes", "extsnd.html#sndregions", "extsnd.html#editlists", "extsnd.html#sndtransforms",
2796   "extsnd.html#snderrors", "extsnd.html#colors", "extsnd.html#fonts", "extsnd.html#sndgraphics",
2797   "extsnd.html#sndwidgets", "grfsnd.html#emacssnd", "grfsnd.html#sndwithclm",
2798   "grfsnd.html#sndinstruments", "grfsnd.html#sndwithcm", "sndscm.html#musglyphs",
2799   "sndlib.html#introduction", "grfsnd.html#sndwithmotif",
2800   "grfsnd.html#sndwithnogui", "grfsnd.html#sndandruby", "grfsnd.html#sndands7",
2801   "grfsnd.html#sndandladspa",
2802   "grfsnd.html#sndandgl", "grfsnd.html#sndandgdb", "extsnd.html#customcontrols",
2803   "grfsnd.html#sndresources", "grfsnd.html#sndswitches", "grfsnd.html#sndinitfile", "extsnd.html#extsndcontents",
2804   "extsnd.html#movingwindows", "extsnd.html#colors", "sndscm.html#allrandomnumbers",
2805   "snd.html#wavogram", "grfsnd.html#sndandforth"
2806 };
2807 
min_strlen(const char * a,const char * b)2808 static int min_strlen(const char *a, const char *b)
2809 {
2810   int lena, lenb;
2811   lena = strlen(a);
2812   lenb = strlen(b);
2813   if (lena < lenb) return(lena);
2814   return(lenb);
2815 }
2816 
2817 
topic_url(const char * topic)2818 static const char *topic_url(const char *topic)
2819 {
2820   int i;
2821   for (i = 0; i < NUM_TOPICS; i++)
2822     if (STRNCMP(topic, topic_names[i], min_strlen(topic, topic_names[i])) == 0)
2823       return(topic_urls[i]);
2824   return(NULL);
2825 }
2826 
2827 #define NUM_XREFS 33
2828 static const char *xrefs[NUM_XREFS] = {
2829   "Mark", "Mix", "Region", "Selection", "Cursor", "Tracking cursor", "Delete", "Envelope", "Filter",
2830   "Search", "Insert", "Maxamp", "Play", "Reverse", "Save", "Smooth", "Resample", "FFT", "Reverb",
2831   "Src", I_FIND, "Undo", "Redo", "Sync", "Control panel", "Header", "Key", "Copy",
2832   "Window Size", "Color", "Control", "Random Numbers", "Wavogram"
2833 };
2834 
2835 static const char **xref_tables[NUM_XREFS] = {
2836   Marking_xrefs, Mixing_xrefs, Regions_xrefs, Selections_xrefs, Cursors_xrefs, Tracking_cursors_xrefs,
2837   Deletions_xrefs, Envelopes_xrefs, Filters_xrefs, Searching_xrefs, Insertions_xrefs, Maxamps_xrefs,
2838   Playing_xrefs, Reversing_xrefs, Saving_xrefs, Smoothing_xrefs, Resampling_xrefs, FFTs_xrefs, Reverb_xrefs,
2839   Resampling_xrefs, Searching_xrefs, Undo_and_Redo_xrefs, Undo_and_Redo_xrefs,
2840   sync_xrefs, control_xrefs, header_and_data_xrefs, key_xrefs, Copying_xrefs,
2841   Window_size_and_position_xrefs, Colors_xrefs, control_xrefs, Random_Numbers_xrefs,
2842   Wavogram_xrefs
2843 };
2844 
2845 static const char **xref_url_tables[NUM_XREFS] = {
2846   Marking_urls, Mixing_urls, Regions_urls, Selections_urls, Cursors_urls, Tracking_cursors_urls,
2847   Deletions_urls, Envelopes_urls, Filters_urls, Searching_urls, Insertions_urls, Maxamps_urls,
2848   Playing_urls, Reversing_urls, Saving_urls, Smoothing_urls, Resampling_urls, FFTs_urls, Reverb_urls,
2849   Resampling_urls, Searching_urls, Undo_and_Redo_urls, Undo_and_Redo_urls,
2850   NULL, NULL, NULL, NULL, Copying_urls,
2851   Window_size_and_position_urls, Colors_urls, NULL, Random_Numbers_urls,
2852   Wavogram_urls
2853 };
2854 
2855 typedef void (*help_func)(void);
2856 /* if an entry is null here, the main help window will display "(no help found)" */
2857 static help_func help_funcs[NUM_XREFS] = {
2858   &marks_help, &mix_help, &region_help, &selection_help, &cursor_help, &tracking_cursor_help,
2859   &delete_help, &env_help, &filter_help, &find_help, &insert_help, &maxamp_help,
2860   &play_help, &reverse_help, &save_help, &smooth_help, &resample_help, &fft_help, &reverb_help,
2861   &resample_help, &find_help, &undo_help, &undo_help,
2862   &sync_help, &controls_help, &sound_files_help, &key_help, &copy_help,
2863   &window_size_help, &colors_help, &controls_help, &random_numbers_help,
2864   &wavogram_help
2865 };
2866 
2867 
snd_xrefs(const char * topic)2868 static const char **snd_xrefs(const char *topic)
2869 {
2870   int i;
2871   for (i = 0; i < NUM_XREFS; i++)
2872     if (STRCMP(topic, xrefs[i]) == 0)
2873       return(xref_tables[i]);
2874   return(NULL);
2875 }
2876 
2877 
snd_xref_urls(const char * topic)2878 static const char **snd_xref_urls(const char *topic)
2879 {
2880   int i;
2881   for (i = 0; i < NUM_XREFS; i++)
2882     if (STRCMP(topic, xrefs[i]) == 0)
2883       return(xref_url_tables[i]);
2884   return(NULL);
2885 }
2886 
2887 
levenshtein(const char * s1,int l1,const char * s2,int l2)2888 static int levenshtein(const char *s1, int l1, const char *s2, int l2)
2889 {
2890   /* taken with bug fixes from "The Ruby Way" by Hal Fulton, SAMS Pubs
2891    */
2892   int i, j, val;
2893   int **distance;
2894 
2895   if (!s1) return(l2);
2896   if (!s2) return(l1);
2897 
2898   distance = (int **)calloc(l2 + 1, sizeof(int *));
2899   for (i = 0; i <= l2; i++) distance[i] = (int *)calloc(l1 + 1, sizeof(int));
2900   for (j = 0; j <= l1; j++) distance[0][j] = j;
2901   for (i = 0; i <= l2; i++) distance[i][0] = i;
2902   for (i = 1; i <= l2; i++)
2903     for (j = 1; j <= l1; j++)
2904       {
2905 	int c1, c2, c3;
2906 	c1 = distance[i][j - 1] + 1;
2907 	c2 = distance[i - 1][j] + 1;
2908 	c3 = distance[i - 1][j - 1] + ((s2[i - 1] == s1[j - 1]) ? 0 : 1);
2909 	if (c1 > c2) c1 = c2;
2910 	if (c1 > c3) c1 = c3;
2911 	distance[i][j] = c1;
2912       }
2913   val = distance[l2][l1];
2914   for (i = 0; i <= l2; i++) free(distance[i]);
2915   free(distance);
2916   return(val);
2917 }
2918 
2919 
help_name_to_url(const char * name)2920 static int help_name_to_url(const char *name)
2921 {
2922   /* trying to be fancy here just causes trouble -- this is not a function that needs to be fast! */
2923   int i;
2924   for (i = 0; i < HELP_NAMES_SIZE; i++)
2925     {
2926       int comp;
2927 #if HAVE_RUBY
2928       if (name[0] == '$')
2929 	comp = STRCMP(help_names[i], (const char *)(name + 1));
2930       else comp = STRCMP(help_names[i], name);
2931 #else
2932       comp = STRCMP(help_names[i], name);
2933 #endif
2934       if (comp == 0) return(i);
2935     }
2936   return(-1);
2937 }
2938 
2939 
snd_url(const char * name)2940 const char *snd_url(const char *name)
2941 {
2942 #if HAVE_EXTENSION_LANGUAGE
2943   /* (snd-url "save-sound-as") -> "extsnd.html#savesoundas" */
2944   int i;
2945   i = help_name_to_url(name);
2946   if (i >= 0) return(help_urls[i]);
2947 #endif
2948   return(NULL);
2949 }
2950 
2951 
call_grep(const char * defstr,const char * name,const char * endstr,const char * path,char * tempfile)2952 static char *call_grep(const char *defstr, const char *name, const char *endstr, const char *path, char *tempfile)
2953 {
2954   int err;
2955   char *command;
2956 #ifndef __sun
2957   /* Gnu fgrep: -s switch to fgrep = "silent", I guess (--no-messages) [OSX uses Gnu fgrep] */
2958   command = mus_format("grep -F -s \"%s%s%s\" %s/*." Xen_file_extension " --line-number > %s", defstr, name, endstr, path, tempfile);
2959 #else
2960   /* Sun fgrep: here -s means -q and --line-number prints an error message */
2961   command = mus_format("grep -F \"%s%s%s\" %s/*." Xen_file_extension " > %s", defstr, name, endstr, path, tempfile);
2962 #endif
2963   err = system(command);
2964   free(command);
2965   if (err != -1)                      /* no error, so I guess tempfile exists, but might be empty */
2966     return(file_to_string(tempfile)); /* NULL if nothing found */
2967   return(NULL);
2968 }
2969 
2970 
snd_finder(const char * name,bool got_help)2971 static char *snd_finder(const char *name, bool got_help)
2972 {
2973   /* desperation -- search *.scm/rb/fs then even *.html? for 'name' */
2974   const char *url = NULL;
2975   char *fgrep = NULL, *tempfile = NULL, *command = NULL;
2976   bool is_defined;
2977   int a_def = 0, dir_len = 0, i;
2978   Xen dirs = Xen_empty_list;
2979 
2980 #if HAVE_SCHEME || (!HAVE_EXTENSION_LANGUAGE)
2981   #define NUM_DEFINES 7
2982   #define TRAILER " "
2983   const char *defines[NUM_DEFINES] = {"(define (", "(define* (", "(define ", "(define+ (", "(defmacro ", "(defmacro* ", "(definstrument ("};
2984 #endif
2985 
2986 #if HAVE_RUBY
2987   #define NUM_DEFINES 2
2988   #define TRAILER ""
2989   const char *defines[NUM_DEFINES] = {"def ", "class "};
2990 #endif
2991 
2992 #if HAVE_FORTH
2993   #define NUM_DEFINES 3
2994   #define TRAILER ""
2995   const char *defines[NUM_DEFINES] = {": ", "instrument: ", "event: "};
2996 #endif
2997 
2998   if ((!name) || (mus_strlen(name) == 0)) return(NULL);
2999   is_defined = Xen_is_defined(name);
3000 
3001 #if HAVE_SCHEME
3002   {
3003     s7_pointer help;
3004     fgrep = mus_format("(*autoload* '%s)", name);
3005     help = s7_eval_c_string(s7, fgrep);
3006     free(fgrep);
3007     fgrep = NULL;
3008     if (help != Xen_false)
3009       {
3010 	command = mus_format("%s is defined in %s", name, s7_object_to_c_string(s7, help));
3011 	return(command);
3012       }
3013   }
3014 #endif
3015 
3016   url = snd_url(name);
3017   tempfile = snd_tempnam(); /* this will have a .snd extension */
3018   dirs = Xen_load_path;
3019   dir_len = Xen_list_length(dirs);
3020 
3021   for (i = 0; (!fgrep) && (i < dir_len); i++)
3022     {
3023       if (Xen_is_string(Xen_list_ref(dirs, i))) /* *load-path* might have garbage */
3024 	{
3025 	  const char *path;
3026 	  path = Xen_string_to_C_string(Xen_list_ref(dirs, i));
3027 	  if (!path) continue;
3028 
3029 	  for (a_def = 0; (!fgrep) && (a_def < NUM_DEFINES); a_def++)
3030 	    fgrep = call_grep(defines[a_def], name, TRAILER, path, tempfile);
3031 #if HAVE_SCHEME
3032 	  if (!fgrep)
3033 	    fgrep = call_grep("(define (", name, ")", path, tempfile);
3034 	  if (!fgrep)
3035 	    fgrep = call_grep("(define ", name, "\n", path, tempfile);
3036 #endif
3037 	}
3038     }
3039   snd_remove(tempfile, IGNORE_CACHE);
3040   free(tempfile);
3041 
3042   if (url)
3043     {
3044       if (fgrep)
3045 	command = mus_format("%s is %sdefined%s; it appears to be defined in:\n%sand documented at %s",
3046 			     name,
3047 			     (is_defined) ? "" : "not ",
3048 			     (is_defined && (!got_help)) ? ", but has no help string" : "",
3049 			     fgrep,
3050 			     url);
3051       else command = mus_format("%s is %sdefined%s; it is documented at %s",
3052 				name,
3053 				(is_defined) ? "" : "not ",
3054 				(is_defined && (!got_help)) ? ", but has no help string" : "",
3055 				url);
3056     }
3057   else
3058     {
3059       if (fgrep)
3060 	command = mus_format("%s is %sdefined%s; it appears to be defined in:\n%s",
3061 			     name,
3062 			     (is_defined) ? "" : "not ",
3063 			     (is_defined && (!got_help)) ? ", but has no help string" : "",
3064 			     fgrep);
3065       else command = NULL;
3066     }
3067   if (fgrep) free(fgrep); /* don't free url! */
3068   return(command);
3069 }
3070 
3071 
snd_topic_help(const char * topic)3072 bool snd_topic_help(const char *topic)
3073 {
3074   /* called only in snd-x|ghelp.c */
3075   int i, topic_len;
3076 
3077   for (i = 0; i < NUM_XREFS; i++)
3078     if (STRCMP(topic, xrefs[i]) == 0)
3079       {
3080 	if (help_funcs[i])
3081 	  {
3082 	    (*help_funcs[i])();
3083 	    return(true);
3084 	  }
3085       }
3086 
3087   topic_len = mus_strlen(topic);
3088   for (i = 0; i < NUM_XREFS; i++)
3089     {
3090       int xref_len, j, diff, min_len;
3091       const char *a, *b;
3092       xref_len = strlen(xrefs[i]);
3093       if (xref_len < topic_len)
3094 	{
3095 	  diff = topic_len - xref_len;
3096 	  min_len = xref_len;
3097 	  a = topic;
3098 	  b = xrefs[i];
3099 	}
3100       else
3101 	{
3102 	  diff = xref_len - topic_len;
3103 	  min_len = topic_len;
3104 	  a = xrefs[i];
3105 	  b = topic;
3106 	}
3107       for (j = 0; j < diff; j++)
3108 	if (STRNCMP((char *)(a + j), b, min_len) == 0)
3109 	  {
3110 	    if (help_funcs[i])
3111 	      {
3112 		(*help_funcs[i])();
3113 		return(true);
3114 	      }
3115 	  }
3116     }
3117 
3118   /* try respelling topic */
3119   {
3120     int min_diff = 1000, min_loc = 0, this_diff, topic_len;
3121     topic_len = mus_strlen(topic);
3122     for (i = 0; i < NUM_XREFS; i++)
3123       if (help_funcs[i])
3124 	{
3125 	  this_diff = levenshtein(topic, topic_len, xrefs[i], mus_strlen(xrefs[i]));
3126 	  if (this_diff < min_diff)
3127 	    {
3128 	      min_diff = this_diff;
3129 	      min_loc = i;
3130 	    }
3131 	}
3132     if (min_diff < snd_int_log2(topic_len)) /* was topic_len / 2, but this gives too much leeway for substitutions */
3133       {
3134 	(*help_funcs[min_loc])();
3135 	return(true);
3136       }
3137   }
3138 
3139   /* go searching for it */
3140   {
3141     char *str;
3142     str = snd_finder(topic, false);
3143     if (str)
3144       {
3145 	snd_help(topic, str, WITH_WORD_WRAP);
3146 	free(str);
3147 	return(true);
3148       }
3149   }
3150 
3151   return(false);
3152 }
3153 
3154 
strings_might_match(const char * a,const char * b,int len)3155 static bool strings_might_match(const char *a, const char *b, int len)
3156 {
3157   int i;
3158   for (i = 0; i < len; i++)
3159     {
3160       if (a[i] != b[i]) return(false);
3161 #if HAVE_RUBY
3162       if (a[i] == '_') return(true);
3163 #endif
3164 #if HAVE_SCHEME || HAVE_FORTH
3165       if (a[i] == '-') return(true);
3166 #endif
3167     }
3168   return(true);
3169 }
3170 
3171 
help_name_to_xrefs(const char * name)3172 const char **help_name_to_xrefs(const char *name)
3173 {
3174   const char **xrefs = NULL;
3175   int i, xref_ctr = 0, xrefs_size = 0, name_len;
3176 #if (!HAVE_EXTENSION_LANGUAGE)
3177   return(NULL);
3178 #endif
3179   name_len = strlen(name);
3180   for (i = 0; i < HELP_NAMES_SIZE; i++)
3181     if (name[0] == help_names[i][0])
3182       {
3183 	int cur_len;
3184 	cur_len = strlen(help_names[i]);
3185 	if (strings_might_match(name, help_names[i], (name_len < cur_len) ? name_len : cur_len))
3186 	  {
3187 	    if (xref_ctr >= (xrefs_size - 1)) /* need trailing NULL to mark end of table */
3188 	      {
3189 		xrefs_size += 8;
3190 		if (xref_ctr == 0)
3191 		  xrefs = (const char **)calloc(xrefs_size, sizeof(char *));
3192 		else
3193 		  {
3194 		    int k;
3195 		    xrefs = (const char **)realloc(xrefs, xrefs_size * sizeof(char *));
3196 		    for (k = xref_ctr; k < xrefs_size; k++) xrefs[k] = NULL;
3197 		  }
3198 	      }
3199 	    xrefs[xref_ctr++] = help_names[i];
3200 	  }
3201       }
3202   return(xrefs);
3203 }
3204 
3205 
word_wrap(const char * text,int widget_len)3206 char *word_wrap(const char *text, int widget_len)
3207 {
3208   char *new_text;
3209   int new_len, old_len, i, j, desired_len, line_start = 0;
3210 #if HAVE_RUBY
3211   bool move_paren = false;
3212   int in_paren = 0;
3213 #endif
3214 
3215   old_len = mus_strlen(text);
3216   new_len = old_len + 64;
3217   desired_len = (int)(widget_len * .8);
3218   new_text = (char *)calloc(new_len, sizeof(char));
3219   for (i = 0, j = 0; i < old_len; i++)
3220     if (text[i] == '\n')
3221       {
3222 	new_text[j++] = '\n';
3223 	line_start = j;
3224       }
3225     else
3226       {
3227 	if ((text[i] == ' ') &&
3228 	    (help_text_width(new_text, line_start, j) >= desired_len))
3229 	  {
3230 	    new_text[j++] = '\n';
3231 	    line_start = j;
3232 	  }
3233 	else
3234 	  {
3235 #if (!HAVE_RUBY)
3236 	    new_text[j++] = text[i];
3237 #else
3238 	    /* try to change the reported names to Ruby names */
3239 	    if (text[i] == '-')
3240 	      {
3241 		if ((i > 0) && (isalnum((int)(text[i - 1]))) && (i < old_len))
3242 		  {
3243 		    if (isalnum((int)(text[i + 1])))
3244 		      new_text[j++] = '_';
3245 		    else
3246 		      {
3247 			if (text[i + 1] == '>')
3248 			  {
3249 			    new_text[j++] = '2';
3250 			    i++;
3251 			  }
3252 			else new_text[j++] = text[i];
3253 		      }
3254 		  }
3255 		else new_text[j++] = text[i];
3256 	      }
3257 	    else
3258 	      {
3259 		if ((i < old_len) && (text[i] == '#'))
3260 		  {
3261 		    if (text[i + 1] == 'f')
3262 		      {
3263 			new_text[j++] = 'f';
3264 			new_text[j++] = 'a';
3265 			new_text[j++] = 'l';
3266 			new_text[j++] = 's';
3267 			new_text[j++] = 'e';
3268 			i++;
3269 		      }
3270 		    else
3271 		      {
3272 			if (text[i + 1] == 't')
3273 			  {
3274 			    new_text[j++] = 't';
3275 			    new_text[j++] = 'r';
3276 			    new_text[j++] = 'u';
3277 			    new_text[j++] = 'e';
3278 			    i++;
3279 			  }
3280 			else new_text[j++] = text[i];
3281 		      }
3282 		  }
3283 		else
3284 		  {
3285 		    if ((i == 0) && (text[i] == '('))
3286 		      {
3287 			move_paren = true;
3288 		      }
3289 		    else
3290 		      {
3291 			if ((move_paren) && (text[i] == ')'))
3292 			  {
3293 			    /* no args: use () */
3294 			    new_text[j++] = '(';
3295 			    new_text[j++] = ')';
3296 			    move_paren = false;
3297 			    in_paren = 0;
3298 			  }
3299 			else
3300 			  {
3301 			    if ((move_paren) && (text[i] == ' '))
3302 			      {
3303 				new_text[j++] = '(';
3304 				move_paren = false;
3305 				in_paren = 1;
3306 			      }
3307 			    else
3308 			      {
3309 				if (in_paren > 0)
3310 				  {
3311 				    if ((in_paren == 1) && (text[i] == ' '))
3312 				      {
3313 					new_text[j++] = ',';
3314 					new_text[j++] = ' ';
3315 				      }
3316 				    else
3317 				      {
3318 					if (text[i] == ')')
3319 					  in_paren--;
3320 					else
3321 					  if (text[i] == '(')
3322 					    in_paren++;
3323 					new_text[j++] = text[i];
3324 				      }
3325 				  }
3326 				else new_text[j++] = text[i];
3327 			      }}}}}
3328 #endif
3329 	  }
3330       }
3331   return(new_text);
3332 }
3333 
3334 
3335 #define DOC_DIRECTORIES 6
3336 static const char *doc_directories[DOC_DIRECTORIES] = {
3337   "/usr/share/doc/snd-" SND_VERSION,
3338   "/usr/share/doc/snd-" SND_MAJOR_VERSION,
3339   "/usr/local/share/doc/snd-" SND_VERSION,
3340   "/usr/local/share/doc/snd-" SND_MAJOR_VERSION,
3341   "/usr/doc/snd-" SND_MAJOR_VERSION,
3342   "/usr/share/docs/snd-" SND_VERSION
3343 };
3344 
3345 static const char *doc_files[DOC_DIRECTORIES] = {
3346   "/usr/share/doc/snd-" SND_VERSION "/snd.html",
3347   "/usr/share/doc/snd-" SND_MAJOR_VERSION "/snd.html",
3348   "/usr/local/share/doc/snd-" SND_VERSION "/snd.html",
3349   "/usr/local/share/doc/snd-" SND_MAJOR_VERSION "/snd.html",
3350   "/usr/doc/snd-" SND_MAJOR_VERSION "/snd.html",
3351   "/usr/share/docs/snd-" SND_VERSION "/snd.html"
3352 };
3353 
html_directory(void)3354 static const char *html_directory(void)
3355 {
3356   int i;
3357   if (mus_file_probe("snd.html"))
3358     return(mus_getcwd());
3359 
3360   if (html_dir(ss))
3361     {
3362       bool happy;
3363       int len;
3364       char *hd;
3365       len = mus_strlen(html_dir(ss)) + 16;
3366       hd = (char *)calloc(len, sizeof(char));
3367       snprintf(hd, len, "%s/snd.html", html_dir(ss));
3368       happy = mus_file_probe(hd);
3369       free(hd);
3370       if (happy) return(html_dir(ss));
3371     }
3372 
3373 #ifdef MUS_DEFAULT_DOC_DIR
3374   if (mus_file_probe(MUS_DEFAULT_DOC_DIR "/snd.html"))
3375     return(MUS_DEFAULT_DOC_DIR);
3376 #endif
3377 
3378   for (i = 0; i < DOC_DIRECTORIES; i++)
3379     if (mus_file_probe(doc_files[i])) return(doc_directories[i]);
3380 
3381   return(NULL);
3382 }
3383 
3384 
url_to_html_viewer(const char * url)3385 void url_to_html_viewer(const char *url)
3386 {
3387   const char *dir_path;
3388   dir_path = html_directory();
3389 
3390   if (dir_path)
3391     {
3392       char *program;
3393       program = html_program(ss);
3394       if (program)
3395 	{
3396 	  char *path;
3397 	  int len, err;
3398 	  len = strlen(dir_path) + strlen(url) + 256;
3399 	  path = (char *)calloc(len, sizeof(char));
3400 	  snprintf(path, len, "%s file:%s/%s &", program, dir_path, url);
3401 	  err = system(path);
3402 	  if (err == -1)
3403 	    fprintf(stderr, "can't start %s?", program);
3404 	  free(path);
3405 	}
3406     }
3407 }
3408 
3409 
name_to_html_viewer(const char * red_text)3410 void name_to_html_viewer(const char *red_text)
3411 {
3412   const char *url;
3413   url = snd_url(red_text);
3414   if (!url) url = topic_url(red_text);
3415   if (url)
3416     url_to_html_viewer(url);
3417 }
3418 
3419 
3420 static Xen output_comment_hook;
3421 
output_comment(file_info * hdr)3422 char *output_comment(file_info *hdr)
3423 {
3424   Xen hook;
3425   hook = output_comment_hook;
3426   if (Xen_hook_has_list(hook))
3427     {
3428       Xen result;
3429       result = C_string_to_Xen_string((hdr) ? hdr->comment : NULL);
3430 
3431 #if HAVE_SCHEME
3432       result = s7_call(s7, hook, s7_cons(s7, result, s7_nil(s7)));
3433 #else
3434       {
3435 	Xen procs;
3436 	procs = Xen_hook_list(hook);
3437 	while (!Xen_is_null(procs))
3438 	  {
3439 	    result = Xen_call_with_1_arg(Xen_car(procs), result, S_output_comment_hook);
3440 	    procs = Xen_cdr(procs);
3441 	  }
3442       }
3443 #endif
3444       if (Xen_is_string(result))
3445 	return(mus_strdup(Xen_string_to_C_string(result)));
3446     }
3447   return(mus_strdup((hdr) ? hdr->comment : NULL));
3448 }
3449 
3450 
3451 static Xen help_hook;
3452 
g_snd_help_with_search(Xen text,int widget_wid,bool search)3453 Xen g_snd_help_with_search(Xen text, int widget_wid, bool search)
3454 {
3455   /* snd-help but no search for misspelled name if search=false */
3456 
3457   #if HAVE_SCHEME
3458     #define snd_help_example "(snd-help 'make-float-vector)"
3459     #define snd_help_arg_type "can be a string, symbol, or in some cases, the object itself"
3460   #endif
3461   #if HAVE_RUBY
3462     #define snd_help_example "snd_help(\"make_vct\")"
3463     #define snd_help_arg_type "can be a string or a symbol"
3464   #endif
3465   #if HAVE_FORTH
3466     #define snd_help_example "\"make-vct\" snd-help"
3467     #define snd_help_arg_type "is a string"
3468   #endif
3469 
3470   #define H_snd_help "(" S_snd_help " :optional (arg 'snd-help) (formatted " PROC_TRUE ")): return the documentation \
3471 associated with its argument. " snd_help_example " for example, prints out a brief description of make-" S_vct ". \
3472 The argument " snd_help_arg_type ". \
3473 In the help descriptions, optional arguments are in parens with the default value (if any) as the second entry. \
3474 A ':' as the start of the argument name marks a CLM-style optional keyword argument.  If you load index." Xen_file_extension " \
3475 the functions html and ? can be used in place of help to go to the HTML description, \
3476 and the location of the associated C code will be displayed, if it can be found. \
3477 If " S_help_hook " is not empty, it is invoked with the subject and the snd-help result \
3478 and its value is returned."
3479 
3480   char *str = NULL, *subject = NULL;
3481 
3482   if (Xen_is_keyword(text))
3483     return(C_string_to_Xen_string("keyword"));
3484 
3485 #if HAVE_RUBY
3486   if (Xen_is_string(text))
3487     subject = Xen_string_to_C_string(text);
3488   else
3489     if ((Xen_is_symbol(text)) && (Xen_is_bound(text)))
3490       {
3491 	text = XEN_SYMBOL_TO_STRING(text);
3492 	subject = Xen_string_to_C_string(text);
3493       }
3494     else
3495       {
3496 	char *temp;
3497 	temp = xen_scheme_procedure_to_ruby(S_snd_help);
3498 	text = C_string_to_Xen_string(temp);
3499 	if (temp) free(temp);
3500       }
3501   str = Xen_object_to_C_string(Xen_documentation(text));
3502 #endif
3503 
3504 #if HAVE_FORTH
3505   if (Xen_is_string(text))	/* "play" snd-help */
3506     subject = Xen_string_to_C_string(text);
3507   else if (!Xen_is_bound(text)) /* snd-help play */
3508     {
3509       subject = fth_parse_word();
3510       text = C_string_to_Xen_string(subject);
3511     }
3512   if (!subject)
3513     {
3514       subject = S_snd_help;
3515       text = C_string_to_Xen_string(S_snd_help);
3516     }
3517   str = Xen_object_to_C_string(Xen_documentation(text));
3518 #endif
3519 
3520 #if HAVE_SCHEME
3521   {
3522     Xen sym = Xen_false;
3523     if (Xen_is_string(text))
3524       {
3525 	subject = (char *)Xen_string_to_C_string(text);
3526 	sym = s7_name_to_value(s7, subject);
3527       }
3528     else
3529       {
3530 	if (Xen_is_symbol(text))
3531 	  {
3532 	    subject = (char *)Xen_symbol_to_C_string(text);
3533 	    str = (char *)s7_help(s7, text);
3534 	    sym = s7_symbol_value(s7, text);
3535 	  }
3536 	else
3537 	  {
3538 	    sym = text;
3539 	  }
3540       }
3541 
3542     if (!str) str = (char *)s7_help(s7, sym);
3543 
3544     if ((!str) ||
3545 	(mus_strlen(str) == 0))
3546       {
3547 	if (Xen_is_procedure(sym))
3548 	  {
3549 	    str = (char *)s7_documentation(s7, sym);
3550 
3551 	    if (((!str) ||
3552 		 (mus_strlen(str) == 0)) &&
3553 		(s7_funclet(s7, sym) != sym))
3554 	      {
3555 		s7_pointer e;
3556 		e = s7_funclet(s7, sym);
3557 		str = (char *)calloc(256, sizeof(char));
3558 		/* unavoidable memleak I guess -- we could use a backup statically allocated buffer here */
3559 		if (s7_is_null(s7, e))
3560 		  snprintf(str, 256, "this function appears to come from eval or eval-string?");
3561 		else
3562 		  {
3563 		    s7_pointer x;
3564 		    /* (cdr (assoc '__func__ (let->list (funclet func)))) => (name file line) or name */
3565 		    x = s7_assoc(s7, s7_make_symbol(s7, "__func__"), s7_let_to_list(s7, e));
3566 		    if (s7_is_pair(x))
3567 		      {
3568 			x = s7_cdr(x);
3569 			if (s7_is_pair(x))
3570 			  {
3571 			    const char *url;
3572 			    subject = (char *)s7_symbol_name(s7_car(x));
3573 			    url = snd_url(subject);
3574 			    if (url)
3575 			      snprintf(str, 256, "%s is defined at line %" print_mus_long " of %s, and documented at %s",
3576 				       subject,
3577 				       (int64_t)s7_integer(s7_car(s7_cdr(s7_cdr(x)))),
3578 				       s7_string(s7_car(s7_cdr(x))),
3579 				       url);
3580 			    else
3581 			      snprintf(str, 256, "%s is defined at line %" print_mus_long " of %s",
3582 				       subject,
3583 				       (int64_t)s7_integer(s7_car(s7_cdr(s7_cdr(x)))),
3584 				       s7_string(s7_car(s7_cdr(x))));
3585 			  }
3586 		      }
3587 		  }
3588 	      }
3589 	  }
3590 	else
3591 	  {
3592 	    str = (char *)s7_documentation(s7, s7_make_symbol(s7, subject));
3593 	  }
3594       }
3595   }
3596 #endif
3597 
3598   if (search)
3599     {
3600       bool need_free = false;
3601 
3602       if ((!str) ||
3603 	  (mus_strlen(str) == 0) ||
3604 	  (strcmp(str, PROC_FALSE) == 0)) /* Ruby returns "false" here */
3605 	{
3606 	  if ((!subject) || (mus_strlen(subject) == 0))
3607 	    return(Xen_false);
3608 	  str = snd_finder(subject, false);
3609 	  need_free = true;
3610 	}
3611       if (str)
3612 	{
3613 	  Xen help_text = Xen_false;  /* so that we can free "str" */
3614 	  char *new_str = NULL;
3615 	  if (subject)
3616 	    {
3617 	      Xen hook;
3618 	      hook = help_hook;
3619 	      if (Xen_hook_has_list(hook))
3620 		{
3621 		  Xen result, subj;
3622 
3623 		  result = C_string_to_Xen_string(str);
3624 		  subj = C_string_to_Xen_string(subject);
3625 
3626 #if HAVE_SCHEME
3627 		  result = s7_call(s7, hook, s7_cons(s7, subj, s7_cons(s7, result, s7_nil(s7))));
3628 #else
3629 		  {
3630 		    Xen procs;
3631 		    procs = Xen_hook_list(hook);
3632 		    while (!Xen_is_null(procs))
3633 		      {
3634 			result = Xen_call_with_2_args(Xen_car(procs), subj, result, S_help_hook);
3635 			procs = Xen_cdr(procs);
3636 		      }
3637 		  }
3638 #endif
3639 		  if (Xen_is_string(result))
3640 		    new_str = mus_strdup(Xen_string_to_C_string(result));
3641 		  else new_str = mus_strdup(str);
3642 		}
3643 	      else new_str = mus_strdup(str);
3644 	    }
3645 	  else new_str = mus_strdup(str);
3646 	  if (need_free)
3647 	    {
3648 	      free(str);
3649 	      str = NULL;
3650 	    }
3651 	  if (widget_wid > 0)
3652 	    {
3653 	      str = word_wrap(new_str, widget_wid);
3654 	      if (new_str) free(new_str);
3655 	    }
3656 	  else
3657 	    str = new_str;
3658 	  help_text = C_string_to_Xen_string(str);
3659 	  if (str) free(str);
3660 	  return(help_text);
3661 	}
3662     }
3663 
3664   if (str)
3665     return(C_string_to_Xen_string(str));
3666   return(Xen_false);
3667 }
3668 
3669 
g_snd_help(Xen text,int widget_wid)3670 Xen g_snd_help(Xen text, int widget_wid)
3671 {
3672   return(g_snd_help_with_search(text, widget_wid, true));
3673 }
3674 
3675 
g_listener_help(Xen arg,Xen formatted)3676 static Xen g_listener_help(Xen arg, Xen formatted)
3677 {
3678   Xen_check_type(Xen_is_boolean_or_unbound(formatted), formatted, 2, S_snd_help, "a boolean");
3679   if (Xen_is_false(formatted))
3680     return(g_snd_help(arg, 0));
3681   return(g_snd_help(arg, listener_width()));
3682 }
3683 
3684 
set_html_dir(char * new_dir)3685 void set_html_dir(char *new_dir)
3686 {
3687   if (html_dir(ss)) free(html_dir(ss));
3688   set_html_dir_1(new_dir);
3689 }
3690 
3691 
g_html_dir(void)3692 static Xen g_html_dir(void)
3693 {
3694   #define H_html_dir "(" S_html_dir "): location of Snd documentation"
3695   return(C_string_to_Xen_string(html_dir(ss)));
3696 }
3697 
3698 
g_set_html_dir(Xen val)3699 static Xen g_set_html_dir(Xen val)
3700 {
3701   Xen_check_type(Xen_is_string(val), val, 1, S_set S_html_dir, "a string");
3702   set_html_dir(mus_strdup(Xen_string_to_C_string(val)));
3703   return(val);
3704 }
3705 
3706 
g_html_program(void)3707 static Xen g_html_program(void)
3708 {
3709   #define H_html_program "(" S_html_program "): name of documentation reader (mozilla, by default)"
3710   return(C_string_to_Xen_string(html_program(ss)));
3711 }
3712 
3713 
g_set_html_program(Xen val)3714 static Xen g_set_html_program(Xen val)
3715 {
3716   Xen_check_type(Xen_is_string(val), val, 1, S_set S_html_program, "a string");
3717   if (html_program(ss)) free(html_program(ss));
3718   set_html_program(mus_strdup(Xen_string_to_C_string(val)));
3719   return(val);
3720 }
3721 
3722 
g_snd_url(Xen name)3723 static Xen g_snd_url(Xen name)
3724 {
3725   #define H_snd_url "(" S_snd_url " name): url corresponding to 'name'"
3726   /* given Snd entity ('open-sound) as symbol or string return associated url */
3727   Xen_check_type(Xen_is_string(name) || Xen_is_symbol(name), name, 1, S_snd_url, "a string or symbol");
3728   if (Xen_is_string(name))
3729     return(C_string_to_Xen_string(snd_url(Xen_string_to_C_string(name))));
3730   return(C_string_to_Xen_string(snd_url(Xen_symbol_to_C_string(name))));
3731 }
3732 
3733 
g_snd_urls(void)3734 static Xen g_snd_urls(void)
3735 {
3736   #define H_snd_urls "(" S_snd_urls "): list of all snd names with the associated url (a list of lists)"
3737   Xen lst = Xen_empty_list;
3738 #if HAVE_EXTENSION_LANGUAGE
3739   int i;
3740   for (i = 0; i < HELP_NAMES_SIZE; i++)
3741     lst = Xen_cons(Xen_cons(C_string_to_Xen_string(help_names[i]),
3742 			    C_string_to_Xen_string(help_urls[i])),
3743 		   lst);
3744 #endif
3745   return(lst);
3746 }
3747 
3748 
3749 static const char **refs = NULL, **urls = NULL;
3750 
g_help_dialog(Xen subject,Xen msg,Xen xrefs,Xen xurls)3751 static Xen g_help_dialog(Xen subject, Xen msg, Xen xrefs, Xen xurls)
3752 {
3753   #define H_help_dialog "(" S_help_dialog " subject message xrefs urls): start the Help window with subject and message"
3754 
3755   Xen_check_type(Xen_is_string(subject), subject, 1, S_help_dialog, "a string");
3756   Xen_check_type(Xen_is_string(msg), msg, 2, S_help_dialog, "a string");
3757   Xen_check_type(Xen_is_list(xrefs) || !Xen_is_bound(xrefs), xrefs, 3, S_help_dialog, "a list of related references");
3758   Xen_check_type(Xen_is_list(xurls) || !Xen_is_bound(xurls), xurls, 4, S_help_dialog, "a list of urls");
3759 
3760   if (refs) {free(refs); refs = NULL;}
3761   if (urls) {free(urls); urls = NULL;}
3762 
3763   if (Xen_is_list(xrefs))
3764     {
3765       int i, len;
3766 
3767       len = Xen_list_length(xrefs);
3768       refs = (const char **)calloc(len + 1, sizeof(char *));
3769 
3770       for (i = 0; i < len; i++)
3771 	if (Xen_is_string(Xen_list_ref(xrefs, i)))
3772 	  refs[i] = Xen_string_to_C_string(Xen_list_ref(xrefs, i));
3773 
3774       if (Xen_is_list(xurls))
3775 	{
3776 	  int ulen;
3777 	  ulen = Xen_list_length(xurls);
3778 	  if (ulen > len) ulen = len;
3779 	  urls = (const char **)calloc(ulen + 1, sizeof(char *));
3780 	  for (i = 0; i < ulen; i++)
3781 	    if (Xen_is_string(Xen_list_ref(xurls, i)))
3782 	      urls[i] = Xen_string_to_C_string(Xen_list_ref(xurls, i));
3783 	}
3784       return(Xen_wrap_widget(snd_help_with_xrefs(Xen_string_to_C_string(subject),
3785 						 Xen_string_to_C_string(msg),
3786 						 WITH_WORD_WRAP,
3787 						 refs,
3788 						 urls)));
3789     }
3790   return(Xen_wrap_widget(snd_help(Xen_string_to_C_string(subject),
3791 				  Xen_string_to_C_string(msg),
3792 				  WITH_WORD_WRAP)));
3793 }
3794 
3795 
Xen_wrap_2_optional_args(g_listener_help_w,g_listener_help)3796 Xen_wrap_2_optional_args(g_listener_help_w, g_listener_help)
3797 Xen_wrap_no_args(g_html_dir_w, g_html_dir)
3798 Xen_wrap_1_arg(g_set_html_dir_w, g_set_html_dir)
3799 Xen_wrap_no_args(g_html_program_w, g_html_program)
3800 Xen_wrap_1_arg(g_set_html_program_w, g_set_html_program)
3801 Xen_wrap_1_arg(g_snd_url_w, g_snd_url)
3802 Xen_wrap_no_args(g_snd_urls_w, g_snd_urls)
3803 Xen_wrap_4_optional_args(g_help_dialog_w, g_help_dialog)
3804 
3805 #if HAVE_SCHEME
3806 static s7_pointer acc_html_dir(s7_scheme *sc, s7_pointer args) {return(g_set_html_dir(s7_cadr(args)));}
acc_html_program(s7_scheme * sc,s7_pointer args)3807 static s7_pointer acc_html_program(s7_scheme *sc, s7_pointer args) {return(g_set_html_program(s7_cadr(args)));}
3808 #endif
3809 
g_init_help(void)3810 void g_init_help(void)
3811 {
3812 #if HAVE_SCHEME
3813   s7_pointer p, s, pcl_s, pcl_t;
3814   p = s7_make_symbol(s7, "list?");
3815   s = s7_make_symbol(s7, "string?");
3816   pcl_s = s7_make_circular_signature(s7, 0, 1, s);
3817   pcl_t = s7_make_circular_signature(s7, 0, 1, s7_t(s7));
3818 #endif
3819 
3820   Xen_define_typed_procedure(S_snd_help,    g_listener_help_w,  0, 2, 0, H_snd_help,    pcl_t);
3821 #if HAVE_SCHEME
3822   Xen_eval_C_string("(define s7-help help)");      /* override s7's help */
3823   Xen_define_typed_procedure("help",        g_listener_help_w,  0, 2, 0, H_snd_help,    pcl_t);
3824 #endif
3825   Xen_define_typed_procedure(S_snd_url,     g_snd_url_w,        1, 0, 0, H_snd_url,
3826 			     s7_make_signature(s7, 2, s, s7_make_signature(s7, 2, s, s7_make_symbol(s7, "symbol?"))));
3827   Xen_define_typed_procedure(S_snd_urls,    g_snd_urls_w,       0, 0, 0, H_snd_urls,    s7_make_signature(s7, 1, p));
3828   Xen_define_typed_procedure(S_help_dialog, g_help_dialog_w,    2, 2, 0, H_help_dialog, s7_make_signature(s7, 5, p, s, s, p, p));
3829 
3830   #define H_help_hook S_help_hook "(subject message): called from " S_snd_help ".  If \
3831 it returns a string, it replaces 'message' (the default help)"
3832 
3833   help_hook = Xen_define_hook(S_help_hook, "(make-hook 'subject 'message)", 2, H_help_hook);
3834 
3835 #if HAVE_SCHEME
3836   #define H_output_comment_hook S_output_comment_hook " (comment): called in Save-As dialog, passed current sound's comment, if any. \
3837 If more than one hook function, each function gets the previous function's output as its input.\n\
3838   (hook-push " S_output_comment_hook "\n\
3839     (lambda (hook)\n\
3840       (set! (hook 'result) (string-append (hook 'comment) \n\
3841                                           \": written \"\n\
3842                                           (strftime \"%a %d-%b-%Y %H:%M %Z\"\n\
3843                                              (localtime (current-time)))))))"
3844 #endif
3845 #if HAVE_RUBY
3846   #define H_output_comment_hook S_output_comment_hook " (str): called in Save-As dialog, passed current sound's comment, if any. \
3847 If more than one hook function, each function gets the previous function's output as its input.\n\
3848 $output_comment_hook.add_hook!(\"comment\") do |str|\n\
3849   str + \": written \" + Time.new.localtime.strftime(\"%a %d-%b-%y %H:%M %Z\")\n\
3850 end"
3851 #endif
3852 #if HAVE_FORTH
3853   #define H_output_comment_hook S_output_comment_hook " (str): called in Save-As dialog, passed current sound's comment, if any. \
3854 If more than one hook function, each function gets the previous function's output as its input.\n\
3855 " S_output_comment_hook " lambda: <{ str }>\n\
3856   \"%s: written %s\" '( str date ) format\n\
3857 ; add-hook!"
3858 #endif
3859 
3860   output_comment_hook = Xen_define_hook(S_output_comment_hook, "(make-hook 'comment)", 1, H_output_comment_hook);
3861 
3862   Xen_define_typed_dilambda(S_html_dir, g_html_dir_w, H_html_dir,
3863 			    S_set S_html_dir, g_set_html_dir_w, 0, 0, 1, 0, pcl_s, pcl_s);
3864   Xen_define_typed_dilambda(S_html_program, g_html_program_w, H_html_program,
3865 			    S_set S_html_program, g_set_html_program_w, 0, 0, 1, 0, pcl_s, pcl_s);
3866 
3867 #if HAVE_SCHEME
3868   autoload_info(s7); /* snd-xref.c included above */
3869   s7_set_setter(s7, ss->html_dir_symbol, s7_make_function(s7, "[acc-" S_html_dir "]", acc_html_dir, 2, 0, false, "accessor"));
3870   s7_set_setter(s7, ss->html_program_symbol, s7_make_function(s7, "[acc-" S_html_program "]", acc_html_program, 2, 0, false, "accessor"));
3871 
3872   s7_set_documentation(s7, ss->html_dir_symbol, "*html-dir*: location of Snd documentation");
3873   s7_set_documentation(s7, ss->html_program_symbol, "*html-program*: name of documentation reader (firefox)");
3874 #endif
3875 }
3876