Merge pull request #537 from pimoroni/feature/galactic_unicorn

Galactic Unicorn
pull/540/head v1.19.9
Philip Howard 2022-10-19 14:08:20 +01:00 zatwierdzone przez GitHub
commit e928129d59
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
49 zmienionych plików z 15178 dodań i 14 usunięć

Wyświetl plik

@ -61,7 +61,7 @@ jobs:
build:
needs: deps
name: Build ${{matrix.board}}
name: Build ${{matrix.name}} (${{matrix.board}})
runs-on: ubuntu-20.04
strategy:
matrix:
@ -70,6 +70,8 @@ jobs:
board: PICO_W
- name: picow_enviro
board: PICO_W_ENVIRO
- name: picow_galactic_unicorn
board: PICO_W
env:
# MicroPython version will be contained in github.event.release.tag_name for releases

Wyświetl plik

@ -56,3 +56,4 @@ add_subdirectory(servo2040)
add_subdirectory(motor2040)
add_subdirectory(inventor2040w)
add_subdirectory(encoder)
add_subdirectory(galactic_unicorn)

Wyświetl plik

@ -0,0 +1,125 @@
add_executable(
rainbow_text
rainbow_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(rainbow_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(rainbow_text)
add_executable(
rainbow
rainbow.cpp
)
# Pull in pico libraries that we need
target_link_libraries(rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(rainbow 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(rainbow)
add_executable(
nostalgia_prompt
nostalgia_prompt.cpp
)
# Pull in pico libraries that we need
target_link_libraries(nostalgia_prompt pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(nostalgia_prompt 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(nostalgia_prompt)
add_executable(
eighties_super_computer
eighties_super_computer.cpp
)
# Pull in pico libraries that we need
target_link_libraries(eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(eighties_super_computer 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(eighties_super_computer)
add_executable(
fire_effect
fire_effect.cpp
)
# Pull in pico libraries that we need
target_link_libraries(fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(fire_effect 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(fire_effect)
add_executable(
scroll_text
scroll_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(scroll_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(scroll_text)
add_executable(
balls
balls.cpp
)
# Pull in pico libraries that we need
target_link_libraries(balls pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(balls 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(balls)
add_executable(
lava_lamp
lava_lamp.cpp
)
# Pull in pico libraries that we need
target_link_libraries(lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(lava_lamp 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(lava_lamp)
add_executable(
feature_test
feature_test.cpp
audio_samples.cpp
)
# Pull in pico libraries that we need
target_link_libraries(feature_test pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(feature_test 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(feature_test)

Wyświetl plik

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// extra row of pixels for sourcing flames and averaging
float heat[53][13] = {0.0f};
int main() {
stdio_init_all();
galactic_unicorn.init();
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
graphics.set_pen(0, 0, 0);
graphics.clear();
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
if(heat[x][y] > 0.5f) {
graphics.set_pen(255, 255, 180);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.4f) {
graphics.set_pen(220, 160, 0);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.3f) {
graphics.set_pen(180, 50, 0);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.2f) {
graphics.set_pen(40, 40, 40);
graphics.pixel(Point(x, y));
}
// update this pixel by averaging the below pixels
if(x == 0) {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x + 1][y + 1]) / 4.0f;
} else if(x == 52) {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1]) / 4.0f;
} else {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1] + heat[x + 1][y + 1]) / 5.0f;
}
heat[x][y] -= 0.01f;
heat[x][y] = heat[x][y] < 0.0f ? 0.0f: heat[x][y];
}
}
galactic_unicorn.update(&graphics);
// clear the bottom row and then add a new fire seed to it
for(int x = 0; x < 53; x++) {
heat[x][11] = 0.0f;
}
// add a new random heat source
for(int c = 0; c < 5; c++) {
int px = (rand() % 51) + 1;
heat[px][11] = 1.0f;
heat[px + 1][11] = 1.0f;
heat[px - 1][11] = 1.0f;
heat[px][12] = 1.0f;
heat[px + 1][12] = 1.0f;
heat[px - 1][12] = 1.0f;
}
sleep_ms(50);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
float lifetime[53][11];
float age[53][11];
int main() {
stdio_init_all();
for(int y = 0; y < 11; y++) {
for(int x = 0; x < 53; x++) {
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
}
}
galactic_unicorn.init();
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
graphics.set_pen(0, 0, 0);
graphics.clear();
for(int y = 0; y < 11; y++) {
for(int x = 0; x < 53; x++) {
if(age[x][y] < lifetime[x][y] * 0.3f) {
graphics.set_pen(230, 150, 0);
graphics.pixel(Point(x, y));
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
graphics.set_pen(decay * 230, decay * 150, 0);
graphics.pixel(Point(x, y));
}
if(age[x][y] >= lifetime[x][y]) {
age[x][y] = 0.0f;
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
}
age[x][y] += 0.01f;
}
}
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,164 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// left_channel_bin and right_channel_bin are in audio_samples.cpp
extern uint8_t left_channel_bin[];
extern uint32_t left_channel_bin_len;
extern uint8_t right_channel_bin[];
extern uint32_t right_channel_bin_len;
void gradient(uint8_t r, uint8_t g, uint8_t b) {
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
// graphics.set_pen((r * x) / 52, (g * x) / 52, (b * x) / 52);
graphics.set_pen(x, x, x);
graphics.pixel(Point(x, y));
}
}
}
typedef void (*shader_func_t)(int ms, int x, int y);
void grid(int ms, int x, int y) {
int v = (x + y + (ms / 1000)) % 2 == 0 ? 255 : 0;
graphics.set_pen(v, v, v);
graphics.pixel(Point(x, y));
}
void shader_fill(int ms, shader_func_t f) {
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
f(ms, x, y);
}
}
}
void outline_text(std::string text) {
uint ms = to_ms_since_boot(get_absolute_time());
graphics.set_font("bitmap8");
uint8_t v = (sin(ms / 100.0f) + 1.0f) * 127.0f;
uint w = graphics.measure_text(text, 1);
int x = 53 / 2 - w / 2 + 1, y = 2;
graphics.set_pen(0, 0, 0);
graphics.text(text, Point(x - 1, y - 1), -1, 1);
graphics.text(text, Point(x , y - 1), -1, 1);
graphics.text(text, Point(x + 1, y - 1), -1, 1);
graphics.text(text, Point(x - 1, y ), -1, 1);
graphics.text(text, Point(x + 1, y ), -1, 1);
graphics.text(text, Point(x - 1, y + 1), -1, 1);
graphics.text(text, Point(x , y + 1), -1, 1);
graphics.text(text, Point(x + 1, y + 1), -1, 1);
graphics.set_pen(v, v, v);
graphics.text(text, Point(x, y), -1, 1);
}
int main() {
stdio_init_all();
galactic_unicorn.init();
//galactic_unicorn.set_brightness(0.5f);
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01f);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01f);
}
uint ms = to_ms_since_boot(get_absolute_time());
uint8_t test = (ms / 1000) % 4;
graphics.set_pen(0, 0, 0);
graphics.clear();
switch(test) {
case 0: {
printf("grid pattern");
shader_fill(ms, grid);
}break;
case 1: {
printf("green gradient");
gradient(0, 255, 0);
}break;
case 2: {
printf("blue gradient");
gradient(0, 0, 255);
}break;
case 3: {
printf("white gradient");
gradient(255, 255, 255);
}break;
}
printf("%d\n", galactic_unicorn.light());
std::string text = "";
static bool was_a_pressed = false;
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
if(!was_a_pressed) {
galactic_unicorn.play_sample(left_channel_bin, left_channel_bin_len);
}
was_a_pressed = true;
}else{
was_a_pressed = false;
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
text = "Button A";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_B)) {
text = "Button B";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_C)) {
text = "Button C";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_D)) {
text = "Button D";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_UP)) {
text = "Louder!";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_DOWN)) {
text = "quieter";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_UP)) {
text = "Brighter!";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_DOWN)) {
text = "Darker";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_SLEEP)) {
text = "Zzz... zzz...";
}
outline_text(text);
galactic_unicorn.update(&graphics);
sleep_ms(50);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,122 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// extra row of pixels for sourcing flames and averaging
int width = 53;
int height = 15;
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
float heat[1000] = {0.0f};
void set(int x, int y, float v) {
heat[x + y * width] = v;
}
float get(int x, int y) {
/*if(x < 0 || x >= width || y < 0 || y >= height) {
return 0.0f;
}*/
x = x < 0 ? 0 : x;
x = x >= width ? width - 1 : x;
return heat[x + y * width];
}
int main() {
stdio_init_all();
galactic_unicorn.init();
galactic_unicorn.set_brightness(0.5);
bool landscape = true;
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
landscape = true;
width = 53;
height = 15;
memset(heat, 0, sizeof(heat));
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
landscape = false;
width = 11;
height = 55;
memset(heat, 0, sizeof(heat));
}
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
float value = get(x, y);
graphics.set_pen(0, 0, 0);
if(value > 0.5f) {
graphics.set_pen(255, 255, 180);
}else if(value > 0.4f) {
graphics.set_pen(220, 160, 0);
}else if(value > 0.3f) {
graphics.set_pen(180, 30, 0);
}else if(value > 0.22f) {
graphics.set_pen(20, 20, 20);
}
if(landscape) {
graphics.pixel(Point(x, y));
}else{
graphics.pixel(Point(y, x));
}
// update this pixel by averaging the below pixels
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
// damping factor to ensure flame tapers out towards the top of the displays
average *= landscape ? 0.95f : 0.99f;
// update the heat map with our newly averaged value
set(x, y, average);
}
}
galactic_unicorn.update(&graphics);
// clear the bottom row and then add a new fire seed to it
for(int x = 0; x < width; x++) {
set(x, height - 1, 0.0f);
}
// add a new random heat source
int source_count = landscape ? 5 : 1;
for(int c = 0; c < source_count; c++) {
int px = (rand() % (width - 4)) + 2;
set(px , height - 2, 1.0f);
set(px + 1, height - 2, 1.0f);
set(px - 1, height - 2, 1.0f);
set(px , height - 1, 1.0f);
set(px + 1, height - 1, 1.0f);
set(px - 1, height - 1, 1.0f);
}
sleep_ms(20);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,149 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
struct blob_t {
float x, y;
float r;
float dx, dy;
};
constexpr int blob_count = 20;
int main() {
stdio_init_all();
galactic_unicorn.init();
galactic_unicorn.set_brightness(0.5);
// randomise blob start positions, directions, and size
std::array<blob_t, blob_count> blobs;
for(auto &blob : blobs) {
blob.x = rand() % 11;
blob.y = rand() % 53;
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
}
float hue = 0.0f;
while(true) {
// allow user to adjust brightness
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
uint start_ms = to_ms_since_boot(get_absolute_time());
// calculate the influence of each blob on the liquid based
// on their distance to each pixel. this causes blobs to
// "merge" into each other when we use fixed thresholds to
// determine which colour to draw with
float liquid[11][53] = {0.0f};
for(auto &blob : blobs) {
float r_sq = blob.r * blob.r;
for(int y = 0; y < 53; y++) {
for(int x = 0; x < 11; x++) {
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
if(d_sq <= r_sq) {
// add this blobs influence to this pixel
liquid[x][y] += 1.0f - (d_sq / r_sq);
}
}
}
}
// update the blob positions
for(auto &blob : blobs) {
blob.x += blob.dx;
blob.y += blob.dy;
// if we hit the edge then bounce!
if(blob.x < 0.0f || blob.x >= 11.0f) {
blob.dx *= -1.0f;
}
if(blob.y < 0.0f || blob.y >= 53.0f) {
blob.dy *= -1.0f;
}
}
// rotate the hue
hue += 0.001f;
// calculate dark, medium, and bright shades for rendering the
// lava
uint8_t dark_r, dark_g, dark_b;
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
uint8_t mid_r, mid_g, mid_b;
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
uint8_t bright_r, bright_g, bright_b;
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
// clear the canvas
graphics.set_pen(0, 0, 0);
graphics.clear();
// render the lava
for(int y = 0; y < 53; y++) {
for(int x = 0; x < 11; x++) {
float v = liquid[x][y];
// select a colour for this pixel based on how much
// "blobfluence" there is at this position in the liquid
if(v >= 1.5f) {
graphics.set_pen(bright_r, bright_g, bright_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.25f) {
graphics.set_pen(mid_r, mid_g, mid_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.0f) {
graphics.set_pen(dark_r, dark_g, dark_b);
graphics.pixel(Point(y, x));
}
}
}
uint end_ms = to_ms_since_boot(get_absolute_time());
printf("rendering took %dms\n", end_ms - start_ms);
galactic_unicorn.update(&graphics);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,122 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
std::array<std::string, 11> c64 = {
"",
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX",
" OO OO OO OOOO OO OO OO OO XXXXXXX",
" OO OO OO OO OO OO OO OO OO XXXXXXX",
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX",
" OOOO OO OO OO OO OO OO XXXXXXX",
" OO OO OO OO OO OO OO OO OO XXXXXXX",
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX",
" XXXXXXX"
};
std::array<std::string, 11> spectrum = {
"",
" O OOOO OOOO OOOOO O O O O XXXXXXXX",
" O O O O O O O O O O O X XXXXXX",
" O O O O O O O X XXXXXX",
" O O O OOOOOO O O X XXXXXX",
" O O O O O O O X XXXXXX",
" OOOOOO OOOO O O OOOOO X XXXXXX",
" X X",
" XXXXXXXX"
};
std::array<std::string, 11> bbc_micro = {
"",
" OOOOO OO OOOO OOO OOOO O ",
" O O O O O O O O O O ",
" O O O O O O O O ",
" OOOOO O O OOOO O O O ",
" O O OOOOOO O O O O ",
" O O O O O O O O O O ",
" OOOOO O O OOOO OOO OOOO O ",
" XXXXXXX"
};
constexpr uint PROMPT_C64 = 0;
constexpr uint PROMPT_SPECTRUM = 1;
constexpr uint PROMPT_BBC_MICRO = 2;
uint prompt = 0;
int main() {
stdio_init_all();
galactic_unicorn.init();
while(true) {
uint time_ms = to_ms_since_boot(get_absolute_time());
prompt = (time_ms / 3000) % 3;
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
std::array<std::string, 11> image = c64;
if(prompt == PROMPT_C64) {
image = c64;
}else if(prompt == PROMPT_SPECTRUM) {
image = spectrum;
}else if(prompt == PROMPT_BBC_MICRO) {
image = bbc_micro;
}
if(prompt == PROMPT_C64) {
graphics.set_pen(20, 20, 120);
}else if(prompt == PROMPT_SPECTRUM){
graphics.set_pen(180, 150, 150);
}else if(prompt == PROMPT_BBC_MICRO){
graphics.set_pen(0, 0, 0);
}
graphics.clear();
if(prompt == PROMPT_C64) {
graphics.set_pen(230, 210, 250);
}else if(prompt == PROMPT_SPECTRUM){
graphics.set_pen(0, 0, 0);
}else if(prompt == PROMPT_BBC_MICRO){
graphics.set_pen(255, 255, 255);
}
for(size_t y = 0; y < image.size(); y++) {
for(size_t x = 0; x < image[y].length(); x++) {
// draw the prompt text
if(image[y][x] == 'O') {
graphics.pixel(Point(x, y + 1));
}
// draw the caret blinking
if(image[y][x] == 'X' && (time_ms / 300) % 2) {
graphics.pixel(Point(x, y + 1));
}
}
}
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

Wyświetl plik

@ -0,0 +1,684 @@
#pragma once
// Copyright(c) 2021 Björn Ottosson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this softwareand associated documentation files(the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions :
// The above copyright noticeand this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <cmath>
#include <cfloat>
namespace ok_color
{
struct Lab { float L; float a; float b; };
struct RGB { float r; float g; float b; };
struct HSV { float h; float s; float v; };
struct HSL { float h; float s; float l; };
struct LC { float L; float C; };
// Alternative representation of (L_cusp, C_cusp)
// Encoded so S = C_cusp/L_cusp and T = C_cusp/(1-L_cusp)
// The maximum value for C in the triangle is then found as fmin(S*L, T*(1-L)), for a given L
struct ST { float S; float T; };
constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
float clamp(float x, float min, float max)
{
if (x < min)
return min;
if (x > max)
return max;
return x;
}
float sgn(float x)
{
return (float)(0.f < x) - (float)(x < 0.f);
}
float srgb_transfer_function(float a)
{
return .0031308f >= a ? 12.92f * a : 1.055f * powf(a, .4166666666666667f) - .055f;
}
float srgb_transfer_function_inv(float a)
{
return .04045f < a ? powf((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
}
Lab linear_srgb_to_oklab(RGB c)
{
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
float l_ = cbrtf(l);
float m_ = cbrtf(m);
float s_ = cbrtf(s);
return {
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
};
}
RGB oklab_to_linear_srgb(Lab c)
{
float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
return {
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
};
}
// Finds the maximum saturation possible for a given hue that fits in sRGB
// Saturation here is defined as S = C/L
// a and b must be normalized so a^2 + b^2 == 1
float compute_max_saturation(float a, float b)
{
// Max saturation will be when one of r, g or b goes below zero.
// Select different coefficients depending on which component goes below zero first
float k0, k1, k2, k3, k4, wl, wm, ws;
if (-1.88170328f * a - 0.80936493f * b > 1)
{
// Red component
k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
}
else if (1.81444104f * a - 1.19445276f * b > 1)
{
// Green component
k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
}
else
{
// Blue component
k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
}
// Approximate max saturation using a polynomial:
float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
// Do one step Halley's method to get closer
// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
// this should be sufficient for most applications, otherwise do two/three steps
float k_l = +0.3963377774f * a + 0.2158037573f * b;
float k_m = -0.1055613458f * a - 0.0638541728f * b;
float k_s = -0.0894841775f * a - 1.2914855480f * b;
{
float l_ = 1.f + S * k_l;
float m_ = 1.f + S * k_m;
float s_ = 1.f + S * k_s;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float l_dS = 3.f * k_l * l_ * l_;
float m_dS = 3.f * k_m * m_ * m_;
float s_dS = 3.f * k_s * s_ * s_;
float l_dS2 = 6.f * k_l * k_l * l_;
float m_dS2 = 6.f * k_m * k_m * m_;
float s_dS2 = 6.f * k_s * k_s * s_;
float f = wl * l + wm * m + ws * s;
float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
}
return S;
}
// finds L_cusp and C_cusp for a given hue
// a and b must be normalized so a^2 + b^2 == 1
LC find_cusp(float a, float b)
{
// First, find the maximum saturation (saturation S = C/L)
float S_cusp = compute_max_saturation(a, b);
// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
RGB rgb_at_max = oklab_to_linear_srgb({ 1, S_cusp * a, S_cusp * b });
float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
float C_cusp = L_cusp * S_cusp;
return { L_cusp , C_cusp };
}
// Finds intersection of the line defined by
// L = L0 * (1 - t) + t * L1;
// C = t * C1;
// a and b must be normalized so a^2 + b^2 == 1
float find_gamut_intersection(float a, float b, float L1, float C1, float L0, LC cusp)
{
// Find the intersection for upper and lower half seprately
float t;
if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
{
// Lower half
t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
}
else
{
// Upper half
// First intersect with triangle
t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
// Then one step Halley's method
{
float dL = L1 - L0;
float dC = C1;
float k_l = +0.3963377774f * a + 0.2158037573f * b;
float k_m = -0.1055613458f * a - 0.0638541728f * b;
float k_s = -0.0894841775f * a - 1.2914855480f * b;
float l_dt = dL + dC * k_l;
float m_dt = dL + dC * k_m;
float s_dt = dL + dC * k_s;
// If higher accuracy is required, 2 or 3 iterations of the following block can be used:
{
float L = L0 * (1.f - t) + t * L1;
float C = t * C1;
float l_ = L + C * k_l;
float m_ = L + C * k_m;
float s_ = L + C * k_s;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float ldt = 3 * l_dt * l_ * l_;
float mdt = 3 * m_dt * m_ * m_;
float sdt = 3 * s_dt * s_ * s_;
float ldt2 = 6 * l_dt * l_dt * l_;
float mdt2 = 6 * m_dt * m_dt * m_;
float sdt2 = 6 * s_dt * s_dt * s_;
float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
float t_r = -r * u_r;
float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
float t_g = -g * u_g;
float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
float t_b = -b * u_b;
t_r = u_r >= 0.f ? t_r : FLT_MAX;
t_g = u_g >= 0.f ? t_g : FLT_MAX;
t_b = u_b >= 0.f ? t_b : FLT_MAX;
t += fmin(t_r, fmin(t_g, t_b));
}
}
}
return t;
}
float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
{
// Find the cusp of the gamut triangle
LC cusp = find_cusp(a, b);
return find_gamut_intersection(a, b, L1, C1, L0, cusp);
}
RGB gamut_clip_preserve_chroma(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float L0 = clamp(L, 0, 1);
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_project_to_0_5(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float L0 = 0.5;
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_project_to_L_cusp(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
LC cusp = find_cusp(a_, b_);
float L0 = cusp.L;
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float Ld = L - 0.5f;
float e1 = 0.5f + fabs(Ld) + alpha * C;
float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * fabs(Ld))));
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1.f - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
LC cusp = find_cusp(a_, b_);
float Ld = L - cusp.L;
float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
float e1 = 0.5f * k + fabs(Ld) + alpha * C / k;
float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1.f - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
float toe(float x)
{
constexpr float k_1 = 0.206f;
constexpr float k_2 = 0.03f;
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));
}
float toe_inv(float x)
{
constexpr float k_1 = 0.206f;
constexpr float k_2 = 0.03f;
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
return (x * x + k_1 * x) / (k_3 * (x + k_2));
}
ST to_ST(LC cusp)
{
float L = cusp.L;
float C = cusp.C;
return { C / L, C / (1 - L) };
}
// Returns a smooth approximation of the location of the cusp
// This polynomial was created by an optimization process
// It has been designed so that S_mid < S_max and T_mid < T_max
ST get_ST_mid(float a_, float b_)
{
float S = 0.11516993f + 1.f / (
+7.44778970f + 4.15901240f * b_
+ a_ * (-2.19557347f + 1.75198401f * b_
+ a_ * (-2.13704948f - 10.02301043f * b_
+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
)))
);
float T = 0.11239642f + 1.f / (
+1.61320320f - 0.68124379f * b_
+ a_ * (+0.40370612f + 0.90148123f * b_
+ a_ * (-0.27087943f + 0.61223990f * b_
+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
)))
);
return { S, T };
}
struct Cs { float C_0; float C_mid; float C_max; };
Cs get_Cs(float L, float a_, float b_)
{
LC cusp = find_cusp(a_, b_);
float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);
ST ST_max = to_ST(cusp);
// Scale factor to compensate for the curved part of gamut shape:
float k = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);
float C_mid;
{
ST ST_mid = get_ST_mid(a_, b_);
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
float C_a = L * ST_mid.S;
float C_b = (1.f - L) * ST_mid.T;
C_mid = 0.9f * k * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
}
float C_0;
{
// for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
float C_a = L * 0.4f;
float C_b = (1.f - L) * 0.8f;
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
}
return { C_0, C_mid, C_max };
}
RGB okhsl_to_srgb(HSL hsl)
{
float h = hsl.h;
float s = hsl.s;
float l = hsl.l;
if (l == 1.0f)
{
return { 1.f, 1.f, 1.f };
}
else if (l == 0.f)
{
return { 0.f, 0.f, 0.f };
}
float a_ = cosf(2.f * pi * h);
float b_ = sinf(2.f * pi * h);
float L = toe_inv(l);
Cs cs = get_Cs(L, a_, b_);
float C_0 = cs.C_0;
float C_mid = cs.C_mid;
float C_max = cs.C_max;
float mid = 0.8f;
float mid_inv = 1.25f;
float C, t, k_0, k_1, k_2;
if (s < mid)
{
t = mid_inv * s;
k_1 = mid * C_0;
k_2 = (1.f - k_1 / C_mid);
C = t * k_1 / (1.f - k_2 * t);
}
else
{
t = (s - mid)/ (1 - mid);
k_0 = C_mid;
k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
k_2 = (1.f - (k_1) / (C_max - C_mid));
C = k_0 + t * k_1 / (1.f - k_2 * t);
}
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
return {
srgb_transfer_function(rgb.r),
srgb_transfer_function(rgb.g),
srgb_transfer_function(rgb.b),
};
}
HSL srgb_to_okhsl(RGB rgb)
{
Lab lab = linear_srgb_to_oklab({
srgb_transfer_function_inv(rgb.r),
srgb_transfer_function_inv(rgb.g),
srgb_transfer_function_inv(rgb.b)
});
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
float a_ = lab.a / C;
float b_ = lab.b / C;
float L = lab.L;
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
Cs cs = get_Cs(L, a_, b_);
float C_0 = cs.C_0;
float C_mid = cs.C_mid;
float C_max = cs.C_max;
// Inverse of the interpolation in okhsl_to_srgb:
float mid = 0.8f;
float mid_inv = 1.25f;
float s;
if (C < C_mid)
{
float k_1 = mid * C_0;
float k_2 = (1.f - k_1 / C_mid);
float t = C / (k_1 + k_2 * C);
s = t * mid;
}
else
{
float k_0 = C_mid;
float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
float k_2 = (1.f - (k_1) / (C_max - C_mid));
float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
s = mid + (1.f - mid) * t;
}
float l = toe(L);
return { h, s, l };
}
RGB okhsv_to_srgb(HSV hsv)
{
float h = hsv.h;
float s = hsv.s;
float v = hsv.v;
float a_ = cosf(2.f * pi * h);
float b_ = sinf(2.f * pi * h);
LC cusp = find_cusp(a_, b_);
ST ST_max = to_ST(cusp);
float S_max = ST_max.S;
float T_max = ST_max.T;
float S_0 = 0.5f;
float k = 1 - S_0 / S_max;
// first we compute L and V as if the gamut is a perfect triangle:
// L, C when v==1:
float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * k * s);
float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
float L = v * L_v;
float C = v * C_v;
// then we compensate for both toe and the curved top part of the triangle:
float L_vt = toe_inv(L_v);
float C_vt = C_v * L_vt / L_v;
float L_new = toe_inv(L);
C = C * L_new / L;
L = L_new;
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
L = L * scale_L;
C = C * scale_L;
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
return {
srgb_transfer_function(rgb.r),
srgb_transfer_function(rgb.g),
srgb_transfer_function(rgb.b),
};
}
HSV srgb_to_okhsv(RGB rgb)
{
Lab lab = linear_srgb_to_oklab({
srgb_transfer_function_inv(rgb.r),
srgb_transfer_function_inv(rgb.g),
srgb_transfer_function_inv(rgb.b)
});
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
float a_ = lab.a / C;
float b_ = lab.b / C;
float L = lab.L;
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
LC cusp = find_cusp(a_, b_);
ST ST_max = to_ST(cusp);
float S_max = ST_max.S;
float T_max = ST_max.T;
float S_0 = 0.5f;
float k = 1 - S_0 / S_max;
// first we find L_v, C_v, L_vt and C_vt
float t = T_max / (C + L * T_max);
float L_v = t * L;
float C_v = t * C;
float L_vt = toe_inv(L_v);
float C_vt = C_v * L_vt / L_v;
// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
L = L / scale_L;
C = C / scale_L;
C = C * toe(L) / L;
L = toe(L);
// we can now compute v and s:
float v = L / L_v;
float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
return { h, s, v };
}
} // namespace ok_color

Wyświetl plik

@ -0,0 +1,200 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
struct star_t {
float dx, dy, x, y, a;
uint8_t brightness() {
int b = a / 5;
return b > 15 ? 15 : b;
}
};
void init_star(star_t &s) {
s.x = ((rand() % 100) / 5.0f) - 10.0f;
s.y = ((rand() % 100) / 10.0f) - 5.0f;
s.dx = s.x / 10.0f;
s.dy = s.y / 10.0f;
s.a = 0;
}
void step_star(star_t &s) {
s.x += s.dx;
s.y += s.dy;
s.a++;
if(s.a > 100) {
init_star(s);
}
}
int main() {
stdio_init_all();
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
star_t stars[100];
for(int i = 0; i < 100; i++) {
init_star(stars[i]);
stars[i].a = i;
}
gpio_set_function(28, GPIO_FUNC_SIO);
gpio_set_dir(28, GPIO_OUT);
for(int i = 0; i < 10; i++) {
gpio_put(28, !gpio_get(28));
sleep_ms(100);
}
sleep_ms(1000);
gpio_put(28,true);
galactic_unicorn.init();
/*
bool a_pressed = false;
bool b_pressed = false;
bool x_pressed = false;
bool y_pressed = false;
*/
graphics.set_font("bitmap8");
float i = 0;
float hue_offset = 0.0f;
bool animate = true;
float stripe_width = 3.0f;
float speed = 1.0f;
float curve = 0.0f;
while(true) {
if(animate) {
i += speed;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_UP)) {
curve += 0.05;
if(hue_offset > 1.0f) hue_offset = 1.0f;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_DOWN)) {
curve -= 0.05;
if(hue_offset < 0.0f) hue_offset = 0.0f;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_SLEEP)) {
animate = false;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
speed += 0.05f;
speed = speed >= 10.0f ? 10.0f : speed;
animate = true;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
speed -= 0.05f;
speed = speed <= 0.0f ? 0.0f : speed;
animate = true;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_C)) {
stripe_width += 0.05f;
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_D)) {
stripe_width -= 0.05f;
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
}
/*
graphics.set_pen(255, 255, 255);
float s = 0.65f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin(i / 74.0f) * 80.0f) * s;
float y = (cos(i / 43.0f) * 6.0f) * s;
text("Chester smells!", Point(x, y - 3), s, a);
*/
for(int x = 0; x < 53; x++) {
for(int y = 0; y < 11; y++) {
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
uint8_t r = (hue_map[x][0] * v) / 256;
uint8_t g = (hue_map[x][1] * v) / 256;
uint8_t b = (hue_map[x][2] * v) / 256;
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
}
galactic_unicorn.update(&graphics);
printf("%d\n", galactic_unicorn.light());
sleep_ms(20);
}
printf("done\n");
return 0;
}

