saysynth.cli.options

The saysynth.cli.options module includes shared click.option instances for use throughout all commands.

NOTE: The documentation for this module is not rendered properly by pdoc, so its best to click "View Source" to scan its contents.

  1"""
  2The `saysynth.cli.options` module includes shared `click.option` instances for use throughout all commands.
  3
  4<center><img src="/assets/img/sun-wavy.png"></img></center>
  5
  6**NOTE**: The documentation for this module is not rendered properly by `pdoc`, so its best to  click "View Source" to scan its contents.
  7"""
  8import types
  9from collections import defaultdict
 10from typing import Any, Dict, List, Union
 11
 12import click
 13import yaml
 14from midi_utils.arp import STYLES
 15from midi_utils.constants import CHORDS
 16
 17from saysynth.constants import (DEFAULT_BPM_TIME_BPM, DEFAULT_BPM_TIME_COUNT,
 18                                DEFAULT_BPM_TIME_SIG, SAY_ALL_PHONEMES,
 19                                SAY_DATA_TYPES, SAY_EMPHASIS, SAY_ENDIANNESS,
 20                                SAY_EXTRA_OPTION_DELIMITER,
 21                                SAY_PHONEME_CLASSES, SAY_SEGMENT_MAX_DURATION,
 22                                SAY_TUNED_VOICES, SAY_VOLUME_LEVEL_PER_NOTE,
 23                                SAY_VOLUME_LEVEL_PER_SEGMENT, SAY_VOLUME_RANGE)
 24from saysynth.utils import random_track_name, update_dict
 25
 26
 27def group_options(*options):
 28    def wrapper(function):
 29        for option in reversed(options):
 30            function = option(function)
 31        return function
 32
 33    return wrapper
 34
 35
 36def csv_list(csv: str) -> List[str]:
 37    """
 38    A parser for a csv option
 39    """
 40    return [v.strip() for v in csv.split(",") if v.strip()]
 41
 42
 43def csv_int_list(csv: str) -> List[int]:
 44    """
 45    A parser for an integer-typed csv option
 46    """
 47    return [int(v) for v in csv_list(csv)]
 48
 49
 50def prepare_options_for_say(input_text: str, **kwargs):
 51    """
 52    TODO: Get rid fo this / move to `saysynth.lib.say`?
 53    """
 54    # handle some param edge cases
 55    rp = kwargs.get("randomize_phoneme")
 56    # for convenience, set the voice option to the one specified
 57    # in randomize phoneme.
 58    if rp and ":" in rp:
 59        kwargs["voice"] = rp.split(":")[0].strip().title()
 60    kwargs["input_text"] = input_text
 61    return kwargs
 62
 63
 64def format_opt_value(name: str, value: Any) -> Any:
 65    """
 66    Format an option value given its name and value.
 67    If the name is part of the common `OPTS`
 68    set, the click-configured type function will
 69    be applied, otherwise the value will be returned
 70    as a string.
 71    """
 72    global OPTS
 73    name = expand_opt_name(name)
 74    if name in OPTS:
 75        return OPTS[name]["obj"].type(value)
 76    return str(value).strip()
 77
 78
 79def _standardize_opt_name(opt_name: str) -> str:
 80    """
 81    Strip leading dashes from a cli option.
 82    """
 83    opt_name = opt_name.strip()
 84    while True:
 85        if opt_name[0] == "-":
 86            opt_name = opt_name[1:]
 87        else:
 88            break
 89    return opt_name.lower().replace("-", "_")
 90
 91
 92def shorten_opt_name(opt_name: str) -> str:
 93    """
 94    If an option is present in `OPTS`, return its short_name, otherwise standardize it.
 95    """
 96    o = _standardize_opt_name(opt_name)
 97    return OPTS.get(o, {}).get("short_name", o)
 98
 99
