Cosmic Unicorn: MicroPython bindings.
started on MP driver removed duplacte audio_i2s.pio disabled GU lib options bug fixes bringing Picographics into line Update picographics.cpp fixing naming Cosmic to cosmic fixed H and W
This commit is contained in:
parent
c3672d7e3d
commit
9bc616690e
|
@ -58,3 +58,4 @@ add_subdirectory(inventor2040w)
|
|||
add_subdirectory(encoder)
|
||||
add_subdirectory(galactic_unicorn)
|
||||
add_subdirectory(gfx_pack)
|
||||
add_subdirectory(cosmic_unicorn)
|
||||
|
|
|
@ -39,3 +39,4 @@ add_subdirectory(inky_frame)
|
|||
add_subdirectory(galactic_unicorn)
|
||||
add_subdirectory(gfx_pack)
|
||||
add_subdirectory(interstate75)
|
||||
add_subdirectory(cosmic_unicorn)
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
# Galactic Unicorn MicroPython Examples <!-- omit in toc -->
|
||||
|
||||
- [About Galactic Unicorn](#about-galactic-unicorn)
|
||||
- [Galactic Unicorn and PicoGraphics](#galactic-unicorn-and-picographics)
|
||||
- [Examples](#examples)
|
||||
- [Clock](#clock)
|
||||
- [Eighties Super Computer](#eighties-super-computer)
|
||||
- [Feature Test](#feature-test)
|
||||
- [Feature Test With Audio](#feature-test-with-audio)
|
||||
- [Fire Effect](#fire-effect)
|
||||
- [Lava Lamp](#lava-lamp)
|
||||
- [Nostalgia Prompt](#nostalgia-prompt)
|
||||
- [Rainbow](#rainbow)
|
||||
- [Scrolling Text](#scrolling-text)
|
||||
- [Wireless Examples](#wireless-examples)
|
||||
- [Cheerlights History](#cheerlights-history)
|
||||
- [Galactic Paint](#galactic-paint)
|
||||
- [Other Examples](#other-examples)
|
||||
- [Launch (Demo Reel)](#launch-demo-reel)
|
||||
- [Other Resources](#other-resources)
|
||||
|
||||
## About Galactic Unicorn
|
||||
|
||||
Galactic Unicorn offers 53x11 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
|
||||
|
||||
- :link: [Galactic Unicorn store page](https://shop.pimoroni.com/products/galactic-unicorn)
|
||||
|
||||
Galactic Unicorn ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `galactic-unicorn` image).
|
||||
|
||||
- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases)
|
||||
- [Installing MicroPython](../../../setting-up-micropython.md)
|
||||
|
||||
## Galactic Unicorn and PicoGraphics
|
||||
|
||||
The easiest way to start displaying cool stuff on Galactic Unicorn is using our Galactic Unicorn module (which contains a bunch of helpful functions for interacting with the buttons, adjusting brightness and suchlike) and our PicoGraphics library, which is chock full of useful functions for drawing on the LED matrix.
|
||||
|
||||
- [Galactic Unicorn function reference](../../modules/galactic_unicorn/README.md)
|
||||
- [PicoGraphics function reference](../../modules/picographics/README.md)
|
||||
|
||||
## Examples
|
||||
|
||||
### Clock
|
||||
|
||||
[clock.py](clock.py)
|
||||
|
||||
Clock example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the time by pressing A.
|
||||
|
||||
### Eighties Super Computer
|
||||
|
||||
[eighties_super_computer.py](eighties_super_computer.py)
|
||||
|
||||
Random LEDs blink on and off mimicing the look of a movie super computer doing its work in the eighties. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Feature Test
|
||||
|
||||
[feature_test.py](feature_test.py)
|
||||
|
||||
Displays some text, gradients and colours and demonstrates button use. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Feature Test With Audio
|
||||
|
||||
[feature_test_with_audio.py](feature_test_with_audio.py)
|
||||
|
||||
Displays some text, gradients and colours and demonstrates button use. Also demonstrates some of the audio / synth features.
|
||||
- Button A plays a synth tune
|
||||
- Button B plays a solo channel of the synth tune
|
||||
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
|
||||
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
|
||||
- Sleep button stops the sounds
|
||||
|
||||
### Fire Effect
|
||||
|
||||
[fire_effect.py](fire_effect.py)
|
||||
|
||||
A pretty, procedural fire effect. Switch between landscape fire and vertical fire using the A and B buttons! You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Lava Lamp
|
||||
|
||||
[lava_lamp.py](lava_lamp.py)
|
||||
|
||||
A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Nostalgia Prompt
|
||||
|
||||
[nostalgia_prompt.py](nostalgia_prompt.py)
|
||||
|
||||
A collection of copies of classic terminal styles including C64, MS-DOS, Spectrum, and more. Images and text are drawn pixel by pixel from a pattern of Os and Xs. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Rainbow
|
||||
|
||||
[rainbow.py](rainbow.py)
|
||||
|
||||
Some good old fashioned rainbows! You can adjust the cycling speed with A and B, stripe width with C and D, hue with VOL + and -, and the brightness with LUX + and -. The sleep button stops the animation (can be started again with A or B).
|
||||
|
||||
### Scrolling Text
|
||||
|
||||
[scrolling_text.py](scrolling_text.py)
|
||||
|
||||
Display scrolling wisdom, quotes or greetz. You can adjust the brightness with LUX + and -.
|
||||
|
||||
## Wireless Examples
|
||||
|
||||
These examples 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).
|
||||
|
||||
- [micropython/examples/common](../../examples/common)
|
||||
|
||||
### Cheerlights History
|
||||
|
||||
[cheerlights_history.py](cheerlights_history.py)
|
||||
|
||||
Updates one pixel every five minutes to display the most recent #Cheerlights colour. Discover the most popular colours over time, or use it as an avant garde (but colourful) 53 hour clock! Find out more about the Cheerlights API at https://cheerlights.com/
|
||||
|
||||
Requires `WIFI_CONFIG.py` and `network_manager.py` from the `common` directory.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Galactic Paint
|
||||
|
||||
[galactic_paint](galactic_paint)
|
||||
|
||||
Draw on your Galactic Unicorn from another device in real time, over wifi!
|
||||
|
||||
Requires `WIFI_CONFIG.py` from the `common` directory. It also needs the `micropython-phew` and `microdot` libraries (you can install these using Thonny's 'Tools > Manage Packages').
|
||||
|
||||
## Other Examples
|
||||
|
||||
### Launch (Demo Reel)
|
||||
|
||||
[launch](launch)
|
||||
|
||||
If you want to get the demo reel that Galactic Unicorn ships with back, copy the contents of this `launch` folder to your Pico W.
|
||||
|
||||
## Other Resources
|
||||
|
||||
Here are some cool Galactic Unicorn community projects and resources that you might find useful / inspirational! Note that code at the links below has not been tested by us and we're not able to offer support with it.
|
||||
|
||||
- :link: [Galactic Unicorn MQTT scroller (and 3D printed case)](https://github.com/ucl-casa-ce/Galactic-Unicorn-MQTT-Scroller)
|
||||
- :link: [Compiling custom pimoroni-pico MicroPython (with ulab)](https://medium.com/@iestynlloyd/galactic-unicorns-and-custom-pimoroni-pico-firmware-38dd7c5913b8)
|
||||
- :link: [Galactic Unicorn Graphical Workout](https://www.instructables.com/Galactic-Unicorn-Graphical-Workout/)
|
||||
- :link: [Galactic Unicorn Bounce - Simple GFX Demo](https://www.instructables.com/Galactic-Unicorn-Bounce-Simple-GFX-Demo/)
|
||||
- :link: [Cheerlights + Galactic Unicorn + MicroPython (beginner-friendly tutorial)](https://cheerlights.com/cheerlights-raspberry-pi-pico-w-micropython/)
|
||||
- :link: [CheerClock (plus laser-cut templates for a fancy case/diffuser)](https://github.com/seanosteen/CheerClock)
|
|
@ -0,0 +1,129 @@
|
|||
# This Galactic Unicorn example updates a pixel every five(ish) minutes
|
||||
# to display the most recent #cheerlights colour. Discover the most popular
|
||||
# colours over time, or use it as an avant garde (but colourful) 53 hour clock!
|
||||
# Find out more about the Cheerlights API at https://cheerlights.com/
|
||||
#
|
||||
# To run this example you'll need WIFI_CONFIG.py and network_manager.py from
|
||||
# the pimoroni-pico micropython/examples/common folder
|
||||
|
||||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio
|
||||
import urequests
|
||||
import time
|
||||
from machine import Timer, Pin
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
URL = 'http://api.thingspeak.com/channels/1417/field/2/last.json'
|
||||
|
||||
UPDATE_INTERVAL = 327 # refresh interval in secs. Be nice to free APIs!
|
||||
# this esoteric number is used so that a column of LEDs equates (approximately) to an hour
|
||||
|
||||
|
||||
def status_handler(mode, status, ip):
|
||||
# reports wifi connection status
|
||||
print(mode, status, ip)
|
||||
print('Connecting to wifi...')
|
||||
if status is not None:
|
||||
if status:
|
||||
print('Wifi connection successful!')
|
||||
else:
|
||||
print('Wifi connection failed!')
|
||||
|
||||
|
||||
def hex_to_rgb(hex):
|
||||
# converts a hex colour code into RGB
|
||||
h = hex.lstrip('#')
|
||||
r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
|
||||
return r, g, b
|
||||
|
||||
|
||||
def get_data():
|
||||
# open the json file
|
||||
print(f'Requesting URL: {URL}')
|
||||
r = urequests.get(URL)
|
||||
# open the json data
|
||||
j = r.json()
|
||||
print('Data obtained!')
|
||||
r.close()
|
||||
|
||||
# flash the onboard LED after getting data
|
||||
pico_led.value(True)
|
||||
time.sleep(0.2)
|
||||
pico_led.value(False)
|
||||
|
||||
# extract hex colour from the json data
|
||||
hex = j['field2']
|
||||
|
||||
# add the new hex colour to the end of the array
|
||||
colour_array.append(hex)
|
||||
print(f'Colour added to array: {hex}')
|
||||
# remove the oldest colour in the array
|
||||
colour_array.pop(0)
|
||||
update_leds()
|
||||
|
||||
|
||||
def update_leds():
|
||||
# light up the LEDs
|
||||
# this step takes a second, it's doing a lot of hex_to_rgb calculations!
|
||||
print("Updating LEDs...")
|
||||
i = 0
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
r = hex_to_rgb(colour_array[i])[0]
|
||||
g = hex_to_rgb(colour_array[i])[1]
|
||||
b = hex_to_rgb(colour_array[i])[2]
|
||||
current_colour = graphics.create_pen(r, g, b)
|
||||
graphics.set_pen(current_colour)
|
||||
graphics.pixel(x, y)
|
||||
i = i + 1
|
||||
gu.update(graphics)
|
||||
print("LEDs updated!")
|
||||
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.HEIGHT
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
# set up the Pico W's onboard LED
|
||||
pico_led = Pin('LED', Pin.OUT)
|
||||
|
||||
current_colour = graphics.create_pen(0, 0, 0)
|
||||
|
||||
# set up an list to store the colours
|
||||
colour_array = ["#000000"] * 583
|
||||
|
||||
# set up wifi
|
||||
try:
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
|
||||
except Exception as e:
|
||||
print(f'Wifi connection failed! {e}')
|
||||
|
||||
# get the first lot of data
|
||||
get_data()
|
||||
|
||||
# start timer (the timer will call the function to update our data every UPDATE_INTERVAL)
|
||||
timer = Timer(-1)
|
||||
timer.init(period=UPDATE_INTERVAL * 1000, mode=Timer.PERIODIC, callback=lambda t: get_data())
|
||||
|
||||
while True:
|
||||
# adjust brightness with LUX + and -
|
||||
# LEDs take a couple of secs to update, so adjust in big (10%) steps
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.1)
|
||||
update_leds()
|
||||
print(f"Brightness set to {gu.get_brightness()}")
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.adjust_brightness(-0.1)
|
||||
update_leds()
|
||||
print(f"Brightness set to {gu.get_brightness()}")
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,230 @@
|
|||
# Clock example with NTP synchronization
|
||||
#
|
||||
# Create a secrets.py with your Wifi details to be able to get the time
|
||||
# when the Galactic Unicorn isn't connected to Thonny.
|
||||
#
|
||||
# secrets.py should contain:
|
||||
# WIFI_SSID = "Your WiFi SSID"
|
||||
# WIFI_PASSWORD = "Your WiFi password"
|
||||
#
|
||||
# Clock synchronizes time on start, and resynchronizes if you press the A button
|
||||
|
||||
import time
|
||||
import math
|
||||
import machine
|
||||
import network
|
||||
import ntptime
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
try:
|
||||
from secrets import WIFI_SSID, WIFI_PASSWORD
|
||||
wifi_available = True
|
||||
except ImportError:
|
||||
print("Create secrets.py with your WiFi credentials to get time from NTP")
|
||||
wifi_available = False
|
||||
|
||||
|
||||
# constants for controlling the background colour throughout the day
|
||||
MIDDAY_HUE = 1.1
|
||||
MIDNIGHT_HUE = 0.8
|
||||
HUE_OFFSET = -0.1
|
||||
|
||||
MIDDAY_SATURATION = 1.0
|
||||
MIDNIGHT_SATURATION = 1.0
|
||||
|
||||
MIDDAY_VALUE = 0.8
|
||||
MIDNIGHT_VALUE = 0.3
|
||||
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
# create the rtc object
|
||||
rtc = machine.RTC()
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.HEIGHT
|
||||
|
||||
# set up some pens to use later
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
BLACK = graphics.create_pen(0, 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 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)
|
||||
|
||||
|
||||
# function for drawing a gradient background
|
||||
def gradient_background(start_hue, start_sat, start_val, end_hue, end_sat, end_val):
|
||||
half_width = width // 2
|
||||
for x in range(0, half_width):
|
||||
hue = ((end_hue - start_hue) * (x / half_width)) + start_hue
|
||||
sat = ((end_sat - start_sat) * (x / half_width)) + start_sat
|
||||
val = ((end_val - start_val) * (x / half_width)) + start_val
|
||||
colour = from_hsv(hue, sat, val)
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
|
||||
for y in range(0, height):
|
||||
graphics.pixel(x, y)
|
||||
graphics.pixel(width - x - 1, y)
|
||||
|
||||
colour = from_hsv(end_hue, end_sat, end_val)
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
|
||||
for y in range(0, height):
|
||||
graphics.pixel(half_width, y)
|
||||
|
||||
|
||||
# function for drawing outlined text
|
||||
def outline_text(text, x, y):
|
||||
graphics.set_pen(BLACK)
|
||||
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(WHITE)
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
# Connect to wifi and synchronize the RTC time from NTP
|
||||
def sync_time():
|
||||
if not wifi_available:
|
||||
return
|
||||
|
||||
# Start connection
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
|
||||
|
||||
# Wait for connect success or failure
|
||||
max_wait = 100
|
||||
while max_wait > 0:
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
break
|
||||
max_wait -= 1
|
||||
print('waiting for connection...')
|
||||
time.sleep(0.2)
|
||||
|
||||
redraw_display_if_reqd()
|
||||
gu.update(graphics)
|
||||
|
||||
if max_wait > 0:
|
||||
print("Connected")
|
||||
|
||||
try:
|
||||
ntptime.settime()
|
||||
print("Time set")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
wlan.disconnect()
|
||||
wlan.active(False)
|
||||
|
||||
|
||||
# NTP synchronizes the time to UTC, this allows you to adjust the displayed time
|
||||
# by one hour increments from UTC by pressing the volume up/down buttons
|
||||
#
|
||||
# We use the IRQ method to detect the button presses to avoid incrementing/decrementing
|
||||
# multiple times when the button is held.
|
||||
utc_offset = 0
|
||||
|
||||
up_button = machine.Pin(GalacticUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
down_button = machine.Pin(GalacticUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
|
||||
|
||||
def adjust_utc_offset(pin):
|
||||
global utc_offset
|
||||
if pin == up_button:
|
||||
utc_offset += 1
|
||||
if pin == down_button:
|
||||
utc_offset -= 1
|
||||
|
||||
|
||||
up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
|
||||
down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
|
||||
|
||||
|
||||
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||
|
||||
last_second = second
|
||||
|
||||
|
||||
# Check whether the RTC time has changed and if so redraw the display
|
||||
def redraw_display_if_reqd():
|
||||
global year, month, day, wd, hour, minute, second, last_second
|
||||
|
||||
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||
if second != last_second:
|
||||
hour += utc_offset
|
||||
time_through_day = (((hour * 60) + minute) * 60) + second
|
||||
percent_through_day = time_through_day / 86400
|
||||
percent_to_midday = 1.0 - ((math.cos(percent_through_day * math.pi * 2) + 1) / 2)
|
||||
print(percent_to_midday)
|
||||
|
||||
hue = ((MIDDAY_HUE - MIDNIGHT_HUE) * percent_to_midday) + MIDNIGHT_HUE
|
||||
sat = ((MIDDAY_SATURATION - MIDNIGHT_SATURATION) * percent_to_midday) + MIDNIGHT_SATURATION
|
||||
val = ((MIDDAY_VALUE - MIDNIGHT_VALUE) * percent_to_midday) + MIDNIGHT_VALUE
|
||||
|
||||
gradient_background(hue, sat, val,
|
||||
hue + HUE_OFFSET, sat, val)
|
||||
|
||||
clock = "{:02}:{:02}:{:02}".format(hour, minute, second)
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# calculate text position so that it is centred
|
||||
w = graphics.measure_text(clock, 1)
|
||||
x = int(width / 2 - w / 2 + 1)
|
||||
y = 2
|
||||
|
||||
outline_text(clock, x, y)
|
||||
|
||||
last_second = second
|
||||
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
sync_time()
|
||||
|
||||
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):
|
||||
sync_time()
|
||||
|
||||
redraw_display_if_reqd()
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
||||
time.sleep(0.01)
|
|
@ -0,0 +1,79 @@
|
|||
import time
|
||||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Random LEDs blink on and off mimicing the look of a movie
|
||||
super computer doing its work in the eighties.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
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()
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,123 @@
|
|||
import time
|
||||
import math
|
||||
from cosmic import CosmicUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Displays some text, gradients and colours and demonstrates button use.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
gu = CosmicUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = 32#CosmicUnicorn.WIDTH
|
||||
height = 32#CosmicUnicorn.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) / 32), int((g * x) / 32), int((b * x) / 32)))
|
||||
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(32 / 2 - w / 2 + 1)
|
||||
y = 12
|
||||
|
||||
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(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.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(CosmicUnicorn.SWITCH_A):
|
||||
text = "Button A"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_B):
|
||||
text = "Button B"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_C):
|
||||
text = "Button C"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_D):
|
||||
text = "Button D"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
|
||||
text = "Louder!"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
|
||||
text = "Quieter"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
text = "Brighter!"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
text = "Darker"
|
||||
|
||||
if gu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
|
||||
text = "Zzz... zzz..."
|
||||
|
||||
outline_text(text)
|
||||
|
||||
gu.update(graphics)
|
|
@ -0,0 +1,359 @@
|
|||
import gc
|
||||
import time
|
||||
import math
|
||||
from machine import Timer
|
||||
from galactic import GalacticUnicorn, Channel
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Displays some text, gradients and colours and demonstrates button use.
|
||||
Also demonstrates some of the audio / synth features.
|
||||
|
||||
- Button A plays a synth tune
|
||||
- Button B plays a solo channel of the synth tune
|
||||
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
|
||||
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
|
||||
- Sleep button stops the sounds
|
||||
'''
|
||||
|
||||
gc.collect()
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.HEIGHT
|
||||
|
||||
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]
|
||||
channels = [gu.synth_channel(i) for i in range(len(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)
|
||||
|
||||
# Vars for storing button state
|
||||
was_a_pressed = False
|
||||
was_b_pressed = False
|
||||
was_c_pressed = False
|
||||
was_d_pressed = False
|
||||
was_z_pressed = False
|
||||
|
||||
# The two frequencies to play
|
||||
tone_a = 0
|
||||
tone_b = 0
|
||||
|
||||
# The current synth beat
|
||||
beat = 0
|
||||
|
||||
|
||||
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:
|
||||
# Configure the synth to play our notes
|
||||
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
|
||||
attack=0.016,
|
||||
decay=0.168,
|
||||
sustain=0xafff / 65535,
|
||||
release=0.168,
|
||||
volume=10000 / 65535)
|
||||
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
|
||||
attack=0.038,
|
||||
decay=0.300,
|
||||
sustain=0,
|
||||
release=0,
|
||||
volume=12000 / 65535)
|
||||
channels[2].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.010,
|
||||
sustain=16000 / 65535,
|
||||
release=0.100,
|
||||
volume=18000 / 65535)
|
||||
channels[3].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.005,
|
||||
sustain=8000 / 65535,
|
||||
release=0.040,
|
||||
volume=8000 / 65535)
|
||||
channels[4].configure(waveforms=Channel.SQUARE,
|
||||
attack=0.010,
|
||||
decay=0.100,
|
||||
sustain=0,
|
||||
release=0.500,
|
||||
volume=12000 / 65535)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
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:
|
||||
# Configure the synth to play our notes, but with only one channel audable
|
||||
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
|
||||
attack=0.016,
|
||||
decay=0.168,
|
||||
sustain=0,
|
||||
release=0.168,
|
||||
volume=0)
|
||||
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
|
||||
attack=0.038,
|
||||
decay=0.300,
|
||||
sustain=0,
|
||||
release=0,
|
||||
volume=12000 / 65535)
|
||||
channels[2].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.010,
|
||||
sustain=16000 / 65535,
|
||||
release=0.100,
|
||||
volume=0)
|
||||
channels[3].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.005,
|
||||
sustain=8000 / 65535,
|
||||
release=0.040,
|
||||
volume=0)
|
||||
channels[4].configure(waveforms=Channel.SQUARE,
|
||||
attack=0.010,
|
||||
decay=0.100,
|
||||
sustain=0,
|
||||
release=0.500,
|
||||
volume=0)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
if not synthing:
|
||||
beat = 0
|
||||
next_beat()
|
||||
|
||||
gu.play_synth()
|
||||
synthing = True
|
||||
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
|
||||
|
||||
was_b_pressed = True
|
||||
else:
|
||||
was_b_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
if not was_c_pressed:
|
||||
# Stop synth (if running) and play Tone A
|
||||
timer.deinit()
|
||||
tone_a = 400
|
||||
channels[0].play_tone(tone_a, 0.06)
|
||||
|
||||
gu.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_c_pressed = True
|
||||
else:
|
||||
was_c_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
if not was_c_pressed:
|
||||
# Stop synth (if running) and play Tone B
|
||||
timer.deinit()
|
||||
tone_b = 600
|
||||
|
||||
channels[1].play_tone(tone_b, 0.06, attack=0.5)
|
||||
|
||||
gu.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_d_pressed = True
|
||||
else:
|
||||
was_d_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Increase Tone B
|
||||
tone_b = min(tone_b + 10, 20000)
|
||||
channels[1].frequency(tone_b)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Decrease Tone B
|
||||
tone_b = max(tone_b - 10, 10)
|
||||
channels[1].frequency(max(tone_b, 10))
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Increase Tone A
|
||||
tone_a = min(tone_a + 10, 20000)
|
||||
channels[0].frequency(tone_a)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Decrease Tone A
|
||||
tone_a = max(tone_a - 10, 10)
|
||||
channels[0].frequency(tone_a)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
if not was_d_pressed:
|
||||
# Stop synth and both tones
|
||||
tone_a = 0
|
||||
tone_b = 0
|
||||
gu.stop_playing()
|
||||
timer.deinit()
|
||||
synthing = False
|
||||
|
||||
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 Synth"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
text = "Solo Synth"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
text = "Tone A"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
text = "Tone B"
|
||||
|
||||
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 = "Lower B"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
text = "Stop"
|
||||
|
||||
outline_text(text)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,145 @@
|
|||
import time
|
||||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A pretty, procedural fire effect.
|
||||
|
||||
Switch between landscape fire and vertical fire using the A and B buttons!
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
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()
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -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!
|
|
@ -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)
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,80 @@
|
|||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
# setup heat value buffer and fire parameters
|
||||
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
|
||||
|
||||
|
||||
def init():
|
||||
# a palette of five firey colours (white, yellow, orange, red, smoke)
|
||||
global palette
|
||||
palette = [
|
||||
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)
|
||||
]
|
||||
|
||||
|
||||
# returns the palette entry for a given heat value
|
||||
@micropython.native # noqa: F821
|
||||
def pen_from_value(value):
|
||||
if value < 0.15:
|
||||
return palette[0]
|
||||
elif value < 0.25:
|
||||
return palette[1]
|
||||
elif value < 0.35:
|
||||
return palette[2]
|
||||
elif value < 0.45:
|
||||
return palette[3]
|
||||
return palette[4]
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
# clear the the rows off the bottom of the display
|
||||
for x in range(width):
|
||||
heat[x][height - 1] = 0.0
|
||||
heat[x][height - 2] = 0.0
|
||||
|
||||
# add new fire spawns
|
||||
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
|
||||
|
||||
# average and damp out each value to create rising flame effect
|
||||
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
|
||||
|
||||
# render the heat values to the graphics buffer
|
||||
for y in range(GalacticUnicorn.HEIGHT):
|
||||
for x in range(GalacticUnicorn.WIDTH):
|
||||
graphics.set_pen(pen_from_value(heat[x + 1][y]))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def test():
|
||||
print("A")
|
|
@ -0,0 +1,113 @@
|
|||
import time
|
||||
import machine
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
# overclock to 200Mhz
|
||||
machine.freq(200000000)
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
galactic = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
brightness = 0.5
|
||||
|
||||
|
||||
# returns the id of the button that is currently pressed or
|
||||
# None if none are
|
||||
def pressed():
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
return GalacticUnicorn.SWITCH_A
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
return GalacticUnicorn.SWITCH_B
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
return GalacticUnicorn.SWITCH_C
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
return GalacticUnicorn.SWITCH_D
|
||||
return None
|
||||
|
||||
|
||||
# wait for a button to be pressed and load that effect
|
||||
while True:
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
graphics.set_pen(graphics.create_pen(155, 155, 155))
|
||||
graphics.text("PRESS", 12, -1, -1, 1)
|
||||
graphics.text("A B C OR D!", 2, 5, -1, 1)
|
||||
|
||||
# brightness up/down
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
galactic.set_brightness(brightness)
|
||||
galactic.update(graphics)
|
||||
|
||||
if pressed() == GalacticUnicorn.SWITCH_A:
|
||||
import fire as effect
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_B:
|
||||
import supercomputer as effect # noqa: F811
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_C:
|
||||
import rainbow as effect # noqa: F811
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_D:
|
||||
import retroprompt as effect # noqa: F811
|
||||
break
|
||||
|
||||
# pause for a moment
|
||||
time.sleep(0.01)
|
||||
|
||||
# wait until all buttons are released
|
||||
while pressed() is not None:
|
||||
time.sleep(0.1)
|
||||
|
||||
effect.graphics = graphics
|
||||
effect.init()
|
||||
|
||||
sleep = False
|
||||
was_sleep_pressed = False
|
||||
|
||||
|
||||
# wait
|
||||
while True:
|
||||
# if A, B, C, or D are pressed then reset
|
||||
if pressed() is not None:
|
||||
machine.reset()
|
||||
|
||||
sleep_pressed = galactic.is_pressed(GalacticUnicorn.SWITCH_SLEEP)
|
||||
if sleep_pressed and not was_sleep_pressed:
|
||||
sleep = not sleep
|
||||
|
||||
was_sleep_pressed = sleep_pressed
|
||||
|
||||
if sleep:
|
||||
# fade out if screen not off
|
||||
galactic.set_brightness(galactic.get_brightness() - 0.01)
|
||||
|
||||
if galactic.get_brightness() > 0.0:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
galactic.update(graphics)
|
||||
else:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
galactic.update(graphics)
|
||||
|
||||
# brightness up/down
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
galactic.set_brightness(brightness)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,59 @@
|
|||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
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)
|
||||
|
||||
|
||||
phase = 0
|
||||
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
|
||||
hue_offset = 0.0
|
||||
stripe_width = 3.0
|
||||
speed = 5.0
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
global hue_offset, phase
|
||||
|
||||
phase += speed
|
||||
|
||||
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)
|
|
@ -0,0 +1,100 @@
|
|||
import time
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
time_ms = time.ticks_ms()
|
||||
prompt = (time_ms // 3000) % 3
|
||||
|
||||
if prompt == PROMPT_C64:
|
||||
image = c64
|
||||
fg = FOREGROUND_C64
|
||||
bg = BACKGROUND_C64
|
||||
elif prompt == PROMPT_SPECTRUM:
|
||||
image = spectrum
|
||||
fg = FOREGROUND_SPECTRUM
|
||||
bg = BACKGROUND_SPECTRUM
|
||||
elif prompt == PROMPT_BBC_MICRO:
|
||||
image = bbc_micro
|
||||
fg = FOREGROUND_BBC_MICRO
|
||||
bg = BACKGROUND_BBC_MICRO
|
||||
|
||||
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)
|
|
@ -0,0 +1,40 @@
|
|||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
|
||||
colour = (230, 150, 0)
|
||||
|
||||
|
||||
def init():
|
||||
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]:
|
||||
age[x][y] = 0.0
|
||||
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
|
||||
|
||||
age[x][y] += 0.025
|
||||
|
||||
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)
|
|
@ -0,0 +1,151 @@
|
|||
import time
|
||||
import random
|
||||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A 70s-tastic, procedural rainbow lava lamp.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
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()
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,122 @@
|
|||
import time
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A collection of copies of classic terminal styles including
|
||||
C64, MS-DOS, Spectrum, and more. Images and text are drawn
|
||||
pixel by pixel from a pattern of Os and Xs.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
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)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,115 @@
|
|||
import time
|
||||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Some good old fashioned rainbows!
|
||||
|
||||
You can adjust the cycling speed with A and B,
|
||||
stripe width with C and D, hue with VOL + and -,
|
||||
and the brightness with LUX + and -.
|
||||
The sleep button stops the animation (can be started again with A or B).
|
||||
'''
|
||||
|
||||
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))
|
|
@ -0,0 +1,96 @@
|
|||
import time
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Display scrolling wisdom, quotes or greetz.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
# constants for controlling scrolling text
|
||||
PADDING = 5
|
||||
MESSAGE_COLOUR = (255, 255, 255)
|
||||
OUTLINE_COLOUR = (0, 0, 0)
|
||||
BACKGROUND_COLOUR = (10, 0, 96)
|
||||
MESSAGE = "\"Space is big. Really big. You just won't believe how vastly hugely mind-bogglingly big it is. I mean, you may think it's a long way down the road to the chemist, but that's just peanuts to space.\" - Douglas Adams"
|
||||
HOLD_TIME = 2.0
|
||||
STEP_TIME = 0.075
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.HEIGHT
|
||||
|
||||
|
||||
# function for drawing outlined text
|
||||
def outline_text(text, x, y):
|
||||
graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2])))
|
||||
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(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2])))
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
# state constants
|
||||
STATE_PRE_SCROLL = 0
|
||||
STATE_SCROLLING = 1
|
||||
STATE_POST_SCROLL = 2
|
||||
|
||||
shift = 0
|
||||
state = STATE_PRE_SCROLL
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# calculate the message width so scrolling can happen
|
||||
msg_width = graphics.measure_text(MESSAGE, 1)
|
||||
|
||||
last_time = time.ticks_ms()
|
||||
|
||||
while True:
|
||||
time_ms = time.ticks_ms()
|
||||
|
||||
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 state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
if msg_width + PADDING * 2 >= width:
|
||||
state = STATE_SCROLLING
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000:
|
||||
shift += 1
|
||||
if shift >= (msg_width + PADDING * 2) - width - 1:
|
||||
state = STATE_POST_SCROLL
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
state = STATE_PRE_SCROLL
|
||||
shift = 0
|
||||
last_time = time_ms
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2])))
|
||||
graphics.clear()
|
||||
|
||||
outline_text(MESSAGE, x=PADDING - shift, y=2)
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,315 @@
|
|||
# Galactic Unicorn (MicroPython) <!-- omit in toc -->
|
||||
|
||||
Galactic Unicorn offers 53x11 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
|
||||
|
||||
You can buy one here: https://shop.pimoroni.com/products/galactic-unicorn
|
||||
|
||||
## These are not your everyday RGB LEDs!
|
||||
|
||||
Internally Galactic Unicorn applies gamma correction to the supplied image data and updates the display with 14-bit precision resulting in extremely linear visual output - including at the low end.
|
||||
|
||||
The display is refreshed around 300 times per second (300fps!) allowing for rock solid stability even when being filmed, no smearing or flickering even when in motion.
|
||||
|
||||
No strobing or brightness stepping here folks - it's the perfect backdrop for your tricked out streaming setup!
|
||||
|
||||
## Getting started
|
||||
|
||||
The Galactic Unicorn library provides a collection of methods that allow you to easily access all of the features on the board.
|
||||
|
||||
Drawing is primarily handled via our [PicoGraphics](https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/modules/picographics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Galactic Unicorn to have it displayed on the screen.
|
||||
|
||||
- [Example Program](#example-program)
|
||||
- [Interleaved Framebuffer](#interleaved-framebuffer)
|
||||
- [Function Reference](#function-reference)
|
||||
- [Imports and Objects](#imports-and-objects)
|
||||
- [System State](#system-state)
|
||||
- [`set_brightness(value)`](#set_brightnessvalue)
|
||||
- [`get_brightness()`](#get_brightness)
|
||||
- [`adjust_brightness(delta)`](#adjust_brightnessdelta)
|
||||
- [`set_volume(value)`](#set_volumevalue)
|
||||
- [`get_volume()`](#get_volume)
|
||||
- [`adjust_volume(delta)`](#adjust_volumedelta)
|
||||
- [`light()`](#light)
|
||||
- [`is_pressed(button)`](#is_pressedbutton)
|
||||
- [Drawing](#drawing)
|
||||
- [`update(PicoGraphics)`](#updatepicographics)
|
||||
- [`clear()`](#clear)
|
||||
- [Audio](#audio)
|
||||
- [`play_sample(data)`](#play_sampledata)
|
||||
- [`synth_channel(channel)`](#synth_channelchannel)
|
||||
- [`play_synth()`](#play_synth)
|
||||
- [`stop_playing()`](#stop_playing)
|
||||
- [Channel Reference](#channel-reference)
|
||||
- [Constants](#constants)
|
||||
- [`WIDTH` & `HEIGHT`](#width--height)
|
||||
- [Using Breakouts](#using-breakouts)
|
||||
|
||||
# Example Program
|
||||
|
||||
The following example shows how to scroll a simple message across the display.
|
||||
|
||||
```python
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN
|
||||
import time
|
||||
|
||||
# create a PicoGraphics framebuffer to draw into
|
||||
graphics = PicoGraphics(display=DISPLAY_GALACTIC_UNICORN)
|
||||
|
||||
# create our GalacticUnicorn object
|
||||
gu = GalacticUnicorn()
|
||||
|
||||
# start position for scrolling (off the side of the display)
|
||||
scroll = float(-GalacticUnicorn.WIDTH)
|
||||
|
||||
# message to scroll
|
||||
MESSAGE = "Pirate. Monkey. Robot. Ninja."
|
||||
|
||||
# pen colours to draw with
|
||||
BLACK = graphics.create_pen(0, 0, 0)
|
||||
YELLOW = graphics.create_pen(255, 255, 0)
|
||||
|
||||
while True:
|
||||
# determine the scroll position of the text
|
||||
width = graphics.measure_text(MESSAGE, 1)
|
||||
scroll += 0.25
|
||||
if scroll > width:
|
||||
scroll = float(-GalacticUnicorn.WIDTH)
|
||||
|
||||
# clear the graphics object
|
||||
graphics.set_pen(BLACK)
|
||||
graphics.clear()
|
||||
|
||||
# draw the text
|
||||
graphics.set_pen(YELLOW)
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55);
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
||||
time.sleep(0.02)
|
||||
```
|
||||
|
||||
# Interleaved Framebuffer
|
||||
|
||||
Galactic Unicorn takes advantage of the RP2040's PIOs to drive screen updates - this is what gives it the performance it needs to render with 14-bit precision at over 300 frames per second.
|
||||
|
||||
The PIO is a powerful, but limited, tool. It has no way to access memory at random and minimal support for decision making and branching. All it can really do is process a stream of data/instructions in order.
|
||||
|
||||
This means that we need to be clever about the way we pass data into the PIO program, the information needs to be delivered in the exact order that the PIO will need to process it. To achieve this we "interleave" our framebuffer - each frame of BCM data is passed one after another with values for the current row, pixel count, and timing inserted as needed:
|
||||
|
||||
row 0 data:
|
||||
for each bcd frame:
|
||||
bit : data
|
||||
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)
|
||||
|
||||
row 1 data:
|
||||
...
|
||||
|
||||
If you're working with our library then you don't need to worry about any of these details, they are handled for you.
|
||||
|
||||
# Function Reference
|
||||
|
||||
## Imports and Objects
|
||||
|
||||
To access these functions, you'll need to first `import` the relevant libraries and then set up a Galactic Unicorn object:
|
||||
|
||||
```python
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
```
|
||||
|
||||
or (with PicoGraphics):
|
||||
|
||||
```python
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(display=DISPLAY_GALACTIC_UNICORN)
|
||||
```
|
||||
|
||||
## System State
|
||||
|
||||
### `set_brightness(value)`
|
||||
|
||||
Set the brightness - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `get_brightness()`
|
||||
|
||||
Returns the current brightness as a value between `0.0` and `1.0`.
|
||||
|
||||
### `adjust_brightness(delta)`
|
||||
|
||||
Adjust the brightness of the display - `delta` is supplied as a floating point value and will be added to the current brightness (and then clamped to the range `0.0` to `1.0`).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
gu.set_brightness(0.5)
|
||||
gu.adjust_brightness(0.1) # brightness is now 0.6
|
||||
gu.adjust_brightness(0.7) # brightness is now 1.0
|
||||
gu.adjust_brightness(-0.2) # brightness is now 0.8
|
||||
```
|
||||
|
||||
### `set_volume(value)`
|
||||
|
||||
Set the volume - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `get_volume()`
|
||||
|
||||
Returns the current volume as a value between `0.0` and `1.0`.
|
||||
|
||||
### `adjust_volume(delta)`
|
||||
|
||||
Adjust the volume - `delta` is supplied as a floating point value and will be added to the current volume (and then clamped to the range `0.0` to `1.0`).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
gu.set_volume(0.5)
|
||||
gu.set_volume(0.1) # volume is now 0.6
|
||||
gu.adjust_volume(0.7) # volume is now 1.0
|
||||
gu.adjust_volume(-0.2) # volume is now 0.8
|
||||
```
|
||||
|
||||
### `light()`
|
||||
|
||||
Get the current value seen by the onboard light sensor as a value between `0` and `4095`.
|
||||
|
||||
### `is_pressed(button)`
|
||||
|
||||
Returns true if the requested `button` is currently pressed.
|
||||
|
||||
There are a set of constants in the GalacticUnicorn class that represent each of the buttons. The brightness, sleep, and volume buttons are not tied to hardware functions (they are implemented entirely in software) so can also be used for user functions if preferred. Here's a list of the constants and their associated pin numbers:
|
||||
|
||||
```python
|
||||
SWITCH_A = 0
|
||||
SWITCH_B = 1
|
||||
SWITCH_C = 3
|
||||
SWITCH_D = 6
|
||||
SWITCH_SLEEP = 27
|
||||
SWITCH_VOLUME_UP = 7
|
||||
SWITCH_VOLUME_DOWN = 8
|
||||
SWITCH_BRIGHTNESS_UP = 21
|
||||
SWITCH_BRIGHTNESS_DOWN = 26
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
while not gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
# wait for switch A to be pressed
|
||||
pass
|
||||
|
||||
print("We did it! We pressed switch A! Heck yeah!")
|
||||
```
|
||||
|
||||
## Drawing
|
||||
|
||||
### `update(PicoGraphics)`
|
||||
|
||||
The PicoGraphics library provides a collection of powerful drawing methods to make things simple.
|
||||
|
||||
The image on the PicoGraphics object provided is copied to the interleaved framebuffer with gamma correction applied.
|
||||
|
||||
For example (assuming you've set up your Galactic Unicorn and PicoGraphics objects up [as we did above](#imports-and-objects)):
|
||||
|
||||
```python
|
||||
gu.update(graphics)
|
||||
```
|
||||
|
||||
⚠️ If you've used PicoGraphics on our other boards note that this `update` function works a little differently. Here it's a Galactic Unicorn function to which you need to pass a PicoGraphics object to.
|
||||
|
||||
### `clear()`
|
||||
|
||||
Clear the contents of the interleaved framebuffer. This will make your Galactic Unicorn display turn off. To show an image again, call the `update()` function as described above.
|
||||
|
||||
## Audio
|
||||
|
||||
Audio functionality is supported by our [PicoSynth library](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_synth) which allows you to create multiple voice channels with ADSR (attack decay sustain release) envelopes. It provides a similar set of functionality to the classic SID chip in the Commodore 64.
|
||||
|
||||
### `play_sample(data)`
|
||||
|
||||
Play the provided 16-bit audio sample. `data` must point to a `bytearray` that contains 16-bit PCM data. The number of samples is retrieved from the array's length.
|
||||
|
||||
### `synth_channel(channel)`
|
||||
|
||||
Gets a `Channel` object which can then be configured with voice, ADSR envelope, etc.
|
||||
|
||||
### `play_synth()`
|
||||
|
||||
Start the synth playing.
|
||||
|
||||
### `stop_playing()`
|
||||
|
||||
Stops any currently playing audio.
|
||||
|
||||
### Channel Reference
|
||||
|
||||
```python
|
||||
configure(waveforms=None, frequency=None, volume=None,
|
||||
attack=None, decay=None, sustain=None,
|
||||
release=None, pulse_width=None)
|
||||
restore()
|
||||
waveforms()
|
||||
waveforms(waveforms)
|
||||
frequency()
|
||||
frequency(frequency)
|
||||
volume()
|
||||
volume(volume)
|
||||
attack_duration()
|
||||
attack_duration(duration)
|
||||
decay_duration()
|
||||
decay_duration(duration)
|
||||
sustain_level()
|
||||
sustain_level(level)
|
||||
release_duration()
|
||||
release_duration(duration)
|
||||
pulse_width()
|
||||
pulse_width(width)
|
||||
trigger_attack() # start the channel playing
|
||||
trigger_release() # stop the channel playing
|
||||
play_tone(frequency, volume=None, attack=None, release=None)
|
||||
```
|
||||
|
||||
## Constants
|
||||
|
||||
### `WIDTH` & `HEIGHT`
|
||||
|
||||
The width and height of Galactic Unicorn are available in constants `WIDTH` and `HEIGHT`.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
num_pixels = GalacticUnicorn.WIDTH * GalacticUnicorn.HEIGHT
|
||||
print(num_pixels)
|
||||
```
|
||||
|
||||
## Using Breakouts
|
||||
|
||||
Galactic Unicorn has two Qw/ST (Qwiic/STEMMA QT) connectors. Breakouts with Qw/ST connectors, can be plugged straight in with a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587). You can connect I2C Breakout Garden breakouts without Qw/ST connectors using a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587) and a [Qw/ST to Breakout Garden adaptor](https://shop.pimoroni.com/products/stemma-qt-qwiic-to-breakout-garden-adapter).
|
||||
|
||||
- [List of breakouts currently supported in our C++/MicroPython build](https://github.com/pimoroni/pimoroni-pico#breakouts)
|
||||
|
||||
Galactic Unicorn uses GP4 and GP5 for its I2C interface. You can use the constants in the shared `pimoroni` module to set up the I2C interface:
|
||||
|
||||
```python
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from pimoroni import BREAKOUT_GARDEN_I2C_PINS
|
||||
|
||||
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
|
||||
```
|
||||
|
||||
Alternatively, you can specify the pin numbers directly:
|
||||
|
||||
```python
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
|
||||
i2c = PimoroniI2C(sda=4, scl=5)
|
||||
```
|
|
@ -0,0 +1,150 @@
|
|||
#include "cosmic_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(CosmicUnicorn___del___obj, CosmicUnicorn___del__);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_clear_obj, CosmicUnicorn_clear);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_update_obj, CosmicUnicorn_update);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_set_brightness_obj, CosmicUnicorn_set_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_get_brightness_obj, CosmicUnicorn_get_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_adjust_brightness_obj, CosmicUnicorn_adjust_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_set_volume_obj, CosmicUnicorn_set_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_get_volume_obj, CosmicUnicorn_get_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_adjust_volume_obj, CosmicUnicorn_adjust_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_light_obj, CosmicUnicorn_light);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_is_pressed_obj, CosmicUnicorn_is_pressed);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_play_sample_obj, CosmicUnicorn_play_sample);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_play_synth_obj, CosmicUnicorn_play_synth);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(CosmicUnicorn_stop_playing_obj, CosmicUnicorn_stop_playing);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(CosmicUnicorn_synth_channel_obj, CosmicUnicorn_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 CosmicUnicorn_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&CosmicUnicorn___del___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&CosmicUnicorn_clear_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&CosmicUnicorn_update_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&CosmicUnicorn_set_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get_brightness), MP_ROM_PTR(&CosmicUnicorn_get_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_adjust_brightness), MP_ROM_PTR(&CosmicUnicorn_adjust_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&CosmicUnicorn_set_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get_volume), MP_ROM_PTR(&CosmicUnicorn_get_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_adjust_volume), MP_ROM_PTR(&CosmicUnicorn_adjust_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_light), MP_ROM_PTR(&CosmicUnicorn_light_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&CosmicUnicorn_is_pressed_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play_sample), MP_ROM_PTR(&CosmicUnicorn_play_sample_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play_synth), MP_ROM_PTR(&CosmicUnicorn_play_synth_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_stop_playing), MP_ROM_PTR(&CosmicUnicorn_stop_playing_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_synth_channel), MP_ROM_PTR(&CosmicUnicorn_synth_channel_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(32) },
|
||||
|
||||
{ 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(CosmicUnicorn_locals_dict, CosmicUnicorn_locals_dict_table);
|
||||
|
||||
/***** Class Definition *****/
|
||||
#ifdef MP_DEFINE_CONST_OBJ_TYPE
|
||||
MP_DEFINE_CONST_OBJ_TYPE(
|
||||
Channel_type,
|
||||
MP_QSTR_Channel,
|
||||
MP_TYPE_FLAG_NONE,
|
||||
make_new, Channel_make_new,
|
||||
print, Channel_print,
|
||||
locals_dict, (mp_obj_dict_t*)&Channel_locals_dict
|
||||
);
|
||||
|
||||
MP_DEFINE_CONST_OBJ_TYPE(
|
||||
CosmicUnicorn_type,
|
||||
MP_QSTR_CosmicUnicorn,
|
||||
MP_TYPE_FLAG_NONE,
|
||||
make_new, CosmicUnicorn_make_new,
|
||||
print, CosmicUnicorn_print,
|
||||
locals_dict, (mp_obj_dict_t*)&CosmicUnicorn_locals_dict
|
||||
);
|
||||
#else
|
||||
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 CosmicUnicorn_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_CosmicUnicorn,
|
||||
.print = CosmicUnicorn_print,
|
||||
.make_new = CosmicUnicorn_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&CosmicUnicorn_locals_dict,
|
||||
};
|
||||
#endif
|
||||
|
||||
/***** Globals Table *****/
|
||||
STATIC const mp_map_elem_t Cosmic_globals_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_cosmic) },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_Channel), (mp_obj_t)&Channel_type },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_CosmicUnicorn), (mp_obj_t)&CosmicUnicorn_type },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_Cosmic_globals, Cosmic_globals_table);
|
||||
|
||||
/***** Module Definition *****/
|
||||
const mp_obj_module_t Cosmic_user_cmodule = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_Cosmic_globals,
|
||||
};
|
||||
#if MICROPY_VERSION <= 70144
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, Cosmic_user_cmodule, MODULE_Cosmic_ENABLED);
|
||||
#else
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, Cosmic_user_cmodule);
|
||||
#endif
|
|
@ -0,0 +1,516 @@
|
|||
#include "libraries/cosmic_unicorn/cosmic_unicorn.hpp"
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "micropython/modules/util.hpp"
|
||||
#include <cstdio>
|
||||
#include <cfloat>
|
||||
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
extern "C" {
|
||||
#include "cosmic_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 CosmicUnicorn.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;
|
||||
}
|
||||
|
||||
|
||||
/********** CosmicUnicorn **********/
|
||||
|
||||
/***** Variables Struct *****/
|
||||
typedef struct _CosmicUnicorn_obj_t {
|
||||
mp_obj_base_t base;
|
||||
CosmicUnicorn* Cosmic;
|
||||
} _CosmicUnicorn_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 CosmicUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
(void)kind; //Unused input parameter
|
||||
//_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
mp_print_str(print, "CosmicUnicorn()");
|
||||
}
|
||||
|
||||
|
||||
/***** Constructor *****/
|
||||
mp_obj_t CosmicUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
_CosmicUnicorn_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");
|
||||
}
|
||||
|
||||
|
||||
CosmicUnicorn *Cosmic = m_new_class(CosmicUnicorn);
|
||||
Cosmic->init();
|
||||
|
||||
self = m_new_obj_with_finaliser(_CosmicUnicorn_obj_t);
|
||||
self->base.type = &CosmicUnicorn_type;
|
||||
self->Cosmic = Cosmic;
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
|
||||
/***** Destructor ******/
|
||||
mp_obj_t CosmicUnicorn___del__(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
m_del_class(CosmicUnicorn, self->Cosmic);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
/***** Methods *****/
|
||||
extern mp_obj_t CosmicUnicorn_clear(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->clear();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
|
||||
|
||||
self->Cosmic->update(picographics->graphics);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->set_brightness(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_get_brightness(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->get_brightness());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->adjust_brightness(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->set_volume(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_get_volume(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->get_volume());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->adjust_volume(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_light(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->light());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_bool(self->Cosmic->is_pressed((uint8_t)mp_obj_get_int(button)));
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_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->Cosmic->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_play_synth(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->play_synth();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_stop_playing(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->stop_playing();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_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->Cosmic->synth_channel(channel);
|
||||
|
||||
return MP_OBJ_FROM_PTR(channel_obj);
|
||||
}
|
||||
}
|
|
@ -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 CosmicUnicorn_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 CosmicUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
|
||||
extern mp_obj_t CosmicUnicorn_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 CosmicUnicorn___del__(mp_obj_t self_in);
|
||||
extern mp_obj_t CosmicUnicorn_clear(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value);
|
||||
extern mp_obj_t CosmicUnicorn_get_brightness(mp_obj_t self_in);
|
||||
extern mp_obj_t CosmicUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value);
|
||||
extern mp_obj_t CosmicUnicorn_get_volume(mp_obj_t self_in);
|
||||
extern mp_obj_t CosmicUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_light(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data);
|
||||
extern mp_obj_t CosmicUnicorn_play_synth(mp_obj_t self_in);
|
||||
extern mp_obj_t CosmicUnicorn_stop_playing(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in);
|
|
@ -0,0 +1,24 @@
|
|||
set(MOD_NAME cosmic_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/cosmic_unicorn/cosmic_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/cosmic_unicorn/cosmic_unicorn.pio)
|
||||
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/cosmic_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_COSMIC_ENABLED=1
|
||||
)
|
||||
|
||||
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})
|
|
@ -49,4 +49,4 @@ include(galactic_unicorn/micropython)
|
|||
|
||||
# include(micropython-common)
|
||||
|
||||
include(modules_py/modules_py)
|
||||
include(modules_py/modules_py)
|
||||
|
|
|
@ -151,7 +151,7 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_192X64), MP_ROM_INT(DISPLAY_INTERSTATE75_192X64) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_256X64), MP_ROM_INT(DISPLAY_INTERSTATE75_256X64) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_7), MP_ROM_INT(DISPLAY_INKY_FRAME_7) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_COSMIC_UNICORN), MP_ROM_INT(DISPLAY_COSMIC_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) },
|
||||
|
|
|
@ -202,6 +202,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_INKY7;
|
||||
break;
|
||||
case DISPLAY_COSMIC_UNICORN:
|
||||
width = 32;
|
||||
height = 32;
|
||||
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;
|
||||
}
|
||||
|
@ -332,6 +340,9 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
|
||||
} else if (display == DISPLAY_INTERSTATE75_32X32 || display == DISPLAY_INTERSTATE75_64X64 || display == DISPLAY_INTERSTATE75_64X32) {
|
||||
self->display = m_new_class(DisplayDriver, width, height, (Rotation)rotate);
|
||||
|
||||
} else if (display == DISPLAY_COSMIC_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);
|
||||
|
|
|
@ -25,6 +25,7 @@ enum PicoGraphicsDisplay {
|
|||
DISPLAY_INTERSTATE75_192X64,
|
||||
DISPLAY_INTERSTATE75_256X64,
|
||||
DISPLAY_INKY_FRAME_7,
|
||||
DISPLAY_COSMIC_UNICORN
|
||||
};
|
||||
|
||||
enum PicoGraphicsPenType {
|
||||
|
|
Loading…
Reference in New Issue