Wyświetl plik

@ -0,0 +1,103 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
int main() {
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 0.1f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
galactic_unicorn.init();
graphics.set_font("sans");
uint i = 0;
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
i++;
graphics.set_pen(0, 0, 0);
graphics.clear();
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin((i) / 50.0f) * 90.0f);
float y = (cos((i) / 40.0f) * 5.0f);
graphics.set_pen(255, 255, 255);
text("Galactic Unicorn", Point(x, y), s, a);
uint8_t *p = (uint8_t *)graphics.frame_buffer;
for(size_t i = 0; i < 53 * 11; i++) {
int x = i % 53;
int y = i / 53;
uint r = *p++;
uint g = *p++;
uint b = *p++;
p++;
if(r > 0) {
r = hue_map[x][0];
g = hue_map[x][1];
b = hue_map[x][2];
}
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
}
printf("done\n");
return 0;
}

Wyświetl plik

@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
std::string message = "Pirate. Monkey. Robot. Ninja.";
int main() {
stdio_init_all();
galactic_unicorn.init();
float scroll = -53.0f;
while(true) {
//uint time_ms = to_ms_since_boot(get_absolute_time());
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
int width = graphics.measure_text(message, 1);
scroll += 0.25f;
if(scroll > width) {
scroll = -53.0f;
}
graphics.set_pen(0, 0, 0);
graphics.clear();
ok_color::HSL hsl{scroll / 100.0f, 1.0f, 0.5f};
ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl);
graphics.set_pen(rgb.r * 255, rgb.g * 255, rgb.b * 255);
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
/*graphics.text(message, Point(0 - scroll + 1, 5), -1, 0.5);
graphics.text(message, Point(0 - scroll, 5 + 1), -1, 0.5);
graphics.text(message, Point(0 - scroll + 1, 5 + 1), -1, 0.5);*/
/*
for(int x = 0; x < 53; x++) {
for(int y = 0; y < 2; y++) {
float b = sin((x - y * 3 + int(scroll) + 100) / 3.0f);
graphics.set_pen(180 * b, 150 * b, 0);
graphics.pixel(Point(52 - x, y));
graphics.pixel(Point(52 - x, y + 9));
}
}*/
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

Wyświetl plik

@ -36,3 +36,4 @@ add_subdirectory(inventor2040w)
add_subdirectory(adcfft)
add_subdirectory(jpegdec)
add_subdirectory(inky_frame)
add_subdirectory(galactic_unicorn)

Wyświetl plik

@ -0,0 +1 @@
include(galactic_unicorn.cmake)

Wyświetl plik

@ -0,0 +1,114 @@
# Galactic Unicorn <!-- omit in toc -->
Galactic Unicorn offers 53x11 7x17 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier and speaker.
Galactic Unicorn uses SM0 of PIO0.
TODO: Update documentation
We've included helper functions to handle every aspect of drawing to the display and interfacing with the buttons. See the [function reference](#function-reference) for details.
- [Example Program](#example-program)
- [Reference](#reference)
- [Constants](#constants)
- [Buttons](#buttons)
- [WIDTH / HEIGHT](#width--height)
- [Functions](#functions)
- [init](#init)
- [set_pixel](#set_pixel)
- [is_pressed](#is_pressed)
- [Examples](#examples)
## Example Program
The following example sets up Pico Unicorn, displays some basic demo text and graphics and will illuminate the RGB LED green if the A button is presse
```c++
```
## Reference
### Constants
#### Buttons
The four buttons, A, B, X and Y have correponding constants set to their respective GPIO pins. For example:
```c++
bool a_is_pressed = pico_unicorn.is_pressed(pico_unicorn.A);
```
#### WIDTH / HEIGHT
The width and height of Pico Unicorn are available in constants `WIDTH` and `HEIGHT`.
For example:
```c++
int num_pixels = pico_unicorn.WIDTH * pico_unicorn.HEIGHT;
```
### Functions
#### init
Sets up Pico Unicorn. `init` must be called before any other functions since it configures the PIO and require GPIO inputs. Just call `init()` like so:
```c++
PicoUnicorn pico_unicorn;
pico_unicorn.init();
```
#### set_pixel
```c++
void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b);
void set_pixel(uint8_t x, uint8_t y, uint8_t v);
```
Sets an RGB LED on Pico Unicorn with an RGB triplet:
```c++
pico_unicorn.set_pixel(x, y, r, g, b);
```
Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off."
Alternatively you can use:
```c++
pico_unicorn.set_pixel(x, y, v);
```
Which sets the R, G and B elements of the pixel to the same value- lighting it up white at your chosen intensity.
#### is_pressed
```c++
bool is_pressed(uint8_t button);
```
Reads the GPIO pin connected to one of Pico Unicorn's buttons, returning a `bool` - `true` if it's pressed and `false` if it is released.
```c++
pico_unicorn.is_pressed(button);
```
The button vaule should be a `uint8_t` denoting a pin, and constants `A`, `B`, `X` and `Y` are supplied to make it easier. e:
```c++
bool is_a_button_pressed = pico_unicorn.is_pressed(PicoUnicorn::A)
```
# Examples
## Game of Life
## Retro Super-computer
Random LEDs blink on and off mimicing the look of a movie super computer doing it's work in the eighties.
## Nostalgia Terminal
A collection of copies of classic terminal styles including C64, MS-DOS, Spectrum, and more.

Wyświetl plik

@ -0,0 +1,63 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Transmit a mono or stereo I2S audio stream as stereo
; This is 16 bits per sample; can be altered by modifying the "set" params,
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
;
; Autopull must be enabled, with threshold set to 32.
; Since I2S is MSB-first, shift direction should be to left.
; Hence the format of the FIFO word is:
;
; | 31 : 16 | 15 : 0 |
; | sample ws=0 | sample ws=1 |
;
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
; Fractional divider will probably be needed to get correct bit clock period,
; but for common syslck freqs this should still give a constant word select period.
;
; One output pin is used for the data output.
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
.program audio_i2s
.side_set 2
; /--- LRCLK
; |/-- BCLK
bitloop1: ; ||
out pins, 1 side 0b10
jmp x-- bitloop1 side 0b11
out pins, 1 side 0b00
set x, 14 side 0b01
bitloop0:
out pins, 1 side 0b00
jmp x-- bitloop0 side 0b01
out pins, 1 side 0b10
public entry_point:
set x, 14 side 0b11
% c-sdk {
static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
pio_sm_init(pio, sm, offset, &sm_config);
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // clear pins
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
}
%}

Wyświetl plik

@ -0,0 +1,15 @@
add_library(galactic_unicorn INTERFACE)
pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.pio)
pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio)
target_sources(galactic_unicorn INTERFACE
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
${CMAKE_CURRENT_LIST_DIR}/../pico_synth/pico_synth.cpp
)
target_include_directories(galactic_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(galactic_unicorn INTERFACE pico_stdlib pico_graphics hardware_adc hardware_pio hardware_dma)

Wyświetl plik

@ -0,0 +1,569 @@
#include <math.h>
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/adc.h"
#include "hardware/clocks.h"
#include "galactic_unicorn.pio.h"
#include "audio_i2s.pio.h"
#include "galactic_unicorn.hpp"
// pixel data is stored as a stream of bits delivered in the
// order the PIO needs to manage the shift registers, row
// selects, delays, and latching/blanking
//
// the pins used are:
//
// - 13: column clock (sideset)
// - 14: column data (out base)
// - 15: column latch
// - 16: column blank
// - 17: row select bit 0
// - 18: row select bit 1
// - 19: row select bit 2
// - 20: row select bit 3
//
// the framebuffer data is structured like this:
//
// for each row:
// for each bcd frame:
// 0: 00110110 // row pixel count (minus one)
// 1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
// 56: xxxxrrrr // row select bits
// 57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
static uint16_t r_gamma_lut[256] = {0};
static uint16_t g_gamma_lut[256] = {0};
static uint16_t b_gamma_lut[256] = {0};
static uint32_t dma_channel;
static uint32_t dma_ctrl_channel;
static uint32_t audio_dma_channel;
namespace pimoroni {
GalacticUnicorn* GalacticUnicorn::unicorn = nullptr;
PIO GalacticUnicorn::bitstream_pio = pio0;
uint GalacticUnicorn::bitstream_sm = 0;
uint GalacticUnicorn::bitstream_sm_offset = 0;
PIO GalacticUnicorn::audio_pio = pio0;
uint GalacticUnicorn::audio_sm = 0;
uint GalacticUnicorn::audio_sm_offset = 0;
// once the dma transfer of the scanline is complete we move to the
// next scanline (or quit if we're finished)
void __isr GalacticUnicorn::dma_complete() {
if(unicorn != nullptr && dma_channel_get_irq0_status(audio_dma_channel)) {
unicorn->next_audio_sequence();
}
}
GalacticUnicorn::~GalacticUnicorn() {
if(unicorn == this) {
partial_teardown();
dma_channel_unclaim(dma_ctrl_channel); // This works now the teardown behaves correctly
dma_channel_unclaim(dma_channel); // This works now the teardown behaves correctly
pio_sm_unclaim(bitstream_pio, bitstream_sm);
pio_remove_program(bitstream_pio, &galactic_unicorn_program, bitstream_sm_offset);
dma_channel_unclaim(audio_dma_channel); // This works now the teardown behaves correctly
pio_sm_unclaim(audio_pio, audio_sm);
pio_remove_program(audio_pio, &audio_i2s_program, audio_sm_offset);
irq_remove_handler(DMA_IRQ_0, dma_complete);
unicorn = nullptr;
}
}
void GalacticUnicorn::partial_teardown() {
// Stop the bitstream SM
pio_sm_set_enabled(bitstream_pio, bitstream_sm, false);
// Make sure the display is off and switch it to an invisible row, to be safe
const uint pins_to_set = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set);
dma_hw->ch[dma_ctrl_channel].al1_ctrl = (dma_hw->ch[dma_ctrl_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_ctrl_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
dma_hw->ch[dma_channel].al1_ctrl = (dma_hw->ch[dma_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
// Abort any in-progress DMA transfer
dma_safe_abort(dma_ctrl_channel);
//dma_channel_abort(dma_ctrl_channel);
//dma_channel_abort(dma_channel);
dma_safe_abort(dma_channel);
// Stop the audio SM
pio_sm_set_enabled(audio_pio, audio_sm, false);
// Reset the I2S pins to avoid popping when audio is suddenly stopped
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
// Abort any in-progress DMA transfer
dma_safe_abort(audio_dma_channel);
}
uint16_t GalacticUnicorn::light() {
adc_select_input(2);
return adc_read();
}
void GalacticUnicorn::init() {
if(unicorn != nullptr) {
// Tear down the old GU instance's hardware resources
partial_teardown();
}
// create 14-bit gamma luts
for(uint16_t v = 0; v < 256; v++) {
// gamma correct the provided 0-255 brightness value onto a
// 0-65535 range for the pwm counter
float r_gamma = 1.8f;
r_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, r_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
float g_gamma = 1.8f;
g_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, g_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
float b_gamma = 1.8f;
b_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, b_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
}
// for each row:
// for each bcd frame:
// 0: 00110110 // row pixel count (minus one)
// 1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
// 56: xxxxrrrr // row select bits
// 57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
// initialise the bcd timing values and row selects in the bitstream
for(uint8_t row = 0; row < HEIGHT; row++) {
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
// find the offset of this row and frame in the bitstream
uint8_t *p = &bitstream[row * ROW_BYTES + (BCD_FRAME_BYTES * frame)];
p[ 0] = WIDTH - 1; // row pixel count
p[56] = row; // row select
// set the number of bcd ticks for this frame
uint32_t bcd_ticks = (1 << frame);
p[57] = (bcd_ticks & 0xff) >> 0;
p[58] = (bcd_ticks & 0xff00) >> 8;
p[59] = (bcd_ticks & 0xff0000) >> 16;
}
}
// setup light sensor adc
adc_init();
adc_gpio_init(LIGHT_SENSOR);
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
gpio_init(COLUMN_DATA); gpio_set_dir(COLUMN_DATA, GPIO_OUT); gpio_put(COLUMN_DATA, false);
gpio_init(COLUMN_LATCH); gpio_set_dir(COLUMN_LATCH, GPIO_OUT); gpio_put(COLUMN_LATCH, false);
gpio_init(COLUMN_BLANK); gpio_set_dir(COLUMN_BLANK, GPIO_OUT); gpio_put(COLUMN_BLANK, true);
// initialise the row select, and set them to a non-visible row to avoid flashes during setup
gpio_init(ROW_BIT_0); gpio_set_dir(ROW_BIT_0, GPIO_OUT); gpio_put(ROW_BIT_0, true);
gpio_init(ROW_BIT_1); gpio_set_dir(ROW_BIT_1, GPIO_OUT); gpio_put(ROW_BIT_1, true);
gpio_init(ROW_BIT_2); gpio_set_dir(ROW_BIT_2, GPIO_OUT); gpio_put(ROW_BIT_2, true);
gpio_init(ROW_BIT_3); gpio_set_dir(ROW_BIT_3, GPIO_OUT); gpio_put(ROW_BIT_3, true);
sleep_ms(100);
// configure full output current in register 2
uint16_t reg1 = 0b1111111111001110;
// clock the register value to the first 9 driver chips
for(int j = 0; j < 9; j++) {
for(int i = 0; i < 16; i++) {
if(reg1 & (1U << (15 - i))) {
gpio_put(COLUMN_DATA, true);
}else{
gpio_put(COLUMN_DATA, false);
}
sleep_us(10);
gpio_put(COLUMN_CLOCK, true);
sleep_us(10);
gpio_put(COLUMN_CLOCK, false);
}
}
// clock the last chip and latch the value
for(int i = 0; i < 16; i++) {
if(reg1 & (1U << (15 - i))) {
gpio_put(COLUMN_DATA, true);
}else{
gpio_put(COLUMN_DATA, false);
}
sleep_us(10);
gpio_put(COLUMN_CLOCK, true);
sleep_us(10);
gpio_put(COLUMN_CLOCK, false);
if(i == 4) {
gpio_put(COLUMN_LATCH, true);
}
}
gpio_put(COLUMN_LATCH, false);
// reapply the blank as the above seems to cause a slight glow.
// Note, this will produce a brief flash if a visible row is selected (which it shouldn't be)
gpio_put(COLUMN_BLANK, false);
sleep_us(10);
gpio_put(COLUMN_BLANK, true);
gpio_init(MUTE); gpio_set_dir(MUTE, GPIO_OUT); gpio_put(MUTE, true);
// setup button inputs
gpio_init(SWITCH_A); gpio_pull_up(SWITCH_A);
gpio_init(SWITCH_B); gpio_pull_up(SWITCH_B);
gpio_init(SWITCH_C); gpio_pull_up(SWITCH_C);
gpio_init(SWITCH_D); gpio_pull_up(SWITCH_D);
gpio_init(SWITCH_SLEEP); gpio_pull_up(SWITCH_SLEEP);
gpio_init(SWITCH_BRIGHTNESS_UP); gpio_pull_up(SWITCH_BRIGHTNESS_UP);
gpio_init(SWITCH_BRIGHTNESS_DOWN); gpio_pull_up(SWITCH_BRIGHTNESS_DOWN);
gpio_init(SWITCH_VOLUME_UP); gpio_pull_up(SWITCH_VOLUME_UP);
gpio_init(SWITCH_VOLUME_DOWN); gpio_pull_up(SWITCH_VOLUME_DOWN);
// setup the pio if it has not previously been set up
bitstream_pio = pio0;
if(unicorn == nullptr) {
bitstream_sm = pio_claim_unused_sm(bitstream_pio, true);
bitstream_sm_offset = pio_add_program(bitstream_pio, &galactic_unicorn_program);
}
pio_gpio_init(bitstream_pio, COLUMN_CLOCK);
pio_gpio_init(bitstream_pio, COLUMN_DATA);
pio_gpio_init(bitstream_pio, COLUMN_LATCH);
pio_gpio_init(bitstream_pio, COLUMN_BLANK);
pio_gpio_init(bitstream_pio, ROW_BIT_0);
pio_gpio_init(bitstream_pio, ROW_BIT_1);
pio_gpio_init(bitstream_pio, ROW_BIT_2);
pio_gpio_init(bitstream_pio, ROW_BIT_3);
// set the blank and row pins to be high, then set all led driving pins as outputs.
// This order is important to avoid a momentary flash
const uint pins_to_set = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set);
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, COLUMN_CLOCK, 8, true);
pio_sm_config c = galactic_unicorn_program_get_default_config(bitstream_sm_offset);
// osr shifts right, autopull on, autopull threshold 8
sm_config_set_out_shift(&c, true, true, 32);
// configure out, set, and sideset pins
sm_config_set_out_pins(&c, ROW_BIT_0, 4);
sm_config_set_set_pins(&c, COLUMN_DATA, 3);
sm_config_set_sideset_pins(&c, COLUMN_CLOCK);
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// setup dma transfer for pixel data to the pio
//if(unicorn == nullptr) {
dma_channel = dma_claim_unused_channel(true);
dma_ctrl_channel = dma_claim_unused_channel(true);
//}
dma_channel_config ctrl_config = dma_channel_get_default_config(dma_ctrl_channel);
channel_config_set_transfer_data_size(&ctrl_config, DMA_SIZE_32);
channel_config_set_read_increment(&ctrl_config, false);
channel_config_set_write_increment(&ctrl_config, false);
channel_config_set_chain_to(&ctrl_config, dma_channel);
dma_channel_configure(
dma_ctrl_channel,
&ctrl_config,
&dma_hw->ch[dma_channel].read_addr,
&bitstream_addr,
1,
false
);
dma_channel_config config = dma_channel_get_default_config(dma_channel);
channel_config_set_transfer_data_size(&config, DMA_SIZE_32);
channel_config_set_bswap(&config, false); // byte swap to reverse little endian
channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, bitstream_sm, true));
channel_config_set_chain_to(&config, dma_ctrl_channel);
dma_channel_configure(
dma_channel,
&config,
&bitstream_pio->txf[bitstream_sm],
NULL,
BITSTREAM_LENGTH / 4,
false);
pio_sm_init(bitstream_pio, bitstream_sm, bitstream_sm_offset, &c);
pio_sm_set_enabled(bitstream_pio, bitstream_sm, true);
// start the control channel
dma_start_channel_mask(1u << dma_ctrl_channel);
// setup audio pio program
audio_pio = pio0;
if(unicorn == nullptr) {
audio_sm = pio_claim_unused_sm(audio_pio, true);
audio_sm_offset = pio_add_program(audio_pio, &audio_i2s_program);
}
pio_gpio_init(audio_pio, I2S_DATA);
pio_gpio_init(audio_pio, I2S_BCLK);
pio_gpio_init(audio_pio, I2S_LRCLK);
audio_i2s_program_init(audio_pio, audio_sm, audio_sm_offset, I2S_DATA, I2S_BCLK);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
uint32_t divider = system_clock_frequency * 4 / SYSTEM_FREQ; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(audio_pio, audio_sm, divider >> 8u, divider & 0xffu);
audio_dma_channel = dma_claim_unused_channel(true);
dma_channel_config audio_config = dma_channel_get_default_config(audio_dma_channel);
channel_config_set_transfer_data_size(&audio_config, DMA_SIZE_16);
//channel_config_set_bswap(&audio_config, false); // byte swap to reverse little endian
channel_config_set_dreq(&audio_config, pio_get_dreq(audio_pio, audio_sm, true));
dma_channel_configure(audio_dma_channel, &audio_config, &audio_pio->txf[audio_sm], NULL, 0, false);
dma_channel_set_irq0_enabled(audio_dma_channel, true);
if(unicorn == nullptr) {
irq_add_shared_handler(DMA_IRQ_0, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(DMA_IRQ_0, true);
}
unicorn = this;
}
void GalacticUnicorn::clear() {
if(unicorn == this) {
for(uint8_t y = 0; y < HEIGHT; y++) {
for(uint8_t x = 0; x < WIDTH; x++) {
set_pixel(x, y, 0, 0, 0);
}
}
}
}
void GalacticUnicorn::dma_safe_abort(uint channel) {
// Tear down the DMA channel.
// This is copied from: https://github.com/raspberrypi/pico-sdk/pull/744/commits/5e0e8004dd790f0155426e6689a66e08a83cd9fc
uint32_t irq0_save = dma_hw->inte0 & (1u << channel);
hw_clear_bits(&dma_hw->inte0, irq0_save);
dma_hw->abort = 1u << channel;
// To fence off on in-flight transfers, the BUSY bit should be polled
// rather than the ABORT bit, because the ABORT bit can clear prematurely.
while (dma_hw->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents();
// Clear the interrupt (if any) and restore the interrupt masks.
dma_hw->ints0 = 1u << channel;
hw_set_bits(&dma_hw->inte0, irq0_save);
}
void GalacticUnicorn::play_sample(uint8_t *data, uint32_t length) {
stop_playing();
if(unicorn == this) {
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
dma_channel_transfer_from_buffer_now(audio_dma_channel, data, length / 2);
play_mode = PLAYING_BUFFER;
}
}
void GalacticUnicorn::play_synth() {
if(play_mode != PLAYING_SYNTH) {
stop_playing();
}
if(unicorn == this && play_mode == NOT_PLAYING) {
// Nothing is playing, so we can set up the first buffer straight away
current_buffer = 0;
populate_next_synth();
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
play_mode = PLAYING_SYNTH;
next_audio_sequence();
}
}
void GalacticUnicorn::next_audio_sequence() {
// Clear any interrupt request caused by our channel
//dma_channel_acknowledge_irq0(audio_dma_channel);
// NOTE Temporary replacement of the above until this reaches pico-sdk main:
// https://github.com/raspberrypi/pico-sdk/issues/974
dma_hw->ints0 = 1u << audio_dma_channel;
if(play_mode == PLAYING_SYNTH) {
dma_channel_transfer_from_buffer_now(audio_dma_channel, tone_buffers[current_buffer], TONE_BUFFER_SIZE);
current_buffer = (current_buffer + 1) % NUM_TONE_BUFFERS;
populate_next_synth();
}
else {
play_mode = NOT_PLAYING;
}
}
void GalacticUnicorn::populate_next_synth() {
int16_t *samples = tone_buffers[current_buffer];
for(uint i = 0; i < TONE_BUFFER_SIZE; i++) {
samples[i] = synth.get_audio_frame();
}
}
void GalacticUnicorn::stop_playing() {
if(unicorn == this) {
// Stop the audio SM
pio_sm_set_enabled(audio_pio, audio_sm, false);
// Reset the I2S pins to avoid popping when audio is suddenly stopped
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
// Abort any in-progress DMA transfer
dma_safe_abort(audio_dma_channel);
play_mode = NOT_PLAYING;
}
}
AudioChannel& GalacticUnicorn::synth_channel(uint channel) {
assert(channel < PicoSynth::CHANNEL_COUNT);
return synth.channels[channel];
}
void GalacticUnicorn::set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
// make those coordinates sane
x = (WIDTH - 1) - x;
y = (HEIGHT - 1) - y;
r = (r * this->brightness) >> 8;
g = (g * this->brightness) >> 8;
b = (b * this->brightness) >> 8;
uint16_t gamma_r = r_gamma_lut[r];
uint16_t gamma_g = g_gamma_lut[g];
uint16_t gamma_b = b_gamma_lut[b];
// for each row:
// for each bcd frame:
// 0: 00110110 // row pixel count (minus one)
// 1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
// 56: xxxxrrrr // row select bits
// 57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
// set the appropriate bits in the separate bcd frames
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
uint8_t *p = &bitstream[y * ROW_BYTES + (BCD_FRAME_BYTES * frame) + 1 + x];
uint8_t red_bit = gamma_r & 0b1;
uint8_t green_bit = gamma_g & 0b1;
uint8_t blue_bit = gamma_b & 0b1;
*p = (blue_bit << 0) | (green_bit << 1) | (red_bit << 2);
gamma_r >>= 1;
gamma_g >>= 1;
gamma_b >>= 1;
}
}
void GalacticUnicorn::set_brightness(float value) {
value = value < 0.0f ? 0.0f : value;
value = value > 1.0f ? 1.0f : value;
this->brightness = floor(value * 256.0f);
}
float GalacticUnicorn::get_brightness() {
return this->brightness / 255.0f;
}
void GalacticUnicorn::adjust_brightness(float delta) {
this->set_brightness(this->get_brightness() + delta);
}
void GalacticUnicorn::set_volume(float value) {
value = value < 0.0f ? 0.0f : value;
value = value > 1.0f ? 1.0f : value;
this->volume = floor(value * 255.0f);
}
float GalacticUnicorn::get_volume() {
return this->volume / 255.0f;
}
void GalacticUnicorn::adjust_volume(float delta) {
this->set_volume(this->get_volume() + delta);
}
void GalacticUnicorn::update(PicoGraphics *graphics) {
if(unicorn == this) {
if(graphics->pen_type == PicoGraphics::PEN_RGB888) {
uint32_t *p = (uint32_t *)graphics->frame_buffer;
for(size_t j = 0; j < 53 * 11; j++) {
int x = j % 53;
int y = j / 53;
uint32_t col = *p;
uint8_t r = (col & 0xff0000) >> 16;
uint8_t g = (col & 0x00ff00) >> 8;
uint8_t b = (col & 0x0000ff) >> 0;
p++;
set_pixel(x, y, r, g, b);
}
}
else if(graphics->pen_type == PicoGraphics::PEN_RGB565) {
uint16_t *p = (uint16_t *)graphics->frame_buffer;
for(size_t j = 0; j < 53 * 11; j++) {
int x = j % 53;
int y = j / 53;
uint16_t col = __builtin_bswap16(*p);
uint8_t r = (col & 0b1111100000000000) >> 8;
uint8_t g = (col & 0b0000011111100000) >> 3;
uint8_t b = (col & 0b0000000000011111) << 3;
p++;
set_pixel(x, y, r, g, b);
}
}
}
}
bool GalacticUnicorn::is_pressed(uint8_t button) {
return !gpio_get(button);
}
}

Wyświetl plik

@ -0,0 +1,127 @@
#pragma once
#include "hardware/pio.h"
#include "pico_graphics.hpp"
#include "../pico_synth/pico_synth.hpp"
namespace pimoroni {
class GalacticUnicorn {
public:
static const int WIDTH = 53;
static const int HEIGHT = 11;
// pin assignments
static const uint8_t COLUMN_CLOCK = 13;
static const uint8_t COLUMN_DATA = 14;
static const uint8_t COLUMN_LATCH = 15;
static const uint8_t COLUMN_BLANK = 16;
static const uint8_t ROW_BIT_0 = 17;
static const uint8_t ROW_BIT_1 = 18;
static const uint8_t ROW_BIT_2 = 19;
static const uint8_t ROW_BIT_3 = 20;
static const uint8_t LIGHT_SENSOR = 28;
static const uint8_t MUTE = 22;
static const uint8_t I2S_DATA = 9;
static const uint8_t I2S_BCLK = 10;
static const uint8_t I2S_LRCLK = 11;
static const uint8_t I2C_SDA = 4;
static const uint8_t I2C_SCL = 5;
static const uint8_t SWITCH_A = 0;
static const uint8_t SWITCH_B = 1;
static const uint8_t SWITCH_C = 3;
static const uint8_t SWITCH_D = 6;
static const uint8_t SWITCH_SLEEP = 27;
static const uint8_t SWITCH_VOLUME_UP = 7;
static const uint8_t SWITCH_VOLUME_DOWN = 8;
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
private:
static const uint32_t ROW_COUNT = 11;
static const uint32_t BCD_FRAME_COUNT = 14;
static const uint32_t BCD_FRAME_BYTES = 60;
static const uint32_t ROW_BYTES = BCD_FRAME_COUNT * BCD_FRAME_BYTES;
static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES);
static const uint SYSTEM_FREQ = 22050;
private:
static PIO bitstream_pio;
static uint bitstream_sm;
static uint bitstream_sm_offset;
static PIO audio_pio;
static uint audio_sm;
static uint audio_sm_offset;
uint16_t brightness = 256;
uint16_t volume = 127;
// must be aligned for 32bit dma transfer
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
const uint32_t bitstream_addr = (uint32_t)bitstream;
static GalacticUnicorn* unicorn;
static void dma_complete();
static const uint NUM_TONE_BUFFERS = 2;
static const uint TONE_BUFFER_SIZE = 4;
int16_t tone_buffers[NUM_TONE_BUFFERS][TONE_BUFFER_SIZE] = {0};
uint current_buffer = 0;
PicoSynth synth;
enum PlayMode {
PLAYING_BUFFER,
//PLAYING_TONE,
PLAYING_SYNTH,
NOT_PLAYING
};
PlayMode play_mode = NOT_PLAYING;
public:
~GalacticUnicorn();
void init();
static inline void pio_program_init(PIO pio, uint sm, uint offset);
void clear();
void update(PicoGraphics *graphics);
void set_brightness(float value);
float get_brightness();
void adjust_brightness(float delta);
void set_volume(float value);
float get_volume();
void adjust_volume(float delta);
private:
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
public:
uint16_t light();
bool is_pressed(uint8_t button);
void play_sample(uint8_t *data, uint32_t length);
void play_synth();
void stop_playing();
AudioChannel& synth_channel(uint channel);
private:
void partial_teardown();
void dma_safe_abort(uint channel);
void next_audio_sequence();
void populate_next_synth();
};
}

Wyświetl plik

@ -0,0 +1,80 @@
.program galactic_unicorn
.side_set 1 opt
; out pins:
;
; - 3: row select bit 0
; - 4: row select bit 1
; - 5: row select bit 2
; - 6: row select bit 3
; set pins:
;
; - 0: column data (base)
; - 1: column latch
; - 2: column blank
; sideset pin:
;
; - 0: column clock
; for each row:
; for each bcd frame:
; 0: 00110110 // row pixel count (minus one)
; 1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
; 54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
; 56: xxxxrrrr // row select bits
; 57 - 59: tttttttt, tttttttt, tttttttt, // bcd tick count (0-65536)
;
; .. and back to the start
.wrap_target
; loop over row pixels
out y, 8 ; get row pixel count (minus 1 because test is pre decrement)
pixels:
; red bit
out x, 1 side 0 [1] ; pull in blue bit from OSR into register x, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endb ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endb:
nop side 1 [2] ; clock in bit
; green bit
out x, 1 side 0 [1] ; pull in green bit from OSR into register X, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endg ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endg:
nop side 1 [2] ; clock in bit
; blue bit
out x, 1 side 0 [1] ; pull in red bit from OSR into register X, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endr ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endr:
out null, 5 side 1 [2] ; clock in bit
;out null, 5 side 0 ; discard the five dummy bits for this pixel
jmp y-- pixels
out null, 16 ; discard dummy bytes
out pins, 8 ; output row select
set pins, 0b110 [5] ; latch high, blank high
set pins, 0b000 ; blank low (enable output)
; loop over bcd delay period
out y, 24 ; get bcd delay counter value
bcd_delay:
jmp y-- bcd_delay
set pins 0b100 ; blank high (disable output)
.wrap

Wyświetl plik

@ -276,8 +276,6 @@ namespace pimoroni {
void PicoGraphics::line(Point p1, Point p2) {
// fast horizontal line
if(p1.y == p2.y) {
p1 = p1.clamp(clip);
p2 = p2.clamp(clip);
int32_t start = std::min(p1.x, p2.x);
int32_t end = std::max(p1.x, p2.x);
pixel_span(Point(start, p1.y), end - start);
@ -286,13 +284,11 @@ namespace pimoroni {
// fast vertical line
if(p1.x == p2.x) {
p1 = p1.clamp(clip);
p2 = p2.clamp(clip);
int32_t start = std::min(p1.y, p2.y);
int32_t length = std::max(p1.y, p2.y) - start;
Point dest(p1.x, start);
while(length--) {
set_pixel(dest);
pixel(dest);
dest.y++;
}
return;
@ -314,7 +310,7 @@ namespace pimoroni {
int32_t y = p1.y << 16;
while(s--) {
Point p(x, y >> 16);
if(clip.contains(p)) set_pixel(p);
pixel(p);
y += sy;
x += sx;
}
@ -327,7 +323,7 @@ namespace pimoroni {
int32_t x = p1.x << 16;
while(s--) {
Point p(x >> 16, y);
if(clip.contains(p)) set_pixel(p);
pixel(p);
y += sy;
x += sx;
}

Wyświetl plik

@ -9,10 +9,11 @@ namespace pimoroni {
}
}
void PicoGraphics_PenRGB888::set_pen(uint c) {
color = RGB(c, c, c).to_rgb888();
color = c;
}
void PicoGraphics_PenRGB888::set_pen(uint8_t r, uint8_t g, uint8_t b) {
color = RGB(r, g, b).to_rgb888();
src_color = {r, g, b};
color = src_color.to_rgb888();
}
int PicoGraphics_PenRGB888::create_pen(uint8_t r, uint8_t g, uint8_t b) {
return RGB(r, g, b).to_rgb888();

Wyświetl plik

@ -0,0 +1,207 @@
#include "pico_synth.hpp"
namespace pimoroni {
uint32_t prng_xorshift_state = 0x32B71700;
uint32_t prng_xorshift_next() {
uint32_t x = prng_xorshift_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
prng_xorshift_state = x;
return x;
}
int32_t prng_normal() {
// rough approximation of a normal distribution
uint32_t r0 = prng_xorshift_next();
uint32_t r1 = prng_xorshift_next();
uint32_t n = ((r0 & 0xffff) + (r1 & 0xffff) + (r0 >> 16) + (r1 >> 16)) / 2;
return n - 0xffff;
}
void AudioChannel::trigger_attack() {
adsr_frame = 0;
adsr_phase = ADSRPhase::ATTACK;
adsr_end_frame = (attack_ms * sample_rate) / 1000;
adsr_step = (int32_t(0xffffff) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
}
void AudioChannel::trigger_decay() {
adsr_frame = 0;
adsr_phase = ADSRPhase::DECAY;
adsr_end_frame = (decay_ms * sample_rate) / 1000;
adsr_step = (int32_t(sustain << 8) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
}
void AudioChannel::trigger_sustain() {
adsr_frame = 0;
adsr_phase = ADSRPhase::SUSTAIN;
adsr_end_frame = 0;
adsr_step = 0;
}
void AudioChannel::trigger_release() {
adsr_frame = 0;
adsr_phase = ADSRPhase::RELEASE;
adsr_end_frame = (release_ms * sample_rate) / 1000;
adsr_step = (int32_t(0) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
}
void AudioChannel::off() {
adsr_frame = 0;
adsr_phase = ADSRPhase::OFF;
adsr_step = 0;
}
void AudioChannel::restore() {
// Put all the parameters back to their initial values
waveforms = 0;
frequency = 660;
volume = 0xffff;
attack_ms = 2;
decay_ms = 6;
sustain = 0xffff;
release_ms = 1;
pulse_width = 0x7fff;
noise = 0;
}
bool PicoSynth::is_audio_playing() {
if(volume == 0) {
return false;
}
bool any_channel_playing = false;
for(uint c = 0; c < CHANNEL_COUNT; c++) {
if(channels[c].volume > 0 && channels[c].adsr_phase != ADSRPhase::OFF) {
any_channel_playing = true;
}
}
return any_channel_playing;
}
int16_t PicoSynth::get_audio_frame() {
int32_t sample = 0; // used to combine channel output
for(uint c = 0; c < CHANNEL_COUNT; c++) {
auto &channel = channels[c];
// increment the waveform position counter. this provides an
// Q16 fixed point value representing how far through
// the current waveform we are
channel.waveform_offset += ((channel.frequency * 256) << 8) / sample_rate;
if(channel.adsr_phase == ADSRPhase::OFF) {
continue;
}
if((channel.adsr_frame >= channel.adsr_end_frame) && (channel.adsr_phase != ADSRPhase::SUSTAIN)) {
switch (channel.adsr_phase) {
case ADSRPhase::ATTACK:
channel.trigger_decay();
break;
case ADSRPhase::DECAY:
channel.trigger_sustain();
break;
case ADSRPhase::RELEASE:
channel.off();
break;
default:
break;
}
}
channel.adsr_level += channel.adsr_step;
channel.adsr_frame++;
if(channel.waveform_offset & 0x10000) {
// if the waveform offset overflows then generate a new
// random noise sample
channel.noise = prng_normal();
}
channel.waveform_offset &= 0xffff;
// check if any waveforms are active for this channel
if(channel.waveforms) {
uint8_t waveform_count = 0;
int32_t channel_sample = 0;
if(channel.waveforms & Waveform::NOISE) {
channel_sample += channel.noise;
waveform_count++;
}
if(channel.waveforms & Waveform::SAW) {
channel_sample += (int32_t)channel.waveform_offset - 0x7fff;
waveform_count++;
}
// creates a triangle wave of ^
if(channel.waveforms & Waveform::TRIANGLE) {
if(channel.waveform_offset < 0x7fff) { // initial quarter up slope
channel_sample += int32_t(channel.waveform_offset * 2) - int32_t(0x7fff);
}
else { // final quarter up slope
channel_sample += int32_t(0x7fff) - ((int32_t(channel.waveform_offset) - int32_t(0x7fff)) * 2);
}
waveform_count++;
}
if(channel.waveforms & Waveform::SQUARE) {
channel_sample += (channel.waveform_offset < channel.pulse_width) ? 0x7fff : -0x7fff;
waveform_count++;
}
if(channel.waveforms & Waveform::SINE) {
// the sine_waveform sample contains 256 samples in
// total so we'll just use the most significant bits
// of the current waveform position to index into it
channel_sample += sine_waveform[channel.waveform_offset >> 8];
waveform_count++;
}
if(channel.waveforms & Waveform::WAVE) {
channel_sample += channel.wave_buffer[channel.wave_buf_pos];
if(++channel.wave_buf_pos == 64) {
channel.wave_buf_pos = 0;
if(channel.wave_buffer_callback)
channel.wave_buffer_callback(channel);
}
waveform_count++;
}
channel_sample = channel_sample / waveform_count;
channel_sample = (int64_t(channel_sample) * int32_t(channel.adsr_level >> 8)) >> 16;
// apply channel volume
channel_sample = (int64_t(channel_sample) * int32_t(channel.volume)) >> 16;
// apply channel filter
//if (channel.filter_enable) {
//float filter_epow = 1 - expf(-(1.0f / 22050.0f) * 2.0f * pi * int32_t(channel.filter_cutoff_frequency));
//channel_sample += (channel_sample - channel.filter_last_sample) * filter_epow;
//}
//channel.filter_last_sample = channel_sample;
// combine channel sample into the final sample
sample += channel_sample;
}
}
sample = (int64_t(sample) * int32_t(volume)) >> 16;
// clip result to 16-bit
sample = sample <= -0x8000 ? -0x8000 : (sample > 0x7fff ? 0x7fff : sample);
return sample;
}
}

Wyświetl plik

@ -0,0 +1,120 @@
#pragma once
//#include <string>
//#include <array>
//#include <cstdint>
//#include <algorithm>
//#include <vector>
//#include <functional>
#include "common/pimoroni_common.hpp"
namespace pimoroni {
// The duration a note is played is determined by the amount of attack,
// decay, and release, combined with the length of the note as defined by
// the user.
//
// - Attack: number of milliseconds it takes for a note to hit full volume
// - Decay: number of milliseconds it takes for a note to settle to sustain volume
// - Sustain: percentage of full volume that the note sustains at (duration implied by other factors)
// - Release: number of milliseconds it takes for a note to reduce to zero volume after it has ended
//
// Attack (750ms) - Decay (500ms) -------- Sustain ----- Release (250ms)
//
// + + + +
// | | | |
// | | | |
// | | | |
// v v v v
// 0ms 1000ms 2000ms 3000ms 4000ms
//
// | XXXX | | | |
// | X X|XX | | |
// | X | XXX | | |
// | X | XXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX| |
// | X | | |X |
// | X | | |X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X + + + | + + + | + + + | + + + | +
// | X | | | | | | | | | | | | | | | | |
// |X | | | | | | | | | | | | | | | | |
// +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+--->
enum Waveform {
NOISE = 128,
SQUARE = 64,
SAW = 32,
TRIANGLE = 16,
SINE = 8,
WAVE = 1
};
enum class ADSRPhase : uint8_t {
ATTACK,
DECAY,
SUSTAIN,
RELEASE,
OFF
};
const int16_t sine_waveform[256] = {-32768,-32758,-32729,-32679,-32610,-32522,-32413,-32286,-32138,-31972,-31786,-31581,-31357,-31114,-30853,-30572,-30274,-29957,-29622,-29269,-28899,-28511,-28106,-27684,-27246,-26791,-26320,-25833,-25330,-24812,-24279,-23732,-23170,-22595,-22006,-21403,-20788,-20160,-19520,-18868,-18205,-17531,-16846,-16151,-15447,-14733,-14010,-13279,-12540,-11793,-11039,-10279,-9512,-8740,-7962,-7180,-6393,-5602,-4808,-4011,-3212,-2411,-1608,-804,0,804,1608,2411,3212,4011,4808,5602,6393,7180,7962,8740,9512,10279,11039,11793,12540,13279,14010,14733,15447,16151,16846,17531,18205,18868,19520,20160,20788,21403,22006,22595,23170,23732,24279,24812,25330,25833,26320,26791,27246,27684,28106,28511,28899,29269,29622,29957,30274,30572,30853,31114,31357,31581,31786,31972,32138,32286,32413,32522,32610,32679,32729,32758,32767,32758,32729,32679,32610,32522,32413,32286,32138,31972,31786,31581,31357,31114,30853,30572,30274,29957,29622,29269,28899,28511,28106,27684,27246,26791,26320,25833,25330,24812,24279,23732,23170,22595,22006,21403,20788,20160,19520,18868,18205,17531,16846,16151,15447,14733,14010,13279,12540,11793,11039,10279,9512,8740,7962,7180,6393,5602,4808,4011,3212,2411,1608,804,0,-804,-1608,-2411,-3212,-4011,-4808,-5602,-6393,-7180,-7962,-8740,-9512,-10279,-11039,-11793,-12540,-13279,-14010,-14733,-15447,-16151,-16846,-17531,-18205,-18868,-19520,-20160,-20788,-21403,-22006,-22595,-23170,-23732,-24279,-24812,-25330,-25833,-26320,-26791,-27246,-27684,-28106,-28511,-28899,-29269,-29622,-29957,-30274,-30572,-30853,-31114,-31357,-31581,-31786,-31972,-32138,-32286,-32413,-32522,-32610,-32679,-32729,-32758};
const uint32_t sample_rate = 22050;
struct AudioChannel {
uint8_t waveforms = 0; // bitmask for enabled waveforms (see Waveform enum for values)
uint16_t frequency = 660; // frequency of the voice (Hz)
uint16_t volume = UINT16_MAX; // channel volume
uint16_t attack_ms = 2; // attack period (cannot be zero)
uint16_t decay_ms = 6; // decay period (cannot be zero)
uint16_t sustain = UINT16_MAX; // sustain volume
uint16_t release_ms = 1; // release period
uint16_t pulse_width = 0x7fff; // duty cycle of square wave (default 50%)
int16_t noise = 0; // current noise value
uint32_t waveform_offset = 0; // voice offset (Q8)
int32_t filter_last_sample = 0;
bool filter_enable = false;
uint16_t filter_cutoff_frequency = 0;
uint32_t adsr_frame = 0; // number of frames into the current ADSR phase
uint32_t adsr_end_frame = 0; // frame target at which the ADSR changes to the next phase
uint32_t adsr_level = 0; // the output level at the current frame of the ADSR phase
int32_t adsr_step = 0; // the amount to increment the level with each frame
ADSRPhase adsr_phase = ADSRPhase::OFF;
uint8_t wave_buf_pos = 0; //
int16_t wave_buffer[64]; // buffer for arbitrary waveforms. small as it's filled by user callback
void *user_data = nullptr;
void (*wave_buffer_callback)(AudioChannel &channel);
void trigger_attack();
void trigger_decay();
void trigger_sustain();
void trigger_release();
void off();
void restore();
};
class PicoSynth {
public:
const uint16_t volume = 0x2fff;
static const uint CHANNEL_COUNT = 8;
AudioChannel channels[CHANNEL_COUNT];
int16_t get_audio_frame();
bool is_audio_playing();
};
constexpr float pi = 3.14159265358979323846f;
}

Wyświetl plik

@ -0,0 +1,69 @@
import time
import random
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
colour = (230, 150, 0)
@micropython.native # noqa: F821
def setup():
global width, height, lifetime, age
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
lifetime = [[0.0 for y in range(height)] for x in range(width)]
age = [[0.0 for y in range(height)] for x in range(width)]
for y in range(height):
for x in range(width):
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
@micropython.native # noqa: F821
def draw():
for y in range(height):
for x in range(width):
if age[x][y] < lifetime[x][y] * 0.3:
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
elif age[x][y] < lifetime[x][y] * 0.5:
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
gu.update(graphics)
@micropython.native # noqa: F821
def update():
for y in range(height):
for x in range(width):
if age[x][y] >= lifetime[x][y]:
age[x][y] = 0.0
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] += 0.025
setup()
gu.set_brightness(0.5)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
start = time.ticks_ms()
draw()
update()
print("total took: {} ms".format(time.ticks_ms() - start))

Wyświetl plik

@ -0,0 +1,117 @@
import time
import math
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(53 / 2 - w / 2 + 1)
y = 2
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
gu.set_brightness(0.5)
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
print("grid pattern")
grid(255, 255, 255)
elif test == 1:
print("red gradient")
gradient(255, 0, 0)
elif test == 2:
print("green gradient")
gradient(0, 255, 0)
elif test == 3:
print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
print("white gradient")
gradient(255, 255, 255)
text = ""
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
text = "Button A"
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
text = "Button B"
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
text = "Button C"
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
text = "Button D"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
text = "Louder!"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
text = "Quieter"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Brighter!"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Darker"
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
text = "Zzz... zzz..."
outline_text(text)
gu.update(graphics)

Wyświetl plik

@ -0,0 +1,936 @@
import gc
import time
import math
from machine import Timer
from galactic import GalacticUnicorn, Channel
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gc.collect()
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
left_channel = bytearray((
0xfe, 0xff, 0xfb, 0xff, 0xf9, 0xff, 0xf6, 0xff, 0xee, 0xff, 0xed, 0xff,
0xec, 0xff, 0xed, 0xff, 0xec, 0xff, 0xed, 0xff, 0xef, 0xff, 0xf3, 0xff,
0x01, 0x00, 0x04, 0x00, 0xe8, 0xff, 0xf6, 0xff, 0x08, 0x00, 0x06, 0x00,
0x09, 0x00, 0x10, 0x00, 0x39, 0x00, 0x5c, 0x00, 0x6e, 0x00, 0x65, 0x00,
0x6e, 0x00, 0x88, 0x00, 0x80, 0x00, 0x70, 0x00, 0x78, 0x00, 0xab, 0x00,
0xa1, 0x00, 0xa9, 0x00, 0xab, 0x00, 0xe1, 0x00, 0x21, 0x01, 0x3c, 0x01,
0x3a, 0x01, 0x3f, 0x01, 0x55, 0x01, 0x88, 0x01, 0xe4, 0x01, 0x02, 0x02,
0xeb, 0x01, 0xef, 0x01, 0x1c, 0x02, 0x1a, 0x02, 0x3d, 0x02, 0x45, 0x02,
0x3f, 0x02, 0x1e, 0x02, 0x1b, 0x02, 0x5d, 0x02, 0xb7, 0x02, 0x3c, 0x03,
0x01, 0x03, 0x31, 0x03, 0x5e, 0x03, 0x47, 0x03, 0x8d, 0x03, 0xf2, 0x03,
0xe4, 0x03, 0xc7, 0x03, 0x2d, 0x03, 0x32, 0x03, 0xba, 0x03, 0x3e, 0x03,
0xb3, 0x02, 0x54, 0x02, 0x14, 0x02, 0xd5, 0x01, 0x4e, 0x01, 0xcc, 0x00,
0xee, 0xff, 0xeb, 0xfe, 0x97, 0xfe, 0x08, 0xfe, 0x83, 0xfd, 0xe7, 0xfc,
0x52, 0xfc, 0x0b, 0xfc, 0x78, 0xfc, 0xc0, 0xfb, 0xe2, 0xfa, 0x3a, 0xfb,
0x57, 0xfb, 0x07, 0xfb, 0x9b, 0xfa, 0x09, 0xfa, 0x18, 0xfa, 0xee, 0xfa,
0xb7, 0xfa, 0x1e, 0xfa, 0x4e, 0xf9, 0x39, 0xf9, 0x7d, 0xf9, 0x21, 0xfa,
0xcb, 0xfa, 0x9e, 0xfa, 0xa1, 0xfa, 0x92, 0xfa, 0x52, 0xfb, 0x8e, 0xfb,
0xa4, 0xfb, 0x5e, 0xfb, 0x03, 0xfc, 0x0c, 0xfd, 0xc3, 0xfd, 0xfe, 0xfd,
0xda, 0xfd, 0x6e, 0xfe, 0x1d, 0xff, 0xf6, 0xff, 0x02, 0x00, 0xfc, 0xff,
0x4b, 0x00, 0xcc, 0x00, 0x40, 0x01, 0x87, 0x01, 0xf0, 0x01, 0x2e, 0x02,
0xf6, 0x02, 0x78, 0x03, 0xa4, 0x03, 0x3f, 0x04, 0x8f, 0x04, 0xdf, 0x04,
0x11, 0x05, 0x97, 0x04, 0x05, 0x05, 0x57, 0x05, 0xae, 0x05, 0xb2, 0x05,
0x84, 0x05, 0xc2, 0x05, 0x86, 0x05, 0x9f, 0x05, 0xfb, 0x05, 0x0e, 0x06,
0x10, 0x06, 0xec, 0x05, 0xc4, 0x05, 0xc1, 0x05, 0xa1, 0x05, 0xa2, 0x06,
0xba, 0x06, 0xbd, 0x05, 0xe9, 0x05, 0xf0, 0x05, 0x9a, 0x06, 0xfd, 0x06,
0x45, 0x06, 0x3b, 0x06, 0x8d, 0x05, 0x69, 0x05, 0x8e, 0x05, 0x09, 0x05,
0xbd, 0x04, 0x5f, 0x04, 0xcd, 0x03, 0xd7, 0x03, 0x74, 0x03, 0x81, 0x02,
0x6e, 0x01, 0x21, 0x00, 0x1a, 0xff, 0xe3, 0xfe, 0x4b, 0xfe, 0x3f, 0xfd,
0xc3, 0xfb, 0x0d, 0xfb, 0x8a, 0xf9, 0x15, 0xf8, 0x6a, 0xf7, 0x92, 0xf7,
0xb3, 0xf7, 0xe6, 0xf7, 0xc3, 0xf6, 0xce, 0xf5, 0xc4, 0xf5, 0xe1, 0xf5,
0xdd, 0xf5, 0x0e, 0xf5, 0xa4, 0xf4, 0xe5, 0xf4, 0xe9, 0xf4, 0x60, 0xf4,
0xac, 0xf4, 0xba, 0xf4, 0xb2, 0xf5, 0xad, 0xf6, 0xf7, 0xf6, 0x4a, 0xf7,
0x63, 0xf7, 0xad, 0xf7, 0xe4, 0xf8, 0x89, 0xf9, 0xc0, 0xf9, 0x80, 0xfa,
0x93, 0xfb, 0xac, 0xfc, 0xae, 0xfd, 0x51, 0xfe, 0xf3, 0xfe, 0x91, 0x00,
0x12, 0x02, 0x4a, 0x03, 0x60, 0x03, 0x84, 0x03, 0x6a, 0x04, 0xdd, 0x04,
0xa3, 0x05, 0x45, 0x06, 0xeb, 0x06, 0xd6, 0x07, 0x74, 0x08, 0xaa, 0x08,
0xdd, 0x08, 0x17, 0x09, 0xb8, 0x09, 0x27, 0x0a, 0x60, 0x0a, 0x7e, 0x0a,
0xff, 0x0a, 0x18, 0x0b, 0x08, 0x0b, 0x3a, 0x0b, 0x97, 0x0b, 0xfe, 0x0b,
0xd7, 0x0b, 0x1c, 0x0b, 0xc8, 0x0a, 0x85, 0x0a, 0x4f, 0x0a, 0x8c, 0x0a,
0x4a, 0x0a, 0xc0, 0x09, 0x80, 0x09, 0xf5, 0x08, 0xc0, 0x08, 0xe4, 0x08,
0x10, 0x08, 0x4a, 0x06, 0x89, 0x05, 0x51, 0x05, 0x7a, 0x04, 0xd6, 0x02,
0x2a, 0x01, 0x5d, 0x00, 0x20, 0x00, 0xb6, 0xff, 0x24, 0xfe, 0xc6, 0xfa,
0x8e, 0xf9, 0xfc, 0xf9, 0xe8, 0xf9, 0x67, 0xf8, 0x84, 0xf5, 0x67, 0xf5,
0xc7, 0xf6, 0x4c, 0xf6, 0xb9, 0xf5, 0x47, 0xf4, 0x12, 0xf3, 0xa4, 0xf3,
0xdd, 0xf3, 0x8f, 0xf3, 0x5f, 0xf2, 0x14, 0xf1, 0xa3, 0xf0, 0xb7, 0xf0,
0xa1, 0xf1, 0x89, 0xf2, 0x7a, 0xf2, 0xa5, 0xf2, 0xea, 0xf2, 0xa1, 0xf3,
0x5d, 0xf4, 0x01, 0xf5, 0x7a, 0xf5, 0x8a, 0xf5, 0x03, 0xf6, 0x4e, 0xf7,
0xe7, 0xf8, 0x41, 0xf9, 0xa7, 0xf9, 0x0b, 0xfb, 0x03, 0xfd, 0x2f, 0xff,
0x30, 0x00, 0xfd, 0x00, 0x2c, 0x01, 0xf8, 0x01, 0xa2, 0x03, 0xa6, 0x04,
0x55, 0x05, 0xaa, 0x05, 0x00, 0x07, 0x3f, 0x08, 0xd0, 0x08, 0xb4, 0x09,
0x30, 0x0a, 0xbc, 0x0a, 0xf0, 0x0b, 0x24, 0x0c, 0xe7, 0x0b, 0x00, 0x0c,
0x7a, 0x0c, 0x30, 0x0d, 0xe2, 0x0c, 0x01, 0x0d, 0x4a, 0x0d, 0x9f, 0x0d,
0xa8, 0x0d, 0x36, 0x0d, 0x0c, 0x0d, 0x97, 0x0c, 0xad, 0x0c, 0xce, 0x0c,
0xe5, 0x0b, 0xb0, 0x0b, 0x59, 0x0b, 0xe7, 0x0a, 0xc0, 0x0a, 0x38, 0x0a,
0xb0, 0x09, 0x08, 0x09, 0xbe, 0x08, 0x6d, 0x08, 0xf0, 0x06, 0xbf, 0x05,
0xd7, 0x04, 0x0d, 0x05, 0x20, 0x04, 0xe4, 0x02, 0x00, 0x02, 0x35, 0x00,
0xd0, 0xfd, 0x28, 0xfd, 0x2d, 0xfc, 0x24, 0xfa, 0xc7, 0xf8, 0xde, 0xf6,
0x2c, 0xf6, 0x5d, 0xf6, 0x74, 0xf5, 0x3f, 0xf5, 0x33, 0xf4, 0xb4, 0xf2,
0xf9, 0xf2, 0xe3, 0xf2, 0xd6, 0xf2, 0x04, 0xf2, 0xf9, 0xef, 0xf7, 0xee,
0xc8, 0xef, 0x12, 0xf1, 0x74, 0xf0, 0xbf, 0xee, 0x3c, 0xf0, 0x79, 0xf1,
0x40, 0xf1, 0x5c, 0xf1, 0xd9, 0xf1, 0xf3, 0xf2, 0xdf, 0xf3, 0xcf, 0xf3,
0xcb, 0xf3, 0x90, 0xf4, 0x9f, 0xf5, 0x5c, 0xf7, 0x3c, 0xf9, 0x02, 0xfa,
0x60, 0xfb, 0xcc, 0xfc, 0x7d, 0xfe, 0x11, 0x00, 0x3e, 0x01, 0x67, 0x02,
0x33, 0x03, 0xbf, 0x04, 0x4a, 0x06, 0x2d, 0x07, 0x49, 0x08, 0x43, 0x09,
0xb9, 0x0a, 0x54, 0x0c, 0x23, 0x0d, 0xc0, 0x0d, 0x6a, 0x0e, 0x0d, 0x0f,
0x1c, 0x0f, 0x48, 0x0f, 0x62, 0x0f, 0xcb, 0x0e, 0xfb, 0x0e, 0x83, 0x0f,
0x99, 0x0f, 0x48, 0x0f, 0x30, 0x0f, 0x28, 0x0f, 0x02, 0x0f, 0x6f, 0x0e,
0xb5, 0x0d, 0x78, 0x0c, 0xd0, 0x0b, 0x34, 0x0b, 0xc1, 0x0a, 0x18, 0x0a,
0x12, 0x09, 0x5b, 0x08, 0x8d, 0x07, 0x72, 0x06, 0x17, 0x05, 0xf9, 0x03,
0xae, 0x03, 0xcf, 0x02, 0xd0, 0x00, 0xf5, 0xfe, 0x4c, 0xfe, 0xb3, 0xfd,
0x1b, 0xfd, 0x63, 0xfb, 0x9a, 0xf7, 0x62, 0xf6, 0xd7, 0xf7, 0xc8, 0xf7,
0x15, 0xf6, 0x6e, 0xf4, 0x22, 0xf3, 0x5f, 0xf3, 0xb8, 0xf3, 0xe3, 0xf2,
0x15, 0xf2, 0x3c, 0xf2, 0x19, 0xf1, 0x8c, 0xf1, 0x98, 0xf1, 0x65, 0xef,
0xfc, 0xef, 0x7b, 0xf0, 0x76, 0xf1, 0xac, 0xf2, 0x59, 0xf2, 0xb7, 0xf1,
0x9e, 0xf2, 0xda, 0xf3, 0x33, 0xf4, 0xca, 0xf4, 0x9d, 0xf4, 0x16, 0xf5,
0x21, 0xf6, 0x9e, 0xf7, 0xfd, 0xf7, 0xb8, 0xf8, 0x85, 0xfa, 0x31, 0xfc,
0x01, 0xfe, 0x32, 0xff, 0x49, 0x00, 0x8e, 0x01, 0x75, 0x02, 0x74, 0x03,
0xa8, 0x04, 0x59, 0x05, 0xaa, 0x06, 0xf9, 0x07, 0x16, 0x09, 0x55, 0x0a,
0x5a, 0x0b, 0xa9, 0x0c, 0x2c, 0x0d, 0xee, 0x0d, 0x32, 0x0e, 0x64, 0x0e,
0xa0, 0x0e, 0x40, 0x0e, 0xb2, 0x0e, 0x9e, 0x0e, 0xfa, 0x0d, 0xed, 0x0d,
0x2e, 0x0e, 0xf5, 0x0e, 0xe1, 0x0e, 0xd1, 0x0d, 0xda, 0x0c, 0x96, 0x0c,
0xa2, 0x0c, 0xd7, 0x0b, 0xaf, 0x0a, 0xdd, 0x09, 0x7e, 0x09, 0x19, 0x09,
0x23, 0x08, 0x0d, 0x07, 0xb5, 0x06, 0xea, 0x06, 0x21, 0x06, 0x62, 0x04,
0x92, 0x02, 0x6d, 0x01, 0xee, 0x00, 0x2b, 0x00, 0x62, 0xfe, 0x5a, 0xfc,
0xeb, 0xfa, 0x63, 0xfa, 0xb5, 0xf9, 0xd0, 0xf7, 0xad, 0xf5, 0xaf, 0xf4,
0x16, 0xf5, 0x35, 0xf5, 0xf6, 0xf3, 0xc7, 0xf2, 0xea, 0xf1, 0x5b, 0xf2,
0xf7, 0xf2, 0x8c, 0xf3, 0xc3, 0xf1, 0x53, 0xef, 0xc9, 0xef, 0x79, 0xf0,
0xc7, 0xf0, 0xbb, 0xf0, 0xfa, 0xef, 0x94, 0xef, 0xae, 0xf0, 0x3b, 0xf2,
0x3e, 0xf3, 0xe9, 0xf2, 0x05, 0xf3, 0xc9, 0xf3, 0xa7, 0xf4, 0x79, 0xf4,
0xe6, 0xf4, 0x56, 0xf6, 0xe7, 0xf7, 0x8c, 0xf9, 0xcd, 0xfa, 0x59, 0xfb,
0x4d, 0xfd, 0xd8, 0xff, 0x8d, 0x01, 0x16, 0x02, 0xdf, 0x02, 0x3f, 0x04,
0xf0, 0x05, 0x71, 0x07, 0xc6, 0x07, 0x1b, 0x09, 0xa0, 0x0a, 0xd1, 0x0b,
0x15, 0x0d, 0x6b, 0x0d, 0x4d, 0x0e, 0x59, 0x0f, 0x33, 0x10, 0xe5, 0x0f,
0x4d, 0x0f, 0x1e, 0x10, 0x0d, 0x11, 0x52, 0x11, 0x90, 0x11, 0xf1, 0x10,
0xf2, 0x0f, 0x95, 0x0f, 0x45, 0x10, 0x25, 0x10, 0x78, 0x0e, 0xf8, 0x0c,
0x90, 0x0c, 0x34, 0x0c, 0x80, 0x0b, 0x40, 0x0a, 0xee, 0x08, 0x5d, 0x08,
0x2e, 0x08, 0x32, 0x07, 0x55, 0x05, 0xdc, 0x03, 0x14, 0x03, 0x6e, 0x02,
0x5c, 0x00, 0x05, 0xfe, 0x32, 0xfc, 0xe6, 0xfb, 0x39, 0xfb, 0x73, 0xf9,
0x14, 0xf7, 0xbe, 0xf4, 0x88, 0xf4, 0x5d, 0xf4, 0xfb, 0xf3, 0x1b, 0xf3,
0xa8, 0xf1, 0x3b, 0xf2, 0x5c, 0xf2, 0x18, 0xf2, 0x2e, 0xf1, 0x97, 0xef,
0xd2, 0xef, 0x8c, 0xf0, 0x3c, 0xef, 0xd8, 0xed, 0x69, 0xed, 0xc5, 0xee,
0x19, 0xf0, 0x67, 0xf0, 0xa4, 0xef, 0x08, 0xf0, 0x46, 0xf1, 0x7b, 0xf2,
0x83, 0xf2, 0x6e, 0xf2, 0x99, 0xf2, 0x60, 0xf4, 0xf2, 0xf5, 0xd2, 0xf5,
0x34, 0xf6, 0xfc, 0xf7, 0x89, 0xfa, 0x07, 0xfd, 0x6e, 0xfe, 0xec, 0xfe,
0xaa, 0xff, 0xb4, 0x00, 0xf7, 0x02, 0x54, 0x04, 0x49, 0x05, 0xe2, 0x05,
0xdc, 0x06, 0xb3, 0x08, 0x46, 0x0a, 0xf6, 0x0b, 0x58, 0x0d, 0xff, 0x0d,
0x86, 0x0f, 0x67, 0x10, 0x62, 0x10, 0xab, 0x0f, 0xdd, 0x0f, 0x00, 0x11,
0x03, 0x12, 0x50, 0x12, 0xd7, 0x11, 0xb9, 0x11, 0x53, 0x12, 0x99, 0x12,
0xd9, 0x12, 0x53, 0x11, 0x4a, 0x0f, 0x97, 0x0e, 0x47, 0x0e, 0x7b, 0x0d,
0x07, 0x0c, 0xdd, 0x0a, 0xfe, 0x09, 0x9c, 0x09, 0x00, 0x09, 0x09, 0x08,
0xfc, 0x06, 0xa6, 0x05, 0xdd, 0x03, 0xfd, 0x01, 0x55, 0x00, 0x0d, 0xff,
0x05, 0xfe, 0x91, 0xfc, 0x46, 0xfb, 0x79, 0xf9, 0x92, 0xf7, 0x52, 0xf6,
0xaa, 0xf4, 0xd8, 0xf3, 0x8d, 0xf3, 0xe6, 0xf2, 0xb4, 0xf1, 0xc5, 0xf0,
0xc6, 0xf0, 0xd8, 0xf0, 0x1a, 0xf1, 0xc7, 0xf0, 0x47, 0xef, 0xf7, 0xee,
0xc7, 0xee, 0x47, 0xee, 0x83, 0xee, 0x8a, 0xed, 0x7f, 0xee, 0x57, 0xef,
0x94, 0xef, 0x4b, 0xf0, 0x5e, 0xf0, 0xc4, 0xf1, 0x7c, 0xf2, 0xbc, 0xf1,
0x90, 0xf2, 0x1b, 0xf4, 0x00, 0xf5, 0x56, 0xf6, 0xa1, 0xf6, 0x54, 0xf7,
0x66, 0xf9, 0x83, 0xfb, 0xfc, 0xfd, 0xb3, 0xff, 0xd0, 0xff, 0xe3, 0x00,
0x1a, 0x03, 0xa6, 0x04, 0xba, 0x05, 0xaa, 0x06, 0x1f, 0x08, 0xa6, 0x09,
0xae, 0x0a, 0xd1, 0x0b, 0xbb, 0x0d, 0x2e, 0x0f, 0xfc, 0x0f, 0x55, 0x10,
0xb5, 0x10, 0x05, 0x11, 0xb8, 0x11, 0x6d, 0x12, 0x14, 0x12, 0xb1, 0x11,
0x56, 0x11, 0xaf, 0x11, 0xe0, 0x12, 0x9a, 0x12, 0x3b, 0x11, 0x0f, 0x10,
0xeb, 0x0f, 0xcb, 0x0f, 0x94, 0x0e, 0xce, 0x0c, 0x8a, 0x0b, 0x36, 0x0b,
0xb8, 0x0a, 0xf9, 0x09, 0x35, 0x09, 0xbd, 0x07, 0xb8, 0x06, 0x45, 0x06,
0xd4, 0x04, 0x19, 0x03, 0x0e, 0x01, 0x59, 0xff, 0x98, 0xfe, 0x4b, 0xfd,
0x97, 0xfb, 0x85, 0xfa, 0x56, 0xf8, 0xc6, 0xf6, 0xe6, 0xf5, 0x5c, 0xf4,
0xf2, 0xf2, 0x07, 0xf4, 0xb8, 0xf3, 0x79, 0xf1, 0x64, 0xf0, 0x2e, 0xef,
0xf8, 0xef, 0x38, 0xf1, 0x86, 0xf0, 0xde, 0xee, 0x24, 0xed, 0x7d, 0xed,
0xad, 0xee, 0x7c, 0xee, 0xb8, 0xed, 0x47, 0xed, 0xfa, 0xed, 0x36, 0xf0,
0x1d, 0xf1, 0x47, 0xf0, 0xca, 0xef, 0xd8, 0xf0, 0x80, 0xf3, 0x71, 0xf4,
0x77, 0xf4, 0x92, 0xf3, 0xe3, 0xf3, 0x52, 0xf7, 0xd6, 0xf9, 0x5b, 0xfb,
0xe9, 0xfb, 0x63, 0xfc, 0x52, 0xff, 0xdf, 0x01, 0xaf, 0x03, 0x8c, 0x04,
0xec, 0x03, 0xd6, 0x05, 0x6b, 0x08, 0x95, 0x0a, 0x20, 0x0c, 0x72, 0x0c,
0xb4, 0x0d, 0x74, 0x0f, 0x08, 0x11, 0xc4, 0x11, 0x1a, 0x12, 0x4c, 0x12,
0x79, 0x12, 0xf7, 0x12, 0x0a, 0x13, 0xa1, 0x12, 0xe6, 0x12, 0xed, 0x12,
0x48, 0x13, 0x8a, 0x13, 0x10, 0x13, 0xcf, 0x12, 0xd2, 0x11, 0x97, 0x10,
0xb8, 0x0f, 0xe2, 0x0e, 0xaf, 0x0d, 0x3a, 0x0c, 0x3e, 0x0c, 0x71, 0x0a,
0x81, 0x08, 0x28, 0x08, 0xc3, 0x07, 0xc2, 0x06, 0x81, 0x04, 0x3a, 0x02,
0x85, 0xff, 0x36, 0xfe, 0x15, 0xfd, 0x98, 0xfb, 0xab, 0xf9, 0xb5, 0xf8,
0x8e, 0xf6, 0xfa, 0xf4, 0x85, 0xf3, 0x51, 0xf2, 0x59, 0xf1, 0x7e, 0xef,
0x44, 0xef, 0xe3, 0xed, 0x0b, 0xee, 0xc2, 0xee, 0x7a, 0xee, 0xd3, 0xed,
0x61, 0xed, 0x7f, 0xed, 0xb9, 0xee, 0x4e, 0xee, 0x7e, 0xec, 0xae, 0xeb,
0xdd, 0xeb, 0x38, 0xed, 0x2e, 0xef, 0xef, 0xef, 0x21, 0xf0, 0x52, 0xf1,
0x7a, 0xf2, 0x16, 0xf4, 0x2d, 0xf5, 0x7a, 0xf5, 0x0f, 0xf6, 0x8a, 0xf7,
0xbb, 0xf8, 0x93, 0xf9, 0x43, 0xfb, 0x55, 0xfd, 0xfb, 0xfe, 0xa3, 0x00,
0xe9, 0x02, 0x2f, 0x05, 0x8f, 0x06, 0x1f, 0x08, 0xe4, 0x08, 0xb4, 0x09,
0x96, 0x0a, 0xbc, 0x0b, 0x64, 0x0d, 0x2d, 0x0f, 0x23, 0x10, 0x72, 0x10,
0x7b, 0x11, 0xee, 0x11, 0x16, 0x13, 0x35, 0x14, 0xfc, 0x13, 0x4f, 0x13,
0x10, 0x13, 0xe2, 0x12, 0x73, 0x12, 0x45, 0x12, 0x74, 0x12, 0x4d, 0x12,
0x01, 0x12, 0xc3, 0x10, 0x5d, 0x0f, 0xfd, 0x0e, 0x74, 0x0e, 0x55, 0x0d,
0x17, 0x0b, 0x72, 0x09, 0x01, 0x09, 0x8d, 0x08, 0x8a, 0x08, 0x29, 0x07,
0x05, 0x05, 0xbf, 0x03, 0xc5, 0x02, 0x02, 0x02, 0x33, 0x00, 0x67, 0xfd,
0x2b, 0xfb, 0x09, 0xfa, 0xbd, 0xf8, 0x7f, 0xf7, 0x7a, 0xf5, 0x62, 0xf2,
0x65, 0xf0, 0x64, 0xef, 0x80, 0xef, 0x98, 0xef, 0xed, 0xed, 0x18, 0xec,
0x43, 0xeb, 0xe7, 0xeb, 0xc9, 0xed, 0xed, 0xed, 0x2a, 0xec, 0x16, 0xea,
0x8a, 0xea, 0xec, 0xec, 0x4b, 0xec, 0x45, 0xed, 0x2b, 0xec, 0x62, 0xeb,
0xdb, 0xee, 0xcc, 0xef, 0x6b, 0xf1, 0x53, 0xf1, 0xac, 0xf1, 0x7e, 0xf3,
0x9c, 0xf4, 0x04, 0xf6, 0x5b, 0xf7, 0xd6, 0xf8, 0xfc, 0xf9, 0xfa, 0xfa,
0xe8, 0xfc, 0x92, 0xff, 0x52, 0x02, 0x2e, 0x04, 0x07, 0x05, 0xaf, 0x06,
0x1d, 0x08, 0x94, 0x0a, 0x3b, 0x0c, 0x21, 0x0c, 0x65, 0x0d, 0x28, 0x0f,
0xbe, 0x0f, 0x6b, 0x11, 0xf0, 0x11, 0xfe, 0x11, 0x04, 0x14, 0xf8, 0x14,
0xdf, 0x14, 0xde, 0x14, 0x2c, 0x14, 0xdd, 0x13, 0xb8, 0x14, 0x01, 0x14,
0xe0, 0x12, 0x03, 0x12, 0xb7, 0x11, 0xa0, 0x11, 0x60, 0x11, 0x14, 0x10,
0x43, 0x0e, 0x6f, 0x0d, 0x85, 0x0c, 0x89, 0x0b, 0x2d, 0x0a, 0x61, 0x08,
0xea, 0x07, 0x4c, 0x07, 0x14, 0x06, 0xe3, 0x04, 0x7d, 0x03, 0x37, 0x02,
0x0f, 0x00, 0xc6, 0xff, 0xba, 0xfd, 0x61, 0xfb, 0x69, 0xfa, 0x4f, 0xf8,
0x4e, 0xf8, 0x70, 0xf6, 0x6f, 0xf3, 0xc0, 0xf1, 0x47, 0xef, 0x03, 0xef,
0x1e, 0xef, 0xa1, 0xed, 0x55, 0xec, 0x8c, 0xeb, 0xa4, 0xec, 0xe9, 0xec,
0x89, 0xed, 0xb0, 0xed, 0x23, 0xed, 0xa5, 0xee, 0xaa, 0xef, 0x46, 0xee,
0x94, 0xeb, 0x0a, 0xeb, 0xc7, 0xec, 0xed, 0xef, 0x33, 0xf1, 0xb8, 0xef,
0xfe, 0xee, 0xf6, 0xf0, 0x2e, 0xf4, 0xac, 0xf6, 0x14, 0xf7, 0xa6, 0xf6,
0x5d, 0xf7, 0xba, 0xf8, 0x9c, 0xfa, 0x9e, 0xfb, 0xd6, 0xfc, 0x83, 0xff,
0x88, 0x01, 0x1f, 0x03, 0x78, 0x05, 0xe3, 0x06, 0x75, 0x09, 0x26, 0x0b,
0x99, 0x0b, 0x1c, 0x0d, 0xfa, 0x0d, 0xc1, 0x0f, 0xc5, 0x10, 0x58, 0x11,
0xdd, 0x11, 0x08, 0x13, 0x41, 0x15, 0x4e, 0x16, 0x56, 0x16, 0xd7, 0x15,
0xc6, 0x15, 0xda, 0x15, 0x69, 0x14, 0x9f, 0x13, 0x1f, 0x14, 0x6b, 0x13,
0xc3, 0x12, 0xbd, 0x11, 0x81, 0x10, 0xf5, 0x10, 0xbe, 0x10, 0x7a, 0x0f,
0x15, 0x0d, 0x45, 0x0c, 0x50, 0x0b, 0xbe, 0x09, 0x9c, 0x08, 0xa5, 0x06,
0x28, 0x05, 0xcb, 0x03, 0x92, 0x03, 0x3d, 0x02, 0x3e, 0xff, 0xe9, 0xfc,
0x75, 0xfb, 0xbb, 0xfa, 0xf1, 0xf8, 0x5d, 0xf6, 0xb1, 0xf3, 0xaa, 0xf1,
0xeb, 0xf0, 0x56, 0xef, 0xa7, 0xec, 0x60, 0xea, 0x00, 0xe9, 0xfc, 0xe9,
0xe8, 0xee, 0x84, 0xec, 0x46, 0xe8, 0x58, 0xe7, 0x35, 0xe9, 0xa7, 0xef,
0x9a, 0xf1, 0x71, 0xec, 0x51, 0xe8, 0xe5, 0xe8, 0x3d, 0xec, 0x40, 0xf0,
0xfd, 0xee, 0xc9, 0xea, 0x54, 0xed, 0x90, 0xf1, 0x65, 0xf6, 0xc9, 0xf6,
0x2e, 0xf3, 0xd1, 0xf4, 0xf8, 0xf7, 0x3d, 0xfb, 0x9d, 0xfc, 0xe9, 0xfa,
0x9c, 0xfb, 0xae, 0xfe, 0x14, 0x02, 0x02, 0x05, 0x13, 0x05, 0xc6, 0x06,
0xf9, 0x08, 0x45, 0x0b, 0xc4, 0x0c, 0xdf, 0x0c, 0x18, 0x0e, 0x61, 0x10,
0x16, 0x11, 0x58, 0x11, 0xae, 0x10, 0x54, 0x12, 0x1d, 0x15, 0x39, 0x16,
0xb0, 0x15, 0xb9, 0x14, 0x30, 0x15, 0x2d, 0x16, 0xc8, 0x15, 0xce, 0x14,
0x3f, 0x13, 0x08, 0x12, 0x00, 0x12, 0x50, 0x11, 0xaa, 0x10, 0x7f, 0x10,
0xa2, 0x0f, 0x2f, 0x0f, 0xb6, 0x0d, 0x39, 0x0c, 0x4e, 0x0b, 0xfe, 0x09,
0x27, 0x09, 0x8a, 0x07, 0x7e, 0x05, 0x0b, 0x04, 0x90, 0x03, 0xf7, 0x02,
0x34, 0x01, 0xca, 0xfe, 0x8d, 0xfd, 0x68, 0xfc, 0xb6, 0xfb, 0x25, 0xf9,
0x10, 0xf6, 0x14, 0xf4, 0xf5, 0xf1, 0x6a, 0xf0, 0x31, 0xee, 0xe9, 0xeb,
0x41, 0xea, 0xe8, 0xea, 0xd6, 0xea, 0xc4, 0xea, 0x24, 0xea, 0xaa, 0xe9,
0x44, 0xeb, 0x84, 0xeb, 0xe7, 0xec, 0xc8, 0xed, 0xe5, 0xeb, 0xa1, 0xeb,
0x0f, 0xeb, 0x06, 0xeb, 0xd2, 0xec, 0x8a, 0xee, 0x43, 0xef, 0xe1, 0xef,
0x59, 0xf1, 0xdf, 0xf2, 0xaa, 0xf5, 0xe9, 0xf6, 0xa5, 0xf6, 0xc2, 0xf7,
0x30, 0xf9, 0x1b, 0xfb, 0x08, 0xfd, 0x95, 0xfd, 0x22, 0xfe, 0x1d, 0x00,
0x4c, 0x04, 0x76, 0x06, 0x67, 0x07, 0xcb, 0x08, 0x8b, 0x0a, 0x79, 0x0c,
0x42, 0x0d, 0xfe, 0x0d, 0x99, 0x0e, 0xab, 0x0f, 0x43, 0x11, 0x6e, 0x11,
0xb3, 0x10, 0xea, 0x12, 0x1f, 0x15, 0xb5, 0x15, 0xd3, 0x15, 0x8d, 0x14,
0x2a, 0x15, 0xa1, 0x15, 0x11, 0x15, 0x54, 0x14, 0x35, 0x13, 0xb9, 0x12,
0xec, 0x12, 0xcd, 0x12, 0x5b, 0x11, 0x8f, 0x10, 0xf5, 0x0f, 0x89, 0x0e,
0x29, 0x0e, 0x13, 0x0d, 0xad, 0x0b, 0x6a, 0x0a, 0x3e, 0x09, 0xab, 0x07,
0xc9, 0x05, 0xc9, 0x04, 0xc7, 0x03, 0x0a, 0x02, 0xa0, 0x01, 0xf1, 0xfe,
0xe7, 0xfa, 0xff, 0xf9, 0xb2, 0xf8, 0x43, 0xf7, 0x7f, 0xf5, 0x1d, 0xf1,
0xbc, 0xee, 0x4a, 0xed, 0x18, 0xec, 0x2f, 0xe9, 0xdb, 0xe8, 0x00, 0xe8,
0xd1, 0xe8, 0xb7, 0xe9, 0x5f, 0xe7, 0x64, 0xe7, 0x65, 0xe8, 0xa7, 0xea,
0xc2, 0xeb, 0xd2, 0xea, 0xfc, 0xe8, 0x76, 0xe8, 0x7a, 0xea, 0x65, 0xec,
0x29, 0xed, 0x82, 0xed, 0x36, 0xee, 0xff, 0xef, 0xbb, 0xf2, 0xc8, 0xf4,
0xe3, 0xf5, 0x3d, 0xf6, 0xe1, 0xf7, 0x25, 0xfb, 0x80, 0xfc, 0xc1, 0xfc,
0x09, 0xfd, 0x0f, 0xff, 0xc0, 0x01, 0x3f, 0x05, 0xb4, 0x06, 0xa2, 0x07,
0xc0, 0x08, 0xa1, 0x0b, 0xb7, 0x0d, 0xf6, 0x0f, 0x16, 0x10, 0xf8, 0x0e,
0x42, 0x10, 0xb8, 0x11, 0xbe, 0x13, 0xbb, 0x14, 0x71, 0x14, 0x07, 0x15,
0xfa, 0x15, 0x1f, 0x16, 0x0a, 0x16, 0xf6, 0x15, 0x3e, 0x17, 0x04, 0x17,
0x1f, 0x15, 0x34, 0x13, 0x34, 0x12, 0xe8, 0x12, 0x9f, 0x12, 0x08, 0x12,
0x85, 0x10, 0x28, 0x0e, 0xa4, 0x0d, 0x2b, 0x0d, 0x25, 0x0c, 0xc1, 0x0a,
0xed, 0x08, 0xa9, 0x07, 0xab, 0x06, 0xed, 0x04, 0xbe, 0x02, 0x3d, 0x01,
0x40, 0x00, 0x87, 0xfe, 0x9b, 0xfc, 0x5f, 0xfa, 0x73, 0xf7, 0x56, 0xf6,
0xa5, 0xf5, 0x6c, 0xf3, 0x3b, 0xf0, 0xc1, 0xec, 0x53, 0xea, 0x3f, 0xea,
0xfb, 0xe8, 0x1c, 0xe8, 0x79, 0xe7, 0xed, 0xe6, 0xf5, 0xe5, 0xde, 0xe6,
0x47, 0xe9, 0x79, 0xeb, 0xff, 0xec, 0x14, 0xec, 0xf2, 0xe9, 0x25, 0xe9,
0xa3, 0xea, 0xf3, 0xec, 0x36, 0xee, 0x28, 0xee, 0x8b, 0xee, 0xf2, 0xee,
0x4f, 0xf2, 0x2d, 0xf5, 0x7e, 0xf7, 0x4c, 0xf9, 0x53, 0xfa, 0x02, 0xfc,
0x3c, 0xfc, 0xf9, 0xfb, 0x98, 0xfe, 0x88, 0x00, 0xf5, 0x02, 0x04, 0x05,
0x99, 0x05, 0x50, 0x07, 0xe3, 0x09, 0x62, 0x0d, 0xa0, 0x0e, 0xf4, 0x0e,
0x9b, 0x0f, 0x1f, 0x11, 0x50, 0x12, 0x2b, 0x12, 0x6b, 0x11, 0xb6, 0x11,
0xc2, 0x13, 0x45, 0x15, 0x8a, 0x15, 0x57, 0x15, 0x35, 0x15, 0x1c, 0x16,
0x84, 0x16, 0x12, 0x15, 0xf0, 0x12, 0xf7, 0x11, 0x54, 0x12, 0x49, 0x12,
0x78, 0x11, 0xd4, 0x0f, 0x4f, 0x0e, 0xcb, 0x0d, 0xc7, 0x0d, 0x35, 0x0d,
0x3b, 0x0c, 0x73, 0x0b, 0x9c, 0x09, 0xe5, 0x06, 0x16, 0x05, 0xea, 0x03,
0x68, 0x03, 0xa9, 0x02, 0x55, 0x00, 0x80, 0xfd, 0x20, 0xfb, 0xf6, 0xf8,
0xff, 0xf8, 0xcd, 0xf7, 0x6a, 0xf5, 0x46, 0xf2, 0xcd, 0xee, 0x3e, 0xec,
0x49, 0xea, 0xd4, 0xe8, 0x99, 0xe7, 0x82, 0xe5, 0xd9, 0xe5, 0x48, 0xe6,
0x35, 0xe6, 0x30, 0xe8, 0xd4, 0xe7, 0xbf, 0xe8, 0xe4, 0xe9, 0xd7, 0xea,
0x72, 0xeb, 0x6f, 0xea, 0x5b, 0xe9, 0xed, 0xe9, 0x74, 0xeb, 0xfa, 0xed,
0xc4, 0xef, 0x91, 0xf0, 0x66, 0xf2, 0xa4, 0xf4, 0x07, 0xf7, 0xaf, 0xfb,
0x2b, 0xfd, 0xf6, 0xfc, 0x9f, 0xfe, 0x65, 0xff, 0xcc, 0xff, 0x51, 0x02,
0x3d, 0x04, 0x18, 0x06, 0xea, 0x08, 0x1c, 0x0a, 0x44, 0x0b, 0xdb, 0x0d,
0x1b, 0x10, 0x76, 0x11, 0xcb, 0x12, 0x69, 0x12, 0xac, 0x11, 0x73, 0x12,
0x1f, 0x13, 0x78, 0x13, 0x2b, 0x15, 0x08, 0x15, 0xd4, 0x14, 0xb1, 0x14,
0xbe, 0x14, 0x15, 0x16, 0x19, 0x16, 0x0d, 0x14, 0xc1, 0x12, 0x9d, 0x11,
0xaf, 0x11, 0xd3, 0x10, 0x15, 0x10, 0xd8, 0x0e, 0x75, 0x0d, 0xa3, 0x0d,
0xeb, 0x0c, 0x90, 0x0b, 0x56, 0x0a, 0x34, 0x09, 0x6e, 0x08, 0x14, 0x07,
0x87, 0x04, 0x27, 0x02, 0xed, 0x00, 0xcd, 0xff, 0x46, 0xfe, 0x7f, 0xfc,
0xae, 0xf9, 0x13, 0xf7, 0x2f, 0xf7, 0xf1, 0xf5, 0x27, 0xf3, 0xe5, 0xef,
0x2c, 0xec, 0x52, 0xea, 0x8f, 0xe9, 0x30, 0xe7, 0x56, 0xe5, 0x16, 0xe5,
0x45, 0xe5, 0xbf, 0xe6, 0xf9, 0xe6, 0x96, 0xe6, 0x8e, 0xe8, 0x51, 0xec,
0xed, 0xec, 0x1d, 0xeb, 0xdd, 0xe8, 0x4c, 0xe9, 0xfb, 0xec, 0x8f, 0xee,
0x0b, 0xee, 0xca, 0xed, 0xfc, 0xef, 0x97, 0xf4, 0xc2, 0xf8, 0x21, 0xf9,
0xb9, 0xf8, 0xe8, 0xfa, 0x0f, 0x00, 0x90, 0x03, 0x9a, 0x01, 0xfd, 0xff,
0xeb, 0x00, 0xd4, 0x03, 0x8e, 0x07, 0xda, 0x08, 0xa0, 0x08, 0xc1, 0x0a,
0xfc, 0x0d, 0x50, 0x10, 0x82, 0x12, 0x43, 0x12, 0xcf, 0x10, 0xdb, 0x11,
0x80, 0x11, 0x41, 0x12, 0xe2, 0x12, 0x8d, 0x12, 0x2c, 0x13, 0xe6, 0x13,
0x25, 0x14, 0x2d, 0x14, 0x7b, 0x14, 0xb2, 0x14, 0x29, 0x14, 0x60, 0x13,
0xb7, 0x11, 0xf1, 0x10, 0x4d, 0x11, 0x57, 0x10, 0xe0, 0x0e, 0x3c, 0x0e,
0x21, 0x0e, 0x23, 0x0d, 0x8c, 0x0c, 0xf8, 0x0b, 0x20, 0x0b, 0xf3, 0x09,
0xd7, 0x07, 0xd8, 0x04, 0xf2, 0x02, 0x8b, 0x01, 0x94, 0xff, 0xa4, 0xfd,
0xc3, 0xfa, 0x15, 0xf9, 0x9d, 0xf8, 0x51, 0xf7, 0x07, 0xf5, 0xd6, 0xf1,
0x3f, 0xee, 0xce, 0xeb, 0x36, 0xe9, 0x51, 0xe6, 0x4c, 0xe4, 0x1e, 0xe4,
0x56, 0xe2, 0x03, 0xe2, 0x40, 0xe3, 0xde, 0xe3, 0x29, 0xe7, 0x20, 0xeb,
0xa4, 0xea, 0x51, 0xea, 0x99, 0xea, 0xba, 0xe9, 0x56, 0xec, 0x92, 0xed,
0x11, 0xec, 0x0f, 0xee, 0x43, 0xf1, 0x96, 0xf3, 0x23, 0xf6, 0xd3, 0xf7,
0xd9, 0xf9, 0x86, 0xfd, 0x31, 0x00, 0xd9, 0x01, 0xda, 0x03, 0xc7, 0x04,
0x2f, 0x05, 0xb0, 0x05, 0x99, 0x06, 0xef, 0x08, 0x4e, 0x0c, 0x17, 0x0e,
0x4e, 0x0f, 0xa9, 0x0f, 0x32, 0x10, 0xc0, 0x11, 0x42, 0x13, 0x10, 0x14,
0xfa, 0x12, 0x9b, 0x11, 0x88, 0x10, 0xb3, 0x10, 0xb0, 0x11, 0xe4, 0x12,
0x0c, 0x13, 0xcb, 0x12, 0xdb, 0x11, 0xd0, 0x11, 0x15, 0x13, 0xb9, 0x12,
0x1c, 0x12, 0x2e, 0x11, 0x37, 0x10, 0x5f, 0x0f, 0x85, 0x0e, 0xe0, 0x0d,
0x30, 0x0e, 0xff, 0x0d, 0xe4, 0x0d, 0x5a, 0x0c, 0x12, 0x0b, 0x09, 0x0a,
0x2c, 0x09, 0x2b, 0x08, 0xac, 0x05, 0xe2, 0x02, 0x51, 0x00, 0x0f, 0xfe,
0xb1, 0xfc, 0x1e, 0xfb, 0x95, 0xf8, 0xd4, 0xf5, 0x1d, 0xf3, 0xaf, 0xf0,
0xaa, 0xed, 0x78, 0xea, 0x98, 0xe6, 0xa9, 0xe4, 0x0f, 0xe4, 0x41, 0xe3,
0x98, 0xe3, 0xf5, 0xe1, 0xd2, 0xe2, 0x21, 0xe6, 0x2b, 0xe7, 0x71, 0xe8,
0x8c, 0xe9, 0x4a, 0xea, 0xe6, 0xeb, 0xeb, 0xeb, 0xe8, 0xeb, 0xfb, 0xea,
0xf0, 0xea, 0x6a, 0xed, 0x96, 0xef, 0xb5, 0xf4, 0xaf, 0xf7, 0x0c, 0xf9,
0xd7, 0xfc, 0x87, 0xfd, 0x2f, 0x00, 0x6e, 0x02, 0x30, 0x03, 0x6c, 0x04,
0xcf, 0x05, 0x9b, 0x05, 0xbc, 0x07, 0xae, 0x09, 0xc4, 0x0b, 0xd3, 0x0e,
0x1e, 0x10, 0xc0, 0x11, 0x8f, 0x12, 0x2d, 0x12, 0x54, 0x13, 0xcf, 0x12,
0x1a, 0x12, 0xee, 0x10, 0x70, 0x0f, 0x42, 0x10, 0xe7, 0x10, 0x99, 0x11,
0x8a, 0x11, 0x26, 0x12, 0xb4, 0x12, 0x71, 0x13, 0xaa, 0x12, 0xd9, 0x11,
0x19, 0x10, 0x4d, 0x10, 0x84, 0x10, 0xc1, 0x0f, 0x61, 0x0f, 0xc9, 0x0e,
0xaf, 0x0e, 0x41, 0x0f, 0x66, 0x0f, 0xc7, 0x0e, 0x12, 0x0e, 0x1d, 0x0c,
0xc3, 0x09, 0x93, 0x07, 0x5c, 0x06, 0x68, 0x04, 0x6e, 0x01, 0x6b, 0xfd,
0x1d, 0xfa, 0x52, 0xfa, 0x39, 0xf8, 0x3e, 0xf5, 0x19, 0xf1, 0x19, 0xee,
0x0d, 0xeb, 0x14, 0xe8, 0x4e, 0xe5, 0xc2, 0xe2, 0x9c, 0xe2, 0xfc, 0xe2,
0x7f, 0xe2, 0x02, 0xe3, 0x41, 0xe4, 0x31, 0xe8, 0xeb, 0xea, 0x21, 0xeb,
0xd0, 0xe9, 0xf5, 0xe8, 0xf7, 0xe9, 0x8b, 0xed, 0x94, 0xed, 0x61, 0xec,
0x75, 0xed, 0x5c, 0xef, 0x27, 0xf4, 0xf2, 0xf6, 0xd2, 0xf8, 0x8f, 0xfb,
0x6b, 0xfe, 0x1d, 0x01, 0x42, 0x02, 0x4c, 0x02, 0x7c, 0x03, 0x3f, 0x04,
0xb6, 0x05, 0x0e, 0x07, 0xc9, 0x07, 0xef, 0x09, 0x6e, 0x0c, 0xd7, 0x0e,
0x21, 0x10, 0xf1, 0x0f, 0x23, 0x10, 0x37, 0x10, 0xc6, 0x11, 0xcd, 0x10,
0x3d, 0x0f, 0xf0, 0x0e, 0xda, 0x0e, 0x24, 0x0f, 0xb6, 0x0f, 0x30, 0x10,
0x2d, 0x0f, 0x6d, 0x10, 0x60, 0x11, 0x04, 0x11, 0xa1, 0x10, 0xd9, 0x0f,
0x0b, 0x10, 0x4e, 0x10, 0x6d, 0x10, 0xdc, 0x10, 0x96, 0x10, 0x9a, 0x10,
0xfb, 0x11, 0x11, 0x12, 0xc7, 0x11, 0x0b, 0x11, 0x56, 0x0f, 0xa4, 0x0c,
0x8d, 0x0a, 0x8a, 0x07, 0xaf, 0x04, 0xc4, 0x00, 0x7b, 0xfd, 0x25, 0xfb,
0x97, 0xf8, 0x93, 0xf5, 0xe3, 0xf2, 0x2c, 0xef, 0xcc, 0xea, 0x98, 0xe6,
0xb3, 0xe3, 0xea, 0xe2, 0x0b, 0xe3, 0xb6, 0xe2, 0x56, 0xe1, 0xfe, 0xdf,
0xf6, 0xe1, 0x1c, 0xe5, 0x62, 0xe8, 0x4d, 0xe9, 0x4d, 0xe8, 0x4f, 0xe8,
0x64, 0xe9, 0xcf, 0xeb, 0x17, 0xed, 0x13, 0xee, 0xcf, 0xec, 0x0b, 0xee,
0xd0, 0xf2, 0xca, 0xf6, 0x9f, 0xf9, 0x6f, 0xfc, 0x14, 0xff, 0x46, 0x02,
0x08, 0x04, 0x72, 0x02, 0x21, 0x04, 0x1d, 0x05, 0xb1, 0x06, 0xd4, 0x09,
0x75, 0x09, 0x9a, 0x09, 0xe3, 0x0a, 0x12, 0x0d, 0x34, 0x0f, 0x26, 0x0f,
0xe5, 0x0e, 0x59, 0x0f, 0xdc, 0x0f, 0x8d, 0x0e, 0x2c, 0x0c, 0xce, 0x0a,
0x2b, 0x0b, 0x63, 0x0d, 0x5a, 0x0e, 0x93, 0x0d, 0x35, 0x0c, 0x60, 0x0d,
0xa0, 0x0f, 0xbe, 0x10, 0xd8, 0x0e, 0xfa, 0x0c, 0x34, 0x0d, 0xa3, 0x0e,
0x49, 0x0f, 0x98, 0x0e, 0xa2, 0x0d, 0x3b, 0x0f, 0xc5, 0x11, 0xcd, 0x12,
0x45, 0x12, 0x70, 0x10, 0x08, 0x0f, 0x87, 0x0e, 0x17, 0x0d, 0x26, 0x0a,
0x57, 0x05, 0xf5, 0xff, 0x1b, 0xfe, 0xa4, 0xfc, 0xef, 0xfa, 0x5f, 0xf6,
0xf6, 0xf1, 0xae, 0xee, 0x0d, 0xea, 0x02, 0xe8, 0x03, 0xe6, 0x9d, 0xe5,
0xd3, 0xe5, 0xf9, 0xe4, 0x04, 0xe5, 0x7e, 0xe6, 0x29, 0xe7, 0x51, 0xe9,
0x73, 0xe9, 0x00, 0xeb, 0xad, 0xeb, 0xf8, 0xe8, 0x59, 0xe8, 0x07, 0xe8,
0x23, 0xeb, 0x63, 0xef, 0x44, 0xf1, 0x52, 0xf1, 0xbe, 0xf2, 0xed, 0xf6,
0x22, 0xfb, 0x2c, 0xff, 0xcf, 0xfe, 0xf6, 0xfe, 0x15, 0x01, 0xaf, 0x02,
0xf1, 0x03, 0xe8, 0x04, 0x87, 0x04, 0x6e, 0x06, 0xb1, 0x08, 0x56, 0x09,
0xfe, 0x0a, 0x71, 0x09, 0x2c, 0x0a, 0xb5, 0x0b, 0x37, 0x0c, 0x18, 0x0c,
0xb5, 0x0a, 0x4e, 0x0a, 0x61, 0x0a, 0x72, 0x0a, 0x9e, 0x09, 0x15, 0x09,
0x2c, 0x0a, 0x8b, 0x0c, 0x46, 0x0e, 0xfc, 0x0c, 0xed, 0x0a, 0xc8, 0x0a,
0x01, 0x0e, 0x99, 0x0f, 0x2f, 0x0f, 0x97, 0x0c, 0x9d, 0x0c, 0x20, 0x0f,
0xbb, 0x11, 0xbe, 0x12, 0x4b, 0x12, 0xe5, 0x11, 0x9b, 0x12, 0x7f, 0x13,
0x56, 0x11, 0x84, 0x0c, 0xd1, 0x08, 0x6a, 0x08, 0xd9, 0x05, 0x72, 0x01,
0xc5, 0xfc, 0x1f, 0xf9, 0xe9, 0xf9, 0xf5, 0xf6, 0xa0, 0xf0, 0x27, 0xeb,
0xcf, 0xe5, 0x11, 0xe6, 0x27, 0xea, 0x4b, 0xe9, 0xc2, 0xe7, 0x49, 0xe7,
0xd2, 0xe5, 0x7b, 0xe9, 0x65, 0xeb, 0xb1, 0xe8, 0xde, 0xe7, 0x33, 0xe9,
0x1e, 0xea, 0x7e, 0xec, 0x60, 0xed, 0x21, 0xea, 0x25, 0xee, 0xad, 0xf1,
0x27, 0xf5, 0x4b, 0xf9, 0xf1, 0xf9, 0xa6, 0xfc, 0x6a, 0x00, 0x3e, 0x00,
0x9f, 0x01, 0x5e, 0x02, 0xac, 0x03, 0xa5, 0x05, 0x6d, 0x05, 0xb9, 0x04,
0xfd, 0x05, 0xd3, 0x07, 0xcd, 0x08, 0xbf, 0x09, 0x34, 0x09, 0x42, 0x09,
0xda, 0x07, 0x4f, 0x08, 0x35, 0x09, 0xd5, 0x08, 0x83, 0x08, 0xfe, 0x08,
0xcc, 0x07, 0x5b, 0x07, 0x58, 0x07, 0x6f, 0x08, 0x83, 0x0a, 0xd1, 0x0a,
0x20, 0x09, 0x1e, 0x0b, 0x1c, 0x0d, 0x16, 0x0d, 0x62, 0x0e, 0x71, 0x0e,
0xa1, 0x0d, 0xdd, 0x0f, 0x69, 0x11, 0x4a, 0x14, 0x03, 0x15, 0xbc, 0x13,
0xb2, 0x12, 0x15, 0x12, 0x9a, 0x10, 0x04, 0x0f, 0x0e, 0x0c, 0x0b, 0x06,
0x3b, 0x02, 0x7f, 0xfe, 0x85, 0xfb, 0x4e, 0xf8, 0xc4, 0xf4, 0xe0, 0xed,
0x09, 0xe8, 0x75, 0xe4, 0xb4, 0xe2, 0x62, 0xe5, 0x8c, 0xe8, 0x98, 0xe7,
0xe3, 0xe5, 0x03, 0xe8, 0x9e, 0xea, 0xc6, 0xf0, 0xf6, 0xf2, 0x95, 0xf1,
0xad, 0xef, 0x91, 0xf0, 0xbe, 0xf1, 0x58, 0xf4, 0x02, 0xf5, 0x1d, 0xf6,
0xb0, 0xfa, 0xfa, 0xfe, 0x10, 0x04, 0x95, 0x06, 0x0f, 0x09, 0xf4, 0x0a,
0xd6, 0x0b, 0xcb, 0x0b, 0x91, 0x09, 0xf6, 0x07, 0xc8, 0x06, 0xeb, 0x07,
0xeb, 0x06, 0xb6, 0x03, 0xde, 0x01, 0x84, 0xff, 0x4d, 0x01, 0xc5, 0x02,
0x03, 0x01, 0x2e, 0xfe, 0x1e, 0xfb, 0x01, 0xfa, 0xc1, 0xf9, 0xb1, 0xfa,
0xd2, 0xf8, 0x03, 0xf8, 0x76, 0xf9, 0x25, 0xfb, 0x31, 0xfe, 0xa9, 0x01,
0xea, 0x03, 0x19, 0x07, 0xe6, 0x08, 0xf9, 0x0a, 0x8c, 0x0c, 0x50, 0x0e,
0x52, 0x10, 0xe4, 0x12, 0x0f, 0x15, 0xa1, 0x16, 0x19, 0x1a, 0xf2, 0x1b,
0x3d, 0x1d, 0x2f, 0x1d, 0xd4, 0x19, 0xa5, 0x15, 0xad, 0x11, 0x2c, 0x0d,
0x71, 0x08, 0x3e, 0x05, 0x7f, 0x01, 0xb4, 0xfb, 0xf3, 0xf6, 0x38, 0xed,
0x46, 0xe8, 0x22, 0xe2, 0x9d, 0xdf, 0x01, 0xdd, 0x06, 0xdc, 0x08, 0xdc,
0xe9, 0xdd, 0xe2, 0xe1, 0x7c, 0xe8, 0xf7, 0xee, 0xb5, 0xf4, 0x42, 0xf7,
0xf9, 0xf7, 0x34, 0xfb, 0xd5, 0xfc, 0x9b, 0xff, 0xa8, 0x01, 0x5f, 0x02,
0xe6, 0x02, 0x35, 0x05, 0xe2, 0x08, 0x42, 0x0e, 0xf1, 0x14, 0x1d, 0x17,
0xd0, 0x16, 0x27, 0x13, 0x62, 0x0e, 0xe2, 0x08, 0xa5, 0x03, 0xc6, 0xfe,
0x7f, 0xf9, 0x63, 0xf7, 0xe5, 0xf5, 0x23, 0xf5, 0xdb, 0xf3, 0xcf, 0xf2,
0x45, 0xf1, 0x44, 0xf0, 0x36, 0xf0, 0x63, 0xee, 0xaf, 0xec, 0x50, 0xe8,
0x4b, 0xe7, 0xa7, 0xe8, 0xff, 0xec, 0xff, 0xf3, 0xca, 0xf7, 0x01, 0x00,
0x75, 0x07, 0xd9, 0x0e, 0x43, 0x15, 0x22, 0x19, 0x89, 0x1d, 0xa6, 0x20,
0x44, 0x22, 0x45, 0x24, 0x5e, 0x26, 0x83, 0x29, 0x4a, 0x2b, 0xbc, 0x2b,
0xba, 0x29, 0x84, 0x25, 0x57, 0x20, 0x6d, 0x18, 0x73, 0x11, 0xad, 0x08,
0x4d, 0xfd, 0xa3, 0xf7, 0xa7, 0xf0, 0x98, 0xed, 0x85, 0xea, 0x54, 0xe4,
0xa5, 0xdd, 0x29, 0xd4, 0x9d, 0xcb, 0xc2, 0xc4, 0x9f, 0xc5, 0x4c, 0xc8,
0x69, 0xd0, 0x13, 0xd7, 0x36, 0xdd, 0x3d, 0xe9, 0x62, 0xf5, 0xf1, 0x02,
0x07, 0x0c, 0x37, 0x11, 0x3f, 0x17, 0xb6, 0x1b, 0x73, 0x1f, 0x91, 0x20,
0x45, 0x20, 0x61, 0x22, 0xcf, 0x23, 0x3f, 0x26, 0xa5, 0x26, 0x33, 0x27,
0xd9, 0x1e, 0xb8, 0x15, 0x25, 0x0b, 0x14, 0xfe, 0xeb, 0xf3, 0x80, 0xec,
0xdf, 0xe0, 0x6b, 0xd8, 0x45, 0xd6, 0x52, 0xd3, 0x55, 0xd7, 0x2d, 0xd8,
0xab, 0xd5, 0x7b, 0xd7, 0x5c, 0xda, 0x76, 0xdf, 0xd4, 0xe3, 0xec, 0xe2,
0x38, 0xe8, 0x94, 0xed, 0x00, 0xf8, 0xf0, 0x02, 0x62, 0x0b, 0x80, 0x16,
0xfb, 0x1f, 0x9a, 0x28, 0xc0, 0x30, 0x64, 0x34, 0x0e, 0x32, 0x46, 0x33,
0x8d, 0x33, 0x66, 0x35, 0xe8, 0x32, 0x50, 0x2f, 0x78, 0x2a, 0xfa, 0x26,
0xc7, 0x20, 0xf3, 0x19, 0x30, 0x11, 0xb9, 0x09, 0x92, 0x02, 0xb1, 0xfa,
0x80, 0xee, 0x49, 0xe3, 0xbe, 0xdd, 0x62, 0xd8, 0x21, 0xd5, 0xc3, 0xcc,
0xf9, 0xc2, 0x3e, 0xb5, 0xdf, 0xb0, 0xb7, 0xae, 0x95, 0xb4, 0xa8, 0xc5,
0x6f, 0xd4, 0x19, 0xe9, 0xa5, 0xfc, 0xc6, 0x10, 0x6d, 0x23, 0xfa, 0x32,
0x5c, 0x3b, 0x69, 0x3d, 0xac, 0x3f, 0x62, 0x3b, 0x95, 0x39, 0xb9, 0x32,
0xff, 0x2e, 0xb1, 0x2b, 0x60, 0x28, 0xb1, 0x23, 0x24, 0x1c, 0xef, 0x0f,
0x29, 0xff, 0x3a, 0xef, 0x03, 0xdf, 0x7f, 0xd2, 0xab, 0xca, 0xf5, 0xc5,
0x0f, 0xc3, 0xb0, 0xc3, 0xb8, 0xc4, 0x71, 0xc8, 0xb2, 0xcd, 0x24, 0xd3,
0xdb, 0xdc, 0x59, 0xe5, 0xd0, 0xeb, 0xb8, 0xf1, 0x0a, 0xf8, 0xbb, 0x00,
0x58, 0x0c, 0x3e, 0x17, 0x42, 0x1e, 0xdc, 0x25, 0x6d, 0x2b, 0xe9, 0x2f,
0xa2, 0x33, 0xa6, 0x32, 0x54, 0x33, 0xa8, 0x31, 0x7d, 0x2d, 0xd7, 0x28,
0xe7, 0x23, 0x8d, 0x1e, 0x00, 0x1b, 0x38, 0x16, 0x4c, 0x0d, 0xa9, 0x05,
0xb6, 0xff, 0x2e, 0xfb, 0x75, 0xf5, 0x4e, 0xef, 0x68, 0xea, 0xf0, 0xe5,
0x29, 0xe2, 0xa9, 0xdc, 0x44, 0xd6, 0xd8, 0xd0, 0x2e, 0xcc, 0x82, 0xc5,
0xb6, 0xbe, 0x15, 0xb8, 0x98, 0xb8, 0xd2, 0xc4, 0x84, 0xd7, 0xc5, 0xeb,
0xfa, 0xff, 0x15, 0x15, 0x7c, 0x28, 0x37, 0x3c, 0x60, 0x46, 0x79, 0x45,
0x3c, 0x43, 0x37, 0x3e, 0xf5, 0x3a, 0xf3, 0x36, 0x24, 0x2d, 0xed, 0x24,
0xcb, 0x1e, 0x3c, 0x19, 0x61, 0x0f, 0xbd, 0x03, 0x31, 0xf5, 0xb2, 0xe8,
0x11, 0xdd, 0x58, 0xd3, 0x62, 0xcb, 0xe0, 0xc6, 0xc3, 0xc3, 0x00, 0xc2,
0xd3, 0xc4, 0xb2, 0xc9, 0x2a, 0xd2, 0xe6, 0xd9, 0xf2, 0xe3, 0x75, 0xec,
0x4a, 0xf6, 0xf8, 0xfe, 0x49, 0x06, 0x5c, 0x0c, 0xf2, 0x10, 0x6e, 0x18,
0x24, 0x1f, 0x81, 0x25, 0xff, 0x28, 0xba, 0x2a, 0x67, 0x2c, 0xe1, 0x2d,
0x97, 0x27, 0x14, 0x21, 0xf8, 0x1b, 0x53, 0x18, 0xdf, 0x19, 0x62, 0x16,
0xe4, 0x11, 0x77, 0x0e, 0x61, 0x09, 0x89, 0x05, 0xed, 0x02, 0x7c, 0xfe,
0x57, 0xf9, 0x48, 0xf7, 0xa9, 0xf2, 0xec, 0xed, 0xca, 0xe9, 0x51, 0xe5,
0x0f, 0xe0, 0x99, 0xda, 0x12, 0xd3, 0xb0, 0xcb, 0x44, 0xc5, 0x46, 0xc0,
0x91, 0xbd, 0xe9, 0xbf, 0x97, 0xcb, 0xa0, 0xdf, 0x56, 0xf9, 0x1e, 0x13,
0x35, 0x22, 0xf2, 0x32, 0xd0, 0x3d, 0xc4, 0x45, 0x55, 0x49, 0x99, 0x43,
0x34, 0x3d, 0x9b, 0x35, 0x06, 0x2f, 0xe1, 0x26, 0x5b, 0x23, 0x70, 0x19,
0x44, 0x10, 0xf4, 0x04, 0x6d, 0xf7, 0x46, 0xe5, 0x9a, 0xd7, 0xf7, 0xcd,
0xde, 0xc9, 0x45, 0xc6, 0x42, 0xc2, 0x13, 0xc4, 0x27, 0xc9, 0x87, 0xd3,
0x06, 0xdc, 0x42, 0xe4, 0x0e, 0xed, 0xf7, 0xf5, 0xf9, 0xfd, 0xae, 0x05,
0x7e, 0x0a, 0x1f, 0x0f, 0xad, 0x16, 0x55, 0x1c, 0xec, 0x20, 0xa3, 0x22,
0x74, 0x21, 0x58, 0x21, 0x9f, 0x20, 0x02, 0x22, 0x59, 0x1e, 0xe9, 0x17,
0x5f, 0x12, 0x5b, 0x0d, 0x47, 0x0d, 0xb2, 0x0d, 0xb6, 0x0d, 0xfc, 0x0c,
0xde, 0x0c, 0x00, 0x0a, 0x6f, 0x04, 0x7e, 0x01, 0x66, 0xfd, 0xb6, 0xfa,
0x36, 0xf7, 0xb0, 0xf0, 0x90, 0xeb, 0xf2, 0xe3, 0x9d, 0xdd, 0x85, 0xd9,
0x15, 0xcf, 0x33, 0xc9, 0x0a, 0xc2, 0x5f, 0xbc, 0x26, 0xbc, 0x4b, 0xc5,
0xdd, 0xd4, 0xe2, 0xf0, 0xfe, 0x0a, 0xd4, 0x1b, 0x16, 0x2d, 0xbc, 0x39,
0xd1, 0x45, 0x70, 0x4a, 0xab, 0x46, 0x25, 0x3b, 0x47, 0x35, 0x8a, 0x30,
0x54, 0x29, 0xd5, 0x1e, 0x34, 0x13, 0xa7, 0x0a, 0x02, 0x05, 0x83, 0xfc,
0xde, 0xef, 0xd8, 0xe1, 0x61, 0xd6, 0xfb, 0xcb, 0x55, 0xc5, 0x52, 0xc3,
0x99, 0xc5, 0x10, 0xcd, 0x65, 0xd7, 0xc6, 0xe3, 0xd5, 0xed, 0xb5, 0xf5,
0x52, 0x00, 0x1e, 0x09, 0xe6, 0x0c, 0xae, 0x0d, 0x76, 0x0c, 0xf9, 0x0a,
0x2e, 0x11, 0x9a, 0x17, 0x17, 0x1b, 0x71, 0x20, 0xd9, 0x22, 0x8d, 0x20,
0xbd, 0x1a, 0x6e, 0x13, 0x8c, 0x0b, 0xe8, 0x08, 0x39, 0x07, 0x39, 0x06,
0xbc, 0x08, 0x9b, 0x0b, 0x19, 0x10, 0x56, 0x12, 0x20, 0x12, 0x9e, 0x10,
0xa9, 0x0b, 0x78, 0x05, 0x00, 0xff, 0xf6, 0xf6, 0xc9, 0xf0, 0x15, 0xe9,
0xad, 0xe2, 0xa0, 0xdb, 0xcc, 0xd2, 0x03, 0xcc, 0x9d, 0xc4, 0x8a, 0xc2,
0x60, 0xbd, 0x26, 0xbb, 0x26, 0xc2, 0xcd, 0xd5, 0x4b, 0xf0, 0x59, 0x0b,
0xc8, 0x20, 0xe9, 0x32, 0xdc, 0x41, 0x5b, 0x47, 0x3f, 0x47, 0xc1, 0x42,
0x30, 0x3e, 0x0d, 0x38, 0x81, 0x2d, 0xce, 0x22, 0x8e, 0x18, 0xd9, 0x11,
0x94, 0x0a, 0x92, 0x03, 0xb6, 0xf6, 0x94, 0xe9, 0xe3, 0xdb, 0x78, 0xd2,
0xf9, 0xcd, 0x19, 0xc9, 0x4e, 0xc7, 0x70, 0xcb, 0x3f, 0xd3, 0x24, 0xde,
0x3b, 0xe9, 0x83, 0xf2, 0xfc, 0xfd, 0xa0, 0x0b, 0x60, 0x12, 0x53, 0x13,
0x4c, 0x10, 0xd2, 0x10, 0x1f, 0x17, 0xfb, 0x19, 0x53, 0x1b, 0x51, 0x1a,
0x21, 0x1b, 0x28, 0x19, 0x56, 0x15, 0xaf, 0x0e, 0x52, 0x05, 0x40, 0x01,
0xe6, 0xff, 0x28, 0xff, 0x7c, 0x01, 0x81, 0x05, 0x90, 0x09, 0x1d, 0x12,
0x40, 0x16, 0x51, 0x16, 0x81, 0x14, 0x24, 0x0f, 0x8b, 0x09, 0x5f, 0x02,
0xa5, 0xf8, 0xc1, 0xef, 0x97, 0xe7, 0x83, 0xda, 0x4f, 0xd0, 0xb1, 0xc5,
0xfa, 0xbd, 0x44, 0xb9, 0xdd, 0xb5, 0x5f, 0xb6, 0x9b, 0xc3, 0x7e, 0xda,
0xda, 0xf7, 0x63, 0x13, 0x05, 0x26, 0xc1, 0x34, 0xd3, 0x3f, 0x02, 0x45,
0x88, 0x46, 0x44, 0x42, 0x06, 0x3d, 0x67, 0x36, 0x4d, 0x2c, 0x93, 0x21,
0x00, 0x16, 0x38, 0x0e, 0x03, 0x0b, 0x6b, 0x01, 0x11, 0xf1, 0x50, 0xe1,
0x23, 0xd6, 0xf1, 0xcd, 0x1b, 0xcc, 0xe3, 0xc9, 0xcb, 0xc8, 0x3c, 0xd0,
0xf6, 0xd9, 0x44, 0xe7, 0x2a, 0xf4, 0x65, 0xfe, 0x4f, 0x09, 0x45, 0x12,
0x33, 0x16, 0xa5, 0x15, 0x57, 0x15, 0x83, 0x14, 0xd5, 0x16, 0xd1, 0x18,
0x0a, 0x14, 0xf1, 0x12, 0xdf, 0x0f, 0x3d, 0x0c, 0xca, 0x09, 0x0c, 0x05,
0x70, 0xfe, 0x26, 0xfa, 0xfa, 0xf9, 0x92, 0xfb, 0x88, 0x01, 0xfd, 0x08,
0x67, 0x12, 0x56, 0x18, 0x44, 0x1a, 0xfa, 0x19, 0xaa, 0x16, 0x15, 0x11,
0x00, 0x0c, 0x89, 0x05, 0x26, 0xfd, 0x2e, 0xf3, 0x01, 0xe5, 0xd2, 0xd6,
0xe1, 0xcb, 0x76, 0xc1, 0x7c, 0xb6, 0xb2, 0xae, 0xb6, 0xaf, 0xc0, 0xbc,
0x10, 0xd4, 0x4a, 0xf0, 0x21, 0x08, 0xdb, 0x19, 0xb4, 0x27, 0xbe, 0x34,
0x17, 0x40, 0x22, 0x42, 0x17, 0x42, 0x34, 0x3d, 0x03, 0x36, 0x27, 0x2e,
0x39, 0x24, 0xba, 0x1b, 0x19, 0x18, 0x0b, 0x12, 0x1e, 0x04, 0x34, 0xf2,
0xf5, 0xe2, 0x19, 0xda, 0xe7, 0xd4, 0x30, 0xd1, 0x98, 0xce, 0x56, 0xd1,
0x76, 0xd6, 0xf3, 0xde, 0x42, 0xe9, 0x55, 0xf3, 0x50, 0xfe, 0x35, 0x09,
0x69, 0x0f, 0x05, 0x13, 0xd5, 0x14, 0xab, 0x17, 0x03, 0x1a, 0x75, 0x1a,
0xf3, 0x16, 0x53, 0x11, 0x6f, 0x0c, 0xce, 0x09, 0xcf, 0x05, 0x5e, 0xff,
0x47, 0xf9, 0x8b, 0xf4, 0xe7, 0xf3, 0x62, 0xf5, 0x39, 0xf9, 0x2c, 0x00,
0x37, 0x0d, 0x52, 0x16, 0x6f, 0x1a, 0x6f, 0x1d, 0x64, 0x1c, 0x1d, 0x19,
0xe4, 0x16, 0xbc, 0x11, 0x6f, 0x09, 0x2b, 0x01, 0x6e, 0xf3, 0x09, 0xe5,
0xda, 0xd5, 0x23, 0xc7, 0xc9, 0xbd, 0x99, 0xb5, 0x14, 0xaf, 0x4a, 0xb2,
0x4f, 0xbf, 0x82, 0xd7, 0x5c, 0xf4, 0x46, 0x0d, 0x93, 0x1c, 0xc0, 0x2b,
0xe5, 0x37, 0xc8, 0x38, 0x85, 0x39, 0x39, 0x3b, 0xe5, 0x38, 0x1a, 0x35,
0x41, 0x2f, 0xed, 0x24, 0xab, 0x1c, 0x59, 0x16, 0xe6, 0x0a, 0xee, 0xfb,
0x97, 0xed, 0x9e, 0xe0, 0x8a, 0xd8, 0x03, 0xd4, 0x92, 0xd2, 0xfc, 0xd4,
0xc2, 0xd7, 0x04, 0xe0, 0x5e, 0xe8, 0x11, 0xf0, 0x19, 0xf9, 0xb3, 0x01,
0x3f, 0x0b, 0xd6, 0x10, 0x9a, 0x12, 0x4b, 0x15, 0x42, 0x15, 0x69, 0x16,
0x44, 0x13, 0xd9, 0x0d, 0x26, 0x0b, 0x1f, 0x06, 0x13, 0xff, 0x09, 0xfb,
0x6b, 0xf8, 0x71, 0xf6, 0x95, 0xf5, 0x88, 0xf6, 0x12, 0xf9, 0xbf, 0xff,
0x9f, 0x08, 0x6d, 0x12, 0x4a, 0x1b, 0xef, 0x20, 0xac, 0x22, 0x06, 0x21,
0x4a, 0x1b, 0x82, 0x14, 0x17, 0x0d, 0xe7, 0x02, 0x8c, 0xf6, 0x7d, 0xe6,
0x7b, 0xd6, 0x7f, 0xc8, 0x04, 0xbb, 0x32, 0xb1, 0xe1, 0xac, 0xe0, 0xaf,
0x3f, 0xc0, 0x79, 0xda, 0x08, 0xf4, 0x07, 0x0b, 0xa2, 0x1a, 0x31, 0x28,
0x62, 0x34, 0x48, 0x39, 0xde, 0x3b, 0x58, 0x3a, 0xc1, 0x37, 0x22, 0x31,
0xcf, 0x2d, 0xa5, 0x23, 0x6b, 0x1c, 0xb5, 0x11, 0xe6, 0x05, 0x71, 0xfb,
0x92, 0xe9, 0x67, 0xdc, 0xe2, 0xd1, 0x0e, 0xd0, 0xf5, 0xd1, 0xe8, 0xd6,
0x80, 0xde, 0xb4, 0xe6, 0x54, 0xf0, 0x6d, 0xfa, 0x1a, 0x02, 0x56, 0x06,
0x52, 0x0b, 0x12, 0x10, 0xfc, 0x12, 0x2a, 0x14, 0xe5, 0x12, 0x3a, 0x11,
0x96, 0x10, 0xad, 0x0d, 0x71, 0x07, 0xe0, 0x00, 0x9c, 0xfc, 0x6a, 0xf7,
0x77, 0xf5, 0x45, 0xf5, 0xf4, 0xf5, 0x87, 0xf8, 0x85, 0xfd, 0x71, 0x04,
0x64, 0x0b, 0x63, 0x16, 0xff, 0x1c, 0x5f, 0x1f, 0x6f, 0x20, 0x71, 0x1d,
0x73, 0x1b, 0x45, 0x12, 0xd4, 0x09, 0x6a, 0xfe, 0xcb, 0xed, 0x8f, 0xdc,
0xd0, 0xcc, 0xa4, 0xbd, 0xe6, 0xb5, 0x32, 0xb0, 0x4c, 0xb3, 0xea, 0xbb,
0xe1, 0xd0, 0xb2, 0xee, 0x3e, 0x09, 0x3e, 0x1c, 0xf4, 0x25, 0x54, 0x2f,
0x0b, 0x33, 0xb0, 0x39, 0x16, 0x3a, 0x72, 0x36, 0x12, 0x33, 0xbb, 0x2c,
0x21, 0x24, 0x3d, 0x1a, 0xd9, 0x10, 0x09, 0x07, 0x2f, 0xfc, 0x88, 0xef,
0x90, 0xe0, 0x3c, 0xd4, 0x4b, 0xcd, 0xd3, 0xd1, 0x2f, 0xdb, 0xb1, 0xe2,
0xc1, 0xea, 0x8a, 0xf3, 0x5d, 0xfd, 0x68, 0x06, 0x23, 0x09, 0xa4, 0x0b,
0x7d, 0x10, 0x50, 0x15, 0x8e, 0x15, 0xfc, 0x11, 0xea, 0x0d, 0x74, 0x0d,
0x88, 0x0b, 0x07, 0x08, 0x7d, 0x00, 0xf6, 0xf9, 0xef, 0xf2, 0x2d, 0xf2,
0xce, 0xf4, 0x94, 0xf4, 0xe1, 0xf7, 0x5e, 0xfd, 0x20, 0x04, 0xe8, 0x0d,
0x1e, 0x15, 0xc4, 0x1a, 0xd6, 0x1f, 0x7a, 0x20, 0xfc, 0x1c, 0x72, 0x1d,
0x78, 0x16, 0x80, 0x0d, 0x24, 0x03, 0x87, 0xef, 0xc6, 0xde, 0x64, 0xcc,
0x62, 0xbf, 0x2a, 0xb6, 0x7c, 0xaf, 0xc9, 0xb0, 0xbe, 0xbb, 0xe0, 0xd1,
0x9d, 0xed, 0x1c, 0x06, 0x3f, 0x18, 0x6b, 0x22, 0x32, 0x2d, 0xf7, 0x32,
0x51, 0x36, 0xf4, 0x3b, 0x87, 0x39, 0xcf, 0x33, 0xa6, 0x2b, 0x20, 0x22,
0x7d, 0x1c, 0xbe, 0x13, 0xba, 0x09, 0x73, 0xfb, 0x96, 0xec, 0x06, 0xdd,
0x22, 0xd6, 0x9a, 0xd3, 0x8d, 0xda, 0x0b, 0xe1, 0xc8, 0xe6, 0x78, 0xeb,
0xa6, 0xf1, 0x68, 0xf7, 0x34, 0x02, 0x52, 0x0a, 0x21, 0x0e, 0x65, 0x13,
0xcf, 0x13, 0xea, 0x12, 0xce, 0x11, 0x27, 0x0e, 0xd1, 0x0b, 0x82, 0x0a,
0x38, 0x05, 0x0e, 0xff, 0x1e, 0xf7, 0xc4, 0xf2, 0x23, 0xf2, 0x7c, 0xf4,
0x43, 0xf7, 0xc7, 0xfa, 0x5e, 0xff, 0x89, 0x04, 0xab, 0x0d, 0xef, 0x14,
0x26, 0x1d, 0x4d, 0x21, 0x0b, 0x21, 0xc6, 0x1c, 0x54, 0x16, 0x12, 0x0f,
0xd6, 0x06, 0xab, 0xf9, 0xe2, 0xe7, 0x37, 0xd8, 0x46, 0xc9, 0xc4, 0xbe,
0x3c, 0xb6, 0x8e, 0xb2, 0xb9, 0xb6, 0x41, 0xc8, 0x13, 0xe0, 0xd4, 0xf8,
0x7b, 0x0e, 0xe1, 0x1c, 0x7d, 0x23, 0x07, 0x2c, 0x3a, 0x2f, 0x15, 0x32,
0xd3, 0x30, 0xc1, 0x2f, 0x44, 0x2c, 0x64, 0x27, 0xc7, 0x20, 0x04, 0x18,
0xb3, 0x10, 0x20, 0x05, 0x09, 0xfa, 0x86, 0xee, 0x9f, 0xe1, 0xee, 0xda,
0x6f, 0xd9, 0x7c, 0xdd, 0x19, 0xe6, 0xb5, 0xf0, 0x1e, 0xf6, 0x21, 0xfb,
0xba, 0xfe, 0x68, 0x01, 0x45, 0x07, 0x5e, 0x09, 0xbb, 0x0c, 0x61, 0x0c,
0xda, 0x0c, 0xac, 0x0e, 0x3e, 0x0d, 0xb1, 0x09, 0x03, 0x05, 0xe9, 0xfe,
0xcb, 0xf9, 0x21, 0xf6, 0xc3, 0xf2, 0x6f, 0xf2, 0xbf, 0xf5, 0x56, 0xfa,
0x85, 0x00, 0x88, 0x07, 0x3a, 0x0d, 0x78, 0x15, 0xfd, 0x1b, 0x5c, 0x20,
0x30, 0x21, 0x79, 0x1d, 0xce, 0x19, 0x84, 0x13, 0x1e, 0x0a, 0x0c, 0xfe,
0x89, 0xed, 0x63, 0xd9, 0x5f, 0xca, 0x4a, 0xbd, 0xc9, 0xb5, 0x59, 0xb3,
0xbb, 0xb8, 0x68, 0xc4, 0xfa, 0xdb, 0x7c, 0xf3, 0x11, 0x05, 0xc6, 0x17,
0x18, 0x21, 0x47, 0x2f, 0x1a, 0x31, 0x7f, 0x30, 0xd8, 0x31, 0xb6, 0x30,
0xd2, 0x2d, 0xe0, 0x27, 0x12, 0x21, 0x58, 0x19, 0x25, 0x11, 0x06, 0x08,
0x84, 0xf7, 0xad, 0xe9, 0xe5, 0xdb, 0xb0, 0xd8, 0x15, 0xdc, 0x07, 0xe0,
0x10, 0xe8, 0x9e, 0xef, 0xc6, 0xf7, 0xef, 0xff, 0xab, 0x05, 0xd8, 0x0a,
0xbf, 0x0e, 0x0d, 0x11, 0x32, 0x10, 0xb1, 0x10, 0xc0, 0x0f, 0x81, 0x0d,
0xe2, 0x0c, 0xaf, 0x07, 0x2a, 0x04, 0x2d, 0xfe, 0x29, 0xf7, 0x9b, 0xf2,
0x1d, 0xf0, 0x00, 0xef, 0xbe, 0xf2, 0x4a, 0xf5, 0x6a, 0xf9, 0x9d, 0x01,
0x6e, 0x0a, 0x44, 0x11, 0x35, 0x1a, 0x52, 0x1e, 0xa5, 0x1e, 0xf4, 0x1c,
0x36, 0x18, 0xd3, 0x10, 0xa7, 0x09, 0x3b, 0xfa, 0x47, 0xeb, 0xa8, 0xdd,
0x85, 0xcd, 0x45, 0xc4, 0x04, 0xbc, 0x7b, 0xb8, 0x3b, 0xbf, 0x26, 0xcb,
0x2e, 0xe1, 0x54, 0xf7, 0xf8, 0x07, 0x0b, 0x18, 0x1f, 0x1f, 0xd0, 0x24,
0xbf, 0x2a, 0x2e, 0x2d, 0x89, 0x2e, 0x55, 0x2d, 0xa2, 0x26, 0x49, 0x21,
0x63, 0x1e, 0x3e, 0x17, 0x0b, 0x11, 0xf6, 0x05, 0x71, 0xf8, 0x95, 0xed,
0x3c, 0xe6, 0x97, 0xe2, 0xbe, 0xe3, 0xf3, 0xe5, 0xae, 0xed, 0x09, 0xf7,
0x73, 0xfc, 0x14, 0x02, 0xbf, 0x09, 0x06, 0x0b, 0xab, 0x0d, 0x4d, 0x0f,
0x59, 0x0e, 0x99, 0x0d, 0x11, 0x0c, 0xa6, 0x08, 0x0d, 0x06, 0x7d, 0x02,
0xeb, 0xfd, 0xd5, 0xfa, 0x6b, 0xf7, 0x0a, 0xf4, 0x6e, 0xf1, 0x7d, 0xef,
0xfa, 0xf0, 0x1a, 0xf7, 0xf7, 0xfa, 0x20, 0x01, 0x60, 0x09, 0x59, 0x12,
0xff, 0x16, 0x46, 0x1d, 0x81, 0x1c, 0x27, 0x1a, 0x26, 0x12, 0x42, 0x07,
0xbd, 0xf9, 0x07, 0xf1, 0xb7, 0xe6, 0x9c, 0xd9, 0x29, 0xce, 0xc8, 0xc7,
0x8f, 0xc1, 0x04, 0xc4, 0xb4, 0xd0, 0xc0, 0xe0, 0x9c, 0xf3, 0xb1, 0xff,
0x64, 0x0b, 0x5c, 0x15, 0xf5, 0x1b, 0x82, 0x20, 0x1b, 0x2a, 0x9e, 0x28,
0xd5, 0x2a, 0xb9, 0x28, 0xb9, 0x21, 0xc1, 0x1a, 0x27, 0x14, 0x4d, 0x0d,
0x1f, 0x07, 0x9f, 0x01, 0x7f, 0xf6, 0x8b, 0xee, 0xaf, 0xe8, 0x15, 0xe7,
0xd2, 0xec, 0x70, 0xf1, 0xff, 0xf5, 0xf1, 0xfb, 0x4a, 0xff, 0x0e, 0x02,
0xf5, 0x05, 0xfc, 0x05, 0xf2, 0x0a, 0xf4, 0x0d, 0x57, 0x0e, 0x81, 0x0c,
0xfb, 0x0a, 0xe8, 0x06, 0x6d, 0x03, 0x6e, 0xff, 0x8f, 0xfb, 0x77, 0xf8,
0x8f, 0xf3, 0x48, 0xee, 0x89, 0xee, 0x36, 0xf0, 0x17, 0xf5, 0xe0, 0xfc,
0x53, 0x02, 0xde, 0x07, 0x75, 0x0e, 0xce, 0x12, 0xaf, 0x16, 0xc0, 0x16,
0x8a, 0x14, 0xd6, 0x12, 0x2a, 0x0a, 0xe7, 0x00, 0x03, 0xf7, 0x6c, 0xe9,
0x69, 0xdc, 0x08, 0xd1, 0xd8, 0xc5, 0xf8, 0xc3, 0x6d, 0xc8, 0x71, 0xd6,
0xb5, 0xe7, 0x0b, 0xf3, 0xd1, 0xfd, 0x55, 0x07, 0xd3, 0x0d, 0x57, 0x18,
0x10, 0x19, 0xea, 0x1d, 0x23, 0x23, 0x62, 0x25, 0x34, 0x28, 0xb5, 0x23,
0x48, 0x20, 0x6d, 0x1b, 0xc5, 0x13, 0xd1, 0x0a, 0xc9, 0x02, 0xfb, 0xfa,
0x67, 0xf2, 0xc2, 0xed, 0x64, 0xea, 0x29, 0xed, 0x8a, 0xf0, 0xe8, 0xf3,
0x95, 0xf8, 0x87, 0xfd, 0x1b, 0x02, 0x1f, 0x07, 0xd0, 0x0a, 0xb3, 0x0d,
0x2c, 0x0f, 0x34, 0x0e, 0x7c, 0x0b, 0xc1, 0x0b, 0xfe, 0x09, 0x7a, 0x08,
0xd9, 0x04, 0x31, 0xff, 0x4d, 0xf9, 0x23, 0xf3, 0x06, 0xef, 0x26, 0xec,
0x46, 0xee, 0x18, 0xf0, 0xea, 0xf3, 0x0f, 0xfb, 0xa7, 0x01, 0x96, 0x07,
0xcb, 0x0b, 0x39, 0x0f, 0x19, 0x12, 0x1e, 0x12, 0x1c, 0x0d, 0xfc, 0x07,
0xb0, 0xff, 0x84, 0xf6, 0x35, 0xea, 0x44, 0xdc, 0x9f, 0xcf, 0xbf, 0xc8,
0xcc, 0xcb, 0x41, 0xd5, 0x4c, 0xe0, 0x77, 0xee, 0xb4, 0xfa, 0x2e, 0x07,
0xc7, 0x0f, 0x82, 0x15, 0x64, 0x16, 0xb9, 0x19, 0xaa, 0x1c, 0x51, 0x1b,
0x74, 0x1f, 0x76, 0x1d, 0x52, 0x1c, 0x47, 0x1a, 0x1b, 0x14, 0xec, 0x0e,
0xe9, 0x05, 0xb6, 0xfc, 0x48, 0xf7, 0x42, 0xf2, 0xcb, 0xee, 0x38, 0xee,
0x03, 0xef, 0xcb, 0xee, 0x42, 0xf5, 0x83, 0xf9, 0xed, 0xff, 0x16, 0x08,
0x18, 0x0d, 0x4b, 0x0f, 0x87, 0x13, 0x4f, 0x12, 0x72, 0x10, 0xf6, 0x12,
0x3e, 0x10, 0x1a, 0x0f, 0xcb, 0x0a, 0x85, 0x03, 0x3d, 0xfd, 0xc9, 0xf8,
0x81, 0xf3, 0xab, 0xf1, 0xe5, 0xef, 0xe3, 0xee, 0x96, 0xf1, 0xb7, 0xf4,
0x6d, 0xf9, 0xd6, 0x01, 0x6f, 0x06, 0xdd, 0x0a, 0xc1, 0x0c, 0xd7, 0x0b,
0xd8, 0x08, 0xdf, 0x03, 0x27, 0xfe, 0x5b, 0xf7, 0x7e, 0xef, 0x49, 0xe6,
0x6a, 0xdf, 0xe3, 0xd8, 0xd8, 0xd5, 0xa3, 0xda, 0x57, 0xe3, 0x59, 0xea,
0x4a, 0xf3, 0xc0, 0xfa, 0x49, 0x01, 0x40, 0x09, 0x31, 0x11, 0x20, 0x16,
0xce, 0x17, 0x79, 0x19, 0x14, 0x1c, 0x5c, 0x1c, 0x1a, 0x1b, 0xfe, 0x15,
0xcb, 0x13, 0x74, 0x12, 0x5b, 0x0e, 0xeb, 0x08, 0x79, 0x01, 0x6e, 0xfb,
0x1f, 0xf7, 0x31, 0xf4, 0x0d, 0xf3, 0x58, 0xf3, 0xb5, 0xf6, 0xff, 0xf5,
0x25, 0xf9, 0x82, 0xfb, 0x38, 0xff, 0xb0, 0x03, 0x83, 0x08, 0xfb, 0x0c,
0x94, 0x11, 0x40, 0x14, 0x00, 0x14, 0x3b, 0x11, 0x3e, 0x0c, 0x17, 0x0a,
0xfd, 0x05, 0x31, 0x01, 0x97, 0xfb, 0xdb, 0xf5, 0xc7, 0xf1, 0x0e, 0xf1,
0x94, 0xf1, 0x2b, 0xf5, 0x99, 0xf9, 0x68, 0xfa, 0xf0, 0xfb, 0x3b, 0xfe,
0xb7, 0x00, 0xa0, 0x03, 0x64, 0x06, 0x92, 0x05, 0xe5, 0x03, 0xbf, 0xfd,
0x81, 0xf9, 0xde, 0xf0, 0xb2, 0xe7, 0x36, 0xdf, 0xa8, 0xd6, 0x0a, 0xd3,
0x1d, 0xd9, 0x4d, 0xe2, 0x6d, 0xed, 0x82, 0xf5, 0xf0, 0xfb, 0x30, 0x03,
0x16, 0x0a, 0x12, 0x0f, 0x2f, 0x13, 0x54, 0x17, 0x51, 0x17, 0xfe, 0x1d,
0x22, 0x22, 0x7f, 0x21, 0xcb, 0x21, 0x4b, 0x1d, 0x79, 0x15, 0xd3, 0x0d,
0xa6, 0x06, 0x12, 0xff, 0xc6, 0xfb, 0xef, 0xf6, 0x4d, 0xf3, 0x1d, 0xf2,
0xf8, 0xef, 0x5d, 0xf2, 0xcc, 0xf6, 0xff, 0xf9, 0xd7, 0xfc, 0x82, 0x00,
0xb2, 0x02, 0x04, 0x07, 0x0b, 0x0d, 0x55, 0x0f, 0x43, 0x11, 0x83, 0x12,
0x19, 0x0d, 0x77, 0x0a, 0xf3, 0x05, 0xd1, 0x02, 0x48, 0xff, 0x59, 0xf9,
0xd4, 0xf3, 0x73, 0xf0, 0x20, 0xf2, 0x46, 0xf4, 0x2b, 0xf8, 0x90, 0xfb,
0xa4, 0xfc, 0x2b, 0xfe, 0x16, 0x03, 0xb0, 0x07, 0x2d, 0x0b, 0xee, 0x08,
0xdd, 0x08, 0xc9, 0x06, 0x91, 0xff, 0xc1, 0xfa, 0x1e, 0xf1, 0x3d, 0xeb,
0x39, 0xe4, 0x0d, 0xdf, 0x75, 0xd8, 0x1d, 0xd7, 0x25, 0xd7, 0x03, 0xde,
0xb4, 0xe8, 0x24, 0xf1, 0x10, 0xfb, 0x9c, 0x06, 0x52, 0x0b, 0x8e, 0x12,
0xa8, 0x16, 0xc5, 0x1b, 0x89, 0x20, 0xb8, 0x21, 0x03, 0x21, 0x51, 0x1d,
0xc8, 0x17, 0x63, 0x14, 0x7f, 0x12, 0xa8, 0x0d, 0xa9, 0x09, 0x9e, 0x02,
0x9b, 0xfa, 0xfb, 0xf6, 0x1b, 0xf2, 0x6d, 0xef, 0x15, 0xf1, 0x80, 0xf3,
0x3f, 0xf6, 0xb6, 0xfc, 0x25, 0x00, 0x86, 0x05, 0x78, 0x0a, 0x4b, 0x0e,
0x2d, 0x11, 0x02, 0x11, 0x49, 0x0f, 0x8d, 0x0d, 0x37, 0x08, 0x81, 0x03,
0x15, 0xff, 0x92, 0xf8, 0xa2, 0xf5, 0x0a, 0xf3, 0x6f, 0xef, 0x19, 0xee,
0xa9, 0xf1, 0xf2, 0xf3, 0x44, 0xfc, 0x01, 0x02, 0x32, 0x05, 0x34, 0x08,
0xaf, 0x08, 0xc2, 0x07, 0x8a, 0x0b, 0xdc, 0x0a, 0x36, 0x08, 0xc4, 0x04,
0xb8, 0xff, 0xc4, 0xfa, 0x8b, 0xf5, 0xbb, 0xef, 0x25, 0xe8, 0xdc, 0xde,
0x56, 0xd9, 0x4f, 0xd4, 0xc4, 0xd8, 0xee, 0xdf, 0xc4, 0xe9, 0x1b, 0xf5,
0xb5, 0xfe, 0xc5, 0x05, 0x33, 0x09, 0xbb, 0x0a, 0x88, 0x0d, 0xb7, 0x0d,
0xcc, 0x12, 0xf3, 0x1a, 0xbc, 0x1a, 0xf2, 0x1a, 0x12, 0x1b, 0x96, 0x17,
0x2c, 0x16, 0x94, 0x11, 0xb1, 0x0a, 0xf2, 0x04, 0x84, 0x01, 0xff, 0xff,
0xa0, 0xfe, 0x6c, 0xfb, 0x59, 0xf8, 0x5a, 0xf8, 0xd0, 0xf8, 0xba, 0xfd,
))
SONG_LENGTH = 384
HAT = 20000
BASS = 500
SNARE = 6000
SUB = 50
melody_notes = (
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 262, 0, 294, 0, 392, 0, 440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
rhythm_notes = (
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0)
drum_beats = (
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0)
hi_hat = (
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1)
bass_notes = (
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0)
notes = [melody_notes, rhythm_notes, drum_beats, hi_hat, bass_notes]
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(53 / 2 - w / 2 + 1)
y = 2
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
gu.set_brightness(0.5)
was_a_pressed = False
was_b_pressed = False
was_c_pressed = False
was_d_pressed = False
was_z_pressed = False
bool_playing = False
freq_a = 0
freq_b = 0
beat = 0
channel0 = gu.synth_channel(0)
channel1 = gu.synth_channel(1)
channel2 = gu.synth_channel(2)
channel3 = gu.synth_channel(3)
channel4 = gu.synth_channel(7)
channels = [channel0, channel1, channel2, channel3, channel4]
def next_beat():
global beat
for i in range(5):
if notes[i][beat] > 0:
channels[i].frequency(notes[i][beat])
channels[i].trigger_attack()
elif notes[i][beat] == -1:
channels[i].trigger_release()
beat = (beat + 1) % SONG_LENGTH
def tick(timer):
next_beat()
timer = Timer(-1)
synthing = False
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
if not was_a_pressed:
channel0.configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
attack=0.016,
decay=0.168,
sustain=0,
release=0.168,
volume=0)
channel1.configure(waveforms=Channel.SINE + Channel.SQUARE,
attack=0.038,
decay=0.300,
sustain=0,
release=0,
volume=12000 / 65535)
channel2.configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.010,
sustain=16000 / 65535,
release=0.100,
volume=0)
channel3.configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.005,
sustain=8000 / 65535,
release=0.040,
volume=0)
channel4.configure(waveforms=Channel.SQUARE,
attack=0.010,
decay=0.100,
sustain=0,
release=0.500,
volume=0)
if not synthing:
beat = 0
next_beat()
gu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_a_pressed = True
else:
was_a_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
if not was_b_pressed:
timer.deinit()
freq_a = 400
channel0.play_tone(freq_a, 0.06)
gu.play_synth()
synthing = False
bool_playing = True
was_b_pressed = True
else:
was_b_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
if not was_c_pressed:
timer.deinit()
freq_b = 600
channel1.play_tone(freq_b, 0.06, attack=0.5)
gu.play_synth()
synthing = False
bool_playing = True
was_c_pressed = True
else:
was_c_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
if not was_d_pressed:
freq_a = 0
freq_b = 0
gu.stop_playing()
timer.deinit()
synthing = False
was_d_pressed = True
else:
was_d_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
# gu.adjust_brightness(+0.01)
if bool_playing:
freq_b = min(freq_b + 10, 20000)
channel1.frequency(freq_b)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
# gu.adjust_brightness(-0.01)
if bool_playing:
freq_b = max(freq_b - 10, 10)
channel1.frequency(max(freq_b, 10))
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
if bool_playing:
freq_a = min(freq_a + 10, 20000)
channel0.frequency(freq_a)
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
if bool_playing:
freq_a = max(freq_a - 10, 10)
channel0.frequency(freq_a)
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
if not was_z_pressed:
channel0.configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
attack=0.016,
decay=0.168,
sustain=0xafff / 65535,
release=0.168,
volume=10000 / 65535)
channel1.configure(waveforms=Channel.SINE + Channel.SQUARE,
attack=0.038,
decay=0.300,
sustain=0,
release=0,
volume=12000 / 65535)
channel2.configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.010,
sustain=16000 / 65535,
release=0.100,
volume=18000 / 65535)
channel3.configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.005,
sustain=8000 / 65535,
release=0.040,
volume=8000 / 65535)
channel4.configure(waveforms=Channel.SQUARE,
attack=0.010,
decay=0.100,
sustain=0,
release=0.500,
volume=12000 / 65535)
if not synthing:
beat = 0
next_beat()
gu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_z_pressed = True
else:
was_z_pressed = False
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
# print("grid pattern")
grid(255, 255, 255)
elif test == 1:
# print("red gradient")
gradient(255, 0, 0)
elif test == 2:
# print("green gradient")
gradient(0, 255, 0)
elif test == 3:
# print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
# print("white gradient")
gradient(255, 255, 255)
text = ""
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
text = "Play Sample"
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
text = "Tone A"
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
text = "Tone B"
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
text = "Stop"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
text = "Raise A"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
text = "Lower A"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Raise B"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Power B"
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
text = "Play Synth"
outline_text(text)
gu.update(graphics)

Wyświetl plik

@ -0,0 +1,135 @@
import time
import random
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
fire_colours = [graphics.create_pen(0, 0, 0),
graphics.create_pen(20, 20, 20),
graphics.create_pen(180, 30, 0),
graphics.create_pen(220, 160, 0),
graphics.create_pen(255, 255, 180)]
@micropython.native # noqa: F821
def setup_landscape():
global width, height, heat, fire_spawns, damping_factor
width = GalacticUnicorn.WIDTH + 2
height = GalacticUnicorn.HEIGHT + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 5
damping_factor = 0.97
@micropython.native # noqa: F821
def setup_portrait():
global width, height, heat, fire_spawns, damping_factor
width = GalacticUnicorn.HEIGHT + 2
height = GalacticUnicorn.WIDTH + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 2
damping_factor = 0.99
@micropython.native # noqa: F821
def update():
# clear the bottom row and then add a new fire seed to it
for x in range(width):
heat[x][height - 1] = 0.0
heat[x][height - 2] = 0.0
for c in range(fire_spawns):
x = random.randint(0, width - 4) + 2
heat[x + 0][height - 1] = 1.0
heat[x + 1][height - 1] = 1.0
heat[x - 1][height - 1] = 1.0
heat[x + 0][height - 2] = 1.0
heat[x + 1][height - 2] = 1.0
heat[x - 1][height - 2] = 1.0
for y in range(0, height - 2):
for x in range(1, width - 1):
# update this pixel by averaging the below pixels
average = (
heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1]
) / 5.0
# damping factor to ensure flame tapers out towards the top of the displays
average *= damping_factor
# update the heat map with our newly averaged value
heat[x][y] = average
@micropython.native # noqa: F821
def draw_landscape():
for y in range(GalacticUnicorn.HEIGHT):
for x in range(GalacticUnicorn.WIDTH):
value = heat[x + 1][y]
if value < 0.15:
graphics.set_pen(fire_colours[0])
elif value < 0.25:
graphics.set_pen(fire_colours[1])
elif value < 0.35:
graphics.set_pen(fire_colours[2])
elif value < 0.45:
graphics.set_pen(fire_colours[3])
else:
graphics.set_pen(fire_colours[4])
graphics.pixel(x, y)
gu.update(graphics)
@micropython.native # noqa: F821
def draw_portrait():
for y in range(GalacticUnicorn.WIDTH):
for x in range(GalacticUnicorn.HEIGHT):
value = heat[x + 1][y]
if value < 0.15:
graphics.set_pen(fire_colours[0])
elif value < 0.25:
graphics.set_pen(fire_colours[1])
elif value < 0.35:
graphics.set_pen(fire_colours[2])
elif value < 0.45:
graphics.set_pen(fire_colours[3])
else:
graphics.set_pen(fire_colours[4])
graphics.pixel(y, x)
gu.update(graphics)
landscape = True
setup_landscape()
gu.set_brightness(0.5)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
landscape = True
setup_landscape()
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
landscape = False
setup_portrait()
start = time.ticks_ms()
update()
if landscape:
draw_landscape()
else:
draw_portrait()
print("total took: {} ms".format(time.ticks_ms() - start))

Wyświetl plik

@ -0,0 +1,11 @@
# Galactic Paint
Galactic Paint lets you paint pixels onto your Galatic Unicorn over WiFi, in realtime!
## Setting Up
You'll need `WIFI_CONFIG.py` from the `common` directory to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
You will also have to install `micropython-phew` and `microdot` through Thonny's Tools -> Manage Packages.
Run the example through Thonny and it should get connected and give you a URL to visit. Open that URL in your browser and start painting!

Wyświetl plik

@ -0,0 +1,113 @@
import os
from microdot_asyncio import Microdot, send_file
from microdot_asyncio_websocket import with_websocket
from phew import connect_to_wifi
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
from WIFI_CONFIG import SSID, PSK
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
mv_graphics = memoryview(graphics)
gu.set_brightness(0.5)
WIDTH, HEIGHT = graphics.get_bounds()
ip = connect_to_wifi(SSID, PSK)
print(f"Start painting at: http://{ip}")
server = Microdot()
@server.route("/", methods=["GET"])
def route_index(request):
return send_file("galactic_paint/index.html")
@server.route("/static/<path:path>", methods=["GET"])
def route_static(request, path):
return send_file(f"galactic_paint/static/{path}")
def get_pixel(x, y):
if x < WIDTH and y < HEIGHT and x >= 0 and y >= 0:
o = (y * WIDTH + x) * 4
return tuple(mv_graphics[o:o + 3])
return None
def flood_fill(x, y, r, g, b):
todo = []
def fill(x, y, c):
if get_pixel(x, y) != c:
return
graphics.pixel(x, y)
up = get_pixel(x, y - 1)
dn = get_pixel(x, y + 1)
lf = get_pixel(x - 1, y)
ri = get_pixel(x + 1, y)
if up == c:
todo.append((x, y - 1))
if dn == c:
todo.append((x, y + 1))
if lf == c:
todo.append((x - 1, y))
if ri == c:
todo.append((x + 1, y))
c = get_pixel(x, y)
if c is None:
return
fill(x, y, c)
while len(todo):
x, y = todo.pop(0)
fill(x, y, c)
@server.route('/paint')
@with_websocket
async def echo(request, ws):
while True:
data = await ws.receive()
try:
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
graphics.pixel(x, y)
except ValueError:
if data == "show":
gu.update(graphics)
if data == "fill":
data = await ws.receive()
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
flood_fill(x, y, r, g, b)
if data == "clear":
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if data == "save":
filename = await ws.receive()
print(f"Saving to {filename}.bin")
try:
os.mkdir("saves")
except OSError:
pass
with open(f"saves/{filename}.bin", "wb") as f:
f.write(graphics)
await ws.send(f"alert: Saved to saves/{filename}.bin")
server.run(host="0.0.0.0", port=80)

Wyświetl plik

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Galactic Paint</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/paint.css">
</head>
<body>
<div class="window">
<h1>Galactic Paint</h1>
<table cellspacing="0" cellpadding="0" border-collapse="collapse">
<tbody></tbody>
</table>
<div id="palette">
<ul>
<li class="selected" style="background:rgb(0,0,0);"></li>
<li style="background:rgb(132,0,0);"></li>
<li style="background:rgb(0,132,0);"></li>
<li style="background:rgb(132,132,0);"></li>
<li style="background:rgb(0,0,132);"></li>
<li style="background:rgb(132,0,132);"></li>
<li style="background:rgb(0,132,132);"></li>
<li style="background:rgb(132,132,132);"></li>
<li style="background:rgb(198,198,198);"></li>
<li style="background:rgb(255,0,0);"></li>
<li style="background:rgb(0,255,0);"></li>
<li style="background:rgb(255,255,0);"></li>
<li style="background:rgb(0,0,255);"></li>
<li style="background:rgb(255,0,255);"></li>
<li style="background:rgb(0,255,255);"></li>
<li style="background:rgb(255,255,255);"></li>
</ul>
<input type="color" id="custom" name="custom" value="#ff0000">
</div>
<ul class="tools">
<li data-tool="paint" class="paint selected"><span class="fa fa-pencil"></span></li>
<li data-tool="fill" class="fill"><span class="fa fa-bitbucket"></span></li>
<li data-tool="erase" class="erase"><span class="fa fa-eraser"></span></li>
<li data-tool="pick" class="pick"><span class="fa fa-eyedropper"></span></li>
<li data-tool="lighten" class="lighten"><span class="fa fa-sun-o"></span></li>
<li data-tool="darken" class="darken"><span class="fa fa-adjust"></span></li>
<li data-tool="trash" class="trash"><span class="fa fa-trash"></span></li>
<li data-tool="save" class="save"><span class="fa fa-save"></span></li>
</ul>
</div>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
<script type="text/javascript" src="/static/tinycolor.js"></script>
<script type="text/javascript" src="/static/paint.js"></script>
</body>
</html>

Wyświetl plik

@ -0,0 +1,131 @@
body {
background:#333;
padding:20px;
font-family:Arial, Verdana, Sans-Serif;
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAaUlEQVQYV33Q0Q3AIAgEUBjBFVyBFRzbWVjBEajXBIOVypcJj1NhETG61BiDVJX4Bh211v5hRDiniV+Elx0wQwd0hEatlUop65srMSah23vf8Auz65AWMc8rDHvCCjAQK2KeDcuQDzh+AHEJX8mbbU1BAAAAAElFTkSuQmCC) repeat;
}
.icons {
position:absolute;
margin:0;
padding:20px;
list-style:none;
}
.icons li {
margin:20px;
padding:0;
list-style:none;
padding-top:80px;
width:100px;
}
.icons li span {
background:#FFF;
color:#000;
border:1px solid #000;
line-height:20px;
padding:5px 10px;
text-align:center;
font-size:10px;
line-height:10px;
display:inline-block;
}
#palette ul, #palette li {
margin:0;padding:0;list-style:none;
}
#palette {
list-style:none;
position:relative;
height: 122px;
padding:0 8px;
}
#palette ul {
display:block;
width:456px;
float: left;
}
#palette li, #palette input {
border: 2px outset;
width:49px;
height:49px;
float:left;
display:block;
margin:2px;
}
#palette input {
width:110px;
height:110px;
}
.window {
width: 976px;
position: relative;
background: #0E071A;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
}
.tools {
margin:0;padding:0;list-style:none;
clear:both;
display:block;
position:absolute;
top: 50px;
right: 8px;
width: 98px;
background:#999999;
font-size:0;
}
.tools span {
line-height:30px;
}
.tools li {
font-size:16px;
width: 45px;
height: 40px;
text-align:center;
margin:0;
padding:0;
display:inline-block;
line-height:40px;
border:2px outset #EEEEEE;
background:#F5F5F5;
cursor:pointer;
color:#000;
}
.tools li.selected {
background:#000;
color:#FFF;
}
h1 {
color: #FFF;
background: #6D38BB;
height:40px;
margin:0;
padding:0 8px;
line-height:40px;
font-weight:normal;
font-size:24px;
}
table {
clear:both;
cursor:pointer;
margin:10px;
border:1px solid #333;
background: #000000;
}
table td {
width:14px;
height:14px;
border:1px solid #333;
}

Wyświetl plik

@ -0,0 +1,214 @@
'use strict';
var md = false;
var color = tinycolor('#840000');
var update;
$(document).ready(function(){
var picker = $('#custom');
var palette = $('#palette');
picker.val(color.toHexString());
$(document)
.on('mousedown',function(e){md=true;})
.on('mouseup',function(e){md=false;});
$('table').on('dragstart', function(e){
e.preventDefault();
return false;
});
for (var y = 0; y < 11; y++) {
var row = $('<tr></tr>');
for (var x = 0; x < 53; x++) {
row.append('<td></td>');
}
$('tbody').append(row);
}
$('.tools li').on('click', function(){
switch($(this).index()){
case 6:
clear();
break;
case 7:
save();
break;
default:
$('.tools li').removeClass('selected');
$(this).addClass('selected');
break;
}
});
picker.on('change', function(){
color = tinycolor($(this).val());
})
palette.find('li').on('click', function(){
pick(this);
});
function handle_tool(obj, is_click){
switch($('.tools li.selected').index()){
case 0: //'paint':
paint(obj);
break;
case 1: // Fill
if( is_click ) fill(obj);
break;
case 2: // Erase
update_pixel(obj, tinycolor('#000000'));
break;
case 3: //'pick':
pick(obj);
break;
case 4: //'lighten':
lighten(obj);
break;
case 5: //'darken':
darken(obj);
break;
}
}
var fill_target = null;
var fill_stack = [];
function fill(obj){
fill_target = tinycolor($(obj).css('background-color')).toRgbString();
if( fill_target == color.toRgbString() ){
return false;
}
var x = $(obj).index();
var y = $(obj).parent().index();
socket.send("fill");
socket.send(new Uint8Array([x, y, color.toRgb().r, color.toRgb().g, color.toRgb().b]));
socket.send('show');
do_fill(obj);
while(fill_stack.length > 0){
var pixel = fill_stack.pop();
do_fill(pixel);
}
}
function is_target_color(obj){
return ( tinycolor($(obj).css('background-color')).toRgbString() == fill_target);
}
function do_fill(obj){
var obj = $(obj);
if( is_target_color(obj) ){
$(obj).css('background-color', color.toRgbString());
var r = obj.next('td'); // Right
var l = obj.prev('td'); // Left
var u = obj.parent().prev('tr').find('td:eq(' + obj.index() + ')'); // Above
var d = obj.parent().next('tr').find('td:eq(' + obj.index() + ')'); // Below
if( r.length && is_target_color(r[0]) ) fill_stack.push(r[0]);
if( l.length && is_target_color(l[0]) ) fill_stack.push(l[0]);
if( u.length && is_target_color(u[0]) ) fill_stack.push(u[0]);
if( d.length && is_target_color(d[0]) ) fill_stack.push(d[0]);
}
}
function save(){
var filename = prompt('Please enter a filename', 'mypaint');
filename = filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
socket.send('save');
socket.send(filename);
}
function clear(){
$('td').css('background-color','rgb(0,0,0)').data('changed',false);
socket.send('clear');
socket.send('show');
}
function lighten(obj){
var c = tinycolor($(obj).css('background-color'));
c.lighten(5);
update_pixel(obj, c);
}
function darken(obj){
var c = tinycolor($(obj).css('background-color'));
c.darken(5);
update_pixel(obj, c);
}
function pick(obj){
color = tinycolor($(obj).css('background-color'));
picker.val(color.toHexString());
}
function update_pixel(obj, col){
var bgcol = tinycolor($(obj).css('background-color'));
if(col != bgcol){
$(obj)
.data('changed', true)
.css('background-color', col.toRgbString());
}
}
function update_pixels(){
var changed = false;
$('td').each(function( index, obj ){
if($(obj).data('changed')){
$(obj).data('changed',false);
changed = true;
var x = $(this).index();
var y = $(this).parent().index();
var col = tinycolor($(obj).css('background-color')).toRgb();
if(socket) {
socket.send(new Uint8Array([x, y, col.r, col.g, col.b]));
}
}
});
if(changed){
socket.send('show');
}
}
function paint(obj){
update_pixel(obj, color);
}
$('table td').on('click', function(){
handle_tool(this, true);
});
$('table td').on('mousemove', function(){
if(!md) return false;
handle_tool(this, false);
})
const socket = new WebSocket('ws://' + window.location.host + '/paint');
socket.addEventListener('message', ev => {
console.log('<<< ' + ev.data);
if(ev.data.substring(0, 6) == "alert:") {
alert(ev.data.substring(6));
}
});
socket.addEventListener('close', ev => {
console.log('<<< closed');
});
socket.addEventListener('open', ev => {
clear();
update = setInterval(update_pixels, 50);
});
});

Wyświetl plik

@ -0,0 +1,142 @@
import time
import random
import math
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
blob_count = 10
class Blob():
def __init__(self):
self.x = float(random.randint(0, width - 1))
self.y = float(random.randint(0, height - 1))
self.r = (float(random.randint(0, 40)) / 10.0) + 5.0
self.dx = (float(random.randint(0, 2)) / 10.0) - 0.1
self.dy = (float(random.randint(0, 2)) / 10.0) - 0.05 # positive bias
@micropython.native # noqa: F821
def setup_portrait():
global width, height, liquid, blobs
width = GalacticUnicorn.HEIGHT
height = GalacticUnicorn.WIDTH
liquid = [[0.0 for y in range(height)] for x in range(width)]
blobs = [Blob() for i in range(blob_count)]
hue = 0.0
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return graphics.create_pen(int(v), int(t), int(p))
if i == 1:
return graphics.create_pen(int(q), int(v), int(p))
if i == 2:
return graphics.create_pen(int(p), int(v), int(t))
if i == 3:
return graphics.create_pen(int(p), int(q), int(v))
if i == 4:
return graphics.create_pen(int(t), int(p), int(v))
if i == 5:
return graphics.create_pen(int(v), int(p), int(q))
@micropython.native # noqa: F821
def update_liquid():
for y in range(height):
for x in range(width):
liquid[x][y] = 0.0
for blob in blobs:
r_sq = blob.r * blob.r
blob_y_range = range(max(math.floor(blob.y - blob.r), 0),
min(math.ceil(blob.y + blob.r), height))
blob_x_range = range(max(math.floor(blob.x - blob.r), 0),
min(math.ceil(blob.x + blob.r), width))
for y in blob_y_range:
for x in blob_x_range:
x_diff = x - blob.x
y_diff = y - blob.y
d_sq = x_diff * x_diff + y_diff * y_diff
if d_sq <= r_sq:
liquid[x][y] += 1.0 - (d_sq / r_sq)
@micropython.native # noqa: F821
def move_blobs():
for blob in blobs:
blob.x += blob.dx
blob.y += blob.dy
if blob.x < 0.0 or blob.x >= float(width):
blob.dx = 0.0 - blob.dx
if blob.y < 0.0 or blob.y >= float(height):
blob.dy = 0.0 - blob.dy
@micropython.native # noqa: F821
def draw_portrait():
global hue
hue += 0.001
dark = from_hsv(hue, 1.0, 0.3)
mid = from_hsv(hue, 1.0, 0.6)
bright = from_hsv(hue, 1.0, 1.0)
for y in range(height):
for x in range(width):
v = liquid[x][y]
# select a colour for this pixel based on how much
# "blobfluence" there is at this position in the liquid
if v >= 1.5:
graphics.set_pen(bright)
elif v >= 1.25:
graphics.set_pen(mid)
elif v >= 1.0:
graphics.set_pen(dark)
else:
graphics.set_pen(0)
graphics.pixel(y, x)
gu.update(graphics)
setup_portrait()
gu.set_brightness(0.5)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
setup_portrait()
start = time.ticks_ms()
update_liquid()
move_blobs()
draw_portrait()
print("total took: {} ms".format(time.ticks_ms() - start))

Wyświetl plik

@ -0,0 +1,111 @@
import time
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
c64 = [
" ",
" ",
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX ",
" OO OO OO OOOO OO OO OO OO XXXXXXX ",
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX ",
" OOOO OO OO OO OO OO OO XXXXXXX ",
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX ",
" XXXXXXX ",
" "
]
FOREGROUND_C64 = (230, 210, 250)
BACKGROUND_C64 = (20, 20, 120)
spectrum = [
" ",
" ",
" O OOOO OOOO OOOOO O O O O XXXXXXXX ",
" O O O O O O O O O O O X XXXXXX ",
" O O O O O O O X XXXXXX ",
" O O O OOOOOO O O X XXXXXX ",
" O O O O O O O X XXXXXX ",
" OOOOOO OOOO O O OOOOO X XXXXXX ",
" X X ",
" XXXXXXXX ",
" "
]
FOREGROUND_SPECTRUM = (0, 0, 0)
BACKGROUND_SPECTRUM = (180, 150, 150)
bbc_micro = [
" ",
" ",
" OOOOO OO OOOO OOO OOOO O ",
" O O O O O O O O O O ",
" O O O O O O O O ",
" OOOOO O O OOOO O O O ",
" O O OOOOOO O O O O ",
" O O O O O O O O O O ",
" OOOOO O O OOOO OOO OOOO O ",
" XXXXXXX ",
" "
]
FOREGROUND_BBC_MICRO = (255, 255, 255)
BACKGROUND_BBC_MICRO = (0, 0, 0)
PROMPT_C64 = 0
PROMPT_SPECTRUM = 1
PROMPT_BBC_MICRO = 2
prompt = 0
@micropython.native # noqa: F821
def draw(image, fg, bg, time_ms):
fg_pen = graphics.create_pen(fg[0], fg[1], fg[2])
bg_pen = graphics.create_pen(bg[0], bg[1], bg[2])
for y in range(len(image)):
row = image[y]
for x in range(len(row)):
pixel = row[x]
# draw the prompt text
if pixel == 'O':
graphics.set_pen(fg_pen)
# draw the caret blinking
elif pixel == 'X' and (time_ms // 300) % 2:
graphics.set_pen(fg_pen)
else:
graphics.set_pen(bg_pen)
graphics.pixel(x, y)
gu.update(graphics)
gu.set_brightness(0.5)
while True:
time_ms = time.ticks_ms()
prompt = (time_ms // 3000) % 3
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
start = time.ticks_ms()
if prompt == PROMPT_C64:
draw(c64, FOREGROUND_C64, BACKGROUND_C64, time_ms)
elif prompt == PROMPT_SPECTRUM:
draw(spectrum, FOREGROUND_SPECTRUM, BACKGROUND_SPECTRUM, time_ms)
elif prompt == PROMPT_BBC_MICRO:
draw(bbc_micro, FOREGROUND_BBC_MICRO, BACKGROUND_BBC_MICRO, time_ms)
print("total took: {} ms".format(time.ticks_ms() - start))

Wyświetl plik

@ -0,0 +1,106 @@
import time
import math
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
@micropython.native # noqa: F821
def draw():
global hue_offset, phase
phase_percent = phase / 15
for x in range(width):
colour = hue_map[int((x + (hue_offset * width)) % width)]
for y in range(height):
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
graphics.pixel(x, y)
gu.update(graphics)
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
hue_offset = 0.0
animate = True
stripe_width = 3.0
speed = 1.0
gu.set_brightness(0.5)
phase = 0
while True:
if animate:
phase += speed
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
hue_offset += 0.01
hue_offset = 1.0 if hue_offset > 1.0 else hue_offset
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
hue_offset -= 0.01
hue_offset = 0.0 if hue_offset < 0.0 else hue_offset
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
animate = False
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
speed += 0.05
speed = 10.0 if speed > 10.0 else speed
animate = True
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
speed -= 0.05
speed = 0.0 if speed < 0.0 else speed
animate = True
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
stripe_width += 0.05
stripe_width = 10.0 if stripe_width > 10.0 else stripe_width
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
stripe_width -= 0.05
stripe_width = 1.0 if stripe_width < 1.0 else stripe_width
start = time.ticks_ms()
draw()
print("total took: {} ms".format(time.ticks_ms() - start))

Wyświetl plik

@ -0,0 +1,130 @@
#include "galactic_unicorn.h"
/***** Methods *****/
MP_DEFINE_CONST_FUN_OBJ_1(Channel___del___obj, Channel___del__);
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_configure_obj, 1, Channel_configure);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_restore_obj, Channel_restore);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_waveforms_obj, 1, 2, Channel_waveforms);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_frequency_obj, 1, 2, Channel_frequency);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_volume_obj, 1, 2, Channel_volume);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_attack_duration_obj, 1, 2, Channel_attack_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_decay_duration_obj, 1, 2, Channel_decay_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_sustain_level_obj, 1, 2, Channel_sustain_level);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_release_duration_obj, 1, 2, Channel_release_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_pulse_width_obj, 1, 2, Channel_pulse_width);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_attack_obj, Channel_trigger_attack);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_release_obj, Channel_trigger_release);
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_play_tone_obj, 2, Channel_play_tone);
//MP_DEFINE_CONST_FUN_OBJ_1(Channel_stop_playing_obj, Channel_stop_playing);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn___del___obj, GalacticUnicorn___del__);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_clear_obj, GalacticUnicorn_clear);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_update_obj, GalacticUnicorn_update);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_set_brightness_obj, GalacticUnicorn_set_brightness);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_get_brightness_obj, GalacticUnicorn_get_brightness);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_adjust_brightness_obj, GalacticUnicorn_adjust_brightness);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_set_volume_obj, GalacticUnicorn_set_volume);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_get_volume_obj, GalacticUnicorn_get_volume);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_adjust_volume_obj, GalacticUnicorn_adjust_volume);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_light_obj, GalacticUnicorn_light);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_is_pressed_obj, GalacticUnicorn_is_pressed);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_play_sample_obj, GalacticUnicorn_play_sample);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_play_synth_obj, GalacticUnicorn_play_synth);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_stop_playing_obj, GalacticUnicorn_stop_playing);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_synth_channel_obj, GalacticUnicorn_synth_channel);
/***** Binding of Methods *****/
STATIC const mp_rom_map_elem_t Channel_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Channel___del___obj) },
{ MP_ROM_QSTR(MP_QSTR_configure), MP_ROM_PTR(&Channel_configure_obj) },
{ MP_ROM_QSTR(MP_QSTR_restore), MP_ROM_PTR(&Channel_restore_obj) },
{ MP_ROM_QSTR(MP_QSTR_waveforms), MP_ROM_PTR(&Channel_waveforms_obj) },
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&Channel_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_volume), MP_ROM_PTR(&Channel_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_attack_duration), MP_ROM_PTR(&Channel_attack_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_decay_duration), MP_ROM_PTR(&Channel_decay_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_sustain_level), MP_ROM_PTR(&Channel_sustain_level_obj) },
{ MP_ROM_QSTR(MP_QSTR_release_duration), MP_ROM_PTR(&Channel_release_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_pulse_width), MP_ROM_PTR(&Channel_pulse_width_obj) },
{ MP_ROM_QSTR(MP_QSTR_trigger_attack), MP_ROM_PTR(&Channel_trigger_attack_obj) },
{ MP_ROM_QSTR(MP_QSTR_trigger_release), MP_ROM_PTR(&Channel_trigger_release_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_tone), MP_ROM_PTR(&Channel_play_tone_obj) },
{ MP_ROM_QSTR(MP_QSTR_NOISE), MP_ROM_INT(128) },
{ MP_ROM_QSTR(MP_QSTR_SQUARE), MP_ROM_INT(64) },
{ MP_ROM_QSTR(MP_QSTR_SAW), MP_ROM_INT(32) },
{ MP_ROM_QSTR(MP_QSTR_TRIANGLE), MP_ROM_INT(16) },
{ MP_ROM_QSTR(MP_QSTR_SINE), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_WAVE), MP_ROM_INT(1) },
};
STATIC const mp_rom_map_elem_t GalacticUnicorn_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&GalacticUnicorn___del___obj) },
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&GalacticUnicorn_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&GalacticUnicorn_update_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&GalacticUnicorn_set_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_brightness), MP_ROM_PTR(&GalacticUnicorn_get_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_adjust_brightness), MP_ROM_PTR(&GalacticUnicorn_adjust_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&GalacticUnicorn_set_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_volume), MP_ROM_PTR(&GalacticUnicorn_get_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_adjust_volume), MP_ROM_PTR(&GalacticUnicorn_adjust_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_light), MP_ROM_PTR(&GalacticUnicorn_light_obj) },
{ MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&GalacticUnicorn_is_pressed_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_sample), MP_ROM_PTR(&GalacticUnicorn_play_sample_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_synth), MP_ROM_PTR(&GalacticUnicorn_play_synth_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop_playing), MP_ROM_PTR(&GalacticUnicorn_stop_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_synth_channel), MP_ROM_PTR(&GalacticUnicorn_synth_channel_obj) },
{ MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(53) },
{ MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(11) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_A), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_B), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_C), MP_ROM_INT(3) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_D), MP_ROM_INT(6) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_SLEEP), MP_ROM_INT(27) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_UP), MP_ROM_INT(7) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_DOWN), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_UP), MP_ROM_INT(21) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_DOWN), MP_ROM_INT(26) },
};
STATIC MP_DEFINE_CONST_DICT(Channel_locals_dict, Channel_locals_dict_table);
STATIC MP_DEFINE_CONST_DICT(GalacticUnicorn_locals_dict, GalacticUnicorn_locals_dict_table);
/***** Class Definition *****/
const mp_obj_type_t Channel_type = {
{ &mp_type_type },
.name = MP_QSTR_Channel,
.print = Channel_print,
.make_new = Channel_make_new,
.locals_dict = (mp_obj_dict_t*)&Channel_locals_dict,
};
const mp_obj_type_t GalacticUnicorn_type = {
{ &mp_type_type },
.name = MP_QSTR_GalacticUnicorn,
.print = GalacticUnicorn_print,
.make_new = GalacticUnicorn_make_new,
.locals_dict = (mp_obj_dict_t*)&GalacticUnicorn_locals_dict,
};
/***** Globals Table *****/
STATIC const mp_map_elem_t galactic_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_galactic) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_Channel), (mp_obj_t)&Channel_type },
{ MP_OBJ_NEW_QSTR(MP_QSTR_GalacticUnicorn), (mp_obj_t)&GalacticUnicorn_type },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_galactic_globals, galactic_globals_table);
/***** Module Definition *****/
const mp_obj_module_t galactic_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_galactic_globals,
};
#if MICROPY_VERSION <= 70144
MP_REGISTER_MODULE(MP_QSTR_galactic, galactic_user_cmodule, MODULE_GALACTIC_ENABLED);
#else
MP_REGISTER_MODULE(MP_QSTR_galactic, galactic_user_cmodule);
#endif