100def expand_opt_name(opt_name: str) -> str:
101    """
102    Strip leading dashes from an option name, lower case and strip.
103    and then expand any shortened / non-canonical opts with the global `OPTS` set.
104    """
105    o = _standardize_opt_name(opt_name)
106    return OPTS.get(o, {}).get("full_name", o)
107
108
109def get_unspecified_opts(
110    context: click.Context,
111) -> Dict[str, Union[Dict[str, Any], Any]]:
112    """
113    Get unspecified options from the click.Context and parse their
114    accompanying values using the global `OPTS` set.
115    """
116    opts = {}
117    try:
118        for i in range(0, len(context.args), 2):
119            raw_cli_opt_name = context.args[i]
120            raw_cli_opt_val = context.args[i + 1]
121            if SAY_EXTRA_OPTION_DELIMITER in raw_cli_opt_name:
122                # handle options which are designed to be nested (eg: tracks in a sequence for config_overrides)
123                parent_opt_name, child_opt_name = raw_cli_opt_val.split(
124                    SAY_EXTRA_OPTION_DELIMITER
125                )
126                parent_opt_name = _standardize_opt_name(parent_opt_name)
127                child_opt_name = expand_opt_name(child_opt_name)
128                if parent_opt_name not in opts:
129                    opts[parent_opt_name] = {}
130                opts[parent_opt_name][child_opt_name] = format_opt_value(
131                    child_opt_name, raw_cli_opt_val
132                )
133            else:
134                cli_opt_name = expand_opt_name(raw_cli_opt_name)
135                opts[cli_opt_name] = format_opt_value(
136                    cli_opt_name, raw_cli_opt_val
137                )
138    except IndexError:
139        pass
140    return opts
141
142
143def set_config_overrides_opt(
144    context: click.Context, **kwargs
145) -> Dict[str, Any]:
146    """
147    Combine the config overrides option and additional, unspecified cli options
148    """
149    cli_config_overrides = get_unspecified_opts(context)
150    yaml_config_overrides = yaml.safe_load(
151        kwargs.get("config_overrides", "{}")
152    )
153    kwargs["config_overrides"] = update_dict(
154        cli_config_overrides, yaml_config_overrides
155    )
156    return kwargs
157
158
159def log_configurations(track_type, **options) -> Dict[str, Any]:
160    """
161    Log configurations as yaml and exit
162    """
163    track_name = random_track_name(track_type, **options)
164    options.pop("yaml", None)  # remove yaml option
165    configs = {"tracks": [{track_name: {"type": track_type, "options": options}}]}
166    click.echo(yaml.safe_dump(configs, indent=4))
167    return configs
168
169
170# Duration Options
171
172
173duration_opt = click.option(
174    "-d",
175    "--duration",
176    default=10000,
177    type=int,
178    help="The duration of the note in milliseconds.",
179)
180
181bpm_opt = click.option(
182    "-db",
183    "--duration-bpm",
184    "duration_bpm",
185    default=DEFAULT_BPM_TIME_BPM,
186    show_default=True,
187    type=float,
188    help="The bpm to use when calculating note duration.",
189)
190count_opt = click.option(
191    "-dc",
192    "--duration-count",
193    "duration_count",
194    default=DEFAULT_BPM_TIME_COUNT,
195    type=str,
196    show_default=True,
197    help="The note length to use when calculating note duration (eg: 1/8 or 0.123 or 3)",
198)
199time_sig_opt = click.option(
200    "-dts",
201    "--duration-time-sig",
202    "duration_time_sig",
203    default=DEFAULT_BPM_TIME_SIG,
204    type=str,
205    show_default=True,
206    help="The time signature to use when calculating note duration",
207)
208
209"""
210CLI options for controlling note duration.
211"""
212duration_opts = group_options(duration_opt, bpm_opt, count_opt, time_sig_opt)
213
214
215phoneme_opt = click.option(
216    "-ph",
217    "--phoneme",
218    default="m",
219    help=(
220        f"One or more valid phoneme to use. Choose from {', '.join(SAY_ALL_PHONEMES)}. "
221        "Multiple phonemes can be combined together into one option eg: ''"
222    ),
223    show_default=True,
224    type=csv_list,
225)
226randomize_phoneme_opt = click.option(
227    "-rp",
228    "--randomize-phoneme",
229    show_default=True,
230    type=str,
231    help=(
232        "Randomize the phoneme for every note. "
233        "If `all` is passed, all phonemes will be used. "
234        "Alternatively pass a comma-separated list of phonemes (eg 'm,l,n') or a voice and style, eg: Fred:drone. "
235        f"Valid voices include: {', '.join(SAY_TUNED_VOICES)}. "
236        f"Valid styles include: {', '.join(SAY_PHONEME_CLASSES)}."
237    ),
238)
239randomize_octave_opt = click.option(
240    "-ro",
241    "--randomize-octave",
242    type=csv_int_list,
243    required=False,
244    default="",
245    help="A comma-separate list of octaves to randomly vary between. You can weight certain octaves by providing them multiple times (eg: 0,0,0-1,-1,2 would prefer the root octave first, one octave down second, and two octaves up third.)",
246)
247
248"""
249CLI options for controlling phonemes.
250"""
251phoneme_opts = group_options(
252    phoneme_opt, randomize_phoneme_opt, randomize_octave_opt
253)
254
255
256# Start Options
257
258
259randomize_start_opt = click.option(
260    "-rt",
261    "--randomize-start",
262    type=int,
263    nargs=2,
264    help="Randomize the number of milliseconds to silence to add before the say text. The first number passed in is the minimum of the range, the second is the max.",
265)
266start_opt = click.option(
267    "-t",
268    "--start",
269    default=None,
270    show_default=True,
271    type=float,
272    help="The number of milliseconds of silence to add before the say text.",
273)
274start_bpm_opt = click.option(
275    "-tb",
276    "--start-bpm",
277    default=DEFAULT_BPM_TIME_BPM,
278    type=float,
279    help="The bpm to use when calculating start time",
280)
281start_count_opt = click.option(
282    "-tc",
283    "--start-count",
284    default=0,
285    type=str,
286    show_default=True,
287    help="The note length to use when calculating start time (eg: 1/8 or 0.123 or 3)",
288)
289start_time_sig_opt = click.option(
290    "-tts",
291    "--start-time-sig",
292    default=DEFAULT_BPM_TIME_SIG,
293    type=str,
294    show_default=True,
295    help="The time signature to use when calculating start time",
296)
297
298"""
299CLI options for adding silence to the beginning of a musical passage.
300"""
301start_opts = group_options(
302    randomize_start_opt,
303    start_opt,
304    start_bpm_opt,
305    start_count_opt,
306    start_time_sig_opt,
307)
308
309
310# Segment Options
311
312randomize_segments_opt = click.option(
313    "-rs",
314    "--randomize-segments",
315    type=csv_list,
316    required=False,
317    default="",
318    help="Randomize every segment's 'phoneme', 'octave', and/or 'velocity'. Use commas to separate multiple randomization strategies",
319)
320
321
322segment_duration_opt = click.option(
323    "-sd",
324    "--segment-duration",
325    default=SAY_SEGMENT_MAX_DURATION,
326    show_default=True,
327    type=float,
328    help="The duration an individual phoneme",
329)
330segment_bpm_opt = click.option(
331    "-sb",
332    "--segment-bpm",
333    default=120.0,
334    show_default=True,
335    type=float,
336    help="The bpm to use when calculating phoneme duration",
337)
338segment_count_opt = click.option(
339    "-sc",
340    "--segment-count",
341    default="1/16",
342    type=str,
343    show_default=True,
344    help="The note length to use when calculating phoneme duration (eg: 1/8 or 0.123 or 3)",
345)
346segment_time_sig_opt = click.option(
347    "-sts",
348    "--segment-time-sig",
349    default=DEFAULT_BPM_TIME_SIG,
350    type=str,
351    show_default=True,
352    help="The time signature to use when calculating phoneme duration",
353)
354
355"""
356CLI options for controlling segment generation.
357"""
358segment_opts = group_options(
359    randomize_segments_opt,
360    segment_duration_opt,
361    segment_bpm_opt,
362    segment_count_opt,
363    segment_time_sig_opt,
364)
365
366
367# Velocity Options
368
369velocity_opt = click.option(
370    "-vl",
371    "--velocity",
372    type=int,
373    show_default=True,
374    default=110,
375    help="The midi velocity value to use for each note.",
376)
377velocity_emphasis_opt = click.option(
378    "-ve",
379    "--velocity-emphasis",
380    "emphasis",
381    type=int,
382    nargs=2,
383    show_default=True,
384    default=SAY_EMPHASIS,
385    help="Two midi velocity values (between 0 and 127) at which to add emphasis to a note/segment",
386)
387volume_range_opt = click.option(
388    "-vr",
389    "--volume-range",
390    type=float,
391    nargs=2,
392    show_default=True,
393    default=SAY_VOLUME_RANGE,
394    help="The min and max volumes (range: 0.0-1.0) to use when mapping from midi velocities",
395)
396randomize_velocity_opt = click.option(
397    "-rv",
398    "--randomize-velocity",
399    type=int,
400    nargs=2,
401    help="Randomize a note's velocity by supplying a min and max midi velocity (eg: -rv 40 120)",
402)
403
404"""
405CLI options for controlling velocities
406"""
407velocity_opts = group_options(
408    velocity_opt,
409    velocity_emphasis_opt,
410    volume_range_opt,
411    randomize_velocity_opt,
412)
413
414
415volume_level_per_segment_opt = click.option(
416    "-vps",
417    "--render-volume-level-per-segment",
418    "volume_level_per_segment",
419    default=SAY_VOLUME_LEVEL_PER_SEGMENT,
420    type=int,
421    show_default=True,
422    help="The number of segments per note to render volume tags. Rendering too many can cause random drop-outs, while too few decreases the granularity of ADSR settings.",
423)
424
425volume_level_per_note_opt = click.option(
426    "-vpn",
427    "--render-volume-level-per-note",
428    "volume_level_per_note",
429    default=SAY_VOLUME_LEVEL_PER_NOTE,
430    type=int,
431    show_default=True,
432    help="The number of notes per sequence to render volume tags. Rendering too many can cause random drop-outs, while too few decreases the granularity of ADSR settings.",
433)
434
435"""
436CLI options for adjusting the granularity of volume envelopes.
437"""
438volume_level_opts = group_options(
439    volume_level_per_note_opt, volume_level_per_segment_opt
440)
441
442
443attack_opt = click.option(
444    "-at",
445    "--attack",
446    default=0.0,
447    show_default=True,
448    type=float,
449    help="The percentage of the duration it takes to reach the max volume of the note",
450)
451decay_opt = click.option(
452    "-de",
453    "--decay",
454    default=0.0,
455    show_default=True,
456    type=float,
457    help="The percentage of the duration it takes to reach the sustain volume of the note",
458)
459sustain_opt = click.option(
460    "-su",
461    "--sustain",
462    default=1.0,
463    type=float,
464    show_default=True,
465    help="The the sustain volume of the note",
466)
467release_opt = click.option(
468    "-re",
469    "--release",
470    default=0.0,
471    type=float,
472    show_default=True,
473    help="The percentage of the duration it takes to reach the min volume of the note",
474)
475
476"""
477CLI options for ADSR functionality.
478"""
479adsr_opts = group_options(
480    attack_opt,
481    decay_opt,
482    sustain_opt,
483    release_opt,
484)
485
486
487# Say Options
488exec_opt = click.option(
489    "-p",
490    "--pipe",
491    is_flag=True,
492    default=False,
493    help=(
494        "Don't execute the say command and print the text to the console instead. "
495        "NOTE: This doesn't work with the `chord` command since this launches "
496        "multiple subprocesses and the outputted text will be jumbled. "
497        "In order to output the text representation of a chord, use "
498        "the `--output-file` option."
499    ),
500)
501
502rate_opt = click.option(
503    "-r",
504    "--rate",
505    type=int,
506    default=70,
507    show_default=True,
508    help="Rate to speak at (see `man say`)",
509)
510voice_opt = click.option(
511    "-v",
512    "--voice",
513    type=click.Choice(SAY_TUNED_VOICES),
514    default="Fred",
515    show_default=True,
516    help="Voice to use",
517)
518input_file_opt = click.option(
519    "-i",
520    "--input-file",
521    type=str,
522    help="Filepath to read text input for say from",
523    default=None,
524)
525audio_output_file_opt = click.option(
526    "-ao",
527    "--audio-output-file",
528    type=str,
529    help="File to write audio output to",
530)
531audio_device_opt = click.option(
532    "-ad",
533    "--audio-device",
534    type=str,
535    help="Name of the audio device to send the signal to",
536)
537networks_send_opt = click.option(
538    "-ns",
539    "--network-send",
540    type=str,
541    help="Network address to send the signal to",
542)
543stereo_opt = click.option(
544    "-st",
545    "--stereo",
546    is_flag=True,
547    default=False,
548    help="Whether or not to generate a stereo signal",
549)
550endianness_opt = click.option(
551    "-en",
552    "--endianness",
553    type=click.Choice(SAY_ENDIANNESS),
554    default="BE",
555    help="Whether or not to generate a stereo signal. See say's documentation on data/file formats for more details.",
556)
557data_type_opt = click.option(
558    "-dt",
559    "--data-type",
560    type=click.Choice(SAY_DATA_TYPES),
561    default="I",
562    help="One of F (float), I (integer), or, rarely, UI (unsigned integer). See say's documentation on data/file formats for more details.",
563)
564sample_size_opt = click.option(
565    "-ss",
566    "--sample-size",
567    type=int,
568    default=16,
569    show_default=True,
570    help="Sample size of the signal. When --data-type is 'I', One of 8, 16, 24, 32, 64. When --data-type is 'F', either 32 or 64. See say's documentation on data/file formats for more details.",
571)
572sample_rate_opt = click.option(
573    "-sr",
574    "--sample-rate",
575    type=int,
576    default=22050,
577    show_default=True,
578    help="Sample rate of the signal (0:22050). See say's documentation on data/file formats for more details.",
579)
580quality_opt = click.option(
581    "-qu",
582    "--quality",
583    type=int,
584    default=127,
585    help="Quality of the signal (1:127). See say's documentation on data/file formats for more details.",
586    show_default=True,
587)
588wait_opt = click.option(
589    "-w",
590    "--wait",
591    is_flag=True,
592    default=False,
593    help="Whether or not to wait for the process to complete.",
594)
595yaml_opt = click.option(
596    "-y",
597    "--yaml",
598    is_flag=True,
599    default=False,
600    help="Optionally print these configurations to the console as yaml. This is useful when constructing a sequence.",
601)
602# progress_bar_opt = click.option(
603#     "-pg",
604#     "--progress",
605#     is_flag=True,
606#     default=False,
607#     help="Whether or not to display an interactive progress bar",
608# )
609# interactive_opt = click.option(
610#     "-in",
611#     "--interactive",
612#     is_flag=True,
613#     default=False,
614#     help="Whether or not to display highlighted text",
615# )
616# text_color_opt = click.option(
617#     "-cf",
618#     "--text-color",
619#     type=click.Choice(SAY_COLORS),
620#     default="white",
621#     help="The text color to use when displaying highlighted text",
622# )
623# bg_color_opt = click.option(
624#     "-cb",
625#     "--bg-color",
626#     type=click.Choice(SAY_COLORS),
627#     default="black",
628#     help="The background color to use when displaying highlighted text",
629# )
630
631output_file_opt = click.option(
632    "-o",
633    "--output-file",
634    type=str,
635    help="A filepath to write the generated text to",
636)
637
638"""
639CLI options for `say`
640"""
641say_opts = group_options(
642    exec_opt,
643    wait_opt,
644    rate_opt,
645    voice_opt,
646    audio_output_file_opt,
647    audio_device_opt,
648    networks_send_opt,
649    stereo_opt,
650    endianness_opt,
651    data_type_opt,
652    sample_size_opt,
653    sample_rate_opt,
654    quality_opt,
655    yaml_opt,
656    # progress_bar_opt,
657    # interactive_opt,
658    # text_color_opt,
659    # bg_color_opt,
660)
661
662
663# Chord Options
664
665chord_opt = click.option(
666    "-c",
667    "--chord",
668    required=False,
669    default="min69",
670    type=click.Choice([c.lower() for c in CHORDS.keys()]),
671    help="An optional name of a chord to build using the note as root.",
672)
673chord_notes_opt = click.option(
674    "-cn",
675    "--chord-notes",
676    required=False,
677    default="",
678    type=csv_int_list,
679    help="An optional list of midi numbers to build a chord from based off of the root. For example, the notes '0,3,7' with the root of 'C1' would create a C-minor chord.",
680)
681chord_velocities_opt = click.option(
682    "-cv",
683    "--chord-velocities",
684    required=False,
685    type=csv_int_list,
686    help="A comma-separated list of integers (eg: '50,100,127') specifying the midi velocity each note i the chord. The length of this list much match the number of notes in the chord. --volume-range and --velocity-steps also modify this parameter",
687)
688chord_inversions_opt = click.option(
689    "-ci",
690    "--chord-inversions",
691    "inversions",
692    default="",
693    required=False,
694    type=csv_int_list,
695    help="A comma-separated list of integers (eg: '0,1,-1') specifying the direction and amplitude to invert each note. The length of this list much match the number of notes in the chord (post-stack).",
696)
697chord_stack_opt = click.option(
698    "-cs",
699    "--chord-stack",
700    "stack",
701    default=0,
702    required=False,
703    type=int,
704    help="Stack a chord up (eg: '1' or '2') or down (eg: '-1' or '-2').",
705)
706
707"""
708CLI options for handling chords (`sy arp` + `sy chord`)
709"""
710chord_opts = group_options(
711    chord_opt,
712    chord_notes_opt,
713    chord_inversions_opt,
714    chord_stack_opt,
715)
716
717# Arp Options
718
719#
720notes_opt = click.option(
721    "-ns",
722    "--notes",
723    required=False,
724    default="",
725    type=csv_list,
726    help="A comma-separated list of note names / midi note numbers to argpeggiate",
727)
728
729octaves_opt = click.option(
730    "-oc",
731    "--octaves",
732    required=False,
733    default="0",
734    type=csv_int_list,
735    help="A comma-separated list of octaves to add to the notes",
736)
737
738styles_opt = click.option(
739    "-sl",
740    "--styles",
741    required=False,
742    default="down",
743    type=csv_list,
744    help=f"A comma-separated list of styles/sorting algorithms to apply to the notes. This occurs after octaves are added. \nchoose from:\n {', '.join([str(k) for k in STYLES.keys()])}",
745)
746
747velocities_opt = click.option(
748    "-vl",
749    "--velocities",
750    required=False,
751    default="100",
752    show_default=True,
753    type=csv_int_list,
754    help="A comma-separated list of velocities to apply to the notes, if this list is shorter than the list of notes, a modulo lookup is performed.",
755)
756
757loops_opt = click.option(
758    "-l",
759    "--loops",
760    default=None,
761    show_default=True,
762    type=int,
763    help="The number of times to loop the notes in the pattern. If this is set, it will override the '--duration' option.",
764)
765
766## beat duration
767
768beat_duration_opt = click.option(
769    "-bd",
770    "--beat-duration",
771    default=None,
772    required=False,
773    type=int,
774    help="The duration of the beat in milliseconds.",
775)
776beat_bpm_opt = click.option(
777    "-bb",
778    "--beat-bpm",
779    default=DEFAULT_BPM_TIME_BPM,
780    type=float,
781    show_default=True,
782    help="The bpm to use when calculating beat duration.",
783)
784beat_count_opt = click.option(
785    "-bc",
786    "--beat-count",
787    default=DEFAULT_BPM_TIME_COUNT,
788    type=str,
789    show_default=True,
790    help="The note count to use when calculating beat duration (eg: 1/8 or 0.123 or 3)",
791)
792beat_time_sig_opt = click.option(
793    "-bts",
794    "--beat-time-sig",
795    default=DEFAULT_BPM_TIME_SIG,
796    type=str,
797    show_default=True,
798    help="The time signature to use when calculating beat duration",
799)
800
801
802note_duration_opt = click.option(
803    "-nd",
804    "--note-duration",
805    default=None,
806    required=False,
807    type=int,
808    help="The duration of a single note in the arp. Defaults to the beat duration",
809)
810note_bpm_opt = click.option(
811    "-nb",
812    "--note-bpm",
813    "note_bpm",
814    default=DEFAULT_BPM_TIME_BPM,
815    type=float,
816    show_default=True,
817    help="The bpm to use when calculating note duration.",
818)
819note_count_opt = click.option(
820    "-nc",
821    "--note-count",
822    "note_count",
823    default=DEFAULT_BPM_TIME_COUNT,
824    type=str,
825    show_default=True,
826    help="The note length to use when calculating note duration (eg: 1/8 or 0.123 or 3)",
827)
828note_time_sig_opt = click.option(
829    "-nts",
830    "--note-time-sig",
831    default=DEFAULT_BPM_TIME_SIG,
832    type=str,
833    show_default=True,
834    help="The time signature to use when calculating note duration",
835)
836
837"""
838CLI options specific to `sy arp`.
839"""
840arp_opts = group_options(
841    notes_opt,
842    octaves_opt,
843    styles_opt,
844    velocities_opt,
845    loops_opt,
846    beat_duration_opt,
847    beat_bpm_opt,
848    beat_count_opt,
849    beat_time_sig_opt,
850    note_duration_opt,
851    note_bpm_opt,
852    note_count_opt,
853    note_time_sig_opt,
854)
855
856seq_tracks_opt = click.option(
857    "-t",
858    "--tracks",
859    type=csv_list,
860    help="A comma-separated list of track names to `play`, `start`, `stop`, or `render`",
861)
862seq_audio_devices_opt = click.option(
863    "-ad",
864    "--audio-devices",
865    type=csv_list,
866    help="A comma-separated list of audio-devices  to `play`, `start`, `stop`, or `render`",
867)
868seq_output_dir_opt = click.option(
869    "-o",
870    "--output-dir",
871    type=str,
872    default="./",
873    help="When using `render`, the directory to write audio files of sequence's individual tracks into.",
874)
875seq_config_overrides_opt = click.option(
876    "-c",
877    "--config-overrides",
878    type=str,
879    default="{}",
880    help="""
881    Override global and track-level configurations at runtime
882    by passing in yaml-formatted configurations,
883    eg: `-c '{"foo":"bar"}'`.
884    These configurations can be specified at the track-level
885    by nesting them under the track name,
886    eg: `-c '{"track":{"foo":"bar"}}'`.
887
888    You can also override configurations by providing extra command line arguments
889    available to `midi`, `note`, `chord`, rand/or `arp` tracks, eg: `-sd 10` or `--segment-duration 10`.
890    These can be similarly nested by using a `__` separator, eg: `--track__segment-duration 10`.
891    Parameters specified via the --config-overrides option will
892    take precedence over any extra CLI arguments.
893    """,
894)
895seq_command_arg = click.argument(
896    "command",
897    type=click.Choice(["play", "start", "stop", "render", "echo"]),
898    required=True,
899)
900
901"""
902CLI options specific to `sy seq` and `sy demo`.
903"""
904seq_opts = group_options(
905    seq_tracks_opt,
906    seq_audio_devices_opt,
907    seq_output_dir_opt,
908    seq_config_overrides_opt,
909)
910
911"""
912Text to to use when selecting phonemes; the text to 'sing'
913"""
914text_opt = click.option(
915    "-tx",
916    "--text",
917    type=str,
918    default=None,
919    help="Text to to use when selecting phonemes; the text to 'sing'. Can also be a path to a file containing text to sing.",
920)
921
922
923def _build_option_set(locals) -> Dict[str, click.Parameter]:
924    """
925    Meta-programming HACK to build an option set we can use to dynamically
926    parse extra cli options.
927    """
928    click_params = defaultdict(dict)
929    for _, object in locals.items():
930        if not (
931            isinstance(object, types.FunctionType)
932            and object.__module__.startswith("click")
933        ):
934            continue
935        # instantiate this option and fetch its param object
936        param: click.Parameter = object(lambda x: x).__click_params__[0]
937        full_name = _standardize_opt_name(param.name)
938        option = {"obj": param, "full_name": full_name}
939        click_params[param.name] = option
940
941        # also add lookup for short name flags
942        short_cli_opt = [
943            o
944            for o in param.opts
945            if o.startswith("-") and not o.startswith("--")
946        ]
947        if not len(short_cli_opt):
948            continue
949
950        # add lookup to short name
951        short_name = _standardize_opt_name(short_cli_opt[0])
952        click_params[param.name]["short_name"] = short_name
953        # also add reverse lookup
954        click_params[short_name] = option
955        click_params[short_name]["short_name"] = short_name
956    return click_params
957
958
959OPTS: Dict[str, click.Parameter] = _build_option_set(locals())
def group_options(*options):
28def group_options(*options):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
33
34    return wrapper
def csv_list(csv: str) -> List[str]:
37def csv_list(csv: str) -> List[str]:
38    """
39    A parser for a csv option
40    """
41    return [v.strip() for v in csv.split(",") if v.strip()]

