diff --git a/EXAMPLE_USAGE.md b/EXAMPLE_USAGE.md index 6346112..ce9b232 100644 --- a/EXAMPLE_USAGE.md +++ b/EXAMPLE_USAGE.md @@ -1,9 +1,12 @@ ## Example usage ```text -$ echo 't&((t>>7)-t)&t>>8' | python ./bytebeat_compiler.py - -p 44100 -v +$ echo 't&((t>>7)-t)&t>>8' | python ./bytebeat_compiler.py - -p 44100 --verbose :: C bytebeat generator: compiler unit +Reading from STDIN... Compiling +cc -Ofast -march=native -mtune=native -Wall -Wextra -Wpedantic -pedantic -Wno-unused-variable -Wno-unused-but-set-variable -Wno-dangling-else -Wno-parentheses -std=c99 ./bin/substituted.c ./src/fwrite_le.c -o ./bin/render_bytebeat -I./include +./bin/render_bytebeat :: C bytebeat generator runtime unit Sample rate: 44100 Hz @@ -11,9 +14,7 @@ Channels: 1 (mono) Bit depth: unsigned 8-bit Duration: 30 seconds -remaining samples = 0 (100.00% done) -Writing out file output.wav... -Done! +Writing WAVE headers... -$ +Done! ``` diff --git a/README.md b/README.md index 2c8dc2e..867203e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ ## License -Dual-licensed under the [Unlicense](http://unlicense.org) -([`LICENSE`](LICENSE)) and Creative Commons Zero 1.0 Universal -([`COPYING`](COPYING)). +Dual-licensed under the [Creative Commons Zero 1.0 Universal][CC0-1.0] +([`COPYING`](COPYING)) or [Unlicense][Unlicense] ([`LICENSE`](LICENSE)). + +[CC0-1.0]: https://creativecommons.org/publicdomain/zero/1.0/legalcode +[Unlicense]: https://unlicense.org diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 170367a..2842451 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,12 +4,12 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser -from os import environ, listdir, makedirs, name as os_name, \ - remove as delete_file, rmdir +from os import getcwd, environ, makedirs, name as os_name, rename from os.path import exists, join as path_join from shlex import join as command_line_join, split as command_line_split from shutil import which from sys import stdin, stdout +from tempfile import TemporaryDirectory from typing import Dict, Union import re import subprocess @@ -31,25 +31,23 @@ PATHS = { "include_directory": "include" } +# Add current directory before all paths for compilation +CURRENT_DIRECTORY = getcwd() +for key in ["src_dir", "bin_dir", "include_directory"]: + PATHS[key] = path_join(CURRENT_DIRECTORY, PATHS[key]) + # Resolve paths PATHS["template"] = path_join(PATHS["src_dir"], PATHS["template"]) -PATHS["substitute"] = path_join(PATHS["bin_dir"], PATHS["substitute"]) -PATHS["output"] = path_join(PATHS["bin_dir"], PATHS["output"]) +PATHS["substitute_kept"] = path_join(PATHS["bin_dir"], PATHS["substitute"]) +PATHS["output_kept"] = path_join(PATHS["bin_dir"], PATHS["output"]) PATHS["fwrite_le"] = path_join(PATHS["src_dir"], PATHS["fwrite_le"]) -# Add `.` directory before all paths for compilation -for key in ["template", "substitute", "output", "fwrite_le", - "include_directory"]: - PATHS[key] = path_join(".", PATHS[key]) - # Default parameters DEFAULT_PARAMETERS = { "CC": "cc", "CFLAGS": "-Ofast -march=native -mtune=native -Wall -Wextra -Wpedantic " "-pedantic -Wno-unused-variable -Wno-unused-but-set-variable " - "-Wno-dangling-else -Wno-parentheses -std=c99", - "INPUT_FILE": PATHS["substitute"], - "OUTPUT_FILE": PATHS["output"] + "-Wno-dangling-else -Wno-parentheses -std=c99" } stdout_atty = hasattr(stdout, "isatty") and stdout.isatty() @@ -63,7 +61,7 @@ def fetch(name: str): def read_file(path: str) -> str: return open(path, "r", encoding="utf-8-sig").read() -def rewrite_file(path: str, content: str): +def overwrite_file(path: str, content: str) -> int: return open(path, "w", encoding="utf-8").write(content) def read_from_file_or_stdin(path: str) -> str: @@ -91,13 +89,27 @@ def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, return text def run_command(*command: list[str]) -> None: - print(command_line_join(command), flush=True) + print("[>]", command_line_join(command), flush=True) if subprocess.run(command).returncode != EXIT_SUCCESS: raise SystemExit(EXIT_FAILURE) -def delete_empty_dir(path: str) -> None: - if exists(path) and len(listdir(path)) == 0: - rmdir(path) +def compile_substituted_file(input_file: str, output_file: str) -> None: + print("Compiling") + + run_command( + CC, + *command_line_split(CFLAGS), + input_file, + PATHS["fwrite_le"], + "-o", output_file, + "-I" + PATHS["include_directory"] + ) + run_command(output_file) + +def main_workflow(input_file: str, output_file: str, \ + substitute_contents: Dict[str, str]) -> None: + overwrite_file(input_file, substitute_contents) + compile_substituted_file(input_file, output_file) preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' @@ -116,8 +128,6 @@ if os_name == "nt": ] CFLAGS = fetch("CFLAGS") -INPUT_FILE = fetch("INPUT_FILE") -OUTPUT_FILE = fetch("OUTPUT_FILE") if extra := fetch("CFLAGS_EXTRA"): CFLAGS += " " + extra @@ -148,8 +158,8 @@ if __name__ == "__main__": parser = ArgumentParser(description=\ "Substitutes supplied C (non-JavaScript!) bytebeat into the template, " "then attempts to compile the instance of the template. Accepts " - "environmental variables `CC`, `CFLAGS`, `INPUT_FILE`, `OUTPUT_FILE`. " - "`CFLAGS_EXTRA` can be used to add to default `CFLAGS`.") + "environmental variables `CC`, `CFLAGS`. `CFLAGS_EXTRA` can be used to " + "add to default `CFLAGS`.") parser.add_argument("file", type=str, help="bytebeat formula file (use `-` to read from stdin)") parser.add_argument("-o", "--output", default="output.wav", type=str, @@ -251,8 +261,6 @@ if __name__ == "__main__": args.signed = False # - Compilation - makedirs(PATHS["bin_dir"], exist_ok=True) - if not args.no_return: # Insert `return` statement # XXX: The bytebeat code is enclosed in parentheses to allow for the # use of commas as a comma operator, enabling more formulas to function. @@ -337,7 +345,26 @@ if __name__ == "__main__": gen_length = length_formula(args.channels, samples, 0) loop_end = length_formula(args.channels, samples, skip_first_samples) - rewrite_file(PATHS["substitute"], substitute_vars({ + if is_cmd_unavailable(CC): + print(f"Compiler {CC} is not available, searching:") + + still_unavailable = True + for compiler in CC_SEARCH_LIST: + print(f"* Trying CC={compiler}", end="") + if is_cmd_available(compiler): + print(": OK") + CC = compiler + still_unavailable = False + break + else: + print() + + if still_unavailable: + raise SystemExit("Could not find an available compiler. Please " + "specify it by setting\nan environmental variable " + "CC.") + + substitute_contents = substitute_vars({ "bytebeat_contents": bytebeat_contents, "output_file": C_str_repr(args.output), "sample_rate": actual_sample_rate, @@ -354,6 +381,7 @@ if __name__ == "__main__": "loop_end_minus_1": loop_end - 1, "initial_time": skip_first_samples, "repeat_times": args.repeat, + "length": samples, "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), "gen_length": gen_length, "sequential_mode": args.mode == "sequential", @@ -362,7 +390,7 @@ if __name__ == "__main__": "verbose_mode": args.verbose and not args.silent, "fwrite_le": PATHS["fwrite_le_header"], "ansi_escape_codes_supported": ansi_escape_codes_supported - }, read_file(PATHS["template"]), args.show_substituted_values)) + }, read_file(PATHS["template"]), args.show_substituted_values) if is_cmd_unavailable(CC): print(f"Compiler {CC} is not available, searching:") @@ -383,20 +411,18 @@ if __name__ == "__main__": "specify it by setting\nan environmental variable " "CC.") - # Compile - print("Compiling") + if args.keep_files: + makedirs(PATHS["bin_dir"], exist_ok=True) - run_command( - CC, - *command_line_split(CFLAGS), - INPUT_FILE, - PATHS["fwrite_le"], - "-o", OUTPUT_FILE, - "-I" + PATHS["include_directory"] - ) - run_command(OUTPUT_FILE) + substitute_file = PATHS["substitute_kept"] + output_file = PATHS["output_kept"] - if not args.keep_files: - delete_file(PATHS["substitute"]) - delete_file(OUTPUT_FILE) - delete_empty_dir(PATHS["bin_dir"]) + main_workflow(substitute_file, output_file, substitute_contents) + else: + with TemporaryDirectory() as tmpdirname: + temporary_path = lambda path: path_join(tmpdirname, path) + + substitute_temp = temporary_path(PATHS["substitute"]) + output_temp = temporary_path(PATHS["output"]) + + main_workflow(substitute_temp, output_temp, substitute_contents) diff --git a/gitignore_clean.py b/gitignore_clean.py deleted file mode 100644 index ce46552..0000000 --- a/gitignore_clean.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 - -from re import sub as re_sub -from glob import glob -from os import remove as os_remove -from os.path import isdir as path_isdir, isfile as path_isfile, abspath -from shutil import rmtree - -GITIGNORE_PATH = "./.gitignore" -DRY_RUN = False - -remove_comments = lambda s: re_sub(r"(? #include #include @@ -39,6 +41,7 @@ typedef long double bb_fp_return_t; #define BITS_PER_BYTE 8 #if BIT_DEPTH <= BITS_PER_BYTE # define ACTUAL_BIT_DEPTH BITS_PER_BYTE + # define SAMPLE_TYPE uint8_t #elif BIT_DEPTH <= 16 # define ACTUAL_BIT_DEPTH 16 @@ -178,18 +181,18 @@ main(void) "\n" "Sample rate: " INT2STR(SAMPLE_RATE) " Hz\n" "Channels: " INT2STR(CHANNELS) -#if CHANNELS <= 2 +# if CHANNELS <= 2 " (" -# if CHANNELS == 2 +# if CHANNELS == 2 "stereo" -# else +# else "mono" -# endif +# endif ")" -#endif +# endif "\n" "Bit depth: " -#if !IS_SIGNED +# if !IS_SIGNED "un" #endif "signed " INT2STR(BIT_DEPTH) "-bit" @@ -374,14 +377,14 @@ main(void) "%sremaining samples = %18" PRIuMAX " (%6.2Lf%% done)" #if SEQUENTIAL_MODE " (part %" PRIuMAX "/%" PRIuMAX ")" -#endif +# endif , _ANSI_CLEAR_STRING, LOOP_END_MINUS_1 - time, ((long double) time * 100) / (long double) LOOP_END_MINUS_1 #if SEQUENTIAL_MODE , (uintmax_t) seq + 1, (uintmax_t) MAX -#endif +# endif ); smart_fflush(); }