initial commit
This commit is contained in:
commit
ae04b22c7a
12
.clang-format
Normal file
12
.clang-format
Normal file
@ -0,0 +1,12 @@
|
||||
BasedOnStyle: Mozilla
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignTrailingComments: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
ColumnLimit: 80
|
||||
IndentWidth: 4
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
TabWidth: 4
|
||||
UseTab: Always
|
66
.gitignore
vendored
Normal file
66
.gitignore
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# ---> products
|
||||
# Substituted code
|
||||
formula_substituted.*
|
||||
|
||||
# Executables
|
||||
*.a
|
||||
*.out
|
||||
*.exe
|
||||
render_bytebeat.*
|
||||
|
||||
# Output
|
||||
output.wav
|
15
EXAMPLE_USAGE.txt
Normal file
15
EXAMPLE_USAGE.txt
Normal file
@ -0,0 +1,15 @@
|
||||
$ cat | ./bytebeat_compiler.py - && ./render_bytebeat
|
||||
t&(t>>7)-t&t>>8
|
||||
Compiling
|
||||
:: C bytebeat generator runtime unit
|
||||
|
||||
Sample rate: 44100 Hz
|
||||
Channels: 1 (mono)
|
||||
Bit depth: unsigned 8-bit
|
||||
Duration: 30 seconds
|
||||
|
||||
remaining samples = 0 (100.00% done)
|
||||
Writing out file output.wav...
|
||||
Done!
|
||||
|
||||
$
|
10
LICENSE
Normal file
10
LICENSE
Normal file
@ -0,0 +1,10 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# C_bytebeat_render
|
||||
|
||||
a bytebeat rendering engine in C (no support of JavaScript bytebeat)
|
99
bytebeat_compiler.py
Normal file
99
bytebeat_compiler.py
Normal file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
DEFAULT_PARAMETERS = {
|
||||
"CC": "gcc",
|
||||
"CC_FLAGS": "-Os -Wall -Werror -Wpedantic",
|
||||
"INPUT_FILES": ["engine.c", "formula_substituted.c"],
|
||||
"OUTPUT_FILE": "render_bytebeat"
|
||||
}
|
||||
|
||||
from os.path import exists
|
||||
from os import system, environ
|
||||
from argparse import ArgumentParser
|
||||
from sys import stdin
|
||||
import subprocess
|
||||
|
||||
def fetch(name: str):
|
||||
return res if (res := environ.get(name)) else DEFAULT_PARAMETERS[name]
|
||||
|
||||
def read_file(path: str) -> str:
|
||||
return open(path, "r", encoding="utf-8-sig").read()
|
||||
|
||||
def rewrite_file(path: str, content: str):
|
||||
return open(path, "w", encoding="utf-8-sig").write(content)
|
||||
|
||||
def read_from_file_or_stdin(path: str) -> str:
|
||||
if path == "-":
|
||||
return "\n".join(stdin)
|
||||
elif exists(path):
|
||||
return read_file(path)
|
||||
else:
|
||||
print("The specified file doesn't exist")
|
||||
raise SystemExit
|
||||
|
||||
def substitute_value(placeholder: str, replacement, text: str) -> str:
|
||||
return text.replace(f"`{placeholder}`", str(replacement))
|
||||
|
||||
CC = fetch("CC")
|
||||
CC_FLAGS = fetch("CC_FLAGS")
|
||||
INPUT_FILES = fetch("INPUT_FILES")
|
||||
OUTPUT_FILE = fetch("OUTPUT_FILE")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Bytebeat compiler")
|
||||
|
||||
parser = ArgumentParser(description=\
|
||||
"Substitutes supplied C (non-JavaScript!) bytebeat into the template, "
|
||||
"then attempts to compile the instance of the template. Uses "
|
||||
"environmental variables `CC`, `CC_FLAGS`, `INPUT_FILES`, "
|
||||
"`OUTPUT_FILE`.")
|
||||
parser.add_argument("file", type=str,
|
||||
help="bytebeat formula file")
|
||||
parser.add_argument("-r", "--sample-rate", default=8000, type=int,
|
||||
help="sample rate (Hz)")
|
||||
parser.add_argument("-b", "--bit-depth", default=8, type=int,
|
||||
help="bit depth")
|
||||
parser.add_argument("-s", "--signed", default=False, action="store_true",
|
||||
help="is signed?")
|
||||
parser.add_argument("-c", "--channels", default=1, type=int,
|
||||
help="amount of channels")
|
||||
parser.add_argument("-t", "--seconds", default=30, type=int,
|
||||
help="length (seconds)")
|
||||
parser.add_argument("-a", "--no-return", default=False, action="store_true",
|
||||
help="do not insert return statement before the code")
|
||||
args = parser.parse_args()
|
||||
|
||||
bytebeat_contents = read_from_file_or_stdin(args.file).strip()
|
||||
|
||||
if not bytebeat_contents:
|
||||
print("No valid contents")
|
||||
raise SystemExit
|
||||
|
||||
# Substitute all placeholders in formula_template.c -> formula_substitute.c
|
||||
if not args.no_return: # Insert return statement
|
||||
bytebeat_contents = f"\treturn\n\n{bytebeat_contents}"
|
||||
|
||||
substitute_c = read_file("formula_template.c")
|
||||
substitute_c = substitute_value("bytebeat_contents",
|
||||
bytebeat_contents, substitute_c)
|
||||
rewrite_file("formula_substituted.c", substitute_c)
|
||||
|
||||
# Substitute all placeholders in formula_template.h -> formula_substitute.h
|
||||
substitute_h = read_file("formula_template.h")
|
||||
substitute_h = substitute_value("sample_rate",
|
||||
args.sample_rate, substitute_h)
|
||||
substitute_h = substitute_value("bit_depth",
|
||||
args.bit_depth, substitute_h)
|
||||
substitute_h = substitute_value("is_signed",
|
||||
"1" if args.signed else "0", substitute_h)
|
||||
substitute_h = substitute_value("channels",
|
||||
args.channels, substitute_h)
|
||||
substitute_h = substitute_value("seconds",
|
||||
args.seconds, substitute_h)
|
||||
rewrite_file("formula_substituted.h", substitute_h)
|
||||
|
||||
# Compile by invoking the shell script
|
||||
print("Compiling")
|
||||
|
||||
# Let system execute aliases by calling os.system
|
||||
system(" ".join([CC, CC_FLAGS, *INPUT_FILES, "-o", OUTPUT_FILE]))
|
181
engine.c
Normal file
181
engine.c
Normal file
@ -0,0 +1,181 @@
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "formula_substituted.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define __ANSI_CLEAR_STRING "\r"
|
||||
#elif defined(__unix__) || defined(__linux__)
|
||||
#define __ANSI_CLEAR_STRING "\x1B[2K\r"
|
||||
#else
|
||||
#define __ANSI_CLEAR_STRING "\n"
|
||||
#endif
|
||||
const char* ANSI_CLEAR = __ANSI_CLEAR_STRING;
|
||||
|
||||
#define PRODUCT (SAMPLE_RATE * SECONDS * CHANNELS)
|
||||
#define FREQUENCY_OF_STATUS_REPORTING 5000
|
||||
|
||||
#define BIT_DEPTH_LIMITER ((1 << BIT_DEPTH) - 1)
|
||||
#define PCM_COEFFICIENT ((1 << (BIT_DEPTH - 1)) - 1)
|
||||
#define unsigned_to_signed(x) (x - PCM_COEFFICIENT)
|
||||
#define signed_to_unsigned(x) (x + PCM_COEFFICIENT)
|
||||
|
||||
unsigned int dbgpnt_counter = 0;
|
||||
const char* dbgpnt_labels[3] = { "memory allocation",
|
||||
"bytebeat generation",
|
||||
"writing file" };
|
||||
#define dbgpnt_labels_size \
|
||||
(unsigned int)(sizeof(dbgpnt_labels) / sizeof(dbgpnt_labels[0]))
|
||||
|
||||
bool silent_mode = 0;
|
||||
bool debug_mode = 0;
|
||||
|
||||
void
|
||||
debug_print(void)
|
||||
{
|
||||
if (!debug_mode)
|
||||
return;
|
||||
|
||||
int has_label = dbgpnt_counter <= dbgpnt_labels_size;
|
||||
printf("[OK] ");
|
||||
|
||||
if (has_label)
|
||||
printf("%s", dbgpnt_labels[dbgpnt_counter++]);
|
||||
else
|
||||
printf("debug point %d", ++dbgpnt_counter);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long double
|
||||
random(void)
|
||||
{
|
||||
static int initialized = 0;
|
||||
|
||||
if (!initialized) {
|
||||
srand(time(NULL));
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
return (long double)rand() / RAND_MAX;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
silent_mode = argc > 1 && argv[1] != NULL &&
|
||||
(!strcmp(argv[1], "-s") || !strcmp(argv[1], "--silent"));
|
||||
debug_mode = argc > 1 && argv[1] != NULL &&
|
||||
(!strcmp(argv[1], "-d") || !strcmp(argv[1], "--debug"));
|
||||
|
||||
printf(":: C bytebeat generator runtime unit\n");
|
||||
fflush(stdout);
|
||||
|
||||
if (!silent_mode) {
|
||||
printf(
|
||||
"\n"
|
||||
"Sample rate: %d Hz\n"
|
||||
"Channels: %d%s\n"
|
||||
"Bit depth: %ssigned %d-bit\n"
|
||||
"Duration: ",
|
||||
SAMPLE_RATE,
|
||||
CHANNELS,
|
||||
CHANNELS == 1 ? " (mono)" : (CHANNELS > 2 ? "" : " (stereo)"),
|
||||
IS_SIGNED ? "" : "un",
|
||||
BIT_DEPTH);
|
||||
|
||||
if (SECONDS >= 3600)
|
||||
printf(
|
||||
"%d:%02d:%02d",
|
||||
SECONDS / 3600,
|
||||
(SECONDS / 60) % 60,
|
||||
SECONDS % 60);
|
||||
else if (SECONDS >= 60)
|
||||
printf("%d:%02d", SECONDS / 60, SECONDS % 60);
|
||||
else
|
||||
printf("%d seconds", SECONDS);
|
||||
|
||||
printf("\n\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
SAMPLE_TYPE* q = calloc(PRODUCT, sizeof(SAMPLE_TYPE));
|
||||
|
||||
if (q == NULL)
|
||||
return 1;
|
||||
|
||||
debug_print();
|
||||
|
||||
for (uintmax_t w = 0; w < PRODUCT; w++) {
|
||||
long double res = bytebeat((long double)w);
|
||||
|
||||
if (IS_SIGNED)
|
||||
q[w] = (SAMPLE_TYPE)signed_to_unsigned(res) & BIT_DEPTH_LIMITER;
|
||||
else
|
||||
q[w] = (SAMPLE_TYPE)res & BIT_DEPTH_LIMITER;
|
||||
|
||||
#if BIT_DEPTH < 8
|
||||
q[(size_t)w] = (SAMPLE_TYPE)((long double)q[(size_t)w] *
|
||||
((long double)BIT_DEPTH / 8.0));
|
||||
#endif
|
||||
|
||||
if (
|
||||
!silent_mode &&
|
||||
(w % FREQUENCY_OF_STATUS_REPORTING == 0 || w >= PRODUCT - 1)) {
|
||||
printf(
|
||||
"%sremaining samples = %18" PRIuMAX " (%.2Lf%% done)",
|
||||
ANSI_CLEAR,
|
||||
PRODUCT - w - 1,
|
||||
(long double)w * 100 / (long double)PRODUCT);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s", ANSI_CLEAR);
|
||||
debug_print();
|
||||
|
||||
printf("\nWriting out file output.wav...\n");
|
||||
fflush(stdout);
|
||||
FILE* r = fopen("output.wav", "wb");
|
||||
|
||||
if (r == NULL || !r)
|
||||
return 1;
|
||||
|
||||
fwrite("RIFF", 1, 4, r);
|
||||
uint32_t t = PRODUCT;
|
||||
fwrite(&t, sizeof(t), 1, r);
|
||||
fwrite("WAVE", 1, 4, r);
|
||||
fwrite("fmt ", 1, 4, r);
|
||||
uint32_t y = 16; // what? 16-bit depth? or [something else]?
|
||||
fwrite(&y, sizeof(y), 1, r);
|
||||
uint16_t u = 1; // 1 what? 1 = unsigned?
|
||||
fwrite(&u, sizeof(u), 1, r);
|
||||
uint16_t i = CHANNELS;
|
||||
fwrite(&i, sizeof(i), 1, r);
|
||||
uint32_t o = SAMPLE_RATE;
|
||||
fwrite(&o, sizeof(o), 1, r);
|
||||
uint32_t s = SAMPLE_RATE * i;
|
||||
fwrite(&s, sizeof(s), 1, r);
|
||||
uint16_t p = i;
|
||||
fwrite(&p, sizeof(p), 1, r);
|
||||
uint16_t a = BIT_DEPTH >= 8 ? BIT_DEPTH : 8;
|
||||
|
||||
fwrite(&a, sizeof(a), 1, r);
|
||||
fwrite("data", 1, 4, r);
|
||||
fwrite(&t, sizeof(t), 1, r);
|
||||
fwrite(q, sizeof(uint8_t), PRODUCT, r);
|
||||
fclose(r);
|
||||
debug_print();
|
||||
|
||||
free(q);
|
||||
|
||||
printf("Done!\n");
|
||||
|
||||
return 0;
|
||||
}
|
13
formula_template.c
Normal file
13
formula_template.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "formula_substituted.h"
|
||||
|
||||
SAMPLE_TYPE
|
||||
bytebeat(long double w)
|
||||
{
|
||||
uintmax_t t = (uintmax_t)w;
|
||||
|
||||
`bytebeat_contents`
|
||||
|
||||
;
|
||||
}
|
23
formula_template.h
Normal file
23
formula_template.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef _FORMULA_TEMPLATE_H
|
||||
#define _FORMULA_TEMPLATE_H
|
||||
|
||||
#define SAMPLE_RATE `sample_rate`
|
||||
#define BIT_DEPTH `bit_depth`
|
||||
#define IS_SIGNED `is_signed`
|
||||
#define CHANNELS `channels`
|
||||
#define SECONDS `seconds`
|
||||
|
||||
#if BIT_DEPTH <= 8
|
||||
#define SAMPLE_TYPE uint8_t
|
||||
#elif BIT_DEPTH >= 16
|
||||
#if IS_SIGNED
|
||||
#define SAMPLE_TYPE int16_t
|
||||
#else
|
||||
#define SAMPLE_TYPE uint16_t
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SAMPLE_TYPE
|
||||
bytebeat(long double w);
|
||||
|
||||
#endif /* _FORMULA_TEMPLATE_H */
|
65
gitignore_clean.py
Normal file
65
gitignore_clean.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/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"(?<!\\)#.*$", "", s).replace(r"\#", "#")
|
||||
|
||||
|
||||
def read_gitignore():
|
||||
res = ""
|
||||
with open(GITIGNORE_PATH, "r", encoding="utf-8-sig") as gitignore_file:
|
||||
res = gitignore_file.read().splitlines()
|
||||
return res
|
||||
|
||||
|
||||
def delete(file_path: str):
|
||||
is_dir = path_isdir(file_path)
|
||||
is_file = path_isfile(file_path)
|
||||
|
||||
if not (is_dir or is_file):
|
||||
return
|
||||
|
||||
display_file_path = abspath(file_path)
|
||||
if is_dir:
|
||||
print("Removing directory", display_file_path)
|
||||
elif is_file:
|
||||
print("Removing file", display_file_path)
|
||||
|
||||
if DRY_RUN:
|
||||
return
|
||||
|
||||
if is_dir:
|
||||
rmtree(file_path, ignore_errors=True)
|
||||
elif is_file:
|
||||
os_remove(file_path)
|
||||
|
||||
|
||||
def extend_wildcards(patterns: list):
|
||||
res = []
|
||||
for pattern in patterns:
|
||||
processed_line = remove_comments(pattern).strip()
|
||||
|
||||
if processed_line.startswith("!"):
|
||||
exception_pattern = processed_line[1:]
|
||||
exceptions = glob(exception_pattern, recursive=True)
|
||||
res = [path for path in res if path not in exceptions]
|
||||
else:
|
||||
res.extend(glob(processed_line, recursive=True))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def clean_gitignored_files():
|
||||
for file_path in extend_wildcards(read_gitignore()):
|
||||
delete(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
clean_gitignored_files()
|
Loading…
Reference in New Issue
Block a user