A parser for a csv option

def csv_int_list(csv: str) -> List[int]:
44def csv_int_list(csv: str) -> List[int]:
45    """
46    A parser for an integer-typed csv option
47    """
48    return [int(v) for v in csv_list(csv)]

A parser for an integer-typed csv option

def prepare_options_for_say(input_text: str, **kwargs):
51def prepare_options_for_say(input_text: str, **kwargs):
52    """
53    TODO: Get rid fo this / move to `saysynth.lib.say`?
54    """
55    # handle some param edge cases
56    rp = kwargs.get("randomize_phoneme")
57    # for convenience, set the voice option to the one specified
58    # in randomize phoneme.
59    if rp and ":" in rp:
60        kwargs["voice"] = rp.split(":")[0].strip().title()
61    kwargs["input_text"] = input_text
62    return kwargs

TODO: Get rid fo this / move to saysynth.lib.say?

def format_opt_value(name: str, value: Any) -> Any:
65def format_opt_value(name: str, value: Any) -> Any:
66    """
67    Format an option value given its name and value.
68    If the name is part of the common `OPTS`
69    set, the click-configured type function will
70    be applied, otherwise the value will be returned
71    as a string.
72    """
73    global OPTS
74    name = expand_opt_name(name)
75    if name in OPTS:
76        return OPTS[name]["obj"].type(value)
77    return str(value).strip()

