Arnov Sharma
Published © MIT

PIPBOY Drum Kit

Added a Drumkit Functionality to my PIPBOY PROJECT.

BeginnerFull instructions provided2 hours20
PIPBOY Drum Kit

Things used in this project

Software apps and online services

Fusion
Autodesk Fusion

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Code

code

C/C++
/*
 * pipboy_dkit_tab.c – Pip-Boy 3000 Mk.IV DKit (Drum Kit) Tab
 * Landscape 800x388 inside Pip-Boy Tabview (Pure visual drumheads, no text clutter)
 */

#include "pipboy_dkit_tab.h"
#include "pipboy_theme.h"
#include "pipboy_style.h"
#include "drum_mixer.h"
#include "drum_samples.h"
#include "metronome.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#include <string.h>

static const char *TAG = "DKIT_TAB";

typedef struct {
    drum_id_t  id;
    lv_obj_t  *pad;
} pad_t;

#define MAX_PADS 14
static pad_t s_pads[MAX_PADS];
static int   s_npad = 0;

/* 60-Second Live Event Recorder */
#define MAX_REC_EVENTS  2048
#define MAX_REC_MS      60000u
typedef struct { uint32_t ms; drum_id_t id; uint8_t vel; } rec_evt_t;
static rec_evt_t   s_rec[MAX_REC_EVENTS];
static int         s_rec_n   = 0;
static uint32_t    s_rec_t0  = 0;

typedef enum { ST_IDLE, ST_REC, ST_PLAY } ui_state_t;
static volatile ui_state_t s_ui_state = ST_IDLE;

/* Console Bar Widget Pointers */
static lv_obj_t   *s_kit_lbl     = NULL;
static lv_obj_t   *s_bpm_lbl     = NULL;
static lv_obj_t   *s_metro_btn   = NULL;
static lv_obj_t   *s_metro_dot   = NULL;
static lv_obj_t   *s_pat_btn     = NULL;
static lv_obj_t   *s_rec_btn     = NULL;
static lv_obj_t   *s_play_btn    = NULL;
static lv_obj_t   *s_vol_btn     = NULL;
static lv_obj_t   *s_vol_sld     = NULL;
static lv_timer_t *s_rec_blink   = NULL;

/* ── Pad Touch Callback: Play Sound & Flash Drumhead ── */
static void pad_ev(lv_event_t *e) {
    lv_event_code_t c = lv_event_get_code(e);
    pad_t *p = (pad_t *)lv_event_get_user_data(e);
    if (!p) return;

    if (c == LV_EVENT_PRESSED) {
        drum_trigger(p->id, 100);

        if (s_ui_state == ST_REC && s_rec_n < MAX_REC_EVENTS) {
            uint32_t now = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
            uint32_t t   = now - s_rec_t0;
            if (t <= MAX_REC_MS) {
                s_rec[s_rec_n].ms  = t;
                s_rec[s_rec_n].id  = p->id;
                s_rec[s_rec_n].vel = 100;
                s_rec_n++;
            }
        }

        /* Instant visual phosphor flash */
        lv_obj_set_style_bg_color(p->pad, LV_PIPBOY_COLOR, 0);
        lv_obj_set_style_bg_opa(p->pad, LV_OPA_COVER, 0);

    } else if (c == LV_EVENT_RELEASED || c == LV_EVENT_PRESS_LOST) {
        lv_obj_set_style_bg_color(p->pad, LV_PIPBOY_BLACK, 0);
        lv_obj_set_style_bg_opa(p->pad, LV_OPA_COVER, 0);
    }
}

static void reg_pad(drum_id_t id, lv_obj_t *pad) {
    if (s_npad >= MAX_PADS) return;
    s_pads[s_npad].id  = id;
    s_pads[s_npad].pad = pad;
    lv_obj_add_event_cb(pad, pad_ev, LV_EVENT_ALL, &s_pads[s_npad]);
    s_npad++;
}

static inline void bare(lv_obj_t *o) {
    lv_obj_set_style_pad_all(o, 0, 0);
    lv_obj_set_style_shadow_width(o, 0, 0);
    lv_obj_set_style_border_width(o, 0, 0);
    lv_obj_set_style_outline_width(o, 0, 0);
    lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLLABLE);
}

