From bed7cc14f9bd3b3169bcb8c122dc55ec31ff7e66 Mon Sep 17 00:00:00 2001 From: Rawiri Blundell Date: Wed, 29 Oct 2025 13:39:19 +1300 Subject: [PATCH 1/5] gallery/noise: Initial commit --- gallery/noise/config.sh | 10 ++++++ gallery/noise/noise.sh | 78 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 gallery/noise/config.sh create mode 100755 gallery/noise/noise.sh diff --git a/gallery/noise/config.sh b/gallery/noise/config.sh new file mode 100644 index 0000000..f930d55 --- /dev/null +++ b/gallery/noise/config.sh @@ -0,0 +1,10 @@ +# Config for the 'noise' screensaver + +# Name of the screensaver (12 chars max) +name="noise" +# Tagline for the screensaver (40 chars max) +tagline="Retro CRT static noise screensaver" +description="Relive the analog static noise of the yester-years." +authors="" +license="MIT" +settings="" diff --git a/gallery/noise/noise.sh b/gallery/noise/noise.sh new file mode 100755 index 0000000..7e12c18 --- /dev/null +++ b/gallery/noise/noise.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Replicate old-school CRT static noise + +# Handler for SIGINT (Ctrl‑C) +_handle_cleanup_and_exit() { + # show the cursor again + tput cnorm + tput sgr0 + echo + clear + exit 0 +} + +# Handler for SIGWINCH (window resize) +_handle_window_resize() { + width="$(tput cols)" + height="$(tput lines)" + clear +} + +# Capture Ctrl‑C +trap _handle_cleanup_and_exit SIGINT +# Capture SIGWINCH +trap _handle_window_resize SIGWINCH + +# Get terminal dimensions +width="${COLUMNS:-$(tput cols)}" +height="${LINES:-$(tput lines)}" + +# For portability, we use these UTF codes +block100="\xe2\x96\x88" # u2588\0xe2 0x96 0x88 Solid Block 100% +block75="\xe2\x96\x93" # u2593\0xe2 0x96 0x93 Dark shade 75% +block50="\xe2\x96\x92" # u2592\0xe2 0x96 0x92 Half shade 50% +block25="\xe2\x96\x91" # u2591\0xe2 0x96 0x91 Light shade 25% +block00=' ' # Literal space + +blocks=( "${block100}" "${block75}" "${block50}" "${block25}" "${block00}" ) + +# Randomly select one of the color sets +# This could be moved to an arg in the future +# or split to a separate screensaver +# This would allow desired behaviour to be selectable +while true; do + rand="${RANDOM}" + + # Require range [0, 32766] to evenly divide by 2 + # Reject 32767 to avoid modulo bias + if (( rand < 32767 )); then + result=$(( rand % 2 )) + if (( result == 0 )); then + # Black, white, greys from 256 ANSI set + color_set=( 0 7 8 15 16 145 188 {231..255} ) + else + # Black, white, greys from 256 ANSI set, with RGBY thrown in + color_set=( 0 7 8 15 16 145 188 {231..255} 196 46 21 226 ) + fi + break + fi + # If we're at this line, RANDOM hit 32767! +done + +tput setab 0 # black background +clear +tput civis # no cursor + +while true; do + # Get random location and color + x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) + y=$(( ${SRANDOM:-$RANDOM} % height + 1 )) + color_element=$(( ${SRANDOM:-$RANDOM} % ${#color_set[@]} )) + color_code="${color_set[color_element]}" + block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) + block=${blocks[block_element]} + + # Print the frame + printf -- '%b' "\e[${y};${x}H\e[38;5;${color_code}m${block}" +done + From db18e45abf2b871833a154acf5835aee65d46bfb Mon Sep 17 00:00:00 2001 From: Rawiri Blundell Date: Wed, 29 Oct 2025 23:17:22 +1300 Subject: [PATCH 2/5] noise.sh: Add buffer to improve performance. Benchmarking shows +2.6x improvement. is based on further benchmarking --- gallery/noise/noise.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gallery/noise/noise.sh b/gallery/noise/noise.sh index 7e12c18..ed3e5e3 100755 --- a/gallery/noise/noise.sh +++ b/gallery/noise/noise.sh @@ -63,6 +63,11 @@ tput setab 0 # black background clear tput civis # no cursor +# Initialise vars for batching +buffer="" +count=0 +batch_size=100 + while true; do # Get random location and color x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) @@ -72,7 +77,15 @@ while true; do block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) block=${blocks[block_element]} - # Print the frame - printf -- '%b' "\e[${y};${x}H\e[38;5;${color_code}m${block}" + # Build a buffer of changes to emit + buffer+="\e[${y};${x}H\e[38;5;${color_code}m${block}" + ((count++)) + + # Once the buffer size meets the threshold, dump it and start again + if (( count >= batch_size )); then + printf -- '%b' "${buffer}" + buffer="" + count=0 + fi done From 61c356a59b284fb7adf5b20a82b5ded49ed1cbce Mon Sep 17 00:00:00 2001 From: Rawiri Blundell Date: Thu, 30 Oct 2025 08:17:47 +1300 Subject: [PATCH 3/5] gallery/noise/config.sh: Attribution as requested :) --- gallery/noise/config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/noise/config.sh b/gallery/noise/config.sh index f930d55..2120319 100644 --- a/gallery/noise/config.sh +++ b/gallery/noise/config.sh @@ -5,6 +5,6 @@ name="noise" # Tagline for the screensaver (40 chars max) tagline="Retro CRT static noise screensaver" description="Relive the analog static noise of the yester-years." -authors="" +authors="Rawiri Blundell" license="MIT" settings="" From 7ee35fe7242812587f0060978df95c38c8a80490 Mon Sep 17 00:00:00 2001 From: Rawiri Blundell Date: Thu, 30 Oct 2025 08:20:25 +1300 Subject: [PATCH 4/5] gallery/noise/noise.sh: Add colorless output method --- gallery/noise/noise.sh | 94 +++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/gallery/noise/noise.sh b/gallery/noise/noise.sh index ed3e5e3..c6b8217 100755 --- a/gallery/noise/noise.sh +++ b/gallery/noise/noise.sh @@ -42,21 +42,23 @@ blocks=( "${block100}" "${block75}" "${block50}" "${block25}" "${block00}" ) # This would allow desired behaviour to be selectable while true; do rand="${RANDOM}" - - # Require range [0, 32766] to evenly divide by 2 - # Reject 32767 to avoid modulo bias - if (( rand < 32767 )); then - result=$(( rand % 2 )) + # Require range [0, 32765] to evenly divide by 3 + # Reject 32766 and 32767 to avoid modulo bias + if (( rand < 32766 )); then + result=$(( rand % 3 )) if (( result == 0 )); then # Black, white, greys from 256 ANSI set - color_set=( 0 7 8 15 16 145 188 {231..255} ) - else + color_set=( 0 7 8 15 16 145 188 {231..255} ) + elif (( result == 1 )); then # Black, white, greys from 256 ANSI set, with RGBY thrown in color_set=( 0 7 8 15 16 145 188 {231..255} 196 46 21 226 ) + else + # White only - uses faster emit_without_color() + color_set=( 15 ) fi break fi - # If we're at this line, RANDOM hit 32767! + # If we're at this line, RANDOM hit 32766 or 32767! done tput setab 0 # black background @@ -64,28 +66,64 @@ clear tput civis # no cursor # Initialise vars for batching +# This eliminates "one pixel change per loop iteration" +# Benchmarking led to a batch_size selection of 100 buffer="" count=0 batch_size=100 -while true; do - # Get random location and color - x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) - y=$(( ${SRANDOM:-$RANDOM} % height + 1 )) - color_element=$(( ${SRANDOM:-$RANDOM} % ${#color_set[@]} )) - color_code="${color_set[color_element]}" - block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) - block=${blocks[block_element]} - - # Build a buffer of changes to emit - buffer+="\e[${y};${x}H\e[38;5;${color_code}m${block}" - ((count++)) - - # Once the buffer size meets the threshold, dump it and start again - if (( count >= batch_size )); then - printf -- '%b' "${buffer}" - buffer="" - count=0 - fi -done +emit_with_color() { + while true; do + # Get random location and color + x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) + y=$(( ${SRANDOM:-$RANDOM} % height + 1 )) + color_element=$(( ${SRANDOM:-$RANDOM} % ${#color_set[@]} )) + color_code="${color_set[color_element]}" + block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) + block=${blocks[block_element]} + + # Build a buffer of changes to emit + buffer+="\e[${y};${x}H\e[38;5;${color_code}m${block}" + ((count++)) + + # Once the buffer size meets the threshold, dump it and start again + if (( count >= batch_size )); then + printf -- '%b' "${buffer}" + buffer="" + count=0 + fi + done +} + +# In benchmarking, this performs 25-30% faster than emit_with_color() +# Eliminating the rand calls for color selection clearly has an impact +# Implicit trust of the terminal color state may have unpredictable results +# At the end of the day, we're constrained by a shell loop +# More performance could potentially be gleaned with parallelism (see: forkrun) +emit_without_color() { + color_code="${color_set[*]}" + while true; do + # Get random location and color + x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) + y=$(( ${SRANDOM:-$RANDOM} % height + 1 )) + block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) + block=${blocks[block_element]} + + # Build a buffer of changes to emit + buffer+="\e[${y};${x}H\e[38;5;${color_code}m${block}" + ((count++)) + + # Once the buffer size meets the threshold, dump it and start again + if (( count >= batch_size )); then + printf -- '%b' "${buffer}" + buffer="" + count=0 + fi + done +} +# Select our output method based on the size of ${color_set[@]} +case "${#color_set[@]}" in + (1) emit_without_color ;; + (*) emit_with_color ;; +esac From b80121f3590dad7d2d9ff141050a99a9a1cac826 Mon Sep 17 00:00:00 2001 From: Rawiri Blundell Date: Thu, 30 Oct 2025 10:22:51 +1300 Subject: [PATCH 5/5] gallery/noise/noise.sh: Add third mode for greyscale, refactor mode selection, define function local vars --- gallery/noise/noise.sh | 93 ++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/gallery/noise/noise.sh b/gallery/noise/noise.sh index c6b8217..2516a35 100755 --- a/gallery/noise/noise.sh +++ b/gallery/noise/noise.sh @@ -36,31 +36,6 @@ block00=' ' # Literal space blocks=( "${block100}" "${block75}" "${block50}" "${block25}" "${block00}" ) -# Randomly select one of the color sets -# This could be moved to an arg in the future -# or split to a separate screensaver -# This would allow desired behaviour to be selectable -while true; do - rand="${RANDOM}" - # Require range [0, 32765] to evenly divide by 3 - # Reject 32766 and 32767 to avoid modulo bias - if (( rand < 32766 )); then - result=$(( rand % 3 )) - if (( result == 0 )); then - # Black, white, greys from 256 ANSI set - color_set=( 0 7 8 15 16 145 188 {231..255} ) - elif (( result == 1 )); then - # Black, white, greys from 256 ANSI set, with RGBY thrown in - color_set=( 0 7 8 15 16 145 188 {231..255} 196 46 21 226 ) - else - # White only - uses faster emit_without_color() - color_set=( 15 ) - fi - break - fi - # If we're at this line, RANDOM hit 32766 or 32767! -done - tput setab 0 # black background clear tput civis # no cursor @@ -73,6 +48,9 @@ count=0 batch_size=100 emit_with_color() { + local color_set x y color_element color_code + local block_element block buffer count batch_size + color_set=( 0 7 8 15 16 145 188 {231..255} 196 46 21 226 ) while true; do # Get random location and color x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) @@ -95,13 +73,45 @@ emit_with_color() { done } +# Uses direct color calculation instead of array lookup +# Benchmarking shows ~11% improvement over emit_with_color() +# Generates grey shades from ANSI 231-255 (25 shades) +emit_with_simple_color() { + local x y color_code + local block_element block buffer count batch_size + while true; do + # Get random location + x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) + y=$(( ${SRANDOM:-$RANDOM} % height + 1 )) + + # Direct color calculation - no array needed + color_code=$(( 231 + ${SRANDOM:-$RANDOM} % 25 )) + + block_element=$(( ${SRANDOM:-$RANDOM} % ${#blocks[@]} )) + block=${blocks[block_element]} + + # Build a buffer of changes to emit + buffer+="\e[${y};${x}H\e[38;5;${color_code}m${block}" + ((count++)) + + # Once the buffer size meets the threshold, dump it and start again + if (( count >= batch_size )); then + printf -- '%b' "${buffer}" + buffer="" + count=0 + fi + done +} + # In benchmarking, this performs 25-30% faster than emit_with_color() # Eliminating the rand calls for color selection clearly has an impact # Implicit trust of the terminal color state may have unpredictable results # At the end of the day, we're constrained by a shell loop # More performance could potentially be gleaned with parallelism (see: forkrun) emit_without_color() { - color_code="${color_set[*]}" + local x y color_code + local block_element block buffer count batch_size + color_code=15 while true; do # Get random location and color x=$(( ${SRANDOM:-$RANDOM} % width + 1 )) @@ -122,8 +132,29 @@ emit_without_color() { done } -# Select our output method based on the size of ${color_set[@]} -case "${#color_set[@]}" in - (1) emit_without_color ;; - (*) emit_with_color ;; -esac +# Randomly select one of the color modes +# This could be moved to an arg in the future or split to separate screensavers +# This would allow desired behaviour to be selectable +while true; do + rand="${RANDOM}" + # Require range [0, 32765] to evenly divide by 3 + # Reject 32766 and 32767 to avoid modulo bias + if (( rand < 32766 )); then + color_mode=$(( rand % 3 )) + case "${color_mode}" in + (0) + # Greys only - use simple direct calculation (fastest with color) + emit_with_simple_color + ;; + (1) + # Greys + accent colors - needs array lookup for RGBY + emit_with_color + ;; + (2) + # White only - uses emit_without_color (fastest overall) + emit_without_color + ;; + esac + fi + # If we're at this line, RANDOM hit 32766 or 32767! +done