Format an option value given its name and value. If the name is part of the common OPTS set, the click-configured type function will be applied, otherwise the value will be returned as a string.

def shorten_opt_name(opt_name: str) -> str:
93def shorten_opt_name(opt_name: str) -> str:
94    """
95    If an option is present in `OPTS`, return its short_name, otherwise standardize it.
96    """
97    o = _standardize_opt_name(opt_name)
98    return OPTS.get(o, {}).get("short_name", o)

If an option is present in OPTS, return its short_name, otherwise standardize it.

def expand_opt_name(opt_name: str) -> str:
101def expand_opt_name(opt_name: str) -> str:
102    """
103    Strip leading dashes from an option name, lower case and strip.
104    and then expand any shortened / non-canonical opts with the global `OPTS` set.
105    """
106    o = _standardize_opt_name(opt_name)
107    return OPTS.get(o, {}).get("full_name", o)

Strip leading dashes from an option name, lower case and strip. and then expand any shortened / non-canonical opts with the global OPTS set.

def get_unspecified_opts(context: click.core.Context) -> Dict[str, Union[Dict[str, Any], Any]]:
110def get_unspecified_opts(
111    context: click.Context,
112) -> Dict[str, Union[Dict[str, Any], Any]]:
113    """
114    Get unspecified options from the click.Context and parse their
115    accompanying values using the global `OPTS` set.
116    """
117    opts = {}
118    try:
119        for i in range(0, len(context.args), 2):
120            raw_cli_opt_name = context.args[i]
121            raw_cli_opt_val = context.args[i + 1]
122            if SAY_EXTRA_OPTION_DELIMITER in raw_cli_opt_name:
123                # handle options which are designed to be nested (eg: tracks in a sequence for config_overrides)
124                parent_opt_name, child_opt_name = raw_cli_opt_val.split(
125                    SAY_EXTRA_OPTION_DELIMITER
126                )
127                parent_opt_name = _standardize_opt_name(parent_opt_name)
128                child_opt_name = expand_opt_name(child_opt_name)
129                if parent_opt_name not in opts:
130                    opts[parent_opt_name] = {}
131                opts[parent_opt_name][child_opt_name] = format_opt_value(
132                    child_opt_name, raw_cli_opt_val
133                )
134            else:
135                cli_opt_name = expand_opt_name(raw_cli_opt_name)
136                opts[cli_opt_name] = format_opt_value(
137                    cli_opt_name, raw_cli_opt_val
138                )
139    except IndexError:
140        pass
141    return opts