/* ── Vector Cymbal Generator (Bronze Lathe Rings + Brass Bell) ── */
static void make_cymbal(lv_obj_t *parent, drum_id_t id, int x, int y, int d, int num_rings) {
    lv_obj_t *c = lv_obj_create(parent);
    bare(c);
    lv_obj_set_pos(c, x, y);
    lv_obj_set_size(c, d, d);
    lv_obj_set_style_radius(c, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(c, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(c, LV_OPA_COVER, 0);
    lv_obj_set_style_border_color(c, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(c, 2, 0);
    lv_obj_add_flag(c, LV_OBJ_FLAG_CLICKABLE);

    /* Concentric lathe rings */
    for (int i = 1; i <= num_rings; i++) {
        int rd = (d * (i * 22 + 25)) / 100;
        lv_obj_t *ring = lv_obj_create(c);
        bare(ring);
        lv_obj_set_size(ring, rd, rd);
        lv_obj_center(ring);
        lv_obj_set_style_radius(ring, LV_RADIUS_CIRCLE, 0);
        lv_obj_set_style_bg_opa(ring, LV_OPA_TRANSP, 0);
        lv_obj_set_style_border_color(ring, LV_PIPBOY_COLOR_DIM, 0);
        lv_obj_set_style_border_width(ring, 1, 0);
        lv_obj_clear_flag(ring, LV_OBJ_FLAG_CLICKABLE);
    }

    /* Raised center brass bell */
    int bell_d = d / 5 < 16 ? 16 : d / 5;
    lv_obj_t *bell = lv_obj_create(c);
    bare(bell);
    lv_obj_set_size(bell, bell_d, bell_d);
    lv_obj_center(bell);
    lv_obj_set_style_radius(bell, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(bell, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_border_color(bell, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(bell, 1, 0);
    lv_obj_clear_flag(bell, LV_OBJ_FLAG_CLICKABLE);

    /* Center hole */
    lv_obj_t *hole = lv_obj_create(bell);
    bare(hole);
    lv_obj_set_size(hole, 4, 4);
    lv_obj_center(hole);
    lv_obj_set_style_radius(hole, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(hole, LV_PIPBOY_COLOR, 0);
    lv_obj_clear_flag(hole, LV_OBJ_FLAG_CLICKABLE);

    reg_pad(id, c);
}

/* ── Vector Shell Drum Generator (Chrome Rim + 4 Tuning Lugs) ── */
static void make_drum(lv_obj_t *parent, drum_id_t id, int x, int y, int d, bool is_snare) {
    lv_obj_t *dm = lv_obj_create(parent);
    bare(dm);
    lv_obj_set_pos(dm, x, y);
    lv_obj_set_size(dm, d, d);
    lv_obj_set_style_radius(dm, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(dm, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(dm, LV_OPA_COVER, 0);
    lv_obj_set_style_border_color(dm, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(dm, 3, 0);
    lv_obj_add_flag(dm, LV_OBJ_FLAG_CLICKABLE);

    /* 4 Perimeter Tuning Lugs */
    int lug_w = 4, lug_h = 6;
    lv_obj_t *lt = lv_obj_create(dm); bare(lt);
    lv_obj_set_size(lt, lug_w, lug_h);
    lv_obj_align(lt, LV_ALIGN_TOP_MID, 0, 0);
    lv_obj_set_style_bg_color(lt, LV_PIPBOY_COLOR, 0);

    lv_obj_t *lb = lv_obj_create(dm); bare(lb);
    lv_obj_set_size(lb, lug_w, lug_h);
    lv_obj_align(lb, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_set_style_bg_color(lb, LV_PIPBOY_COLOR, 0);

    lv_obj_t *ll = lv_obj_create(dm); bare(ll);
    lv_obj_set_size(ll, lug_h, lug_w);
    lv_obj_align(ll, LV_ALIGN_LEFT_MID, 0, 0);
    lv_obj_set_style_bg_color(ll, LV_PIPBOY_COLOR, 0);

    lv_obj_t *lr = lv_obj_create(dm); bare(lr);
    lv_obj_set_size(lr, lug_h, lug_w);
    lv_obj_align(lr, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_obj_set_style_bg_color(lr, LV_PIPBOY_COLOR, 0);

    /* Inner Head Ring */
    int in_d = (d * 82) / 100;
    lv_obj_t *head = lv_obj_create(dm);
    bare(head);
    lv_obj_set_size(head, in_d, in_d);
    lv_obj_center(head);
    lv_obj_set_style_radius(head, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_opa(head, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_color(head, LV_PIPBOY_COLOR_DIM, 0);
    lv_obj_set_style_border_width(head, 1, 0);
    lv_obj_clear_flag(head, LV_OBJ_FLAG_CLICKABLE);

    /* Snare Strainer Wire Accent */
    if (is_snare) {
        lv_obj_t *sw = lv_obj_create(head); bare(sw);
        lv_obj_set_size(sw, in_d - 16, 2);
        lv_obj_center(sw);
        lv_obj_set_style_bg_color(sw, LV_PIPBOY_COLOR_DIM, 0);
        lv_obj_clear_flag(sw, LV_OBJ_FLAG_CLICKABLE);
    }

    reg_pad(id, dm);
}

/* ── Vector Dual-Zone Hi-Hat (Split Semicircle: Closed Left / Open Right) ── */
static void make_hihat(lv_obj_t *parent, int x, int y, int d) {
    lv_obj_t *rim = lv_obj_create(parent);
    bare(rim);
    lv_obj_set_pos(rim, x, y);
    lv_obj_set_size(rim, d, d);
    lv_obj_set_style_radius(rim, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(rim, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_border_color(rim, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(2, 0);
    lv_obj_set_style_clip_corner(rim, true, 0);
    lv_obj_clear_flag(rim, LV_OBJ_FLAG_CLICKABLE);

    /* Left Half: Closed Hi-Hat */
    lv_obj_t *cl = lv_obj_create(rim); bare(cl);
    lv_obj_set_size(cl, d / 2, d);
    lv_obj_set_pos(cl, 0, 0);
    lv_obj_set_style_radius(cl, 0, 0);
    lv_obj_set_style_bg_color(cl, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(cl, LV_OPA_COVER, 0);
    lv_obj_add_flag(cl, LV_OBJ_FLAG_CLICKABLE);

    /* Right Half: Open Hi-Hat */
    lv_obj_t *op = lv_obj_create(rim); bare(op);
    lv_obj_set_size(op, d / 2, d);
    lv_obj_set_pos(op, d / 2, 0);
    lv_obj_set_style_radius(op, 0, 0);
    lv_obj_set_style_bg_color(op, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(op, LV_OPA_COVER, 0);
    lv_obj_add_flag(op, LV_OBJ_FLAG_CLICKABLE);

    /* Vertical Divider */
    lv_obj_t *div = lv_obj_create(rim); bare(div);
    lv_obj_set_size(div, 2, d);
    lv_obj_set_pos(div, d / 2 - 1, 0);
    lv_obj_set_style_bg_color(div, LV_PIPBOY_COLOR, 0);
    lv_obj_clear_flag(div, LV_OBJ_FLAG_CLICKABLE);

    /* Center Bell */
    int bell_d = d / 5 < 16 ? 16 : d / 5;
    lv_obj_t *bell = lv_obj_create(rim); bare(bell);
    lv_obj_set_size(bell, bell_d, bell_d);
    lv_obj_center(bell);
    lv_obj_set_style_radius(bell, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(bell, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_border_color(bell, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(bell, 1, 0);
    lv_obj_clear_flag(bell, LV_OBJ_FLAG_CLICKABLE);

    reg_pad(DRUM_HIHAT_CLOSED, cl);
    reg_pad(DRUM_HIHAT_OPEN,   op);
}

/* ── Vector Auxiliary Bar Trigger (Pill Shape with Rubber Strike Rib) ── */
static void make_trigger_pad(lv_obj_t *parent, drum_id_t id, int x, int y, int w, int h) {
    lv_obj_t *tg = lv_obj_create(parent);
    bare(tg);
    lv_obj_set_pos(tg, x, y);
    lv_obj_set_size(tg, w, h);
    lv_obj_set_style_radius(tg, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_bg_color(tg, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(tg, LV_OPA_COVER, 0);
    lv_obj_set_style_border_color(tg, LV_PIPBOY_COLOR, 0);
    lv_obj_set_style_border_width(tg, 2, 0);
    lv_obj_add_flag(tg, LV_OBJ_FLAG_CLICKABLE);

    /* Horizontal Rubber Rib */
    lv_obj_t *rib = lv_obj_create(tg); bare(rib);
    lv_obj_set_size(rib, w - 16, 2);
    lv_obj_center(rib);
    lv_obj_set_style_bg_color(rib, LV_PIPBOY_COLOR_DIM, 0);
    lv_obj_clear_flag(rib, LV_OBJ_FLAG_CLICKABLE);

    reg_pad(id, tg);
}

/* ── Assembly: Drum Stage & Bottom Control Console ── */
void pipboy_dkit_tab_build(lv_obj_t *parent) {
    bare(parent);
    lv_obj_set_style_bg_color(parent, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);

    /* 1. Main Drum Stage (800 x 340) */
    lv_obj_t *stage = lv_obj_create(parent);
    bare(stage);
    lv_obj_set_size(stage, 800, 340);
    lv_obj_set_pos(stage, 0, 0);
    lv_obj_set_style_bg_color(stage, LV_PIPBOY_BLACK, 0);
    lv_obj_set_style_bg_opa(stage, LV_OPA_COVER, 0);

    /* Cymbals */
    make_cymbal(stage, DRUM_CRASH,       42,   8, 114, 2);
    make_cymbal(stage, DRUM_RIDE,       600,   8, 134, 3);
    make_cymbal(stage, DRUM_CRASH,      646, 168, 108, 2);

    /* Dual Hi-Hat */
    make_hihat(stage, 22, 138, 116);

    /* Auxiliary Bar Triggers */
    make_trigger_pad(stage, DRUM_CLAP,   40, 268, 78, 38);
    make_trigger_pad(stage, DRUM_RIDE,  340, 292, 78, 38);

    /* Shell Drums */
    make_drum(stage, DRUM_TOM_HIGH,     230,  14, 104, false);
    make_drum(stage, DRUM_TOM_MID,      426,  14, 104, false);
    make_drum(stage, DRUM_SNARE,        158, 148, 120, true);
    make_drum(stage, DRUM_KICK,         310, 142, 136, false);
    make_drum(stage, DRUM_TOM_FLOOR,    484, 148, 124, false);

    /* 2. Bottom Phosphor Console Bar (800 x 44) */
    /* ... Controls for Kit, BPM, Metronome, Backing Beat, Rec, Play, Volume ... */
}

Credits

Arnov Sharma
403 projects β€’ 444 followers
I'm Arnov. I build, design, and experiment with tech; 3D printing, PCB design, and retro consoles are my jam.

Comments