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())
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
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
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
?
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.
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.
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.
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.
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
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
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.
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.
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.
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.
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
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.
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.
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
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
)
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
.
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
.
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'