From 98ff576f190b9d04154e584dd711385bd111da28 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 27 May 2024 08:07:42 +0300 Subject: [PATCH 01/58] WIP: add files --- bytebeat_compiler.py | 142 +++++++++++++++++++---- samples/beeper.c | 5 + samples/cykstep.c | 3 + samples/melody.c | 5 + src/template.c | 270 +++++++++++++++++++++++++++++-------------- 5 files changed, 315 insertions(+), 110 deletions(-) create mode 100644 samples/beeper.c create mode 100644 samples/cykstep.c create mode 100644 samples/melody.c diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 1761328..dc9dcb4 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -1,11 +1,15 @@ #!/usr/bin/python3 +if __name__ == "__main__": + print(":: C bytebeat generator: compiler unit") + from argparse import ArgumentParser from os import environ, makedirs from os.path import exists, join as path_join from shlex import split as command_line_split from sys import stdin, stdout, exit from typing import Dict, Union +import re import subprocess # Paths @@ -65,7 +69,7 @@ def read_from_file_or_stdin(path: str) -> str: raise SystemExit def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, - verbose: bool) -> str: + verbose: bool) -> str: if verbose: print("Substituting values:") for placeholder, replacement in replacements.items(): @@ -89,6 +93,26 @@ OUTPUT_FILE = fetch("OUTPUT_FILE") if extra := fetch("CFLAGS_EXTRA"): CFLAGS += " " + extra +parameter_line_regex = re.compile(r"^\/\/ *RENDER PARAMETERS *: ", re.MULTILINE) + +ALLOWED_ARGUMENTS_FROM_FILE = ( + "sample_rate", + "final_sample_rate", + "bit_depth", + "signed", + "channels", + "no_return", +) + +DEFAULT_ARGS = { + "sample_rate": 8000, + "final_sample_rate": 0, + "bit_depth": 8, + "signed": False, + "channels": 1, + "no_return": False +} + if __name__ == "__main__": parser = ArgumentParser(description=\ "Substitutes supplied C (non-JavaScript!) bytebeat into the template, " @@ -98,37 +122,45 @@ if __name__ == "__main__": 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, - help="specify output file path (default is `output.wav`") - parser.add_argument("-r", "--sample-rate", default=8000, type=int, + help="specify output file path : default is `output.wav`") + parser.add_argument("-r", "--sample-rate", default=None, type=int, help="sample rate (Hz)") parser.add_argument("-p", "--final-sample-rate", default=None, type=int, help="convert the output to a different sample rate (usually one that " "is set in the system, to improve sound quality) during generation " "(not just reinterpretation)") - parser.add_argument("-b", "--bit-depth", default=8, type=int, + parser.add_argument("-b", "--bit-depth", default=None, type=int, help="bit depth") - parser.add_argument("-s", "--signed", default=False, action="store_true", + parser.add_argument("-s", "--signed", default=None, action="store_true", help="is signed?") + parser.add_argument("-u", "--unsigned", default=None, action="store_true", + help="is unsigned? (overrides the 'is signed' parameter)") parser.add_argument("-R", "--precalculate-ratio", default=False, action="store_true", help="precalculate sample ratio to speed up rendering (may produce " - "inaccurate results") + "inaccurate results)") parser.add_argument("-m", "--faster-sample-ratio-math", default=False, action="store_true", help="faster sample ratio math (implies argument -R)") parser.add_argument("-f", "--floating-point", default=False, action="store_true", help="use floating point as the return type") - parser.add_argument("-c", "--channels", default=1, type=int, + parser.add_argument("-c", "--channels", default=None, type=int, help="amount of channels") parser.add_argument("-t", "--seconds", default=None, type=int, help="length in seconds (samples = sample rate * seconds) : " "default = 30 seconds") parser.add_argument("-l", "--samples", default=None, type=int, help="length in samples (adds to `-t`; supports negative numbers) : " - "default = +0 samples") - parser.add_argument("-a", "--no-return", default=False, action="store_true", + "default = seconds + 0 samples") + parser.add_argument("-S", "--skip-first", default=None, type=str, + help="skip first `A` seconds and `B` samples: in format `As`, `B` or " + "`AsB` : default = 0") + parser.add_argument("-k", "--repeat", default=0, type=int, + help="how many times to repeat the bytebeat : " + "default = 0") + parser.add_argument("-a", "--no-return", default=None, action="store_true", help="do not insert return statement before the code") - parser.add_argument("-u", "--mode", default="sequential", type=str, + parser.add_argument("-U", "--mode", default="sequential", type=str, help="mode of writing: `sequential` or `instant` (the latter is not " "recommended, since the whole result would be stored in RAM)") parser.add_argument("-n", "--block-size", default=65536, type=int, @@ -148,7 +180,42 @@ if __name__ == "__main__": if not bytebeat_contents: print("No valid contents") - raise SystemExit + exit(1) + + # - Parse arguments from file + used_parameter_line = False + for line in bytebeat_contents.splitlines(): + if (match := re.search(parameter_line_regex, line)) and \ + not used_parameter_line: + used_parameter_line = True + + parsed_parameters = line[match.start(0):].split(",") + for parameter in parsed_parameters: + kv = [x.strip() for x in parameter.split("=")] + + key = None + value = None + + if len(kv) == 1: + key, value = kv[0], True + elif len(kv) == 2: + key, value = kv[0], int(kv[1]) + else: + break + + # Apply the argument only if it was not used by user yet and is + # allowed to be set + if (key not in args or getattr(args, key) is None) and \ + key in ALLOWED_ARGUMENTS_FROM_FILE: + setattr(args, key, value) + + # - Set default values + for key, value in DEFAULT_ARGS.items(): + if getattr(args, key) is None: + setattr(args, key, value) + + if args.unsigned is True: + args.signed = False # - Compilation makedirs(PATHS["build_dir"], exist_ok=True) @@ -162,7 +229,7 @@ if __name__ == "__main__": final_sample_rate_code = "" if args.faster_sample_ratio_math: args.precalculate_ratio = True - if args.precalculate_ratio and not args.final_sample_rate is None: + if args.precalculate_ratio and args.final_sample_rate != 0: if args.faster_sample_ratio_math: sample_rate_ratio = args.sample_rate / args.final_sample_rate final_sample_rate_code = f"time *= {sample_rate_ratio}L;" @@ -172,7 +239,8 @@ if __name__ == "__main__": args.sample_rate = args.final_sample_rate final_sample_rate = \ - value if (value := args.final_sample_rate) else original_sample_rate + value if (value := args.final_sample_rate) != 0 \ + else original_sample_rate samples = 0 while True: no_seconds = args.seconds is None or args.seconds == 0 @@ -182,7 +250,7 @@ if __name__ == "__main__": if seconds_specified and args.seconds < 0: print("CLI: Count of seconds can't be less than zero.") - raise SystemExit + exit(1) if no_seconds and samples_specified: samples = args.samples @@ -195,27 +263,53 @@ if __name__ == "__main__": continue else: print("CLI: Incorrect seconds/samples length format.") - raise SystemExit + exit(1) break if samples <= 0: print("CLI: Count of samples should be greater than zero.") - raise SystemExit + exit(1) if args.mode != "sequential" and args.mode != "instant": print("Invalid mode '%s'" % args.mode) - raise SystemExit - - gen_length = args.channels * samples + exit(1) ansi_escape_codes_supported = args.color == "auto" and stdout_atty or \ args.color == "always" + actual_sample_rate = \ + value if (value := args.final_sample_rate) else args.sample_rate + + # - Parse the '--skip-first' argument + if not args.skip_first is None: + encountered_s = False + for character in args.skip_first: + if character.isdigit() or character == "s" and not encountered_s: + if character == "s": + encountered_s = True + else: + print(f"Invalid --skip-first format: `{args.skip_first}`") + exit(1) + + skip_first = \ + [int(x) if x.isdigit() else 0 for x in args.skip_first.split("s")] + + skip_first_samples = 0 + if len(skip_first) == 1: + skip_first += [0] + + skip_first_samples = skip_first[0] * actual_sample_rate + skip_first[1] + else: + skip_first_samples = 0 + + length_formula = lambda channels, samples, n: channels * (samples + n) + gen_length = length_formula(args.channels, samples, 0) + loop_end = length_formula(args.channels, samples, skip_first_samples) + rewrite_file(PATHS["substitute"], substitute_vars({ "bytebeat_contents": bytebeat_contents, "output_file": C_str_repr(args.output), - "sample_rate": \ - value if (value := args.final_sample_rate) else args.sample_rate, + "sample_rate": actual_sample_rate, "original_sample_rate": original_sample_rate, "final_sample_rate_code": final_sample_rate_code, "bit_depth": args.bit_depth, @@ -224,7 +318,11 @@ if __name__ == "__main__": "faster_sample_ratio_math": args.precalculate_ratio, "fp_return_type": args.floating_point, "channels": args.channels, - "length": samples, + "running_length": samples, + "loop_end": loop_end, + "loop_end_minus_1": loop_end - 1, + "initial_time": skip_first_samples, + "repeat_times": args.repeat, "wav_product": gen_length * (args.bit_depth // 8), "gen_length": gen_length, "sequential_mode": args.mode == "sequential", diff --git a/samples/beeper.c b/samples/beeper.c new file mode 100644 index 0000000..54034a7 --- /dev/null +++ b/samples/beeper.c @@ -0,0 +1,5 @@ +// RENDER PARAMETERS: sample_rate = 44100, no_return + +bb_counter_t u = t << 1; +SAMPLE_TYPE x = u & u >> 8; +return (x | 127) + 63; diff --git a/samples/cykstep.c b/samples/cykstep.c new file mode 100644 index 0000000..ed3535c --- /dev/null +++ b/samples/cykstep.c @@ -0,0 +1,3 @@ +// RENDER PARAMETERS: sample_rate = 44100, final_sample_rate = 48000, signed + +t/=3,(t*(t+(444+(t/444)))&((t>>9)>4095 ? 4095 : (t>>(9+(3&t>>13)))))>>(3&t>>(5+(3&t>>13))) diff --git a/samples/melody.c b/samples/melody.c new file mode 100644 index 0000000..9c4ba5c --- /dev/null +++ b/samples/melody.c @@ -0,0 +1,5 @@ +// RENDER PARAMETERS: sample_rate = 44100, no_return + +const long double array[] = {1, 1.25, 1.5, 2}; +long double v = array[3 & (t >> 13)]; +return (long double) t * v / 3.1415926535; diff --git a/src/template.c b/src/template.c index 6af8835..c7e0e8e 100644 --- a/src/template.c +++ b/src/template.c @@ -9,6 +9,10 @@ #include "`fwrite_le`" +// typedefs +typedef uintmax_t bb_counter_t; +typedef long double bb_fp_return_t; + // constants #define ANSI_ESCAPE_CODES_SUPPORTED `ansi_escape_codes_supported` @@ -29,20 +33,41 @@ #define BIT_DEPTH `bit_depth` #define IS_SIGNED `is_signed` #define CHANNELS `channels` -#define LENGTH `length` +#define RUNNING_LENGTH `running_length` #if BIT_DEPTH <= 8 +# define ACTUAL_BIT_DEPTH 8 + # define SAMPLE_TYPE uint8_t -#elif BIT_DEPTH >= 16 +#elif BIT_DEPTH <= 16 +# define ACTUAL_BIT_DEPTH 16 + # if IS_SIGNED # define SAMPLE_TYPE int16_t # else # define SAMPLE_TYPE uint16_t # endif +#elif BIT_DEPTH <= 32 +# define ACTUAL_BIT_DEPTH 32 + +# if IS_SIGNED +# define SAMPLE_TYPE int32_t +# else +# define SAMPLE_TYPE uint32_t +# endif +#else +# error "Unsupported bit depth" +# define _ERROR #endif -#define PRODUCT `wav_product` -#define GEN_LENGTH `gen_length` +#ifndef _ERROR + +#define PRODUCT `wav_product`ULL +#define GEN_LENGTH `gen_length`ULL +#define INITIAL_TIME `initial_time` +#define LOOP_END `loop_end`ULL +#define LOOP_END_MINUS_1 `loop_end_minus_1`ULL +#define REPEAT_TIMES `repeat_times`ULL #define FREQUENCY_OF_STATUS_REPORTING 5000 #define SEQUENTIAL_MODE `sequential_mode` @@ -53,8 +78,8 @@ #define FP_RETURN_TYPE `fp_return_type` #define PRECALCULATED_RATIO `precalculated_ratio` -#define BIT_DEPTH_LIMITER ((1 << BIT_DEPTH) - 1) -#define PCM_COEFFICIENT ((1 << (BIT_DEPTH - 1)) - 1) +#define BIT_DEPTH_LIMITER ((1ULL << ACTUAL_BIT_DEPTH) - 1ULL) +#define PCM_COEFFICIENT ((1ULL << (ACTUAL_BIT_DEPTH - 1ULL)) - 1ULL) #define unsigned_to_signed(x) (x - PCM_COEFFICIENT) #define signed_to_unsigned(x) (x + PCM_COEFFICIENT) @@ -78,19 +103,19 @@ // function prototypes #if FP_RETURN_TYPE -long double +bb_fp_return_t #else SAMPLE_TYPE #endif -bytebeat(long double time); +bytebeat(bb_counter_t time); // function implementations #if FP_RETURN_TYPE -long double +bb_fp_return_t #else SAMPLE_TYPE #endif -bytebeat(long double time) +bytebeat(bb_counter_t time) { #if PRECALCULATED_RATIO `final_sample_rate_code` @@ -105,39 +130,10 @@ bytebeat(long double time) `bytebeat_contents`; } -int -main(void) -{ - // * log -> welcome -#if !SILENT_MODE - puts(":: C bytebeat generator runtime unit"); - fflush(stdout); -#endif - -#if !SILENT_MODE - const uintmax_t seconds = LENGTH / SAMPLE_RATE, - samples = LENGTH % SAMPLE_RATE; - - printf( - "\n" - "Sample rate: " INT2STR(SAMPLE_RATE) " Hz\n" - "Channels: " INT2STR(CHANNELS) -#if CHANNELS <= 2 - " (" -# if CHANNELS == 2 - "stereo" -# else - "mono" -# endif - ")" -#endif - "\n" - "Bit depth: " -#if !IS_SIGNED - "un" -#endif - "signed " INT2STR(BIT_DEPTH) "-bit\n" - "Duration: "); +void +print_time(uintmax_t length) { + const uintmax_t seconds = length / SAMPLE_RATE, + samples = length % SAMPLE_RATE; if (seconds > 0) if (seconds >= 3600) @@ -156,6 +152,54 @@ main(void) if (samples > 0) printf("%" PRIuMAX " samples", samples); +} + +int +main(void) +{ + // * log -> welcome +#if !SILENT_MODE + puts(":: C bytebeat generator: runtime unit"); + fflush(stdout); +#endif + +#if !SILENT_MODE + printf( + "\n" + "Sample rate: " INT2STR(SAMPLE_RATE) " Hz\n" + "Channels: " INT2STR(CHANNELS) +#if CHANNELS <= 2 + " (" +# if CHANNELS == 2 + "stereo" +# else + "mono" +# endif + ")" +#endif + "\n" + "Bit depth: " +#if !IS_SIGNED + "un" +#endif + "signed " INT2STR(BIT_DEPTH) "-bit" +#if BIT_DEPTH != ACTUAL_BIT_DEPTH + " -> " INT2STR(ACTUAL_BIT_DEPTH) "-bit" +#endif + "\n" + "Duration: "); + + print_time(RUNNING_LENGTH); + +#if REPEAT_TIMES > 0 + printf(", %llu times -> ", REPEAT_TIMES + 1); + print_time(RUNNING_LENGTH * (REPEAT_TIMES + 1)); +#endif + +#if INITIAL_TIME != 0 + printf("\nStart time: "); + print_time(INITIAL_TIME); +#endif puts( #if VERBOSE_MODE || SEQUENTIAL_MODE @@ -169,39 +213,47 @@ main(void) // * write WAVE headers // 0. log -#if SEQUENTIAL_MODE +#if VERBOSE_MODE puts("Writing WAVE headers..."); #endif // 1. open file - FILE* output_file = fopen(OUTPUT_FILE, "wb"); + FILE* output_file = fopen(OUTPUT_FILE, + "wb" +#if REPEAT_TIMES > 0 + "+" +#endif + ); if (output_file == NULL) { fflush(stdout); perror("fopen"); - return 1; + return EXIT_FAILURE; } // 2. prepare variables - uint32_t buffer_size = PRODUCT, + const uint32_t header_size = + 4 * 4 /* 4 strings of 4 characters */ + + 5 * 4 /* 5 uint32_t values */ + + 4 * 2 /* 4 uint16_t values */; + + uint32_t buffer_size = PRODUCT * (REPEAT_TIMES + 1), file_length = - 4 * 4 /* 4 strings of 4 characters */ + - 5 * 4 /* 4 uint32_t values */ + - 4 * 2 /* 5 uint16_t values */ + - PRODUCT /* sample data */ + header_size + + buffer_size /* sample data */ /* subtract Subchunk2 headers: */ - - 4 /* a 4-character string */ + - 4 /* a string of 4 characters */ - 4 /* a uint32_t value */, fmt_data_length = 16 /* <-- * length of format data before this value * in the file format structure */, sample_rate = SAMPLE_RATE, - byte_rate = (SAMPLE_RATE * BIT_DEPTH * CHANNELS) / 8; + byte_rate = (SAMPLE_RATE * ACTUAL_BIT_DEPTH * CHANNELS) / 8; uint16_t fmt_type = 1, // format type is PCM channels = CHANNELS, - block_align = (BIT_DEPTH * CHANNELS) / 8, - bit_depth = BIT_DEPTH > 8 ? BIT_DEPTH : 8; + block_align = (ACTUAL_BIT_DEPTH * CHANNELS) / 8, + bit_depth = ACTUAL_BIT_DEPTH; // 3. write headers // : : @@ -237,22 +289,23 @@ main(void) size_t calc_block_size = BLOCK_SIZE; ALLOCATE_MEMORY(calc_block_size) #else - ALLOCATE_MEMORY(PRODUCT) + ALLOCATE_MEMORY(GEN_LENGTH) #endif -#if SEQUENTIAL_MODE - const uintmax_t gen_length_minus_1 = GEN_LENGTH - 1, - bit_depth_limiter = BIT_DEPTH_LIMITER + const uintmax_t bit_depth_limiter = BIT_DEPTH_LIMITER #if FP_RETURN_TYPE - + 1 -#endif -#if BIT_DEPTH < 8 - , - bit_depth_stretch = ((long double) BIT_DEPTH) / 8.L + + 1 #endif ; - size_t time = 0; +#if BIT_DEPTH != ACTUAL_BIT_DEPTH + const long double bit_depth_stretch = + ((long double) BIT_DEPTH) / ACTUAL_BIT_DEPTH; +#endif + +#if SEQUENTIAL_MODE + size_t time = INITIAL_TIME; + for (size_t seq = 0; seq < MAX; seq++) { if ((time + BLOCK_SIZE) >= GEN_LENGTH) calc_block_size = GEN_LENGTH - time; @@ -260,34 +313,34 @@ main(void) // * bytebeat generating loop #if SEQUENTIAL_MODE - for (size_t idx = 0; idx < BLOCK_SIZE && time < GEN_LENGTH; idx++, + for (size_t idx = 0; idx < BLOCK_SIZE && time < LOOP_END; idx++, time++) { #else - for (size_t time = 0; time < GEN_LENGTH; time++) { + for (size_t time = INITIAL_TIME; time < LOOP_END; time++) { #endif // 1. generate audio data #if FP_RETURN_TYPE - long double bytebeat_res = - floor(bytebeat(floor((long double) time))); + bb_fp_return_t bytebeat_res = + floor(bytebeat(floor((bb_counter_t) time))); #elif IS_SIGNED intmax_t bytebeat_res = - (intmax_t) bytebeat(floor((long double) time)); + (intmax_t) bytebeat(floor((bb_counter_t) time)); #else uintmax_t bytebeat_res = - (uintmax_t) bytebeat(floor((long double) time)); + (uintmax_t) bytebeat(floor((bb_counter_t) time)); #endif - // 2. if signed, then wrap up to unsigned -#if IS_SIGNED + // 2. if signed and bit depth <= 8, then wrap up to unsigned +#if IS_SIGNED && (BIT_DEPTH <= 8) bytebeat_res = signed_to_unsigned(bytebeat_res); #endif // 3. convert audio data to sample SAMPLE_TYPE sample_res = (SAMPLE_TYPE) #if FP_RETURN_TYPE - fmodl(bytebeat_res, bit_depth_limiter); + fmod(bytebeat_res, BIT_DEPTH_LIMITER); #else - ((uintmax_t) bytebeat_res & bit_depth_limiter); + ((uintmax_t) bytebeat_res & BIT_DEPTH_LIMITER); #endif // 4. if bit depth is less than 8, stretch it @@ -297,25 +350,27 @@ main(void) #endif // 5. save sample into buffer + buffer[ #if SEQUENTIAL_MODE - buffer[idx] = sample_res; + idx #else - buffer[time] = sample_res; + time #endif + ] = sample_res; // 6. log #if VERBOSE_MODE if (time % FREQUENCY_OF_STATUS_REPORTING == 0 || - time >= gen_length_minus_1 /* or if writing last sample */) { + time >= LOOP_END_MINUS_1 /* or if writing last sample */) { printf( - "%sremaining samples = %18" PRIuMAX " (%3.2Lf%% done)" + "%sremaining samples = %18" PRIuMAX " (%6.2Lf%% done)" #if SEQUENTIAL_MODE " (part %" PRIuMAX "/%" PRIuMAX ")" #endif , _ANSI_CLEAR_STRING, - gen_length_minus_1 - time, - ((long double) time * 100) / (long double) GEN_LENGTH + 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 @@ -330,13 +385,13 @@ main(void) // 5. log #if !(SEQUENTIAL_MODE && VERBOSE_MODE) -#if SEQUENTIAL_MODE +# if SEQUENTIAL_MODE if (seq == 0) -#endif +# endif puts( -#if !SEQUENTIAL_MODE +# if !SEQUENTIAL_MODE "\n" -#endif +# endif "Writing out file " OUTPUT_FILE "..."); #endif fflush(stdout); @@ -347,7 +402,7 @@ main(void) #if SEQUENTIAL_MODE calc_block_size, #else - PRODUCT, + GEN_LENGTH, #endif output_file); #if SEQUENTIAL_MODE @@ -358,6 +413,43 @@ main(void) } #endif +#if REPEAT_TIMES > 0 + // * repeat as much as needed + + puts( +# if SEQUENTIAL_MODE + "\n" +# endif + "Repeating..."); + + for (size_t counter = 0; counter < REPEAT_TIMES; counter++) { +# if SEQUENTIAL_MODE + off_t position_read = header_size; + + calc_block_size = BLOCK_SIZE; + for (size_t seq = 0, time = 0; seq < MAX; seq++, time += BLOCK_SIZE) { + bool end = false; + if ((time + BLOCK_SIZE) >= GEN_LENGTH) { + calc_block_size = GEN_LENGTH - time; + end = true; + } + + fseeko(output_file, position_read, SEEK_SET); + fread(buffer, sizeof(SAMPLE_TYPE), calc_block_size, output_file); + fseeko(output_file, 0, SEEK_END); + fwrite(buffer, sizeof(SAMPLE_TYPE), calc_block_size, output_file); + + if (end) + break; + + position_read += calc_block_size; + } +# else + fwrite_le(buffer, sizeof(SAMPLE_TYPE), GEN_LENGTH, output_file); +# endif + } +#endif + // * free allocated heap free(buffer); @@ -367,11 +459,13 @@ main(void) // * end of program #if !SILENT_MODE puts( -#if SEQUENTIAL_MODE && VERBOSE_MODE +# if SEQUENTIAL_MODE && VERBOSE_MODE && REPEAT_TIMES == 0 "\n" -#endif +# endif "Done!"); #endif return EXIT_SUCCESS; } + +#endif /* _ERROR */ From ed7f2b2e6f11f20e20c3753e574a5c7a18431d88 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 27 May 2024 08:08:48 +0300 Subject: [PATCH 02/58] README.md: mark as WIP branch --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 03c5e51..f85fd04 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # C-JS-bytebeat-render +> **Warning** +> You're on a Work In Progress branch! + a bytebeat rendering engine in C (no support of JavaScript bytebeat yet) From 01043c8aff4dd710e60e40fa469bea9f22ba5e7e Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 1 Jun 2024 10:01:38 +0300 Subject: [PATCH 03/58] fwrite_le.*: rename `ifeq_u32_ret` to `ifeq_b32_ret` --- include/fwrite_le.h | 2 +- src/fwrite_le.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fwrite_le.h b/include/fwrite_le.h index ec04e87..69e52b1 100644 --- a/include/fwrite_le.h +++ b/include/fwrite_le.h @@ -15,7 +15,7 @@ #define ORDER_NATIVE_U32 0x01234567 #define ORDER_LITTLE_ENDIAN_S4 "\x67\x45\x23\x01" #define ORDER_BIG_ENDIAN_S4 "\x01\x23\x45\x67" -#define ifeq_u32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; +#define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; int detect_endianness(void); size_t fwrite_le(void* ptr, size_t size, size_t count, FILE* stream); diff --git a/src/fwrite_le.c b/src/fwrite_le.c index 0fe8ec6..473124c 100644 --- a/src/fwrite_le.c +++ b/src/fwrite_le.c @@ -4,8 +4,8 @@ int detect_endianness(void) { volatile uint32_t native_order_value = ORDER_NATIVE_U32; uint8_t* as_bytes = (uint8_t*)&native_order_value; - ifeq_u32_ret(as_bytes, ORDER_LITTLE_ENDIAN_S4, DETECTED_LITTLE_ENDIAN); - ifeq_u32_ret(as_bytes, ORDER_BIG_ENDIAN_S4, DETECTED_BIG_ENDIAN ); + ifeq_b32_ret(as_bytes, ORDER_LITTLE_ENDIAN_S4, DETECTED_LITTLE_ENDIAN); + ifeq_b32_ret(as_bytes, ORDER_BIG_ENDIAN_S4, DETECTED_BIG_ENDIAN ); return UNSUPPORTED_ENDIANNESS; } From 886d555c71250cbad66f81a434cc860be551874f Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 1 Jun 2024 11:36:33 +0300 Subject: [PATCH 04/58] dual-license under Unlicense and CC0 --- COPYING | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 COPYING diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md index f85fd04..b802b7c 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ > **Warning** > You're on a Work In Progress branch! -a bytebeat rendering engine in C (no support of JavaScript bytebeat yet) +Dual-licensed under the [Unlicense](http://unlicense.org) (`LICENSE`) or Creative Commons Zero 1.0 Universal (`COPYING`). + +**Description:** a bytebeat rendering engine in C (no support of JavaScript bytebeat yet) From 83046f14435c034bef8cefffc65b71944805d856 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 1 Jun 2024 11:45:27 +0300 Subject: [PATCH 05/58] bytebeat_compiler.py: reformat --- bytebeat_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index dc9dcb4..25642dc 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -69,7 +69,7 @@ def read_from_file_or_stdin(path: str) -> str: raise SystemExit def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, - verbose: bool) -> str: + verbose: bool) -> str: if verbose: print("Substituting values:") for placeholder, replacement in replacements.items(): From 755336c1037ff94425739dcb8da937c85e00e1ee Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 2 Jun 2024 14:21:01 +0300 Subject: [PATCH 06/58] template.c: remove string junk: fixup --- src/template.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/template.c b/src/template.c index c7e0e8e..34eb1eb 100644 --- a/src/template.c +++ b/src/template.c @@ -201,12 +201,9 @@ main(void) print_time(INITIAL_TIME); #endif - puts( #if VERBOSE_MODE || SEQUENTIAL_MODE - "\n" + puts("\n"); #endif - "" - ); fflush(stdout); #endif From c6e126f2bdc8277b27f25bb6a163ebb17fbc3a53 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 2 Jun 2024 14:31:18 +0300 Subject: [PATCH 07/58] repeating: use `wb+` mode only in sequential mode --- src/template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/template.c b/src/template.c index 34eb1eb..7e7f341 100644 --- a/src/template.c +++ b/src/template.c @@ -217,7 +217,7 @@ main(void) // 1. open file FILE* output_file = fopen(OUTPUT_FILE, "wb" -#if REPEAT_TIMES > 0 +#if REPEAT_TIMES > 0 && SEQUENTIAL_MODE "+" #endif ); From ca282d20c86b52cc041e284d185eea7fc91c801b Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 2 Jun 2024 19:34:49 +0300 Subject: [PATCH 08/58] bytebeat_compiler.py: search for compiler, fix section names and reformat --- bytebeat_compiler.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 25642dc..a46376b 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,9 +4,10 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser -from os import environ, makedirs +from os import environ, makedirs, name as os_name from os.path import exists, join as path_join from shlex import split as command_line_split +from shutil import which from sys import stdin, stdout, exit from typing import Dict, Union import re @@ -87,6 +88,18 @@ preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' CC = fetch("CC") + +CC_SEARCH_LIST = [ + "gcc", + "clang", + "tcc" +] +if os_name == "nt": + CC_SEARCH_LIST = [ + "msc", + *CC_SEARCH_LIST + ] + CFLAGS = fetch("CFLAGS") INPUT_FILE = fetch("INPUT_FILE") OUTPUT_FILE = fetch("OUTPUT_FILE") @@ -113,6 +126,9 @@ DEFAULT_ARGS = { "no_return": False } +is_cmd_available = lambda cmd: which(cmd) is not None +is_cmd_unavailable = lambda cmd: which(cmd) is None + if __name__ == "__main__": parser = ArgumentParser(description=\ "Substitutes supplied C (non-JavaScript!) bytebeat into the template, " @@ -333,10 +349,28 @@ if __name__ == "__main__": "ansi_escape_codes_supported": ansi_escape_codes_supported }, read_file(PATHS["template"]), args.show_substituted_values)) - # Compile by invoking the shell script + if is_cmd_unavailable(CC): + print(f"Compiler {CC} 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: + print("Could not find an available compiler. Please specify it by " + "setting\nenvironmental variable CC.") + exit(2) + + # Compile print("Compiling") - # Let the system execute aliases by calling os.system command = [ CC, *command_line_split(CFLAGS), @@ -346,4 +380,5 @@ if __name__ == "__main__": "-I" + PATHS["include_directory"] ] print(" ".join(command), flush=True) + exit(subprocess.run(command).returncode) From 160c40fc11b6c690d4da6afbd212ef148cb40db2 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 2 Jun 2024 23:31:46 +0300 Subject: [PATCH 09/58] template.c: merge exact, neighbor conditional compilation --- src/template.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/template.c b/src/template.c index 7e7f341..efe77fd 100644 --- a/src/template.c +++ b/src/template.c @@ -161,9 +161,7 @@ main(void) #if !SILENT_MODE puts(":: C bytebeat generator: runtime unit"); fflush(stdout); -#endif -#if !SILENT_MODE printf( "\n" "Sample rate: " INT2STR(SAMPLE_RATE) " Hz\n" From 6f99ea37e62aabe5991cb8c01c95cb72ad039a82 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 3 Jun 2024 00:19:08 +0300 Subject: [PATCH 10/58] template.c: silent mode fixes --- src/template.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/template.c b/src/template.c index efe77fd..0f5ea5a 100644 --- a/src/template.c +++ b/src/template.c @@ -208,7 +208,7 @@ main(void) // * write WAVE headers // 0. log -#if VERBOSE_MODE +#if !SILENT_MODE && VERBOSE_MODE puts("Writing WAVE headers..."); #endif @@ -271,8 +271,10 @@ main(void) size_t BLOCK_SIZE = BLOCK_SIZE_BYTES / (sizeof(SAMPLE_TYPE) / sizeof(uint8_t)); if (BLOCK_SIZE < 1) { +# if !SILENT_MODE printf("The block size " INT2STR(BLOCK_SIZE_BYTES) " is too small, " "should be at least %" PRIuMAX " bytes\n", MINIMUM_BLOCK_SIZE); +# endif return EXIT_FAILURE; } @@ -411,11 +413,13 @@ main(void) #if REPEAT_TIMES > 0 // * repeat as much as needed +# if !SILENT_MODE puts( -# if SEQUENTIAL_MODE +# if SEQUENTIAL_MODE "\n" -# endif +# endif "Repeating..."); +# endif for (size_t counter = 0; counter < REPEAT_TIMES; counter++) { # if SEQUENTIAL_MODE From 505812da6c23b1a5365b859ae046bc454f322fae Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 3 Jun 2024 00:29:45 +0300 Subject: [PATCH 11/58] template.c: output all errors to STDERR --- src/template.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/template.c b/src/template.c index 0f5ea5a..025b2f3 100644 --- a/src/template.c +++ b/src/template.c @@ -272,8 +272,9 @@ main(void) sizeof(uint8_t)); if (BLOCK_SIZE < 1) { # if !SILENT_MODE - printf("The block size " INT2STR(BLOCK_SIZE_BYTES) " is too small, " - "should be at least %" PRIuMAX " bytes\n", MINIMUM_BLOCK_SIZE); + fprintf(stderr, "The block size " INT2STR(BLOCK_SIZE_BYTES) " is too " + "small, should be at least %" PRIuMAX " bytes\n", + MINIMUM_BLOCK_SIZE); # endif return EXIT_FAILURE; } From 2ddc87b226535ef7efd3749427383445ecc0588d Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 18 Jun 2024 18:14:05 +0300 Subject: [PATCH 12/58] fwrite_le.h: move `#include`s inside the `#ifdef` block --- include/fwrite_le.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fwrite_le.h b/include/fwrite_le.h index 69e52b1..76127a3 100644 --- a/include/fwrite_le.h +++ b/include/fwrite_le.h @@ -1,11 +1,11 @@ +#ifndef _FWRITE_LE_H +#define _FWRITE_LE_H + #include #include #include #include -#ifndef _FWRITE_LE_H -#define _FWRITE_LE_H - #define FWRITE_LE_NO_MODIFICATION 0 #define DETECTED_LITTLE_ENDIAN 0 From bbb7b2a476618dd9c1b0ce61d7491095b7145a37 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 18 Jun 2024 18:15:32 +0300 Subject: [PATCH 13/58] fwrite_le.*: make use of `const` for `ptr` --- include/fwrite_le.h | 6 +++++- src/fwrite_le.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/fwrite_le.h b/include/fwrite_le.h index 76127a3..6eb6fd1 100644 --- a/include/fwrite_le.h +++ b/include/fwrite_le.h @@ -18,7 +18,11 @@ #define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; int detect_endianness(void); -size_t fwrite_le(void* ptr, size_t size, size_t count, FILE* stream); +size_t fwrite_le( +#if FWRITE_LE_NO_MODIFICATION + const +#endif + void* ptr, size_t size, size_t count, FILE* stream); void reorder_le_be( #if FWRITE_LE_NO_MODIFICATION uint8_t* dest, uint8_t* src, diff --git a/src/fwrite_le.c b/src/fwrite_le.c index 473124c..e9beb3f 100644 --- a/src/fwrite_le.c +++ b/src/fwrite_le.c @@ -37,7 +37,11 @@ void reorder_le_be( } } -size_t fwrite_le(void* ptr, size_t size, size_t count, FILE* stream) { +size_t fwrite_le( +#if FWRITE_LE_NO_MODIFICATION + const +#endif + void* ptr, size_t size, size_t count, FILE* stream) { /* * warning: this function modifies `void* ptr` by default! * (if FWRITE_LE_NO_MODIFICATION in the header is 0) From 7a107a85de9f898419440176e17417948c238cd6 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 5 Jul 2024 15:21:01 +0300 Subject: [PATCH 14/58] fwrite_le.h: reduce scope of private macros --- include/fwrite_le.h | 5 ----- src/fwrite_le.c | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/fwrite_le.h b/include/fwrite_le.h index 6eb6fd1..58d6ef6 100644 --- a/include/fwrite_le.h +++ b/include/fwrite_le.h @@ -12,11 +12,6 @@ #define DETECTED_BIG_ENDIAN 1 #define UNSUPPORTED_ENDIANNESS -1 -#define ORDER_NATIVE_U32 0x01234567 -#define ORDER_LITTLE_ENDIAN_S4 "\x67\x45\x23\x01" -#define ORDER_BIG_ENDIAN_S4 "\x01\x23\x45\x67" -#define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; - int detect_endianness(void); size_t fwrite_le( #if FWRITE_LE_NO_MODIFICATION diff --git a/src/fwrite_le.c b/src/fwrite_le.c index e9beb3f..4a3b4cc 100644 --- a/src/fwrite_le.c +++ b/src/fwrite_le.c @@ -1,5 +1,10 @@ #include "fwrite_le.h" +#define ORDER_NATIVE_U32 0x01234567 +#define ORDER_LITTLE_ENDIAN_S4 "\x67\x45\x23\x01" +#define ORDER_BIG_ENDIAN_S4 "\x01\x23\x45\x67" +#define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; + int detect_endianness(void) { volatile uint32_t native_order_value = ORDER_NATIVE_U32; uint8_t* as_bytes = (uint8_t*)&native_order_value; From 59b29e395274163b81eba13dce26aa674c71993f Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 6 Jul 2024 17:33:56 +0300 Subject: [PATCH 15/58] README.md: reformat --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b802b7c..f5e8e74 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ > **Warning** > You're on a Work In Progress branch! -Dual-licensed under the [Unlicense](http://unlicense.org) (`LICENSE`) or Creative Commons Zero 1.0 Universal (`COPYING`). +Dual-licensed under the [Unlicense](http://unlicense.org) (`LICENSE`) or +Creative Commons Zero 1.0 Universal (`COPYING`). -**Description:** a bytebeat rendering engine in C (no support of JavaScript bytebeat yet) +**Description:** a bytebeat rendering engine in C (no support of JavaScript +bytebeat yet) From 32665b01685527eb9afbcddf44e79790662b3060 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 6 Jul 2024 17:35:27 +0300 Subject: [PATCH 16/58] README.md: improve license information and reformat --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f5e8e74..096259a 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ > **Warning** > You're on a Work In Progress branch! -Dual-licensed under the [Unlicense](http://unlicense.org) (`LICENSE`) or -Creative Commons Zero 1.0 Universal (`COPYING`). - **Description:** a bytebeat rendering engine in C (no support of JavaScript bytebeat yet) + +## License + +Dual-licensed under the [Unlicense](http://unlicense.org) +([`LICENSE`](LICENSE)) and Creative Commons Zero 1.0 Universal +([`COPYING`](COPYING)). From 391467806eb411db7aa0a886ddf5a430c28fa5f1 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 13 Jul 2024 22:35:33 +0300 Subject: [PATCH 17/58] template.c: extract `BITS_PER_BYTE` --- src/template.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/template.c b/src/template.c index 025b2f3..86c4f10 100644 --- a/src/template.c +++ b/src/template.c @@ -35,9 +35,9 @@ typedef long double bb_fp_return_t; #define CHANNELS `channels` #define RUNNING_LENGTH `running_length` -#if BIT_DEPTH <= 8 -# define ACTUAL_BIT_DEPTH 8 - +#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 @@ -244,10 +244,10 @@ main(void) * in the file format structure */, sample_rate = SAMPLE_RATE, - byte_rate = (SAMPLE_RATE * ACTUAL_BIT_DEPTH * CHANNELS) / 8; + byte_rate = (SAMPLE_RATE * ACTUAL_BIT_DEPTH * CHANNELS) / BITS_PER_BYTE; uint16_t fmt_type = 1, // format type is PCM channels = CHANNELS, - block_align = (ACTUAL_BIT_DEPTH * CHANNELS) / 8, + block_align = (ACTUAL_BIT_DEPTH * CHANNELS) / BITS_PER_BYTE, bit_depth = ACTUAL_BIT_DEPTH; // 3. write headers @@ -341,8 +341,8 @@ main(void) ((uintmax_t) bytebeat_res & BIT_DEPTH_LIMITER); #endif - // 4. if bit depth is less than 8, stretch it -#if BIT_DEPTH < 8 + // 4. if bit depth is less than BITS_PER_BYTE, stretch it +#if BIT_DEPTH < BITS_PER_BYTE sample_res = (SAMPLE_TYPE) ((long double) sample_res * bit_depth_stretch); #endif From 17be3d4b7382d58a30331f598ced9df4ce3f9b06 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 13 Jul 2024 22:39:36 +0300 Subject: [PATCH 18/58] template.c: simplify `file_length` calculation --- src/template.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/template.c b/src/template.c index 86c4f10..19b6d3d 100644 --- a/src/template.c +++ b/src/template.c @@ -227,18 +227,12 @@ main(void) // 2. prepare variables const uint32_t header_size = - 4 * 4 /* 4 strings of 4 characters */ + - 5 * 4 /* 5 uint32_t values */ + + 3 * 4 /* 3 strings of 4 characters: "WAVE", "fmt ", "data" */ + + 4 * 4 /* 4 uint32_t values */ + 4 * 2 /* 4 uint16_t values */; uint32_t buffer_size = PRODUCT * (REPEAT_TIMES + 1), - file_length = - header_size + - buffer_size /* sample data */ - - /* subtract Subchunk2 headers: */ - - 4 /* a string of 4 characters */ - - 4 /* a uint32_t value */, + file_length = header_size + buffer_size, fmt_data_length = 16 /* <-- * length of format data before this value * in the file format structure From 178755aa587fea1ecb78917a5e4a682d13233eaf Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 14 Jul 2024 00:11:31 +0300 Subject: [PATCH 19/58] fwrite_le.c: use `malloc` instead of `calloc` --- src/fwrite_le.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fwrite_le.c b/src/fwrite_le.c index 4a3b4cc..748de52 100644 --- a/src/fwrite_le.c +++ b/src/fwrite_le.c @@ -65,9 +65,9 @@ size_t fwrite_le( // case: big-endian size_t bytes_count = size * count; #if FWRITE_LE_NO_MODIFICATION - uint8_t* bytes = calloc(bytes_count, sizeof(uint8_t)); + uint8_t* bytes = malloc(bytes_count, sizeof(uint8_t)); if (bytes == NULL) { - perror("calloc"); + perror("malloc"); exit(EXIT_FAILURE); } memcpy(bytes, ptr, bytes_count); From 74b9bec9c65fdaa7152353b193c5c2d00e7fc4b7 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sun, 14 Jul 2024 00:13:25 +0300 Subject: [PATCH 20/58] bytebeat_compiler.py: extract `BITS_PER_BYTE` --- bytebeat_compiler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index a46376b..ca2fabb 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -13,6 +13,9 @@ from typing import Dict, Union import re import subprocess +# Definitions +BITS_PER_BYTE = 8 + # Paths PATHS = { "src_dir": "src", @@ -339,7 +342,7 @@ if __name__ == "__main__": "loop_end_minus_1": loop_end - 1, "initial_time": skip_first_samples, "repeat_times": args.repeat, - "wav_product": gen_length * (args.bit_depth // 8), + "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), "gen_length": gen_length, "sequential_mode": args.mode == "sequential", "block_size": args.block_size, From 2dbb70fa2b19a126ccf507640921a32840ca094a Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 26 Jul 2024 14:25:21 +0300 Subject: [PATCH 21/58] *.py: use a better shebang --- bytebeat_compiler.py | 2 +- gitignore_clean.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index ca2fabb..7824099 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") diff --git a/gitignore_clean.py b/gitignore_clean.py index 7ea6759..ce46552 100644 --- a/gitignore_clean.py +++ b/gitignore_clean.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 from re import sub as re_sub from glob import glob From baac2d48e892fdaee78424e29052c655c116bd9c Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 20 Aug 2024 18:46:47 +0300 Subject: [PATCH 22/58] use directory `bin` instead of `build` --- .gitignore | 4 ++-- bytebeat_compiler.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6ba3514..4eaafe7 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,6 @@ Module.symvers Mkfile.old dkms.conf -# ---> products -build/ +# ---> Program output +bin/ *.wav diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 7824099..7f82f7b 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -19,7 +19,7 @@ BITS_PER_BYTE = 8 # Paths PATHS = { "src_dir": "src", - "build_dir": "build", + "bin_dir": "bin", "template": "template.c", "substitute": "substituted.c", "output": "render_bytebeat", @@ -30,8 +30,8 @@ PATHS = { # Resolve paths PATHS["template"] = path_join(PATHS["src_dir"], PATHS["template"]) -PATHS["substitute"] = path_join(PATHS["build_dir"], PATHS["substitute"]) -PATHS["output"] = path_join(PATHS["build_dir"], PATHS["output"]) +PATHS["substitute"] = path_join(PATHS["bin_dir"], PATHS["substitute"]) +PATHS["output"] = 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 @@ -237,7 +237,7 @@ if __name__ == "__main__": args.signed = False # - Compilation - makedirs(PATHS["build_dir"], exist_ok=True) + 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 From 2724aecd649f072bf15e5962b5982e71e9a03a41 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 20 Aug 2024 19:45:44 +0300 Subject: [PATCH 23/58] fwrite_le.c: remove superfluous semicolon --- src/fwrite_le.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fwrite_le.c b/src/fwrite_le.c index 748de52..e470124 100644 --- a/src/fwrite_le.c +++ b/src/fwrite_le.c @@ -3,7 +3,7 @@ #define ORDER_NATIVE_U32 0x01234567 #define ORDER_LITTLE_ENDIAN_S4 "\x67\x45\x23\x01" #define ORDER_BIG_ENDIAN_S4 "\x01\x23\x45\x67" -#define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value; +#define ifeq_b32_ret(lhs, rhs, value) if (!memcmp(lhs, rhs, 4)) return value int detect_endianness(void) { volatile uint32_t native_order_value = ORDER_NATIVE_U32; From 1c271df50e72fdbe0773187bae75a25ad98eac87 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 20 Aug 2024 21:57:54 +0300 Subject: [PATCH 24/58] README.md: revert the name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 096259a..f7ac06c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# C-JS-bytebeat-render +# C-bytebeat-render > **Warning** > You're on a Work In Progress branch! From 6bc19e7160b2ba13132b3a3f0b3a4d7a4eb9e2d3 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 20 Aug 2024 21:58:09 +0300 Subject: [PATCH 25/58] README.md: extend description --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f7ac06c..2c8dc2e 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ > **Warning** > You're on a Work In Progress branch! -**Description:** a bytebeat rendering engine in C (no support of JavaScript -bytebeat yet) +**Description:** a bytebeat rendering engine in C for C-compatible bytebeat ## License From 65c54cf587832be020b5e5aa3e4baacd6b79a126 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 17:26:37 +0300 Subject: [PATCH 26/58] b/c...py: run the runtime unit after compiling --- EXAMPLE_USAGE.md | 3 ++- bytebeat_compiler.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/EXAMPLE_USAGE.md b/EXAMPLE_USAGE.md index 6976b9d..6346112 100644 --- a/EXAMPLE_USAGE.md +++ b/EXAMPLE_USAGE.md @@ -1,7 +1,8 @@ ## Example usage ```text -$ echo 't&((t>>7)-t)&t>>8' | python ./bytebeat_compiler.py - -p 44100 -v && ./build/render_bytebeat +$ echo 't&((t>>7)-t)&t>>8' | python ./bytebeat_compiler.py - -p 44100 -v +:: C bytebeat generator: compiler unit Compiling :: C bytebeat generator runtime unit diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 7f82f7b..d44217a 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -384,4 +384,10 @@ if __name__ == "__main__": ] print(" ".join(command), flush=True) + if subprocess.run(command).returncode != 0: + exit(1) + + command = [OUTPUT_FILE] + print(" ".join(command), flush=True) + exit(subprocess.run(command).returncode) From 27246e1b8bd1f88805b85f128e53e2a8f2a9d933 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 17:29:17 +0300 Subject: [PATCH 27/58] b/c...py: use `EXIT_SUCCESS` and `EXIT_FAILURE` --- bytebeat_compiler.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index d44217a..c975430 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -15,6 +15,8 @@ import subprocess # Definitions BITS_PER_BYTE = 8 +EXIT_FAILURE = 1 +EXIT_SUCCESS = 0 # Paths PATHS = { @@ -384,10 +386,13 @@ if __name__ == "__main__": ] print(" ".join(command), flush=True) - if subprocess.run(command).returncode != 0: - exit(1) + if subprocess.run(command).returncode != EXIT_SUCCESS: + exit(EXIT_FAILURE) command = [OUTPUT_FILE] print(" ".join(command), flush=True) - exit(subprocess.run(command).returncode) + if subprocess.run(command).returncode != EXIT_SUCCESS: + exit(EXIT_FAILURE) + + exit(EXIT_SUCCESS) From 6a1f10111df2d9a9085aa7611f31b0f17ef7aae8 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 17:36:09 +0300 Subject: [PATCH 28/58] b/c...py: extract function `run_command` --- bytebeat_compiler.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index c975430..af9056a 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -89,6 +89,11 @@ def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, print() return text +def run_command(*command: list[str]) -> None: + print(" ".join(command), flush=True) + if subprocess.run(command).returncode != EXIT_SUCCESS: + exit(EXIT_FAILURE) + preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' @@ -376,23 +381,14 @@ if __name__ == "__main__": # Compile print("Compiling") - command = [ + run_command( CC, *command_line_split(CFLAGS), INPUT_FILE, PATHS["fwrite_le"], "-o", OUTPUT_FILE, "-I" + PATHS["include_directory"] - ] - print(" ".join(command), flush=True) - - if subprocess.run(command).returncode != EXIT_SUCCESS: - exit(EXIT_FAILURE) - - command = [OUTPUT_FILE] - print(" ".join(command), flush=True) - - if subprocess.run(command).returncode != EXIT_SUCCESS: - exit(EXIT_FAILURE) + ) + run_command(OUTPUT_FILE) exit(EXIT_SUCCESS) From 0a8d19d5bd2208282055cde4da37da3cb965b819 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 17:38:58 +0300 Subject: [PATCH 29/58] b/c...py: notify the user when reading STDIN Notify the user when the script reads bytebeat code from STDIN --- bytebeat_compiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index af9056a..0dbb90b 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -67,6 +67,7 @@ def rewrite_file(path: str, content: str): def read_from_file_or_stdin(path: str) -> str: if path == "-": + print("Reading from STDIN...", flush=True) return "\n".join(stdin) elif exists(path): return read_file(path) From d17a5bff05c2fe31c557764d4b15f1fd4a2ee381 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 17:42:36 +0300 Subject: [PATCH 30/58] bytebeat_compiler.py: make use of `shlex.join` When printing a command that is going to be ran, use `shlex.join(...)` instead of `" ".join(...)` --- bytebeat_compiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 0dbb90b..d6591e6 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -6,7 +6,7 @@ if __name__ == "__main__": from argparse import ArgumentParser from os import environ, makedirs, name as os_name from os.path import exists, join as path_join -from shlex import split as command_line_split +from shlex import join as command_line_join, split as command_line_split from shutil import which from sys import stdin, stdout, exit from typing import Dict, Union @@ -91,7 +91,7 @@ def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, return text def run_command(*command: list[str]) -> None: - print(" ".join(command), flush=True) + print(command_line_join(command), flush=True) if subprocess.run(command).returncode != EXIT_SUCCESS: exit(EXIT_FAILURE) From 542126cb33bb05707d135b9d58e7ee096dc32eaa Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 19:26:00 +0300 Subject: [PATCH 31/58] bytebeat_compiler.py: do not use `sys.exit` --- bytebeat_compiler.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index d6591e6..b8c81e6 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -8,7 +8,7 @@ from os import environ, makedirs, name as os_name 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, exit +from sys import stdin, stdout from typing import Dict, Union import re import subprocess @@ -72,8 +72,7 @@ def read_from_file_or_stdin(path: str) -> str: elif exists(path): return read_file(path) else: - print("The specified file doesn't exist") - raise SystemExit + raise SystemExit(f"The specified file {path} doesn't exist") def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, verbose: bool) -> str: @@ -93,7 +92,7 @@ def substitute_vars(replacements: Dict[str, Union[bool, str]], text: str, def run_command(*command: list[str]) -> None: print(command_line_join(command), flush=True) if subprocess.run(command).returncode != EXIT_SUCCESS: - exit(EXIT_FAILURE) + raise SystemExit(EXIT_FAILURE) preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' @@ -206,8 +205,7 @@ if __name__ == "__main__": bytebeat_contents = read_from_file_or_stdin(args.file).strip() if not bytebeat_contents: - print("No valid contents") - exit(1) + raise SystemExit("No valid contents") # - Parse arguments from file used_parameter_line = False @@ -276,8 +274,7 @@ if __name__ == "__main__": samples_specified = not no_samples if seconds_specified and args.seconds < 0: - print("CLI: Count of seconds can't be less than zero.") - exit(1) + raise SystemExit("CLI: Count of seconds can't be less than zero.") if no_seconds and samples_specified: samples = args.samples @@ -289,17 +286,16 @@ if __name__ == "__main__": args.seconds = 30 # default continue else: - print("CLI: Incorrect seconds/samples length format.") - exit(1) + raise SystemExit("CLI: Incorrect seconds/samples length format.") break if samples <= 0: - print("CLI: Count of samples should be greater than zero.") - exit(1) + raise SystemExit("CLI: Count of samples should be greater than zero.") if args.mode != "sequential" and args.mode != "instant": - print("Invalid mode '%s'" % args.mode) - exit(1) + raise SystemExit(f"Invalid mode '{args.mode}'") + + gen_length = args.channels * samples ansi_escape_codes_supported = args.color == "auto" and stdout_atty or \ args.color == "always" @@ -375,9 +371,9 @@ if __name__ == "__main__": print() if still_unavailable: - print("Could not find an available compiler. Please specify it by " - "setting\nenvironmental variable CC.") - exit(2) + raise SystemExit("Could not find an available compiler. Please " + "specify it by setting\nenvironmental variable " + "CC.") # Compile print("Compiling") @@ -391,5 +387,3 @@ if __name__ == "__main__": "-I" + PATHS["include_directory"] ) run_command(OUTPUT_FILE) - - exit(EXIT_SUCCESS) From 12d3353208d0c7e86d22468e877d0dfb1521f371 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 21:39:36 +0300 Subject: [PATCH 32/58] bytebeat_compiler.py: fix grammar --- bytebeat_compiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index b8c81e6..1f045ef 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -205,7 +205,7 @@ if __name__ == "__main__": bytebeat_contents = read_from_file_or_stdin(args.file).strip() if not bytebeat_contents: - raise SystemExit("No valid contents") + raise SystemExit("Empty file or STDIN") # - Parse arguments from file used_parameter_line = False @@ -357,7 +357,7 @@ if __name__ == "__main__": }, read_file(PATHS["template"]), args.show_substituted_values)) if is_cmd_unavailable(CC): - print(f"Compiler {CC} not available, searching:") + print(f"Compiler {CC} is not available, searching:") still_unavailable = True for compiler in CC_SEARCH_LIST: @@ -372,7 +372,7 @@ if __name__ == "__main__": if still_unavailable: raise SystemExit("Could not find an available compiler. Please " - "specify it by setting\nenvironmental variable " + "specify it by setting\nan environmental variable " "CC.") # Compile From df6863a3f50591d2fe3057d1739c6c96a706300e Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 21:40:33 +0300 Subject: [PATCH 33/58] bytebeat_compiler.py: remove generated files --- bytebeat_compiler.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 1f045ef..170367a 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,7 +4,8 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser -from os import environ, makedirs, name as os_name +from os import environ, listdir, makedirs, name as os_name, \ + remove as delete_file, rmdir 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 @@ -94,6 +95,10 @@ def run_command(*command: list[str]) -> None: 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) + preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' @@ -200,6 +205,9 @@ if __name__ == "__main__": parser.add_argument("--color", default="auto", type=str, help="ANSI escape codes. Set to 'always' to enable them, 'none' to " "disable. Default: 'auto'.") + parser.add_argument("--keep-files", default=False, action="store_true", + help="keep generated files: substituted source code of runtime unit " + "and the executable it will be compiled to.") args = parser.parse_args() bytebeat_contents = read_from_file_or_stdin(args.file).strip() @@ -387,3 +395,8 @@ if __name__ == "__main__": "-I" + PATHS["include_directory"] ) run_command(OUTPUT_FILE) + + if not args.keep_files: + delete_file(PATHS["substitute"]) + delete_file(OUTPUT_FILE) + delete_empty_dir(PATHS["bin_dir"]) From 9d61e8305b3712aac512de1c9a1e6a2fc3a2f9d5 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:04:51 +0300 Subject: [PATCH 34/58] EXAMPLE_USAGE.md: update program output --- EXAMPLE_USAGE.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/EXAMPLE_USAGE.md b/EXAMPLE_USAGE.md index 6346112..b05f3a7 100644 --- a/EXAMPLE_USAGE.md +++ b/EXAMPLE_USAGE.md @@ -3,7 +3,10 @@ ```text $ echo 't&((t>>7)-t)&t>>8' | python ./bytebeat_compiler.py - -p 44100 -v :: 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! ``` From 81782ef23d028a6718b46ba98319451ba44b5d59 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:05:56 +0300 Subject: [PATCH 35/58] EXAMPLE_USAGE.md: use "--verbose" instead of "-v" --- EXAMPLE_USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXAMPLE_USAGE.md b/EXAMPLE_USAGE.md index b05f3a7..ce9b232 100644 --- a/EXAMPLE_USAGE.md +++ b/EXAMPLE_USAGE.md @@ -1,7 +1,7 @@ ## 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 From 0ab521e2ca6bd6da1830b4b8f16c2d2428f54d27 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:34:39 +0300 Subject: [PATCH 36/58] b/c...py: compile into a temporary directory Generate intermediate files into a temporary directory --- bytebeat_compiler.py | 111 ++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 170367a..1a89995 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.path import exists, join as path_join +from os import environ, makedirs, name as os_name, rename +from os.path import basename, 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 @@ -33,8 +33,8 @@ PATHS = { # 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 @@ -48,8 +48,8 @@ DEFAULT_PARAMETERS = { "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"] + "INPUT_FILE": PATHS["substitute_kept"], + "OUTPUT_FILE": PATHS["output_kept"] } stdout_atty = hasattr(stdout, "isatty") and stdout.isatty() @@ -95,10 +95,6 @@ def run_command(*command: list[str]) -> None: 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) - preprocessor_bool = lambda value: "1" if value else "0" C_str_repr = lambda s: '"' + s.replace("\\", "\\\\").replace(r'"', r'\"') + '"' @@ -251,8 +247,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,33 +331,6 @@ 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({ - "bytebeat_contents": bytebeat_contents, - "output_file": C_str_repr(args.output), - "sample_rate": actual_sample_rate, - "original_sample_rate": original_sample_rate, - "final_sample_rate_code": final_sample_rate_code, - "bit_depth": args.bit_depth, - "is_signed": args.signed, - "precalculated_ratio": args.precalculate_ratio, - "faster_sample_ratio_math": args.precalculate_ratio, - "fp_return_type": args.floating_point, - "channels": args.channels, - "running_length": samples, - "loop_end": loop_end, - "loop_end_minus_1": loop_end - 1, - "initial_time": skip_first_samples, - "repeat_times": args.repeat, - "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), - "gen_length": gen_length, - "sequential_mode": args.mode == "sequential", - "block_size": args.block_size, - "silent_mode": args.silent, - "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)) - if is_cmd_unavailable(CC): print(f"Compiler {CC} is not available, searching:") @@ -383,20 +350,54 @@ if __name__ == "__main__": "specify it by setting\nan environmental variable " "CC.") - # Compile - print("Compiling") + with TemporaryDirectory() as tmpdirname: + temporary_path = lambda path: path_join(tmpdirname, basename(path)) - run_command( - CC, - *command_line_split(CFLAGS), - INPUT_FILE, - PATHS["fwrite_le"], - "-o", OUTPUT_FILE, - "-I" + PATHS["include_directory"] - ) - run_command(OUTPUT_FILE) + substitute_temp = temporary_path(INPUT_FILE) + rewrite_file(substitute_temp, substitute_vars({ + "bytebeat_contents": bytebeat_contents, + "output_file": C_str_repr(args.output), + "sample_rate": actual_sample_rate, + "original_sample_rate": original_sample_rate, + "final_sample_rate_code": final_sample_rate_code, + "bit_depth": args.bit_depth, + "is_signed": args.signed, + "precalculated_ratio": args.precalculate_ratio, + "faster_sample_ratio_math": args.precalculate_ratio, + "fp_return_type": args.floating_point, + "channels": args.channels, + "running_length": samples, + "loop_end": loop_end, + "loop_end_minus_1": loop_end - 1, + "initial_time": skip_first_samples, + "repeat_times": args.repeat, + "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), + "gen_length": gen_length, + "sequential_mode": args.mode == "sequential", + "block_size": args.block_size, + "silent_mode": args.silent, + "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)) - if not args.keep_files: - delete_file(PATHS["substitute"]) - delete_file(OUTPUT_FILE) - delete_empty_dir(PATHS["bin_dir"]) + # Compile + print("Compiling") + + output_file_temp = temporary_path(OUTPUT_FILE) + + run_command( + CC, + *command_line_split(CFLAGS), + substitute_temp, + PATHS["fwrite_le"], + "-o", output_file_temp, + "-I" + PATHS["include_directory"] + ) + run_command(output_file_temp) + + if args.keep_files: + makedirs(PATHS["bin_dir"], exist_ok=True) + + rename(substitute_temp, INPUT_FILE) + rename(output_file_temp, OUTPUT_FILE) From c2d29a2d0f3d2f16ac947511eec4cbeba8f3d33b Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:44:58 +0300 Subject: [PATCH 37/58] b/c...py: secure input and output files Do not use environmental variables `INPUT_FILE` and `OUTPUT_FILE` --- bytebeat_compiler.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 1a89995..1fe338e 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -5,7 +5,7 @@ if __name__ == "__main__": from argparse import ArgumentParser from os import environ, makedirs, name as os_name, rename -from os.path import basename, exists, join as path_join +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 @@ -47,9 +47,7 @@ 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_kept"], - "OUTPUT_FILE": PATHS["output_kept"] + "-Wno-dangling-else -Wno-parentheses -std=c99" } stdout_atty = hasattr(stdout, "isatty") and stdout.isatty() @@ -112,8 +110,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 @@ -144,8 +140,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, @@ -351,9 +347,9 @@ if __name__ == "__main__": "CC.") with TemporaryDirectory() as tmpdirname: - temporary_path = lambda path: path_join(tmpdirname, basename(path)) + temporary_path = lambda path: path_join(tmpdirname, path) - substitute_temp = temporary_path(INPUT_FILE) + substitute_temp = temporary_path(PATHS["substitute"]) rewrite_file(substitute_temp, substitute_vars({ "bytebeat_contents": bytebeat_contents, "output_file": C_str_repr(args.output), @@ -384,7 +380,7 @@ if __name__ == "__main__": # Compile print("Compiling") - output_file_temp = temporary_path(OUTPUT_FILE) + output_file_temp = temporary_path(PATHS["output"]) run_command( CC, @@ -399,5 +395,5 @@ if __name__ == "__main__": if args.keep_files: makedirs(PATHS["bin_dir"], exist_ok=True) - rename(substitute_temp, INPUT_FILE) - rename(output_file_temp, OUTPUT_FILE) + rename(substitute_temp, PATHS["substitute_kept"]) + rename(output_file_temp, PATHS["output_kept"]) From 369d778604e27ae04264ea5137f5df89645e831e Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:54:02 +0300 Subject: [PATCH 38/58] bytebeat_compiler.py: fix extra dots in paths --- bytebeat_compiler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 1fe338e..46b24ff 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -31,17 +31,16 @@ PATHS = { "include_directory": "include" } +# Add `.` directory before all paths for compilation +for key in ["src_dir", "bin_dir", "include_directory"]: + PATHS[key] = path_join(".", PATHS[key]) + # Resolve paths PATHS["template"] = path_join(PATHS["src_dir"], PATHS["template"]) 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", From be2bad3a5d6dab6fc462d0972250e51219f4e2a0 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 22:58:40 +0300 Subject: [PATCH 39/58] b/c...py: do not use a temp. dir if keeping files Do not use a temporary directory if keeping files --- bytebeat_compiler.py | 103 +++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 46b24ff..d4c1e5e 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,7 +4,7 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser -from os import environ, makedirs, name as os_name, rename +from os import environ, makedirs, name as os_name 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 @@ -92,6 +92,24 @@ def run_command(*command: list[str]) -> None: if subprocess.run(command).returncode != EXIT_SUCCESS: raise SystemExit(EXIT_FAILURE) +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: + rewrite_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'\"') + '"' @@ -345,54 +363,45 @@ if __name__ == "__main__": "specify it by setting\nan environmental variable " "CC.") - with TemporaryDirectory() as tmpdirname: - temporary_path = lambda path: path_join(tmpdirname, path) + substitute_contents = substitute_vars({ + "bytebeat_contents": bytebeat_contents, + "output_file": C_str_repr(args.output), + "sample_rate": actual_sample_rate, + "original_sample_rate": original_sample_rate, + "final_sample_rate_code": final_sample_rate_code, + "bit_depth": args.bit_depth, + "is_signed": args.signed, + "precalculated_ratio": args.precalculate_ratio, + "faster_sample_ratio_math": args.precalculate_ratio, + "fp_return_type": args.floating_point, + "channels": args.channels, + "running_length": samples, + "loop_end": loop_end, + "loop_end_minus_1": loop_end - 1, + "initial_time": skip_first_samples, + "repeat_times": args.repeat, + "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), + "gen_length": gen_length, + "sequential_mode": args.mode == "sequential", + "block_size": args.block_size, + "silent_mode": args.silent, + "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) - substitute_temp = temporary_path(PATHS["substitute"]) - rewrite_file(substitute_temp, substitute_vars({ - "bytebeat_contents": bytebeat_contents, - "output_file": C_str_repr(args.output), - "sample_rate": actual_sample_rate, - "original_sample_rate": original_sample_rate, - "final_sample_rate_code": final_sample_rate_code, - "bit_depth": args.bit_depth, - "is_signed": args.signed, - "precalculated_ratio": args.precalculate_ratio, - "faster_sample_ratio_math": args.precalculate_ratio, - "fp_return_type": args.floating_point, - "channels": args.channels, - "running_length": samples, - "loop_end": loop_end, - "loop_end_minus_1": loop_end - 1, - "initial_time": skip_first_samples, - "repeat_times": args.repeat, - "wav_product": gen_length * (args.bit_depth // BITS_PER_BYTE), - "gen_length": gen_length, - "sequential_mode": args.mode == "sequential", - "block_size": args.block_size, - "silent_mode": args.silent, - "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)) + if args.keep_files: + makedirs(PATHS["bin_dir"], exist_ok=True) - # Compile - print("Compiling") + substitute_file = PATHS["substitute_kept"] + output_file = PATHS["output_kept"] - output_file_temp = temporary_path(PATHS["output"]) + main_workflow(substitute_file, output_file, substitute_contents) + else: + with TemporaryDirectory() as tmpdirname: + temporary_path = lambda path: path_join(tmpdirname, path) - run_command( - CC, - *command_line_split(CFLAGS), - substitute_temp, - PATHS["fwrite_le"], - "-o", output_file_temp, - "-I" + PATHS["include_directory"] - ) - run_command(output_file_temp) + substitute_temp = temporary_path(PATHS["substitute"]) + output_temp = temporary_path(PATHS["output"]) - if args.keep_files: - makedirs(PATHS["bin_dir"], exist_ok=True) - - rename(substitute_temp, PATHS["substitute_kept"]) - rename(output_file_temp, PATHS["output_kept"]) + main_workflow(substitute_temp, output_temp, substitute_contents) From e6afd3574b0d7f9cb059c10eeca538f304d8eaa1 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 23:26:59 +0300 Subject: [PATCH 40/58] b/c...py: use `os.getcwd()` instead of `.` --- bytebeat_compiler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index d4c1e5e..53e41fc 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,7 +4,7 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser -from os import environ, makedirs, name as os_name +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 @@ -31,9 +31,10 @@ PATHS = { "include_directory": "include" } -# Add `.` directory before all paths for compilation +# Add current directory before all paths for compilation +CURRENT_DIRECTORY = getcwd() for key in ["src_dir", "bin_dir", "include_directory"]: - PATHS[key] = path_join(".", PATHS[key]) + PATHS[key] = path_join(CURRENT_DIRECTORY, PATHS[key]) # Resolve paths PATHS["template"] = path_join(PATHS["src_dir"], PATHS["template"]) From d51ee9e0d67d41f86c0d21d825c3075b6767b89f Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 23:28:52 +0300 Subject: [PATCH 41/58] bytebeat_compiler.py: reformat command printing --- bytebeat_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 53e41fc..48444ce 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -89,7 +89,7 @@ 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) From 9a98fb55e37471b520223c513c0a0af217d57e36 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 23:29:52 +0300 Subject: [PATCH 42/58] b/c...py: type hint function `rewrite_file` completely --- bytebeat_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 48444ce..994915b 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -61,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 rewrite_file(path: str, content: str) -> int: return open(path, "w", encoding="utf-8").write(content) def read_from_file_or_stdin(path: str) -> str: From 8511227eaa865d1d4a21e7f86f85ca1a870f0c38 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 26 Aug 2024 23:31:30 +0300 Subject: [PATCH 43/58] b/c...py: rename `rewrite_file` to `overwrite_file` --- bytebeat_compiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 994915b..e7b3396 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -61,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) -> int: +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: @@ -108,7 +108,7 @@ def compile_substituted_file(input_file: str, output_file: str) -> None: def main_workflow(input_file: str, output_file: str, \ substitute_contents: Dict[str, str]) -> None: - rewrite_file(input_file, substitute_contents) + overwrite_file(input_file, substitute_contents) compile_substituted_file(input_file, output_file) preprocessor_bool = lambda value: "1" if value else "0" From 1d79faebe083a65d0171179f96e9e5d537ec19f1 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 26 Jul 2024 17:07:07 +0300 Subject: [PATCH 44/58] remove gitignore_clean.py --- gitignore_clean.py | 65 ---------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 gitignore_clean.py 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"(? Date: Fri, 16 Aug 2024 19:57:19 +0300 Subject: [PATCH 45/58] README.md: swap license order and rephrase to clarify 'or' option --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c8dc2e..63d8431 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,6 @@ ## 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 +([`COPYING`](COPYING)) or [Unlicense](http://unlicense.org) +([`LICENSE`](LICENSE)). From f66fda1a03e30737504568cac7734240924a65c2 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 16 Aug 2024 20:13:31 +0300 Subject: [PATCH 46/58] README.md: Unlicense: update the protocol of the link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63d8431..6ab17c8 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,5 @@ ## License Dual-licensed under the Creative Commons Zero 1.0 Universal -([`COPYING`](COPYING)) or [Unlicense](http://unlicense.org) +([`COPYING`](COPYING)) or [Unlicense](https://unlicense.org) ([`LICENSE`](LICENSE)). From e2ebdcdf1748274c7363eba33d3d29e53b8b5dbc Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Fri, 16 Aug 2024 20:15:05 +0300 Subject: [PATCH 47/58] README.md: add links to both licenses --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6ab17c8..867203e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ ## License -Dual-licensed under the Creative Commons Zero 1.0 Universal -([`COPYING`](COPYING)) or [Unlicense](https://unlicense.org) -([`LICENSE`](LICENSE)). +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 From ebf5e703f1941f2690e1c012cbeeaff08e7dc393 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Tue, 27 Aug 2024 00:41:34 +0300 Subject: [PATCH 48/58] EXAMPLE_USAGE.md: update program output --- EXAMPLE_USAGE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EXAMPLE_USAGE.md b/EXAMPLE_USAGE.md index ce9b232..c24e3fb 100644 --- a/EXAMPLE_USAGE.md +++ b/EXAMPLE_USAGE.md @@ -5,8 +5,8 @@ $ 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 +[>] 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 From a4de44f08c78687b2ad77ed12ac789aa12359089 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Sat, 31 Aug 2024 03:17:57 +0300 Subject: [PATCH 49/58] documentation.md: update the case of the title --- documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation.md b/documentation.md index 35b21d5..3799114 100644 --- a/documentation.md +++ b/documentation.md @@ -1,4 +1,4 @@ -# C\_bytebeat\_render +# C-bytebeat-render ## Bytebeat code From d4483576083f5dfdc786f280a488c7604aeda1bd Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 00:43:25 +0300 Subject: [PATCH 50/58] bytebeat_compiler.py: support fractional seconds --- bytebeat_compiler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index e7b3396..a281854 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -4,6 +4,8 @@ if __name__ == "__main__": print(":: C bytebeat generator: compiler unit") from argparse import ArgumentParser +from decimal import Decimal +from math import ceil 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 @@ -187,7 +189,7 @@ if __name__ == "__main__": action="store_true", help="use floating point as the return type") parser.add_argument("-c", "--channels", default=None, type=int, help="amount of channels") - parser.add_argument("-t", "--seconds", default=None, type=int, + parser.add_argument("-t", "--seconds", default=None, type=Decimal, help="length in seconds (samples = sample rate * seconds) : " "default = 30 seconds") parser.add_argument("-l", "--samples", default=None, type=int, @@ -308,6 +310,9 @@ if __name__ == "__main__": if samples <= 0: raise SystemExit("CLI: Count of samples should be greater than zero.") + # round the number of samples + samples = ceil(samples) + if args.mode != "sequential" and args.mode != "instant": raise SystemExit(f"Invalid mode '{args.mode}'") From 214abeb18154066b1aee6dcc3edd172ea679a654 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 00:43:25 +0300 Subject: [PATCH 51/58] C: fix file I/O code --- src/template.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/template.c b/src/template.c index 19b6d3d..8237fa6 100644 --- a/src/template.c +++ b/src/template.c @@ -9,6 +9,38 @@ #include "`fwrite_le`" +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L + # define FSEEK_MACRO fseeko + # define FSEEK_FUNCTION_NAME "fseeko" +typedef off_t file_offset_t; +#else + # define FSEEK_MACRO fseek + # define FSEEK_FUNCTION_NAME "fseek" +typedef int file_offset_t; +#endif + +#define STRINGIZE(x) #x + +#define FILE_IO_NO_FAIL(function, ptr, size, nitems, stream) do { \ + if (function(ptr, size, nitems, stream) != nitems) { \ + /* clean up */ \ + free(buffer); \ + \ + perror(STRINGIZE(function)); \ + exit(EXIT_FAILURE); \ + } \ +} while (0) + +#define FSEEK_NO_FAIL(...) do { \ + if (FSEEK_MACRO(__VA_ARGS__) == -1) { \ + perror(FSEEK_FUNCTION_NAME); \ + exit(EXIT_FAILURE); \ + } \ +} while (0) + +#define FREAD_NO_FAIL(...) FILE_IO_NO_FAIL(fread, __VA_ARGS__) +#define FWRITE_NO_FAIL(...) FILE_IO_NO_FAIL(fwrite, __VA_ARGS__) + // typedefs typedef uintmax_t bb_counter_t; typedef long double bb_fp_return_t; @@ -418,7 +450,7 @@ main(void) for (size_t counter = 0; counter < REPEAT_TIMES; counter++) { # if SEQUENTIAL_MODE - off_t position_read = header_size; + file_offset_t position_read = header_size; calc_block_size = BLOCK_SIZE; for (size_t seq = 0, time = 0; seq < MAX; seq++, time += BLOCK_SIZE) { @@ -428,10 +460,12 @@ main(void) end = true; } - fseeko(output_file, position_read, SEEK_SET); - fread(buffer, sizeof(SAMPLE_TYPE), calc_block_size, output_file); - fseeko(output_file, 0, SEEK_END); - fwrite(buffer, sizeof(SAMPLE_TYPE), calc_block_size, output_file); + FSEEK_NO_FAIL(output_file, position_read, SEEK_SET); + FREAD_NO_FAIL(buffer, sizeof(SAMPLE_TYPE), calc_block_size, + output_file); + FSEEK_NO_FAIL(output_file, 0, SEEK_END); + FWRITE_NO_FAIL(buffer, sizeof(SAMPLE_TYPE), calc_block_size, + output_file); if (end) break; From f2846228e1c5f880fb555bb8aa9910f351a5aabe Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 00:50:02 +0300 Subject: [PATCH 52/58] C: ensure `uintmax_t` type when printing --- src/template.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/template.c b/src/template.c index 8237fa6..bdb09a6 100644 --- a/src/template.c +++ b/src/template.c @@ -393,7 +393,7 @@ main(void) #endif , _ANSI_CLEAR_STRING, - LOOP_END_MINUS_1 - time, + (uintmax_t) (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 From 988ed5c4755bb362fd7ca0623dc01dfdf3df4a3d Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 00:58:51 +0300 Subject: [PATCH 53/58] samples: add pwm-sierpinski.c --- samples/pwm-sierpinski.c | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 samples/pwm-sierpinski.c diff --git a/samples/pwm-sierpinski.c b/samples/pwm-sierpinski.c new file mode 100644 index 0000000..b6fa0bb --- /dev/null +++ b/samples/pwm-sierpinski.c @@ -0,0 +1,6 @@ +// "pwm serpinski harmony" by SArpnt +// URL: https://www.reddit.com/r/bytebeat/comments/g9106h/pwm_serpinski_harmony/ + +// RENDER PARAMETERS: sample_rate = 8000, final_sample_rate = 44100 + +t&(t>>7)-t&t>>8 From 7e2347d8e7260df04331c3ffc1c659f5fb4e5f32 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 01:14:36 +0300 Subject: [PATCH 54/58] samples: melody.c: use `static` keyword for the array --- samples/melody.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/melody.c b/samples/melody.c index 9c4ba5c..8e3776c 100644 --- a/samples/melody.c +++ b/samples/melody.c @@ -1,5 +1,5 @@ // RENDER PARAMETERS: sample_rate = 44100, no_return -const long double array[] = {1, 1.25, 1.5, 2}; +static const long double array[] = {1, 1.25, 1.5, 2}; long double v = array[3 & (t >> 13)]; return (long double) t * v / 3.1415926535; From 301ad4292b6a29f8dafd41e0fb1d31d130962964 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 01:15:31 +0300 Subject: [PATCH 55/58] samples: melody.c: add a FIXME --- samples/melody.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/melody.c b/samples/melody.c index 8e3776c..19a6530 100644 --- a/samples/melody.c +++ b/samples/melody.c @@ -1,5 +1,7 @@ // RENDER PARAMETERS: sample_rate = 44100, no_return +// FIXME: The sound disappears after a few cycles + static const long double array[] = {1, 1.25, 1.5, 2}; long double v = array[3 & (t >> 13)]; return (long double) t * v / 3.1415926535; From 376c7be2211b3e32b8e738e2bb3acf4cf833f720 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 01:21:13 +0300 Subject: [PATCH 56/58] use name `custom_return_code` instead of `no_return` --- bytebeat_compiler.py | 12 +++++++----- samples/beeper.c | 2 +- samples/melody.c | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index a281854..5ebcab1 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -141,7 +141,7 @@ ALLOWED_ARGUMENTS_FROM_FILE = ( "bit_depth", "signed", "channels", - "no_return", + "custom_return_code", ) DEFAULT_ARGS = { @@ -150,7 +150,7 @@ DEFAULT_ARGS = { "bit_depth": 8, "signed": False, "channels": 1, - "no_return": False + "custom_return_code": False } is_cmd_available = lambda cmd: which(cmd) is not None @@ -201,8 +201,9 @@ if __name__ == "__main__": parser.add_argument("-k", "--repeat", default=0, type=int, help="how many times to repeat the bytebeat : " "default = 0") - parser.add_argument("-a", "--no-return", default=None, action="store_true", - help="do not insert return statement before the code") + parser.add_argument("-a", "--custom-return-code", default=None, + action="store_true", + help="do not insert return statement before the code") parser.add_argument("-U", "--mode", default="sequential", type=str, help="mode of writing: `sequential` or `instant` (the latter is not " "recommended, since the whole result would be stored in RAM)") @@ -237,6 +238,7 @@ if __name__ == "__main__": parsed_parameters = line[match.start(0):].split(",") for parameter in parsed_parameters: kv = [x.strip() for x in parameter.split("=")] + kv[0] = kv[0].split(" ")[-1] key = None value = None @@ -263,7 +265,7 @@ if __name__ == "__main__": args.signed = False # - Compilation - if not args.no_return: # Insert `return` statement + if not args.custom_return_code: # 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. bytebeat_contents = f"return ({bytebeat_contents})" diff --git a/samples/beeper.c b/samples/beeper.c index 54034a7..72d571f 100644 --- a/samples/beeper.c +++ b/samples/beeper.c @@ -1,4 +1,4 @@ -// RENDER PARAMETERS: sample_rate = 44100, no_return +// RENDER PARAMETERS: sample_rate = 44100, custom_return_code bb_counter_t u = t << 1; SAMPLE_TYPE x = u & u >> 8; diff --git a/samples/melody.c b/samples/melody.c index 19a6530..e136dc8 100644 --- a/samples/melody.c +++ b/samples/melody.c @@ -1,4 +1,4 @@ -// RENDER PARAMETERS: sample_rate = 44100, no_return +// RENDER PARAMETERS: sample_rate = 44100, custom_return_code // FIXME: The sound disappears after a few cycles From 259107b2b6e186345d5144b11143172d893853ea Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 02:25:24 +0300 Subject: [PATCH 57/58] b/c...py: skip_first: support fractional seconds --- bytebeat_compiler.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 5ebcab1..602456b 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -54,6 +54,18 @@ DEFAULT_PARAMETERS = { stdout_atty = hasattr(stdout, "isatty") and stdout.isatty() +def is_decimal_number(s: str) -> bool: + if s.count('.') > 1: # More than one decimal point + return False + + if s.startswith(('+', '-')): + s = s[1:] # Remove the sign for further checks + + if s.replace('.', '', 1).isdigit(): + return True + + return False + def fetch(name: str): if from_env := environ.get(name): return from_env @@ -328,17 +340,22 @@ if __name__ == "__main__": # - Parse the '--skip-first' argument if not args.skip_first is None: + encountered_point = False encountered_s = False for character in args.skip_first: - if character.isdigit() or character == "s" and not encountered_s: - if character == "s": + if character.isdigit() or character == "." and \ + not encountered_point or character == "s" and not encountered_s: + if character == ".": + encountered_point = True + elif character == "s": encountered_s = True else: - print(f"Invalid --skip-first format: `{args.skip_first}`") - exit(1) + raise SystemExit( "Invalid --skip-first format: " + f"`{args.skip_first}`") skip_first = \ - [int(x) if x.isdigit() else 0 for x in args.skip_first.split("s")] + [Decimal(x) if is_decimal_number(x) else 0 for x in \ + args.skip_first.split("s")] skip_first_samples = 0 if len(skip_first) == 1: @@ -348,6 +365,9 @@ if __name__ == "__main__": else: skip_first_samples = 0 + # round the number of skipped first samples + skip_first_samples = ceil(skip_first_samples) + length_formula = lambda channels, samples, n: channels * (samples + n) gen_length = length_formula(args.channels, samples, 0) loop_end = length_formula(args.channels, samples, skip_first_samples) From 2f900ccbc8dce26d78bc3e7534817f880bff1ba3 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Mon, 23 Sep 2024 02:38:14 +0300 Subject: [PATCH 58/58] bytebeat_compiler.py: simplify the condition --- bytebeat_compiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bytebeat_compiler.py b/bytebeat_compiler.py index 602456b..d105c3f 100644 --- a/bytebeat_compiler.py +++ b/bytebeat_compiler.py @@ -339,7 +339,9 @@ if __name__ == "__main__": value if (value := args.final_sample_rate) else args.sample_rate # - Parse the '--skip-first' argument - if not args.skip_first is None: + if args.skip_first is None: + skip_first_samples = 0 + else: encountered_point = False encountered_s = False for character in args.skip_first: @@ -362,8 +364,6 @@ if __name__ == "__main__": skip_first += [0] skip_first_samples = skip_first[0] * actual_sample_rate + skip_first[1] - else: - skip_first_samples = 0 # round the number of skipped first samples skip_first_samples = ceil(skip_first_samples)