Saltomanga
I developed a website to showcase artwork for Saltomanga
Built with SvelteKit 5, Tailwind, PostgreSQL, Nginx, and Docker. We now offer a sticker pack sold in the shop

No Concessions
I recently built and deployed a web application called No Concessions that allows users to find new movies based on shared taste with other users.
Create an account to save movies to your watchlist and get new recommendations. Invite your friends and build out your taste profile.
Comic Strip
I host my comics on my art website findingfocus.art
The site is built using React, TypeScript, Tailwind, and Nginx with pm2 as the process manager. There are over 300 comics included and some are multi-panel and some are animated.
Bitrate Calculator
Enter the duration and filesize of a video to calculate its bitrate with this web application. I use this for analyzing old videos and estimating filesizes for exports.Bitrate is 0.00 Mbps
Video Worksheet Application
I use this python program to format word document files, making it easier for videographers to do their job. The application is deployed using gunicorn as the WSGI, with Nginx as the web server.
# This app generates .docx files and zips them for video deposition preparation
# Currently deployed to the web at trattel.xyz
from docxtpl import DocxTemplate
import os
import zipfile
from datetime import datetime
import shutil
OUTPUT_FOLDER = "Output"
def clear_output_folder(folder_path):
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
print(f'Failed to delete {file_path}. Reason: {e}')
def perform_task(year, videographer, month_str, day, start_time, deponent, case_name, plaintiff_attorney, defense_attorney):
# Convert month to integer and validate
try:
month = int(month_str)
if month < 1 or month > 12:
raise ValueError("Month must be between 1 and 12.")
except ValueError as e:
return str(e), None
month_text = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][month - 1]
def get_day_suffix(day):
if 10 <= day <= 20:
return 'th'
last_digit = day % 10
return {1: 'st', 2: 'nd', 3: 'rd'}.get(last_digit, 'th')
try:
day = int(day)
if day < 1 or day > 31:
raise ValueError("Day must be between 1 and 31.")
except ValueError as e:
return str(e), None
day_suffix = get_day_suffix(day)
# Clear the output folder before generating new files
clear_output_folder(OUTPUT_FOLDER)
# Define file paths
date_str = f"{year}-{month:02}-{day:02}"
readon_path = f"{OUTPUT_FOLDER}/READON {deponent} {month}-{day}-{year}.docx"
videolog_path = f"{OUTPUT_FOLDER}/{deponent} {month}-{day}-{year}.docx"
zip_filename = f"{OUTPUT_FOLDER}/{date_str} {deponent} Video Worksheets.zip"
# Create Output directory if it doesn't exist
os.makedirs(os.path.dirname(readon_path), exist_ok=True)
# Create and save .docx files
try:
subject_line = f"{month}-{day}-{year} {deponent} Deposition"
doc = DocxTemplate("readonTemplate.docx")
context = {
'year': year,
'videographer': videographer,
'month': month_text,
'day': day,
'suffix': day_suffix,
'start_time': start_time,
'deponent': deponent,
'case_name': case_name,
'plaintiff_attorney': plaintiff_attorney,
'defense_attorney': defense_attorney,
'subject_line': subject_line
}
doc.render(context)
doc.save(readon_path)
# Use videologTemplate
videolog_template_path = "videologTemplate.docx"
doc = DocxTemplate(videolog_template_path)
doc.render(context)
doc.save(videolog_path)
# Create the ZIP file
with zipfile.ZipFile(zip_filename, 'w') as zipf:
zipf.write(readon_path, os.path.basename(readon_path))
zipf.write(videolog_path, os.path.basename(videolog_path))
except Exception as e:
return f"Error generating files: {e}", None
return zip_filename, None
SonicPi music for programming
This is the ruby code for the soundtrack that backs my live programming streams.
use_bpm 90
live_loop :met1 do
sleep 1
end
define :pattern do |pattern|
return pattern.ring.tick == "x"
end
# Ambient Wind
live_loop :wind do
use_synth :dark_ambience
use_synth_defaults sustain: 15, release: 12, xamp: 0.8, attack: 3
play [:E1, :E2].choose, pan: rrand(-0.5, 0.5), cutoff: rrand(100, 130)
sleep rrand(10, 20)
end
# Kick
live_loop :kick, sync: :met1 do
a = 1
cu = 95
sample :drum_heavy_kick, cutoff: cu, amp: a if pattern "x-----x---x--x--"
sleep 0.25
end
# HiHats
live_loop :hihat, sync: :met1 do
a = 0.2
sample :hat_tap, amp: a if pattern "x-x---x-x-x-----"
sleep 0.125
end
# Clap
live_loop :snare, sync: :met1 do
a = 0.5
with_fx :echo, mix: 0.1 do
sample :drum_snare_soft, cutoff: 120, amp: a if pattern "----x-------x---"
end
sleep 0.5
end
# Melody
live_loop :melody, sync: :met1 do
use_synth :saw #saw, sine, pluck, tech_saws, tri
notes = scale(:E2, :minor_pentatonic, num_octaves: 2)
cu = line(40, 95, steps: 32).tick
#cu = 90
if one_in(4)
sleep [0.25, 0.5].choose
else
note = notes.choose
a = 0.1
with_fx :reverb, room: rrand(0.5, 0.9), mix: 0.6 do
with_fx :echo, phase: [0.25, 0.5].choose, decay: 2 do
with_fx :tanh, krunch: 20 do
play note, release: rrand(0.1, 0.5), cutoff: cu, amp: a
end
end
end
sleep [0.25, 0.5, 0.75].choose
end
end
em_pent = (scale :E1, :minor_pentatonic, num_octaves: 2)
# Pad
live_loop :ambient_pad do
use_synth :prophet
a = 0.3
cu = line(55, 75, steps: 32).tick
with_fx :reverb, room: 0.8 do
use_synth_defaults attack: 2, sustain: 7, release: 10, amp: a
play em_pent.choose, pan: rrand(-0.5, 0.5), cutoff: cu
end
sleep rrand(8, 16)
end
# Bass
live_loop :bass, sync: :met1 do
use_synth :fm
a = 0.5
notes = scale(:E2, :minor_pentatonic)
play notes.tick, release: 0.3, amp: a if pattern "-x------x---x-x-"
sleep 0.5
end
Artist's Prayer in C
This is a small C program that prints out a prayer that I wrote.
#include <stdio.h>
#define LINES 25
const char *Prayer[LINES];
int main()
{
printf("%s\n", Prayer[0]);
char input;
for (int i = 1; i < LINES; i++)
{
scanf("%c", &input);
if (input == 10)
{
printf("%s\n", Prayer[i]);
}
}
}
const char *Prayer[] = {
"O Great Muse",
"Gift us with your presence",
"And we shall commit to the work of showing up",
"Let us handle the quantity, and you handle the quality",
"Help us love ourselves so we can love others",
"Help us grow so we can help others grow",
"Please alert us when we are on the wrong path",
"By showing us interesting new directions to follow",
"Let us not get wrapped up in the end product, but in the process itself",
"Help us love the present and work out of it not for an imaginary future",
"But to make art for its own sake and its own beauty",
"Grant us the courage to try, and the confidence for having done so",
"Help us unite with other creators so we can encourage each other",
"If we are depressed, help us cling to what is beautiful in the moment",
"And help nudge us towards contentment even in difficult times",
"We promise to offer our work to you consistently",
"And we will listen to where our hearts tell us to go",
"We dedicate our creations to you and they do not belong to us",
"For we were only gifted the idea through the ether",
"And we are merely forging it and returning it to the universe",
"Allow us to realize we all deserve love",
"And that we are all the same divine light",
"We cannot be alone when we realize this",
"We create in honor of each other's divinity",
"Please accept our gifts as they are only made possible through your grace"
};