Skip to content

Instantly share code, notes, and snippets.

@ology
Created June 3, 2026 20:52
Show Gist options
  • Select an option

  • Save ology/481c656ee7f8bca1e18a1663fcbd5444 to your computer and use it in GitHub Desktop.

Select an option

Save ology/481c656ee7f8bca1e18a1663fcbd5444 to your computer and use it in GitHub Desktop.
Adding a volume_scale setting. Untested!
import binascii
import random
def make_config(args):
config = {
"glitch_prob": 5,
"hex_min": 0,
"hex_max": 16,
"frame_min": 0,
"frame_max": 1,
"frame_spacing_min": 1,
"frame_spacing_max": 1,
"glitch_width": 8,
"max_glitches_per_frame": 0,
"glitch_volume_scale": 0.5, # Volume multiplier when glitch occurs (0.0 - 1.0)
}
# TODO:
# for key in config:
# if key in <config file heading>:
# argument variables
if args.prob:
config["glitch_prob"] = args.prob
if args.hexmin:
config["hex_min"] = args.hexmin
if args.hexmax:
config["hex_max"] = args.hexmax
if args.framemin:
config["frame_min"] = args.framemin
if args.framemax:
config["frame_max"] = args.framemax
if args.spacingmin:
config["frame_spacing_min"] = args.spacingmin
if args.spacingmax:
config["frame_spacing_max"] = args.spacingmax
if args.width:
config["glitch_width"] = args.width
if args.limit:
config["max_glitches_per_frame"] = args.limit
if hasattr(args, "volume_scale") and args.volume_scale is not None:
config["glitch_volume_scale"] = args.volume_scale
return config
def read_file(input_path):
with open(input_path, "rb") as input_file:
hexdata = input_file.read().hex()
header_start_indices = []
header_start_index = 0
while hexdata.find("fff", header_start_index) >= 0:
header_start_index = hexdata.find("fff", header_start_index)
if header_start_index >= 0:
header_start_indices.append(header_start_index)
header_start_index += 8
frames = [
hexdata[header_start_indices[i] : header_start_indices[i + 1]]
for i in range(len(header_start_indices) - 1)
]
return frames
def attenuate_frame(frame_digits, volume_scale):
"""
Lower the volume of a frame by scaling each byte's value.
Skips the first 8 hex digits (4 bytes) to preserve the MP3 frame header.
"""
attenuated = list(frame_digits)
# Process pairs of hex digits (bytes), skipping the 4-byte header (8 hex digits)
for i in range(8, len(attenuated) - 1, 2):
try:
byte_val = int(attenuated[i] + attenuated[i + 1], 16)
scaled = int(byte_val * volume_scale) & 0xFF
hex_scaled = format(scaled, "02x")
attenuated[i] = hex_scaled[0]
attenuated[i + 1] = hex_scaled[1]
except ValueError:
pass # Skip malformed hex pairs
return attenuated
def apply_glitches(frames, config):
hex_digits = "0123456789abcdef"
output_hex = []
num_glitches_this_frame = 0
testval = 0
frame_counter = 0
frame_spacing = 1
for idx_frame, frame in enumerate(frames):
num_glitches_this_frame = 0
frame_digits = list(frame)
frame_was_glitched = False
for idx_digit, digit in enumerate(frame_digits):
if idx_frame > 0:
if idx_digit % config["glitch_width"] == 0:
testval = random.uniform(0, 100)
if testval < config["glitch_prob"]:
num_glitches_this_frame += 1
if (
testval < config["glitch_prob"]
and (
True,
num_glitches_this_frame <= config["max_glitches_per_frame"],
)[config["max_glitches_per_frame"] > 0]
and idx_digit >= (len(frame) * config["frame_min"])
and idx_digit <= (len(frame) * config["frame_max"])
and idx_digit >= 8
and frame_counter == 0
):
frame_digits[idx_digit] = random.choice(
hex_digits[config["hex_min"] : config["hex_max"] + 1]
)
frame_was_glitched = True
# If a glitch occurred in this frame, attenuate the audio data
if frame_was_glitched:
frame_digits = attenuate_frame(
frame_digits, config["glitch_volume_scale"]
)
output_hex.extend(frame_digits)
if frame_counter == 0:
frame_spacing = random.randrange(
config["frame_spacing_min"], config["frame_spacing_max"] + 1
)
frame_counter += 1
frame_counter %= frame_spacing
return output_hex
def write_file(output_hex, output_path):
rejoined_frames = "".join(output_hex)
if len(rejoined_frames) % 2 != 0:
rejoined_frames = rejoined_frames + "0"
with open(output_path, "wb") as output_file:
output_file.write(binascii.unhexlify(rejoined_frames))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment