Sandias Mountains
Video preview

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


Saltomanga website

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.


Comic Strip - Be Prepared Comic Strip - TMNT

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.
Total Duration: 00:00:00

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"
};