Wyświetl plik

@ -0,0 +1,516 @@
#include "libraries/galactic_unicorn/galactic_unicorn.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "micropython/modules/util.hpp"
#include <cstdio>
#include <cfloat>
using namespace pimoroni;
extern "C" {
#include "galactic_unicorn.h"
#include "micropython/modules/pimoroni_i2c/pimoroni_i2c.h"
#include "py/builtin.h"
/********** Channel **********/
/***** Variables Struct *****/
typedef struct _Channel_obj_t {
mp_obj_base_t base;
AudioChannel* channel;
} _Channel_obj_t;
/***** Print *****/
void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
//AudioChannel* channel = self->channel;
mp_print_str(print, "Channel(");
mp_print_str(print, ")");
}
/***** Constructor *****/
mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_raise_msg(&mp_type_RuntimeError, "Cannot create Channel objects. They can only be accessed from GalacticUnicorn.synth_channel()");
return mp_const_none;
}
/***** Destructor ******/
mp_obj_t Channel___del__(mp_obj_t self_in) {
return mp_const_none;
}
/***** Helper Functions *****/
void set_channel_waveforms(AudioChannel& channel, mp_obj_t in) {
int waveforms = mp_obj_get_int(in);
const int mask = (NOISE | SQUARE | SAW | TRIANGLE | SINE | WAVE);
if(waveforms < 0 || (waveforms & mask) == 0) {
mp_raise_ValueError("waveforms invalid. Expected a combination of NOISE, SQUARE, SAW, TRIANGLE, SINE, or WAVE");
}
channel.waveforms = (uint8_t)waveforms;
}
void set_channel_frequency(AudioChannel& channel, mp_obj_t in) {
int freq = mp_obj_get_int(in);
if(freq <= 0 || freq > UINT16_MAX) {
mp_raise_ValueError("frequency out of range. Expected greater than 0Hz to 65535Hz");
}
channel.frequency = (uint16_t)freq;
}
void set_channel_volume(AudioChannel& channel, mp_obj_t in) {
float volume = mp_obj_get_float(in);
if(volume < 0.0f || volume > 1.0f) {
mp_raise_ValueError("volume out of range. Expected 0.0 to 1.0");
}
channel.volume = (uint16_t)(volume * UINT16_MAX);
}
void set_channel_attack(AudioChannel& channel, mp_obj_t in) {
int attack_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(attack_ms < 0 || attack_ms > UINT16_MAX) {
mp_raise_ValueError("attack out of range. Expected 0.0s to 65.5s");
}
channel.attack_ms = MAX(attack_ms, 1);
}
void set_channel_decay(AudioChannel& channel, mp_obj_t in) {
int decay_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(decay_ms < 0 || decay_ms > UINT16_MAX) {
mp_raise_ValueError("decay out of range. Expected 0.0s to 65.5s");
}
channel.decay_ms = MAX(decay_ms, 1);
}
void set_channel_sustain(AudioChannel& channel, mp_obj_t in) {
float sustain = mp_obj_get_float(in);
if(sustain < 0.0f || sustain > 1.0f) {
mp_raise_ValueError("sustain out of range. Expected 0.0 to 1.0");
}
channel.sustain = (uint16_t)(sustain * UINT16_MAX);
}
void set_channel_release(AudioChannel& channel, mp_obj_t in) {
int release_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(release_ms < 0 || release_ms > UINT16_MAX) {
mp_raise_ValueError("release out of range. Expected 0.0s to 65.5s");
}
channel.release_ms = MAX(release_ms, 1);
}
void set_channel_pulse_width(AudioChannel& channel, mp_obj_t in) {
float pulse_width = mp_obj_get_float(in);
if(pulse_width < 0.0f || pulse_width > 1.0f) {
mp_raise_ValueError("pulse_width out of range. Expected 0.0 to 1.0");
}
channel.pulse_width = (uint16_t)(pulse_width * UINT16_MAX);
}
/***** Methods *****/
mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_waveforms, ARG_frequency, ARG_volume, ARG_attack, ARG_decay, ARG_sustain, ARG_release, ARG_pulse_width };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_waveforms, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_frequency, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_decay, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_sustain, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_pulse_width, MP_ARG_OBJ, {.u_obj = mp_const_none} }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
mp_obj_t waveforms = args[ARG_waveforms].u_obj;
if(waveforms != mp_const_none) {
set_channel_waveforms(*self->channel, waveforms);
}
mp_obj_t frequency = args[ARG_frequency].u_obj;
if(frequency != mp_const_none) {
set_channel_frequency(*self->channel, frequency);
}
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
mp_obj_t attack = args[ARG_attack].u_obj;
if(attack != mp_const_none) {
set_channel_attack(*self->channel, attack);
}
mp_obj_t decay = args[ARG_decay].u_obj;
if(decay != mp_const_none) {
set_channel_decay(*self->channel, decay);
}
mp_obj_t sustain = args[ARG_sustain].u_obj;
if(sustain != mp_const_none) {
set_channel_sustain(*self->channel, sustain);
}
mp_obj_t release = args[ARG_release].u_obj;
if(release != mp_const_none) {
set_channel_release(*self->channel, release);
}
mp_obj_t pulse_width = args[ARG_pulse_width].u_obj;
if(pulse_width != mp_const_none) {
set_channel_pulse_width(*self->channel, pulse_width);
}
return mp_const_none;
}
mp_obj_t Channel_restore(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->restore();
return mp_const_none;
}
mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->waveforms);
}
set_channel_waveforms(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->frequency);
}
set_channel_frequency(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->volume / UINT16_MAX);
}
set_channel_volume(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->attack_ms / 1000.0f);
}
set_channel_attack(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->decay_ms / 1000.0f);
}
set_channel_decay(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->sustain / UINT16_MAX);
}
set_channel_sustain(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->release_ms / 1000.0f);
}
set_channel_release(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->pulse_width / 0xffff);
}
set_channel_pulse_width(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_trigger_attack(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_attack();
return mp_const_none;
}
mp_obj_t Channel_trigger_release(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_release();
return mp_const_none;
}
mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_freq, ARG_volume, ARG_fade_in, ARG_fade_out };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
set_channel_frequency(*self->channel, args[ARG_freq].u_obj);
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
else {
self->channel->volume = UINT16_MAX;
}
mp_obj_t attack_ms = args[ARG_fade_in].u_obj;
if(attack_ms != mp_const_none) {
set_channel_attack(*self->channel, attack_ms);
}
else {
self->channel->attack_ms = 1;
}
mp_obj_t release_ms = args[ARG_fade_out].u_obj;
if(release_ms != mp_const_none) {
set_channel_release(*self->channel, release_ms);
}
else {
self->channel->release_ms = 1;
}
self->channel->waveforms = Waveform::SINE;
self->channel->decay_ms = 1;
self->channel->sustain = UINT16_MAX;
self->channel->trigger_attack();
return mp_const_none;
}
/********** GalacticUnicorn **********/
/***** Variables Struct *****/
typedef struct _GalacticUnicorn_obj_t {
mp_obj_base_t base;
GalacticUnicorn* galactic;
} _GalacticUnicorn_obj_t;
typedef struct _ModPicoGraphics_obj_t {
mp_obj_base_t base;
PicoGraphics *graphics;
DisplayDriver *display;
void *spritedata;
void *buffer;
_PimoroniI2C_obj_t *i2c;
//mp_obj_t scanline_callback; // Not really feasible in MicroPython
} ModPicoGraphics_obj_t;
/***** Print *****/
void GalacticUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
mp_print_str(print, "GalacticUnicorn()");
}
/***** Constructor *****/
mp_obj_t GalacticUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
_GalacticUnicorn_obj_t *self = nullptr;
enum { ARG_pio, ARG_sm, ARG_pins, ARG_common_pin, ARG_direction, ARG_counts_per_rev, ARG_count_microsteps, ARG_freq_divider };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_pio, MP_ARG_INT },
{ MP_QSTR_sm, MP_ARG_INT }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
int pio_int = args[ARG_pio].u_int;
if(pio_int < 0 || pio_int > (int)NUM_PIOS) {
mp_raise_ValueError("pio out of range. Expected 0 to 1");
}
//PIO pio = pio_int == 0 ? pio0 : pio1;
int sm = args[ARG_sm].u_int;
if(sm < 0 || sm > (int)NUM_PIO_STATE_MACHINES) {
mp_raise_ValueError("sm out of range. Expected 0 to 3");
}
GalacticUnicorn *galactic = m_new_class(GalacticUnicorn);
galactic->init();
self = m_new_obj_with_finaliser(_GalacticUnicorn_obj_t);
self->base.type = &GalacticUnicorn_type;
self->galactic = galactic;
return MP_OBJ_FROM_PTR(self);
}
/***** Destructor ******/
mp_obj_t GalacticUnicorn___del__(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
m_del_class(GalacticUnicorn, self->galactic);
return mp_const_none;
}
/***** Methods *****/
extern mp_obj_t GalacticUnicorn_clear(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->clear();
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
self->galactic->update(picographics->graphics);
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->set_brightness(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_get_brightness(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
return mp_obj_new_float(self->galactic->get_brightness());
}
extern mp_obj_t GalacticUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->adjust_brightness(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->set_volume(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_get_volume(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
return mp_obj_new_float(self->galactic->get_volume());
}
extern mp_obj_t GalacticUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->adjust_volume(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_light(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
return mp_obj_new_float(self->galactic->light());
}
extern mp_obj_t GalacticUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
return mp_obj_new_bool(self->galactic->is_pressed((uint8_t)mp_obj_get_int(button)));
}
extern mp_obj_t GalacticUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_RW);
if(bufinfo.len < 1) {
mp_raise_ValueError("Supplied buffer is too small!");
}
self->galactic->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_play_synth(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->play_synth();
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_stop_playing(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->stop_playing();
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
// Check that the channel is valid
int channel = mp_obj_get_int(channel_in);
if(channel < 0 || channel >= (int)PicoSynth::CHANNEL_COUNT) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("channel out of range. Expected 0 to %d"), PicoSynth::CHANNEL_COUNT - 1);
}
// NOTE This seems to work, in that it give MP access to the calibration object
// Could very easily mess up in weird ways once object deletion is considered?
_Channel_obj_t *channel_obj = m_new_obj_with_finaliser(_Channel_obj_t);
channel_obj->base.type = &Channel_type;
channel_obj->channel = &self->galactic->synth_channel(channel);
return MP_OBJ_FROM_PTR(channel_obj);
}
}

Wyświetl plik

@ -0,0 +1,50 @@
// Include MicroPython API.
#include "py/runtime.h"
/***** Extern of Class Definition *****/
extern const mp_obj_type_t Channel_type;
extern const mp_obj_type_t GalacticUnicorn_type;
/***** Extern of Class Methods *****/
extern void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
extern mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t Channel___del__(mp_obj_t self_in);
extern mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t Channel_restore(mp_obj_t self_in);
extern mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_trigger_attack(mp_obj_t self_in);
extern mp_obj_t Channel_trigger_release(mp_obj_t self_in);
extern mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t Channel_stop_playing(mp_obj_t self_in);
extern void GalacticUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
extern mp_obj_t GalacticUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t GalacticUnicorn___del__(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_clear(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in);
extern mp_obj_t GalacticUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value);
extern mp_obj_t GalacticUnicorn_get_brightness(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta);
extern mp_obj_t GalacticUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value);
extern mp_obj_t GalacticUnicorn_get_volume(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta);
extern mp_obj_t GalacticUnicorn_light(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button);
extern mp_obj_t GalacticUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data);
extern mp_obj_t GalacticUnicorn_play_synth(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_stop_playing(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in);

Wyświetl plik

@ -0,0 +1,24 @@
set(MOD_NAME galactic_unicorn)
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
add_library(usermod_${MOD_NAME} INTERFACE)
target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/galactic_unicorn/galactic_unicorn.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_synth/pico_synth.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb888.cpp
)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/galactic_unicorn/galactic_unicorn.pio)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/galactic_unicorn/audio_i2s.pio)
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/
)
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
MODULE_GALACTIC_ENABLED=1
)
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})

