Monorepo for Aesthetic.Computer
aesthetic.computer
1// BUILD_TARGET: CGB
2// Music box using sprites for colored bars
3#include <gb/gb.h>
4#include <gb/cgb.h>
5#include <stdint.h>
6
7#include "aesthetic_boot.h"
8
9// Note definitions
10enum notes {
11 C3 = 36, Cd3, D3, Dd3, E3, F3, Fd3, G3, Gd3, A3, Ad3, B3,
12 C4, Cd4, D4, Dd4, E4, F4, Fd4, G4, Gd4, A4, Ad4, B4,
13 C5, Cd5, D5, Dd5, E5, F5, Fd5, G5, Gd5, A5, Ad5, B5,
14 REST
15};
16
17const uint16_t frequencies[] = {
18 1798, 1812, 1825, 1837, 1849, 1860, 1871, 1881, 1890, 1899, 1907, 1915,
19 1923, 1930, 1936, 1943, 1949, 1954, 1959, 1964, 1969, 1974, 1978, 1982,
20 1985, 1988, 1992, 1995, 1998, 2001, 2004, 2006, 2009, 2011, 2013, 2015,
21 0
22};
23
24// Twinkle Twinkle melody
25const uint8_t melody[] = {
26 C4, C4, G4, G4, A4, A4, G4, REST,
27 F4, F4, E4, E4, D4, D4, C4, REST,
28 G4, G4, F4, F4, E4, E4, D4, REST,
29 G4, G4, F4, F4, E4, E4, D4, REST,
30 C4, C4, G4, G4, A4, A4, G4, REST,
31 F4, F4, E4, E4, D4, D4, C4, REST,
32 0xFF
33};
34
35// Sprite tile: 8x8 filled square
36const uint8_t sprite_tile[] = {
37 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
38 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
39};
40
41// OBJ palettes (7 colors + gray)
42const uint16_t obj_palettes[] = {
43 // Palette 0: Red (C)
44 RGB_BLACK, RGB(31, 0, 0), RGB(31, 0, 0), RGB(31, 0, 0),
45 // Palette 1: Orange (D)
46 RGB_BLACK, RGB(31, 15, 0), RGB(31, 15, 0), RGB(31, 15, 0),
47 // Palette 2: Yellow (E)
48 RGB_BLACK, RGB(31, 31, 0), RGB(31, 31, 0), RGB(31, 31, 0),
49 // Palette 3: Green (F)
50 RGB_BLACK, RGB(0, 31, 0), RGB(0, 31, 0), RGB(0, 31, 0),
51 // Palette 4: Cyan (G)
52 RGB_BLACK, RGB(0, 31, 31), RGB(0, 31, 31), RGB(0, 31, 31),
53 // Palette 5: Blue (A)
54 RGB_BLACK, RGB(0, 0, 31), RGB(0, 0, 31), RGB(0, 0, 31),
55 // Palette 6: Purple (B)
56 RGB_BLACK, RGB(31, 0, 31), RGB(31, 0, 31), RGB(31, 0, 31),
57 // Palette 7: Gray (played notes)
58 RGB_BLACK, RGB(10, 10, 10), RGB(10, 10, 10), RGB(10, 10, 10),
59};
60
61#define FRAMES_PER_NOTE 24
62#define SPRITE_BASE_Y 144 // Bottom of screen
63
64uint8_t current_note_index = 0;
65uint8_t current_note = REST;
66uint8_t frame_counter = 0;
67uint8_t auto_play = 1;
68uint8_t sprite_count = 0;
69
70void play_note(uint8_t note) {
71 if(note == REST || note == 0xFF) {
72 NR12_REG = 0x00;
73 NR14_REG = 0x80;
74 return;
75 }
76 uint16_t freq = frequencies[note - C3];
77 NR10_REG = 0x00;
78 NR11_REG = 0x81;
79 NR12_REG = 0xF3;
80 NR13_REG = (uint8_t)(freq & 0xFF);
81 NR14_REG = 0x80 | (uint8_t)((freq >> 8) & 0x07);
82}
83
84uint8_t get_palette_for_note(uint8_t note) {
85 if(note == REST || note == 0xFF) return 7;
86 uint8_t palette = ((note - C3) % 12);
87 if(palette > 6) palette = palette - 5;
88 return palette;
89}
90
91void draw_note_sprites() {
92 sprite_count = 0;
93
94 for(uint8_t i = 0; melody[i] != 0xFF && sprite_count < 40; i++) {
95 uint8_t note = melody[i];
96 if(note == REST) continue;
97
98 // Calculate bar height (0-17 tiles = 0-136 pixels)
99 uint8_t height_tiles = ((note - C3) * 17) / 36;
100 if(height_tiles > 17) height_tiles = 17;
101 uint8_t height_px = height_tiles * 8;
102
103 // X position: each note gets 8 pixels wide
104 uint8_t x = 8 + (i * 8);
105
106 // Palette: color or gray
107 uint8_t palette = (i == current_note_index) ? get_palette_for_note(note) : 7;
108
109 // Draw sprites from bottom up
110 uint8_t y = SPRITE_BASE_Y - height_px;
111 for(uint8_t py = 0; py < height_px && sprite_count < 40; py += 8) {
112 set_sprite_tile(sprite_count, 0);
113 set_sprite_prop(sprite_count, palette);
114 move_sprite(sprite_count, x, y + py);
115 sprite_count++;
116 }
117 }
118
119 // Hide unused sprites
120 for(uint8_t i = sprite_count; i < 40; i++) {
121 move_sprite(i, 0, 0);
122 }
123}
124
125void init_graphics() {
126 DISPLAY_OFF;
127
128 // Load sprite tile
129 set_sprite_data(0, 1, sprite_tile);
130
131 // Set OBJ palettes
132 set_sprite_palette(0, 8, obj_palettes);
133
134 // Clear background to black
135 BGP_REG = 0xE4;
136
137 // Draw initial sprites
138 draw_note_sprites();
139
140 SHOW_SPRITES;
141 DISPLAY_ON;
142}
143
144void main() {
145 show_aesthetic_boot(0);
146
147 // Sound setup
148 NR52_REG = 0x80;
149 NR51_REG = 0x11;
150 NR50_REG = 0x77;
151
152 init_graphics();
153
154 // Start playing
155 current_note = melody[0];
156 play_note(current_note);
157
158 while(1) {
159 vsync();
160 frame_counter++;
161
162 uint8_t joy = joypad();
163
164 // Manual control
165 if(joy & J_RIGHT) {
166 if(melody[current_note_index + 1] != 0xFF) {
167 current_note_index++;
168 current_note = melody[current_note_index];
169 play_note(current_note);
170 draw_note_sprites();
171 frame_counter = 0;
172 }
173 auto_play = 0;
174 }
175
176 if(joy & J_LEFT) {
177 if(current_note_index > 0) {
178 current_note_index--;
179 current_note = melody[current_note_index];
180 play_note(current_note);
181 draw_note_sprites();
182 frame_counter = 0;
183 }
184 auto_play = 0;
185 }
186
187 if(joy & J_A) {
188 auto_play = 1;
189 frame_counter = 0;
190 }
191
192 // Auto-play
193 if(auto_play && frame_counter >= FRAMES_PER_NOTE) {
194 if(melody[current_note_index + 1] != 0xFF) {
195 current_note_index++;
196 } else {
197 current_note_index = 0;
198 }
199
200 current_note = melody[current_note_index];
201 play_note(current_note);
202 draw_note_sprites();
203 frame_counter = 0;
204 }
205 }
206}