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, ®ion_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, ©_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