Wyświetl plik

@ -44,6 +44,8 @@ include(pcf85063a/micropython)
include(picographics/micropython)
include(jpegdec/micropython)
include(galactic_unicorn/micropython)
include(modules_py/modules_py)

Wyświetl plik

@ -0,0 +1,52 @@
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
# Essential
include(pimoroni_i2c/micropython)
include(pimoroni_bus/micropython)
# Pico Graphics Essential
include(hershey_fonts/micropython)
include(bitmap_fonts/micropython)
include(picographics/micropython)
# Pico Graphics Extra
include(jpegdec/micropython)
include(qrcode/micropython/micropython)
# Sensors & Breakouts
include(breakout_dotmatrix/micropython)
include(breakout_encoder/micropython)
include(breakout_ioexpander/micropython)
include(breakout_ltr559/micropython)
include(breakout_as7262/micropython)
include(breakout_rgbmatrix5x5/micropython)
include(breakout_matrix11x7/micropython)
include(breakout_msa301/micropython)
include(breakout_pmw3901/micropython)
include(breakout_mics6814/micropython)
include(breakout_potentiometer/micropython)
include(breakout_rtc/micropython)
include(breakout_trackball/micropython)
include(breakout_sgp30/micropython)
include(breakout_bh1745/micropython)
include(breakout_bme68x/micropython)
include(breakout_bme280/micropython)
include(breakout_bmp280/micropython)
include(breakout_icp10125/micropython)
include(breakout_scd41/micropython)
include(breakout_vl53l5cx/micropython)
include(pcf85063a/micropython)
# Utility
include(adcfft/micropython)
# LEDs & Matrices
include(galactic_unicorn/micropython)
# include(micropython-common)
include(modules_py/modules_py)