Get unspecified options from the click.Context and parse their accompanying values using the global OPTS set.

def set_config_overrides_opt(context: click.core.Context, **kwargs) -> Dict[str, Any]:
144def set_config_overrides_opt(
145    context: click.Context, **kwargs
146) -> Dict[str, Any]:
147    """
148    Combine the config overrides option and additional, unspecified cli options
149    """
150    cli_config_overrides = get_unspecified_opts(context)
151    yaml_config_overrides = yaml.safe_load(
152        kwargs.get("config_overrides", "{}")
153    )
154    kwargs["config_overrides"] = update_dict(
155        cli_config_overrides, yaml_config_overrides
156    )
157    return kwargs

Combine the config overrides option and additional, unspecified cli options

def log_configurations(track_type, **options) -> Dict[str, Any]:
160def log_configurations(track_type, **options) -> Dict[str, Any]:
161    """
162    Log configurations as yaml and exit
163    """
164    track_name = random_track_name(track_type, **options)
165    options.pop("yaml", None)  # remove yaml option
166    configs = {"tracks": [{track_name: {"type": track_type, "options": options}}]}
167    click.echo(yaml.safe_dump(configs, indent=4))
168    return configs

Log configurations as yaml and exit

def duration_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def bpm_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def count_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def time_sig_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for controlling note duration.