Wyświetl plik

@ -18,6 +18,7 @@ target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_p8.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb332.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb565.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb888.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/types.cpp
)

Wyświetl plik

@ -125,12 +125,14 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_PACK), MP_ROM_INT(DISPLAY_INKY_PACK) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME), MP_ROM_INT(DISPLAY_INKY_FRAME) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_4), MP_ROM_INT(DISPLAY_INKY_FRAME_4) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_GALACTIC_UNICORN), MP_ROM_INT(DISPLAY_GALACTIC_UNICORN) },
{ MP_ROM_QSTR(MP_QSTR_PEN_1BIT), MP_ROM_INT(PEN_1BIT) },
{ MP_ROM_QSTR(MP_QSTR_PEN_P4), MP_ROM_INT(PEN_P4) },
{ MP_ROM_QSTR(MP_QSTR_PEN_P8), MP_ROM_INT(PEN_P8) },
{ MP_ROM_QSTR(MP_QSTR_PEN_RGB332), MP_ROM_INT(PEN_RGB332) },
{ MP_ROM_QSTR(MP_QSTR_PEN_RGB565), MP_ROM_INT(PEN_RGB565) },
{ MP_ROM_QSTR(MP_QSTR_PEN_RGB888), MP_ROM_INT(PEN_RGB888) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_picographics_globals, picographics_globals_table);

Wyświetl plik

@ -112,6 +112,14 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height,
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_P4;
break;
case DISPLAY_GALACTIC_UNICORN:
width = 53;
height = 11;
bus_type = BUS_PIO;
// Portrait to match labelling
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_RGB888;
break;
default:
return false;
}
@ -132,6 +140,8 @@ size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint h
return PicoGraphics_PenRGB332::buffer_size(width, height);
case PEN_RGB565:
return PicoGraphics_PenRGB565::buffer_size(width, height);
case PEN_RGB888:
return PicoGraphics_PenRGB888::buffer_size(width, height);
default:
return 0;
}
@ -223,7 +233,10 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
} else if (display == DISPLAY_INKY_PACK) {
self->display = m_new_class(UC8151, width, height, (Rotation)rotate, spi_bus);
} else {
} else if (display == DISPLAY_GALACTIC_UNICORN) {
self->display = m_new_class(DisplayDriver, width, height, (Rotation)rotate);
}
else {
self->display = m_new_class(ST7789, width, height, (Rotation)rotate, round, spi_bus);
}
@ -267,6 +280,9 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
case PEN_RGB565:
self->graphics = m_new_class(PicoGraphics_PenRGB565, self->display->width, self->display->height, self->buffer);
break;
case PEN_RGB888:
self->graphics = m_new_class(PicoGraphics_PenRGB888, self->display->width, self->display->height, self->buffer);
break;
default:
break;
}

Wyświetl plik

@ -13,7 +13,8 @@ enum PicoGraphicsDisplay {
DISPLAY_I2C_OLED_128X128,
DISPLAY_INKY_PACK,
DISPLAY_INKY_FRAME,
DISPLAY_INKY_FRAME_4
DISPLAY_INKY_FRAME_4,
DISPLAY_GALACTIC_UNICORN
};
enum PicoGraphicsPenType {
@ -23,13 +24,15 @@ enum PicoGraphicsPenType {
PEN_P4,
PEN_P8,
PEN_RGB332,
PEN_RGB565
PEN_RGB565,
PEN_RGB888
};
enum PicoGraphicsBusType {
BUS_I2C,
BUS_SPI,
BUS_PARALLEL
BUS_PARALLEL,
BUS_PIO
};
// Type