def duration_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def phoneme_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def randomize_phoneme_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def randomize_octave_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for controlling phonemes.

def phoneme_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def randomize_start_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def start_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def start_bpm_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def start_count_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def start_time_sig_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for adding silence to the beginning of a musical passage.

def start_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def randomize_segments_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def segment_duration_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def segment_bpm_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def segment_count_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def segment_time_sig_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for controlling segment generation.

def segment_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def velocity_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def velocity_emphasis_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def volume_range_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def randomize_velocity_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for controlling velocities

def velocity_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def volume_level_per_segment_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def volume_level_per_note_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for adjusting the granularity of volume envelopes.

def volume_level_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def attack_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def decay_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def sustain_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def release_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for ADSR functionality.

def adsr_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def exec_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def rate_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def voice_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def input_file_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def audio_output_file_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def audio_device_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def networks_send_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def stereo_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def endianness_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def data_type_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def sample_size_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def sample_rate_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def quality_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def wait_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def yaml_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def output_file_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for say

def say_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def chord_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def chord_notes_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def chord_velocities_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def chord_inversions_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def chord_stack_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options for handling chords (sy arp + sy chord)

def chord_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def notes_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def octaves_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def styles_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def velocities_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def loops_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def beat_duration_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def beat_bpm_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def beat_count_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def beat_time_sig_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def note_duration_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def note_bpm_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def note_count_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def note_time_sig_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f

CLI options specific to sy arp.

def arp_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function
def seq_tracks_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def seq_audio_devices_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def seq_output_dir_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def seq_config_overrides_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f
def seq_command_arg(f: ~FC) -> ~FC:
286    def decorator(f: FC) -> FC:
287        ArgumentClass = attrs.pop("cls", None) or Argument
288        _param_memo(f, ArgumentClass(param_decls, **attrs))
289        return f

CLI options specific to sy seq and sy demo.

def seq_opts(function):
29    def wrapper(function):
30        for option in reversed(options):
31            function = option(function)
32        return function

Text to to use when selecting phonemes; the text to 'sing'

def text_opt(f: ~FC) -> ~FC:
305    def decorator(f: FC) -> FC:
306        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
307        option_attrs = attrs.copy()
308        OptionClass = option_attrs.pop("cls", None) or Option
309        _param_memo(f, OptionClass(param_decls, **option_attrs))
310        return f