From df6e70473c82712953907902c1c8b1bec3bd0e90 Mon Sep 17 00:00:00 2001 From: Me Here Date: Thu, 28 May 2026 17:11:30 -0500 Subject: [PATCH] PM_K-1 firmware: smooth AA fonts, full set list, LED-off-on-stop, touch edge-detect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four firmware improvements (web simulator already had these): - Smooth lettering: replace the upscaled 8x8 bitmap font (and the 7-seg BPM) with baked anti-aliased proportional fonts (DejaVuSans-Bold 22 + 78), rendered by blending fg over bg through a 16-entry alpha LUT. Generator: pico/gen_font.py. - All grooves: PROGRAMS now carries the full web set list (23 grooves), not 5. - Fix: the RGB LED stayed lit when stopped — the stop path now zeroes the colour and the fade only runs while playing (led_off()). - Fix: the on-screen play button played one beep then stopped — a finger held past the old lockout re-fired the toggle. Touch is now edge-detected like the hardware buttons (fires once on finger-down, ignores held). Co-Authored-By: Claude Opus 4.7 (1M context) --- pico/__pycache__/main.cpython-312.pyc | Bin 32138 -> 72023 bytes pico/gen_font.py | 110 ++++++++ pico/main.py | 374 ++++++++++++++------------ 3 files changed, 307 insertions(+), 177 deletions(-) create mode 100644 pico/gen_font.py diff --git a/pico/__pycache__/main.cpython-312.pyc b/pico/__pycache__/main.cpython-312.pyc index 7f226689cb74b7713d8f792f5de007ef39673680..2c1d997a90865e88810432c010069ae9559f5f62 100644 GIT binary patch literal 72023 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

#zVe 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 delta 14061 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 DZw=;- diff --git a/pico/gen_font.py b/pico/gen_font.py new file mode 100644 index 0000000..03f546d --- /dev/null +++ b/pico/gen_font.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# Generate the anti-aliased bitmap fonts baked into main.py. +# +# Each font is one base64 blob: a small metrics table + 4-bit-alpha glyph pixels. +# main.py decodes it at boot (binascii.a2b_base64) and renders text by blending the +# foreground over the background per pixel via a 16-entry LUT (smooth, no upscaling). +# +# Re-run after changing sizes/charset: python3 pico/gen_font.py +# It writes pico/_font_m.b64 + pico/_font_l.b64 and /tmp/font_verify.png (eyeball it). Then +# inject the two base64 strings into main.py's FONT_M_B64 / FONT_L_B64 (replace the existing +# values, or re-add the @@FONT_M@@ / @@FONT_L@@ placeholders first and substitute them in). + +import base64, pathlib +from PIL import Image, ImageDraw, ImageFont + +FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" +HERE = pathlib.Path(__file__).parent + +ASCII = "".join(chr(c) for c in range(0x20, 0x7F)) +SYMBOLS = "◀▶■" # ◀ ▶ ■ (prev / play / stop, anti-aliased) +M_CHARS = ASCII + SYMBOLS +L_CHARS = "0123456789 " + +def build(size, chars): + font = ImageFont.truetype(FONT, size) + recs = [] # (cp, w, h, xoff, top, adv) + pixels = bytearray() + for ch in chars: + cp = ord(ch) + adv = round(font.getlength(ch)) + bbox = font.getbbox(ch) + if not bbox or bbox[2] - bbox[0] <= 0 or bbox[3] - bbox[1] <= 0: + recs.append((cp, 0, 0, 0, 0, adv)); continue + l, t, r, b = bbox + w, h = r - l, b - t + img = Image.new("L", (w, h), 0) + ImageDraw.Draw(img).text((-l, -t), ch, fill=255, font=font) + px = img.load() + # pack 4-bit alpha, row-major, two pixels per byte (first = high nibble) + nib = [] + for y in range(h): + for x in range(w): + nib.append(px[x, y] >> 4) + if len(nib) % 2: nib.append(0) + for i in range(0, len(nib), 2): + pixels.append((nib[i] << 4) | nib[i + 1]) + recs.append((cp, w, h, l & 0xFF, t & 0xFF, adv & 0xFF)) + header = bytearray([len(recs)]) + for cp, w, h, xoff, top, adv in recs: + header += bytes([cp >> 8, cp & 0xFF, w, h, xoff, top, adv]) + return bytes(header) + bytes(pixels) + +# ---- reference decoder + renderer (must match main.py exactly) ---- +def load_font(blob): + count = blob[0]; p = 1; pixoff = 1 + count * 7; glyphs = {} + for _ in range(count): + cp = (blob[p] << 8) | blob[p + 1]; w = blob[p + 2]; h = blob[p + 3] + xoff = blob[p + 4]; xoff = xoff - 256 if xoff > 127 else xoff + top = blob[p + 5]; adv = blob[p + 6]; p += 7 + glyphs[cp] = (w, h, xoff, top, adv, pixoff) + pixoff += (w * h + 1) // 2 + return glyphs, blob + +def lut(fg, bg): + def unp(c): return ((c >> 11) & 0x1F, (c >> 5) & 0x3F, c & 0x1F) + fr, fgc, fb = unp(fg); br, bgc, bb = unp(bg); out = [] + for a in range(16): + t = a * 17 + r = (br * (255 - t) + fr * t) // 255 + g = (bgc * (255 - t) + fgc * t) // 255 + b = (bb * (255 - t) + fb * t) // 255 + out.append((r << 11) | (g << 5) | b) + return out + +def render(draw_img, font, s, x, y, fg, bg): + glyphs, blob = font; L = lut(fg, bg); pen = x + for ch in s: + g = glyphs.get(ord(ch)) + if not g: continue + w, h, xoff, top, adv, off = g + for j in range(h): + for i in range(w): + k = j * w + i; byte = blob[off + (k >> 1)] + nibv = (byte >> 4) if (k & 1) == 0 else (byte & 0xF) + col = L[nibv] + r = (col >> 11) & 0x1F; gg = (col >> 5) & 0x3F; b = col & 0x1F + draw_img.putpixel((pen + xoff + i, y + top + j), (r << 3, gg << 2, b << 3)) + pen += adv + return pen + +blob_m = build(22, M_CHARS) +blob_l = build(78, L_CHARS) +(HERE / "_font_m.b64").write_text(base64.b64encode(blob_m).decode()) +(HERE / "_font_l.b64").write_text(base64.b64encode(blob_l).decode()) +print("FONT_M %d bytes -> %d b64" % (len(blob_m), len(base64.b64encode(blob_m)))) +print("FONT_L %d bytes -> %d b64" % (len(blob_l), len(base64.b64encode(blob_l)))) + +# verification image on a dark bg (565 colours like the firmware) +def c565(r, g, b): return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) +BG = c565(6, 9, 14); CYAN = c565(10, 179, 247); TXT = c565(199, 208, 219); GREEN = c565(47, 224, 122) +img = Image.new("RGB", (320, 220), (6, 9, 14)) +fm = load_font(blob_m); fl = load_font(blob_l) +render(img, fm, "PM_K-1 KIT", 12, 10, CYAN, BG) +render(img, fl, "120", 150, 40, TXT, BG) +render(img, fm, "BPM", 12, 70, TXT, BG) +render(img, fm, "▶ RUN", 12, 120, GREEN, BG) +render(img, fm, "Four-on-the-floor", 12, 150, TXT, BG) +render(img, fm, "◀◀ ▶ ▶▶ - TAP +", 12, 185, TXT, BG) +img.resize((640, 440), Image.NEAREST).save("/tmp/font_verify.png") +print("wrote /tmp/font_verify.png") diff --git a/pico/main.py b/pico/main.py index 0c83c58..46b7fbc 100644 --- a/pico/main.py +++ b/pico/main.py @@ -5,17 +5,20 @@ # It runs the SAME program-string language as https://metronome.varasys.io — design a groove in # the web editor, copy its program string, paste it into PROGRAMS below, and it plays here. # -# FLASH: 1) Hold BOOTSEL, plug in the Pico, drop the MicroPython UF2 on the RPI-RP2 drive -# (https://micropython.org/download/RPI_PICO/ ; use RPI_PICO2 for a Pico 2). -# 2) Copy THIS file to the Pico as main.py (Thonny: File > Save as > Raspberry Pi Pico). -# 3) Reset. It boots straight into the metronome. +# FLASH (two steps — main.py is NOT drag-and-drop): +# 1) Install MicroPython: hold BOOTSEL, plug in, drop the MicroPython .uf2 on the RPI-RP2 drive +# (https://micropython.org/download/RPI_PICO/ ; RPI_PICO2 for a Pico 2). It reboots itself. +# 2) Copy THIS file to the Pico AS main.py over USB serial — Thonny: File > Save as > Raspberry Pi +# Pico (name it main.py); or mpremote cp main.py :main.py . Then reset. # # IF SOMETHING LOOKS WRONG, flip a flag in CONFIG below (colours, inversion, touch axes) — see README.md. # -# MIT-style: do whatever you like with it. VARASYS — Simplifying Complexity. +# Text is drawn with baked anti-aliased fonts (see pico/gen_font.py) for smooth lettering. +# MIT-style. VARASYS — Simplifying Complexity. from machine import Pin, SPI, I2C, ADC, PWM -import time, framebuf +from binascii import a2b_base64 +import time try: import neopixel @@ -23,7 +26,7 @@ except ImportError: neopixel = None # ============================== CONFIG (tweak if needed) ============================== -SPI_BAUD = 40_000_000 # 40 MHz is a safe-fast default; the vendor demo uses 62.5 MHz +SPI_BAUD = 40_000_000 # 40 MHz safe-fast default; vendor demo uses 62.5 MHz WIDTH, HEIGHT = 320, 480 # ST7796 portrait MADCTL = 0x48 # memory access ctrl (MX | BGR) -> portrait, 320 wide x 480 tall INVERT_COLORS = True # most ST7796 modules need display inversion ON; set False if colours look negative @@ -33,7 +36,6 @@ TOUCH_SWAP_XY = False TOUCH_INVERT_X = False TOUCH_INVERT_Y = False TOUCH_DEBUG = False # True -> print raw touch coords over USB serial to calibrate - # Joystick calibration: JOY_INVERT_X = False JOY_INVERT_Y = False @@ -49,13 +51,34 @@ PIN_BTN_B = 14 # tap tempo PIN_JOY_X = 26 # ADC0 PIN_JOY_Y = 27 # ADC1 -# ----- the grooves on the device (paste program strings from the web editor) ----- +# ----- the grooves on the device (the web set lists; paste your own from the editor) ----- PROGRAMS = [ - ("Four on the floor", "v1;t120;kick:4;snare:4=.X.X;hat:4/2"), - ("Son clave 3-2", "v1;t100;clap:4=X..X..X.;kick:4"), - ("7/8 + 4 polymeter", "v1;t132;kick:7/2;hat:4/2~;snare:4=..X."), - ("Shuffle", "v1;t96;kick:4;snare:4=.X.X;hat:4/3"), - ("Straight click", "v1;t120;beep:4"), + # Styles + ("Four on the floor", "t120;kick:4;snare:4=.x.x;hatClosed:4/2"), + ("Swing ride", "t150;ride:4/2s;kick:4=X..x;snare:4=.x.x"), + ("Purdie half shuffle", "t92;kick:4/3=X....x...x..;snare:4/3=..gg.gX.gg.g;hatClosed:4/3=X.xX.xX.xX.x"), + ("Samba (2/4)", "t104;tomLow:2/4=x...X...;hatClosed:2/4;woodblock:2/4=X.xx.xX."), + ("Nanigo (6/8 bembe)", "t130;cowbell:4/3=X.xx.x.xx.x.;kick:4/3=X.....X.....;hatClosed:4/3=..x..x..x..x"), + ("6/8 groove", "t100;kick:3+3=x..x..;snare:3+3=...x..;hatClosed:3+3/2"), + ("7/8 (2+2+3)", "t130;kick:2+2+3=x..x..x;hatClosed:2+2+3/2"), + ("5/4 (3+2)", "t112;kick:3+2=x..x.;snare:3+2=..x..;hatClosed:3+2/2"), + # Practice + ("5 over 4 polyrhythm", "t100;kick:4;claves:5~"), + ("3 over 2 hemiola", "t96;woodblock:2;cowbell:3~"), + ("2 & 4 & 3 per bar", "t100;kick:3;cowbell:2~;claves:4~"), + ("Triplet hats", "t100;kick:4;snare:4=.x.x;hatClosed:4/3"), + ("Accents", "t92;kick:4=X..X;snare:4=.X.X;hatClosed:4/2"), + ("Tempo builder 80+", "t80;woodblock:4;rmp80/4/4"), + ("Gap trainer 2/2", "t100;kick:4;hatClosed:4/2;tr2/2"), + # Song (continuous on the web; here they play as standalone grooves) + ("Intro - hats+kick", "t88;kick:4=X.x.;hatClosed:4/2=gggggggg"), + ("Groove in", "t88;kick:4=X.x.;snare:4=.X.X;hatClosed:4/2"), + ("Half-time shuffle", "t92;kick:4/3=X....x...x..;snare:4/3=..gg.gX.gg.g;hatClosed:4/3=X.xX.xX.xX.x"), + ("Build 92 to 120", "t92;kick:4;snare:4=.X.X;hatClosed:4/2"), + ("Four-floor (909)", "t124;kick909:4;clap909:4=.X.X;hat909:4/2=.X.X.X.X"), + ("Samba break", "t116;tomLow:2/4=x...X...;hatClosed:2/4;woodblock:2/4=X.xx.xX."), + ("Peak - 16ths", "t132;kick:4=X..x;snare:4=.X.X;hatClosed:4/4"), + ("Outro", "t132;kick:4=X..x;hatClosed:4/2=gggggggg"), ] # ============================== COLOURS ============================== @@ -65,18 +88,32 @@ def rgb565(r, g, b): return bytes((v >> 8, v & 0xFF)) # MSB-first for ST7796 C_BG = rgb565(6, 9, 14) -C_PANEL = rgb565(18, 22, 30) +C_PANEL = rgb565(28, 34, 44) C_TXT = rgb565(199, 208, 219) -C_MUTE = rgb565(110, 122, 138) -C_CYAN = rgb565(10, 179, 247) # VARASYS brand cyan / normal beat +C_MUTE = rgb565(120, 132, 148) +C_CYAN = rgb565(10, 179, 247) # brand cyan / normal beat C_AMBER = rgb565(255, 155, 46) # accent -C_VIOLET = rgb565(150, 100, 255) # ghost C_GREEN = rgb565(47, 224, 122) # running C_DIMDOT = rgb565(36, 50, 64) C_BTN = rgb565(28, 34, 44) -C_BTNHI = rgb565(40, 52, 66) -LEVEL_COL = {2: C_AMBER, 1: C_CYAN, 3: C_VIOLET, 0: C_DIMDOT} -LEVEL_RGB = {2: (255, 110, 0), 1: (0, 150, 255), 3: (130, 70, 255)} # WS2812 (logical r,g,b) +C_BTNHI = rgb565(44, 56, 72) +LEVEL_RGB = {2: (255, 110, 0), 1: (0, 150, 255), 3: (130, 70, 255)} # WS2812 accent / normal / ghost + +# ============================== ANTI-ALIASED FONTS (baked; see gen_font.py) ============================== +FONT_M_B64 = "YgAgAAAAAAgAIQoQAAUKACILEAAFCwAjEhAABRIAJA8TAAUPACUWEAAFFgAmExAABRMAJwcQAAUHACgKFAAECgApChQABAoAKgwQAAUMACsSDQAIEgAsCAcAEQgALQkIAA0JAC4IBAARCAAvCRIABQgAMA8QAAUPADEPEAAFDwAyDxAABQ8AMw8QAAUPADQPEAAFDwA1DxAABQ8ANg8QAAUPADcPEAAFDwA4DxAABQ8AOQ8QAAUPADoJDAAJCQA7CQ8ACQkAPBINAAgSAD0SCwAKEgA+Eg0ACBIAPw0QAAUNAEAWEwAFFgBBERAABREAQhEQAAURAEMQEAAFEABEEhAABRIARQ8QAAUPAEYPEAAFDwBHEhAABRIASBIQAAUSAEkIEAAFCABKChT+BQgASxIQAAURAEwOEAAFDgBNFhAABRYAThIQAAUSAE8TEAAFEwBQEBAABRAAURMTAAUTAFIREAAFEQBTEBAABRAAVA8QAAUPAFUSEAAFEgBWERAABREAVxgQAAUYAFgREAAFEQBZEhD/BRAAWhAQAAUQAFsKFAAECgBcCRIABQgAXQoUAAQKAF4SEAAFEgBfCwUAFQsAYAsSAAMLAGEPDAAJDwBiEBEABBAAYw0MAAkNAGQQEQAEEABlDwwACQ8AZgoRAAQKAGcQEQAJEABoEBEABBAAaQgRAAQIAGoJFv8ECABrEBEABA8AbAgRAAQIAG0XDAAJFwBuEAwACRAAbw8MAAkPAHAQEQAJEABxEBEACRAAcgsMAAkLAHMNDAAJDQB0Cw8ABgsAdRAMAAkQAHYPDAAJDgB3FAwACRQAeA4MAAkOAHkOEQAJDgB6DQwACQ0AexAUAAQQAHwIFgAECAB9EBQABBAAfhIKAAsSJcAREgAGESW2ERIABhEloBUSAAYVAA7/8AAADv/wAAAO//AAAA7/8AAADv/wAAAO//AAAA3/0AAAC/+wAAAJ/5AAAAf/cAAAAAAAAAAAAAAAAA7/8AAADv/wAAAO//AAAA7/8AAA76Av9gAO+gL/YADvoC/2AA76Av9gAO+gL/YADvoC/2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/oAf+AAAAAADvcAr7AAAAAAL/QA73AAAAAAX/EB/0AAAAAAj9AE/xAAAE/////////wAE/////////wAAAF/wAv9AAAAAAJ/ABv8AAAAAAN+ACvwAAACP////////sACP////////sAAACvwAb/AAAAAADvgAr7AAAAAAL/MA73AAAAAAb/AC/zAAAAAAAAP4AAAAAAAAP4AAAAAAGM7/2mEAAD//////0AAN/6T4JJsAA//yP4AAAABP/5P4AAAAAv///8cwAAAL/////9QAABr/////8wAAAnv///+gAAAAP5Xv/AAAAAP4Cv+wBMcxP4Lv9wBP//////sAADi9//61AAAAAAP4AAAAAAAAP4AAAAAAAAP4AAAAAATP/EAAAAf+AAAAX///9QAAL/QAAADv4i7+AAC/oAAAA/+ACP8gBf8QAAAE/2AG/0Ae9gAAAAP/gAj/IJ/AAAAAAO/iLv4D/yAAAAAAX///9Q34BM/8UAAEz/xAf9Bf///2AAAAAAL/QO/iLv8AAAAAC/oC/5AH/zAAAABf4QP/cAb/UAAAAe9gAv+QB/8wAAAJ/AAA7+It/gAAAD/yAABf///2AAAA34AAAATP/FAAAAA63+xyAAAAAACP////8AAAAAAf/9ICbAAAAAAD//oAAAAAAAAAH//zAAAAAAAAAK//4QAAAAAAAK///8AADf9wAJ////+gAP/1AD//wu//gD//IAj/9AP//1n/0ACv/xAG////+AAJ//IACf///xAAX/+QAAz//2AAAM//khTf//sAAAHP///////7AAAAW+/ttiz/+wAO+gAA76AADvoAAO+gAA76AADvoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC//EAAAv/kAAAP/8gAACv/AAAAf/2AAAG//IAAAr/4AAADv+wAAAP/6AAAB//kAAAH/+QAAAP/6AAAA7/sAAACv/gAAAG//IAAAH/9gAAAK/8AAAAP/8gAAAL/5AAAAL/8QAP/zAAAAj/wAAAAf/0AAAAv/sAAABf/xAAAB//cAAADv+wAAAK/+AAAAn/8AAACP/yAAAI//IAAAn/8AAACv/wAAAO/7AAAB//cAAAb/8gAAC/+wAAAf/0AAAI/8AAAA//MAAAAAL6AAAAAAL6AAABxgL6AqgD3+j8r/kABM//+BAABM//+BAD3+j8r/kB1gL6AqgAAAL6AAAAAAL6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAACv////////EACv////////EACv////////EAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAAAAAAH/gAAAAADP/xAAz/8QAM//EADf/QAC//MABv9wAAv8AAAN////AN////AN////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz/8QAM//EADP/xAAz/8QAAAI/QAAAN+AAAAv8wAAB/4AAADPkAAAH/QAAAb/AAAAv6AAAA/1AAAF/xAAAK+wAAAO9gAABP8QAACfwAAADvcAAAP/IAAAj9AAAA34AAAAAABK7+xgAAAAn////9EAAI//YD7/wAAf/7AAb/9gB//3AAL//AC//0AAD//wDf/zAADv/yDv/zAADv/zDv/zAADv/zDf/zAADv/yC//0AAD//wB//3AAL//AAf/7AAb/9gAI//YD7/wAAAn////9EAAABK7+xgAAAASM//8gAAAI////8gAAAHtzz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAAAAz/8gAAAG///////AAG///////AADe9/+tQAAA//////7AAA/+iBO//kAA/UAAE//8AA0AAAA//8wAAAAAA7/8wAAAAAB//8QAAAAAI//oAAAAABP/+IAAAAAT//zAAAAAG//9AAAAAB//+MAAAAAn//SAAAAAK//wQAAAABP//////9gBP//////9gABa9/+xxAAAN/////+MAALgxBN//0AAAAAAC//8QAAAAAA//8gAAAAAC//4AAAAABN//UAAADP///DAAAADP///8IAAAAAE7//4AAAAAAA3/9gAAAAAAr/+AAAAAAA3/9gB6UgA6//4QCP/////+MAAFnO/9tgAAAAAAHv//AAAAAAv///AAAAAG////AAAAA//v//AAAADf9e//AAAAr/oO//AAAF/+AO//AAAv/0AO//AADP+QAO//AAD/0AAO//AAD////////0D////////0AAAAAO//AAAAAAAO//AAAAAAAO//AAAAAAAO//AAAK//////kAAK//////kAAK/6AAAAAAAK/6AAAAAAAK/6AAAAAAAK/6AAAAAAAK//7/yBAAAK//////QAAJhBAq//8gAAAAAAz/+AAAAAAAf/+wAAAAAAf/+wAAAAAAz/+ABMcxAq//8QBP/////+MAADjN/+twAAAAAFrv7JMAAAHP////8QAA3/5iAlsQAI//IAAAAAAP/5AAAAAABP/5rf61AACP//////kACf//sRz/9gCf//EAL//QCf/+AAD//wBv/9AADv/xA//+AAD//wAN//EAL/+wAE//sRz/8wAAb/////UAAAAp3/2SAACP//////+QCP//////+QAAAAAA//9wAAAAAG//8QAAAAAN//kAAAAABP//IAAAAAC//6AAAAAAL//zAAAAAAj/+wAAAAAA7/9AAAAAAG//0AAAAAAN//UAAAAAA//+AAAAAACv/3AAAAAAH//wAAAAAAj/+AAAAAAAGM7/2jAAAE//////gAAO//YD7/8wA//8AAf/+ABP/6AAX/+AAf/8AAf/9QAI//YD7/wAAAbv///5AAABn////7MAAO/+QCz/8wB//3AAL/+wCf/0AAD//gCP/2AAL//QA//+QCz/+AAH//////sAAAOd//2lAAAAB87+pAAAAC3////5AAAO/+MX//cAB//3AA3/8QC//zAAn/9wDf/yAAj/+gDP/zAAn//QCf/2AA3//gAv/+MX///QAG///////AAAOu/sf/+QAAAAAAX/9AAAAAAA3/wAAKcwFM//IAAM/////jAAABfO/rYAAAAI//UAAI//UAAI//UAAI//UAAAAAAAAAAAAAAAAAAAAAAAAAAI//UAAI//UAAI//UAAI//UAAI//UAAI//UAAI//UAAI//UAAAAAAAAAAAAAAAAAAAAAAAAAAI//UAAI//UAAI//UAAK//IAAO/2AAA/+wAAB/4QAAAAAAAAAAABfBAAAAAAABa//xAAAAAAW////BAAAAWv///HEAAAOf///HEAAAAAr//GEAAAAAAAr/+2EAAAAAAAOv///GEAAAAAAAWv///GEAAAAAAAW////BAAAAAAABbP/xAAAAAAAAABfBAAAAAAAAAAAAAAr////////xAAr////////xAAr////////xAAAAAAAAAAAAAAAAAAAAAAAAAAr////////xAAr////////xAAr////////xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmTAAAAAAAAAAr/6DAAAAAAAAf///2CAAAAAAAErv//1yAAAAAAAErv//xgAAAAAAAEnv/xAAAAAAAEnv/xAAAAAEnv//xgAAAEnv//1yAAAAf///2CAAAAAAr/6DAAAAAAAAmTAAAAAAAAAAAAAAAAAAAAAAWt/+tAAAf/////gAB6QgPv/yAAAAAJ//QAAAAB7/8gAAAB3/+wAAAB3/+wAAAAv/+wAAAAL//gAAAABP/6AAAAAAAAAAAAAAAAAAAAAABP/5AAAAAE//kAAAAAT/+QAAAABP/5AAAAAAAASd/9thAAAAAAADz/////+AAAAAAG/9cwAVr/wQAAAF/4AAAAAD38AAAC/2AAAAAAAd9wAAr6AAbf1q+QP+AAD/IAj///75AN8wBfsAL/sRv/kAn2AH+QBv8QAf+QCfYAj3AI/wAA75AM9AB/gAb/EAH/kD/wAF+wAv+xG/+V74AAH/EAj///7//5AAAK+QAG39av6kAAAAL/YAAAAAAAAAAAAG/3AAAAAHcAAAAAB//XIAJt/xAAAAAATf/////mAAAAAAAAWt/+pgAAAAAAAC///yAAAAAACP//+AAAAAAA7///4AAAAABP////QAAAAAr/9//6AAAAAP/+Df/wAAAAX/+Aj/9gAAAL//IC//wAAAH//QAN//IAAH//cAB//4AADf///////QAD////////8wCf/yAAAC//kA7/4AAAAN//Bf/6AAAACf/1v/9gAAAAX/+wD/////2TAAAA///////3AAAP//IAX//yAAD//yAAv/9wAA//8gAJ//gAAP//IAC//2AAD//yAF//8QAA///////TAAAP//////+QAAD//yACv/+QAA//8gAB//8AAP//IAAP//IAD//yAAH//yAA//8gAr//0AAP///////jAAD/////7HEAAAAAKM7/24MAAAn/////+wAAz//EEDj7AAr/+wAAABgAP//xAAAAAACP/7AAAAAAAM//cAAAAAAA3/9gAAAAAADf/2AAAAAAAM//cAAAAAAAj/+wAAAAAAA///EAAAAAAAr/+wAAABgAAM//xAAo+wAACf/////7AAAAKM7/3IMAD////tuCAAAAD///////sQAAD//yAUv//iAAD//yAACf/9AAD//yAAAN//YAD//yAAAH//wAD//yAAAE//8AD//yAAAC//8QD//yAAAD//8QD//yAAAE//8AD//yAAAH//wAD//yAAAN//YAD//yAACf/9AAD//yAUv//iAAD///////sQAAD////9uCAAAAD///////MAD///////MAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//////8AAD//////8AAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD///////YAD///////YAD///////MAD///////MAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//////8AAD//////8AAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAD//yAAAAAAAAAnvv/ttzAAAAn//////9AAAM//1RAVr9AACv/8AAAAApAAP//yAAAAAAAAj/+wAAAAAAAAz/9wAAAAAAAA3/9gAA////cA3/9gAA////cAz/9wAAAH//cAj/+wAAAH//cAP//xAAAH//cACv/7AAAH//cAAM//xRAq//cAAAn///////cAAAAozv/spzAAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD/////////YAD/////////YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAAAM//YAD//yAA//8gAP//IAD//yAA//8gAP//IAD//yAA//8gAP//IAD//yAA//8gAP//IAD//yAA//8gAP//IAD//yAAAP//IAAA//8gAAD//yAAAP//IAAA//8gAAD//yAAAP//IAAA//8gAAD//yAAAP//IAAA//8gAAD//yAAAP//IAAA//8gAAD//yAAAP//EAAE//8AAD3/+QA///+wAD/9pQAAAP//IAAG//9gAP//IABv//YAAP//IAb//2AAAP//IH//9QAAAP//J///UAAAAP//r//1AAAAAP////9QAAAAAP////cAAAAAAP////4wAAAAAP/////jAAAAAP//Tv//MAAAAP//Iu//8wAAAP//IC7//zAAAP//IAHf//MAAP//IAAd//8wAP//IAAB3//0AP//IAAAAAD//yAAAAAA//8gAAAAAP//IAAAAAD//yAAAAAA//8gAAAAAP//IAAAAAD//yAAAAAA//8gAAAAAP//IAAAAAD//yAAAAAA//8gAAAAAP//IAAAAAD//yAAAAAA///////2AP//////9gD///cAAACf//0AAP///gAAAf///QAA////UAAH///9AAD////AAA3///0AAP////MAT/7//QAA//+f+QC/9//9AAD//y//Ev/x//0AAP//C/95/5D//QAA//8E/+//IP/9AAD//wDf//sA//0AAP//AG//9QD//QAA//8AD//gAP/9AAD//wAJ/3AA//0AAP//AAAAAAD//QAA//8AAAAAAP/9AAD//wAAAAAA//0AAP//4AAAj/9gAP//9wAAj/9gAP///gAAj/9gAP///4AAj/9gAP////EAj/9gAP//z/kAj/9gAP//T/8Qj/9gAP//C/+gj/9gAP//A//yj/9gAP//AK/7j/9gAP//AC//z/9gAP//AAn///9gAP//AAH///9gAP//AACP//9gAP//AAAf//9gAP//AAAH//9gAAAEnf/sggAAAAAc//////kAAAAe//gQO//7AAAM//cAAAz/9wAE//4AAABP/+AAn/+gAAAA7/9ADP/3AAAADP/3AN//YAAAAL//gA3/9gAAAAv/+ADP/3AAAADP/3AJ//oAAAAO//QAT//gAAAE//4AAM//cAAAz/9wAAHv/4EDv/+wAAABz/////+QAAAAAEnf/sggAAAA/////+pQAAD///////sAAP//IATv/5AA//8gAF//8AD//yAAH//yAP//IAAf//IA//8gAF//8AD//yAE7/+QAP//////+wAA/////+pQAAD//yAAAAAAAP//IAAAAAAA//8gAAAAAAD//yAAAAAAAP//IAAAAAAA//8gAAAAAAAABJ3v7IIAAAAAHP/////5AAAAHv/4EDv/+wAADP/3AAAM//cABP/+AAAAT//gAJ//oAAAAO//QAz/9wAAAAz/9wDf/2AAAAC//4AN//YAAAAL//kAz/9wAAAAz/9wCf/6AAAADv/0AE//4AAABP//AADP/3AAAM//gAAB7/+BA7//wAAAAc//////sAAAAABJ3v//cAAAAAAAAAC//hAAAAAAAAAB3/0QAAAAAAAAAe/9EAAA/////9kgAAAP//////9QAAD//yAY//8QAA//8gAO//UAAP//IAC//2AAD//yAAv/9QAA//8gAO//IAAP//IBj/+AAAD//////nAAAA//////6AAAAP//ID7/+AAAD//yAD//8wAA//8gAK//sAAP//IAAv//MAD//yAACv/7AA//8gAAL//0AAF87/7bgQAALv/////zAADf/lECjvMAA//3AAAAggAG//YAAAAAAAb//TAAAAAAAv///aYwAAAAr/////1QAAAJ//////cAAAAWrv///wAAAAAAOv//MAAAAAAA3/8wBIAAAADf/wAE/5QQKf/6AAT//////9EAAWnN7/62AADv////////7v////////4AAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAAAAn/+QAAAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAT//QAA//8gAAX//AAA3/9AAAb/+wAAn/+AAAr/9wAAL//2EX//4QAABf/////+MAAAABfO/+twAAC//3AAAABv/7X//AAAAAz/9Q7/8gAAAv//AJ//gAAAf/+QA//+AAAN//MADf/zAAP//QAAf/+QAJ//gAAB//4ADv/yAAAL//UE//wAAABf/6Cv/2AAAAD//x//8AAAAAr//P/6AAAAAE////9AAAAAAO///+AAAAAACP//+AAAAAAAL///IAAAP/+wAAD//zAAB//3D//wAAP//3AAC//zDP/zAAf//7AAD//wCP/3AAv///AAP/+wBP/7AA7/v/MAb/+AAP/+AC/7f/YAr/9AAM//IG/3P/oA7/8AAI//YK/0D/4C//wAAF//oN/wDP8m//kAAB//4f/ACP9Z//UAAA3/9/+ABP+d//EAAAn//v9AAP/v/9AAAAX///8QAM///5AAAAL///0AAJ///2AAAADv//kAAF///yAAAACv//UAAB///gAAHv/0AAAF//4QX//hAAH//0AAr/+gAL//kAAB7/9Qb//QAAAE//4///MAAAAJ////+AAAAAAN///9AAAAAABP//8wAAAAAAf///YAAAAAAv////IAAAAAz//f/8AAAACP/8Df/3AAAD//8gP//yAADf/3AAf//AAI//wAAAz/9wP//yAAAC//8w3/+QAAAL//wAP//0AABf//IACf/9AAHv/3AAAN//gAr//QAAAE//80//8wAAAAn//N//gAAAAADv///9AAAAAABP///zAAAAAAAJ//+AAAAAAAAC//8AAAAAAAAB//8AAAAAAAAB//8AAAAAAAAB//8AAAAAAAAB//8AAAAAAAAB//8AAAAAAAAB//8AAAAAz///////+wDP///////7AAAAAADP//sAAAAACP//8gAAAABf//9QAAAAAv//+QAAAAAM///AAAAAAJ///iAAAAAF///0AAAAAC///4AAAAAA3//8AAAAAAr//+EAAAAAb///QAAAAAD///cAAAAAAP////////8A/////////wAf///5AB////kAH/+AAAAf/4AAAB//gAAAH/+AAAAf/4AAAB//gAAAH/+AAAAf/4AAAB//gAAAH/+AAAAf/4AAAB//gAAAH/+AAAAf/4AAAB//gAAAH/+AAAAf///5AB////kN+AAAAI/QAAAD/yAAAA73AAAAn8AAAAT/EAAADvYAAACvsAAABf8QAAAP9QAAAL+gAAAG/wAAAB/0AAAAz5AAAAf+AAAAL/MAAADfgAAACP0Aj///8gCP///yAAAH//IAAAf/8gAAB//yAAAH//IAAAf/8gAAB//yAAAH//IAAAf/8gAAB//yAAAH//IAAAf/8gAAB//yAAAH//IAAAf/8gAAB//yAAAH//IAj///8gCP///yAAAAAJ/+EAAAAAAACP//0QAAAAAAf//v/RAAAAAG/+Qb/8AAAABf/BAAb/sAAAX/gAAAA9+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wB/9gAAAACP8wAAAACP0QAAAACPsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACe+/+xwAAAP/////+EAAMYxAX//oAAAAAAA3/8AABjO////8QA+//////8QDP/4EAz/8QD//gAA7/8QD//gAE//8QDP/3A+//8QBP///8z/8QADv/1wz/8QAv/7AAAAAAAC//sAAAAAAAL/+wAAAAAAAv/7AAAAAAAC//sAAAAAAAL/+wff6iAAAv/7z///4wAC///CBv/9AAL//yAAv/9QAv/9AABf/5AC//sAAE//sAL/+wAAT/+wAv/9AABf/5AC//8gAK//UAL//8IG//0AAv/7z///4wAC//sH3+ogAAAAa+/sYQAC3////5AB7/+BAncAn/+QAAAADv/yAAAAAP//AAAAAA//8AAAAADv/yAAAAAJ//kAAAAAHv/4ECdwAD3////5AAAGvv7HEAAAAAAA//4AAAAAAAD//gAAAAAAAP/+AAAAAAAA//4AAAAAAAD//gAABL/8UP/+AABv///4//4AAv/+QE7//gAJ//YAB//+AA3/8QAB//4AD//wAAD//gAP//AAAP/+AA3/8QAB//4ACf/2AAb//gAC//4wTv/+AABv///4//4AAAS//FD//gAAAHvv7GAAAAPv////0QAB7/5ATv/AAJ//UABv/1AO//AAAv/6AP///////8AP///////9AO//AAAAAAAJ//UAAAAAAB7/5hAjfEAAPv/////0AAAGvv/bcwAAAq7//AAe///8AH//gAAAr/8wAACv/yAAn/////ef////9wCv/yAAAK//IAAAr/8gAACv/yAAAK//IAAAr/8gAACv/yAAAK//IAAAr/8gAACv/yAAAAO//FD//gAAb///+P/+AAL//kBO//4ACf/3AAf//gAN//IAAv/+AA//8AAA//4AD//wAAD//gAN//EAAv/+AAn/9gAH//4AAv/+ME7//gAAb///+P/+AAADv/xQ//0AAAAAAAL/+wAAAAAACP/2AAB5QQKP/9AAAI/////8EAAABb3/2lAAAAL/+wAAAAAAAv/7AAAAAAAC//sAAAAAAAL/+wAAAAAAAv/7AAAAAAAC//sH3+swAAL/+8////MAAv//0xn/+gAC//8gAP/+AAL//QAA//4AAv/7AADv/wAC//sAAO//AAL/+wAA7/8AAv/7AADv/wAC//sAAO//AAL/+wAA7/8AAv/7AADv/wAC//sAAv/7AAL/+wAC//sAAAAAAAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAAAAAAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/7AAAv/6AAA//5AAGv/1AL///AAL/9gAAAL/+wAAAAAAAv/7AAAAAAAC//sAAAAAAAL/+wAAAAAAAv/7AAAAAAAC//sABv/+IAL/+wBv/9IAAv/7Bv/9EAAC//tv/8EAAAL//v/7AAAAAv///+AAAAAC////+gAAAAL/+8//oAAAAv/7Hf/6AAAC//sB3/+QAAL/+wAu//kAAv/7AALv/4AC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wAC//sAAv/7AAL/+wjv1wBc/8UAAC//vf//+o////YAAv//sR3//4FP/+AAL//xAI//0ADP/yAC//wAB//4AAr/8gAv/7AAb/9wAK//MAL/+wAG//cAC//zAC//sABv/3AAv/8wAv/7AAb/9wAL//MAL/+wAG//cAC//zAC//sABv/3AAv/8wAv/7AAb/9wAL//MAL/+wff6zAAAv/7z///8wAC///TGf/6AAL//yAA//4AAv/9AAD//gAC//sAAO//AAL/+wAA7/8AAv/7AADv/wAC//sAAO//AAL/+wAA7/8AAv/7AADv/wAC//sAAO//AAABfO/sgQAAA+/////0AAHv/kA9//MAn/9gAF//sA7/8QAA//8A//8AAA7/8g//8AAA7/8g7/8QAA//8An/9gAF//sAHv/kA9//MAA+/////0AAABfO/8gQAAL/+wff6iAAAv/7z///4wAC///CBv/9AAL//yAAv/9QAv/9AABf/5AC//sAAE//sAL/+wAAT/+wAv/9AABf/5AC//8gAK//UAL//8IG//0AAv/7z///4wAC//sH3+ogAAL/+wAAAAAAAv/7AAAAAAAC//sAAAAAAAL/+wAAAAAAAv/7AAAAAAAABL/8UP/+AABv///4//4AAv/+QE7//gAJ//YAB//+AA3/8QAB//4AD//wAAD//gAP//AAAP/+AA3/8QAB//4ACf/2AAb//gAC//4wTv/+AABv///4//4AAAS//FD//gAAAAAAAP/+AAAAAAAA//4AAAAAAAD//gAAAAAAAP/+AAAAAAAA//4AAv/7CN/AL/+9//wC///kAAAv//MAAAL//QAAAC//sAAAAv/7AAAAL/+wAAAC//sAAAAv/7AAAAL/+wAAAC//sAAAACnf/shAAC//////QAr/0gE3wwDf+gAAAAAM//6oUQAAb/////sQAH/////6AAAEac//8AAAAAC//wCaUhA+/9AK//////QAFazv/ZIAAM//AAAADP/wAAAAz/8AAAv/////8L//////AAz/8AAAAM//AAAADP/wAAAAz/8AAAAM//AAAADP/wAAAAz/8QAAAK//YAAABf///6AABs//+gAE//kAAP/9AAT/+QAA//0ABP/5AAD//QAE//kAAP/9AAT/+QAA//0ABP/5AAD//QAE//kAAP/9AAT/+QAC//0AA//7AAf//QAA//9AX//9AACP///3//0AAAbP+0D//QB//1AAAP/9Af/7AABv/2AL//EAC//xAE//YAH/+gAA7/wAf/8wAAj/8g3/0AAAH/+C//cAAAC//o//EAAABf///6AAAAAO///0AAAAAI///QAAAAAC//9wAAAf/6AAn/4ABP/2Df/gAM//IAj/8gn/8QD//2AM/+AE//UE//+gD/+gAP/5CP+v4E//UADP/Qz7b/KP/xAAj/8v9y/2z/0AAD//r/MO+v/5AAAP///wCv//9QAAC///wAb///AAAAf//4AC///AAAAD//9AAO//gAAv/+EADP/0Bf/6AH//gACf/1P//AAADf/t/+EAAAL///9AAAAAb//5AAAAAJ///AAAAAX///+AAAAv/9v/9AAAz/8x7/4QCP/3AE//sE//wAAJ//eP/0AAAf/8H/+wAAb/9gr/8QAM//AE//cAH/+gAN/+AG//QABv/0DP/gAAD/+x//kAAAn/+P/zAAAC////0AAAAL///3AAAABf//8QAAAADv/7AAAAAAj/9gAAAAAJ//AAAAAAT/+QAAAAz//9AAAAAM/+kQAAAAz//////ADP/////8AAAAAv//sAAAAe//8wAAAd//9QAAAM//9gAAAK//+AAAAJ//+gAAAH//+wAAAA///QAAAAD//////8AP//////wAAAAAW+/+AAAAAAX///4AAAAADP/1AAAAAAAO/9AAAAAAAA7/wAAAAAAADv/AAAAAAAAO/7AAAAAAAB//oAAAAAACv/9gAAAABP//+AAAAAAE///6AAAAAAACv/9wAAAAAAAv/6AAAAAAAA//sAAAAAAADv/AAAAAAAAO/8AAAAAAAA7/0AAAAAAADP/1AAAAAAAF///+AAAAAABb7/4AAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAD/zAAA/8wAAP/MAAE//6iAAAAAAT///4QAAAAAAGv/3AAAAAAAC//gAAAAAAAH/+QAAAAAAAf/5AAAAAAAB//kAAAAAAAD//AAAAAAAAL//cQAAAAAAK///4AAAAAAt///gAAAAAM//gQAAAAAA//wAAAAAAAH/+QAAAAAAAf/5AAAAAAAB//kAAAAAAAL/+AAAAAAAGv/2AAAAAE///+EAAAAAT//qIAAAAAAAAAAAAAAAAAAFvv63MQJ+EACf////////EACv////////EACtUQJZ3/2BAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAB+/QAAAAAAB+//0AAAAAB+///9AAAAB+/////QAAB+//////0AB+///////9B+/////////W//////////0Bj////////9AAGP///////QAAAY//////0AAAABj////9AAAAAAGP///QAAAAAAAY//0AAAAAAAABj9AAAAAAAAAAFgAAAAAAAAAADWAAAAAAAAAA/+YAAAAAAAAP//5gAAAAAAD////mAAAAAA/////+YAAAAP//////5gAAD////////mAA/////////+YP/////////+X////////+cA///////+cAAP/////+cAAAD////+cAAAAA///+cAAAAAAP/+cAAAAAAAD+cAAAAAAAAAcAAAAAAAAAAAAiIiIiIiIiIhAAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAD//////////8AAC7u7u7u7u7u4AA==" # labels / buttons (DejaVuSans-Bold 22) +FONT_L_B64 = "CwAwNjsADzYAMTY5ABA2ADI2OgAPNgAzNjsADzYANDY5ABA2ADU2OgAQNgA2NjsADzYANzY5ABA2ADg2OwAPNgA5NjsADzYAIAAAAAAbAAAAAAAAAAAAACab3v/+ynQAAAAAAAAAAAAAAAAAAAAAAAADjv/////////6QAAAAAAAAAAAAAAAAAAAAAO//////////////VAAAAAAAAAAAAAAAAAAAJ////////////////wgAAAAAAAAAAAAAAAALf/////////////////1AAAAAAAAAAAAAAAD7///////////////////YAAAAAAAAAAAAAAv////////////////////9gAAAAAAAAAAAAHv/////////////////////0AAAAAAAAAAAAv//////////////////////+EAAAAAAAAAAH////////////////////////sAAAAAAAAAAf/////////9YgAVv/////////9QAAAAAAAACf////////+AAAAABf/////////QAAAAAAAAH/////////kAAAAAAE/////////1AAAAAAAAj////////9AAAAAAAAn////////8AAAAAAAA7////////1AAAAAAAAH/////////MAAAAAAE/////////gAAAAAAAAC/////////gAAAAAAJ////////+gAAAAAAAABv////////0AAAAAAN////////9gAAAAAAAAAv////////8gAAAAAf////////8gAAAAAAAAAO////////9gAAAABf////////8AAAAAAAAAAM////////+QAAAACP////////0AAAAAAAAAAJ/////////AAAAACv////////wAAAAAAAAAAH/////////wAAAADf////////oAAAAAAAAAAG/////////xAAAAD/////////kAAAAAAAAAAF/////////zAAAAD/////////gAAAAAAAAAAE/////////0AAAAH/////////cAAAAAAAAAAD/////////2AAAAL/////////YAAAAAAAAAAC/////////3AAAAP/////////YAAAAAAAAAAC/////////3AAAAP/////////YAAAAAAAAAAC/////////4AAAAT/////////YAAAAAAAAAAC/////////4AAAAP/////////YAAAAAAAAAAC/////////4AAAAP/////////YAAAAAAAAAAC/////////3AAAAL/////////cAAAAAAAAAAC/////////3AAAAL/////////cAAAAAAAAAAD/////////2AAAAD/////////gAAAAAAAAAAE/////////0AAAAD/////////kAAAAAAAAAAF/////////zAAAADf////////oAAAAAAAAAAG/////////xAAAACv////////wAAAAAAAAAAI/////////wAAAACP////////4AAAAAAAAAAK/////////AAAAABf////////8AAAAAAAAAAM////////+QAAAAAf////////8wAAAAAAAAAP////////9gAAAAAN////////9gAAAAAAAAAv////////8gAAAAAJ////////+gAAAAAAAABv////////0AAAAAAE/////////wAAAAAAAAC/////////gAAAAAAA7////////2AAAAAAAAL/////////MAAAAAAAj////////9AAAAAAAAr////////8AAAAAAAAH/////////kAAAAAAF/////////1AAAAAAAACf////////+QAAAABf/////////QAAAAAAAAAf/////////9YgAVv/////////9QAAAAAAAAAH////////////////////////sAAAAAAAAAAAz//////////////////////+EAAAAAAAAAAAHv/////////////////////0AAAAAAAAAAAAAv////////////////////9gAAAAAAAAAAAAAD7///////////////////YAAAAAAAAAAAAAAALf/////////////////1AAAAAAAAAAAAAAAAAJ////////////////wgAAAAAAAAAAAAAAAAAAO//////////////VAAAAAAAAAAAAAAAAAAAAADnv/////////6QAAAAAAAAAAAAAAAAAAAAAAAADac7v/+ynQAAAAAAAAAAAAAAAAAAAAAAAAANq7/////////AAAAAAAAAAAAAAAAAAAAAUi/////////////AAAAAAAAAAAAAAAAAAJp3///////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////////////////AAAAAAAAAAAAAAAAAD//////2VLv////////AAAAAAAAAAAAAAAAAD///rdAAADv////////AAAAAAAAAAAAAAAAAD2WIAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAAAAAAAAAADv////////AAAAAAAAAAAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAA3/////////////////////////4AAAAAAAAAAAAAFGis3e///suGIAAAAAAAAAAAAAAAAAAAAVi+/////////////pQAAAAAAAAAAAAAAABJ3//////////////////VAAAAAAAAAAAAAAr/////////////////////wwAAAAAAAAAAAAr//////////////////////3AAAAAAAAAAAAr///////////////////////gAAAAAAAAAAAr///////////////////////+AAAAAAAAAAAr////////////////////////0AAAAAAAAAAr////////////////////////+AAAAAAAAAAr/////////////////////////cAAAAAAAAAr//////7dCABJZ////////////4AAAAAAAAAr////7UAAAAAAAGf//////////9AAAAAAAAAr///kgAAAAAAAAAF//////////+QAAAAAAAAr/+RAAAAAAAAAAAAb//////////AAAAAAAAAr6IAAAAAAAAAAAAAC//////////gAAAAAAAAhAAAAAAAAAAAAAAAA//////////wAAAAAAAAAAAAAAAAAAAAAAAAAO/////////xAAAAAAAAAAAAAAAAAAAAAAAAAM/////////xAAAAAAAAAAAAAAAAAAAAAAAAAK/////////wAAAAAAAAAAAAAAAAAAAAAAAAAK/////////gAAAAAAAAAAAAAAAAAAAAAAAAAM////////+wAAAAAAAAAAAAAAAAAAAAAAAAAP////////9wAAAAAAAAAAAAAAAAAAAAAAAABf////////8gAAAAAAAAAAAAAAAAAAAAAAAADP////////wAAAAAAAAAAAAAAAAAAAAAAAAAX/////////QAAAAAAAAAAAAAAAAAAAAAAAAB7////////8AAAAAAAAAAAAAAAAAAAAAAAAAM/////////yAAAAAAAAAAAAAAAAAAAAAAAAC/////////9gAAAAAAAAAAAAAAAAAAAAAAAAv/////////oAAAAAAAAAAAAAAAAAAAAAAAAc/////////7AAAAAAAAAAAAAAAAAAAAAAAALf/////////AAAAAAAAAAAAAAAAAAAAAAAAD7/////////0QAAAAAAAAAAAAAAAAAAAAAABP/////////8EAAAAAAAAAAAAAAAAAAAAAAAb//////////BAAAAAAAAAAAAAAAAAAAAAAAI//////////sAAAAAAAAAAAAAAAAAAAAAAACv/////////6AAAAAAAAAAAAAAAAAAAAAAABz/////////+AAAAAAAAAAAAAAAAAAAAAAAAt//////////YAAAAAAAAAAAAAAAAAAAAAAAPv/////////0AAAAAAAAAAAAAAAAAAAAAAAE//////////4wAAAAAAAAAAAAAAAAAAAAAABv/////////9EAAAAAAAAAAAAAAAAAAAAAAAj/////////+wAAAAAAAAAAAAAAAAAAAAAAAK//////////kAAAAAAAAAAAAAAAAAAAAAAAHP/////////3AAAAAAAAAAAAAAAAAAAAAAAC3/////////9QAAAAAAAAAAAAAAAAAAAAAAA+/////////+QAAAAAAAAAAAAAAAAAAAAAAAT//////////iAAAAAAAAAAAAAAAAAAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAA3//////////////////////////4AAAAAAAAAAAAATaJvN7v//7cuXQQAAAAAAAAAAAAAAAAACWc///////////////9gwAAAAAAAAAAAAAABf///////////////////9YAAAAAAAAAAAAABf/////////////////////UAAAAAAAAAAAABf//////////////////////gAAAAAAAAAAABf//////////////////////+QAAAAAAAAAABf///////////////////////2AAAAAAAAAABf////////////////////////EAAAAAAAAABf////////////////////////kAAAAAAAAABf////////////////////////8AAAAAAAAABf///9qFMhABJHz///////////9AAAAAAAAABf/6YQAAAAAAAAA8//////////9wAAAAAAAABbUAAAAAAAAAAAAAn/////////+gAAAAAAAAAAAAAAAAAAAAAAAADP////////+wAAAAAAAAAAAAAAAAAAAAAAAABP////////+wAAAAAAAAAAAAAAAAAAAAAAAAAP////////+gAAAAAAAAAAAAAAAAAAAAAAAAAP////////+AAAAAAAAAAAAAAAAAAAAAAAAAAP////////9QAAAAAAAAAAAAAAAAAAAAAAAABP////////8QAAAAAAAAAAAAAAAAAAAAAAAADP////////sAAAAAAAAAAAAAAAAAAAAAAAAAj/////////MAAAAAAAAAAAAAAAAAAAAAAAA7/////////5AAAAAAAAAAAAAAAAAAAAABNY3/////////+wAAAAAAAAAAAAAAAA//////////////////oAAAAAAAAAAAAAAAAA/////////////////mAAAAAAAAAAAAAAAAAA///////////////9cAAAAAAAAAAAAAAAAAAA//////////////1AAAAAAAAAAAAAAAAAAAAA///////////////7UAAAAAAAAAAAAAAAAAAA/////////////////EAAAAAAAAAAAAAAAAAA//////////////////kAAAAAAAAAAAAAAAAA///////////////////AAAAAAAAAAAAAAAAA///////////////////7AAAAAAAAAAAAAAAA////////////////////gAAAAAAAAAAAAAAAAAAAARNp3///////////8gAAAAAAAAAAAAAAAAAAAAAAA6//////////+gAAAAAAAAAAAAAAAAAAAAAAAAT//////////xAAAAAAAAAAAAAAAAAAAAAAAAA//////////2AAAAAAAAAAAAAAAAAAAAAAAAAH/////////6AAAAAAAAAAAAAAAAAAAAAAAAAB/////////9AAAAAAAAAAAAAAAAAAAAAAAAAAz////////+AAAAAAAAAAAAAAAAAAAAAAAAAAr/////////AAAAAAAAAAAAAAAAAAAAAAAAAAr/////////AAAAAAAAAAAAAAAAAAAAAAAAAAz/////////AAAAAAAAAAAAAAAAAAAAAAAAAA//////////AAAAAAAAAAAAAAAAAAAAAAAAAH/////////8AAAAAAAKcQAAAAAAAAAAAAAAAv/////////6AAAAAAAM/5MAAAAAAAAAAAAAPv/////////1AAAAAAAM///pQAAAAAAAAAAq///////////xAAAAAAAM/////8lkIQABJY3///////////+gAAAAAAAM//////////////////////////8gAAAAAAAM//////////////////////////gAAAAAAAAM/////////////////////////8AAAAAAAAAM/////////////////////////RAAAAAAAAAM////////////////////////wQAAAAAAAAAM///////////////////////4AAAAAAAAAAAM//////////////////////swAAAAAAAAAAABa///////////////////+jAAAAAAAAAAAAAAAASL//////////////6lAAAAAAAAAAAAAAAAAAAAAleb3e///+y5dBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//////////kAAAAAAAAAAAAAAAAAAAAAAAC///////////kAAAAAAAAAAAAAAAAAAAAAAAb///////////kAAAAAAAAAAAAAAAAAAAAAAC////////////kAAAAAAAAAAAAAAAAAAAAAAM////////////kAAAAAAAAAAAAAAAAAAAAAB/////////////kAAAAAAAAAAAAAAAAAAAAAL/////////////kAAAAAAAAAAAAAAAAAAAAAz/////////////kAAAAAAAAAAAAAAAAAAAAH//////////////kAAAAAAAAAAAAAAAAAAAA///////////////kAAAAAAAAAAAAAAAAAAADf//////////////kAAAAAAAAAAAAAAAAAAAj///////////////kAAAAAAAAAAAAAAAAAAD//////+/////////kAAAAAAAAAAAAAAAAAAN//////xP////////kAAAAAAAAAAAAAAAAACf//////JP////////kAAAAAAAAAAAAAAAAAP//////2BP////////kAAAAAAAAAAAAAAAAA3//////ABP////////kAAAAAAAAAAAAAAAAJ//////8gBP////////kAAAAAAAAAAAAAAABP//////YABP////////kAAAAAAAAAAAAAAAHv/////7AABP////////kAAAAAAAAAAAAAAAr//////xAABP////////kAAAAAAAAAAAAAAE//////9gAABP////////kAAAAAAAAAAAAAAe//////sAAABP////////kAAAAAAAAAAAAACv//////EAAABP////////kAAAAAAAAAAAAAX//////2AAAABP////////kAAAAAAAAAAAAB7/////+wAAAABP////////kAAAAAAAAAAAAL//////4QAAAABP////////kAAAAAAAAAAABv//////UAAAAABP////////kAAAAAAAAAAAH//////6AAAAAABP////////kAAAAAAAAAAAv//////hAAAAAABP////////kAAAAAAAAAAG//////9QAAAAAABP////////kAAAAAAAAAAv//////oAAAAAAABP////////kAAAAAAAAADP/////+EAAAAAAABP////////kAAAAAAAAAb//////1AAAAAAAABP////////kAAAAAAAAAj/////+gAAAAAAAABP////////kAAAAAAAAAj/////4QAAAAAAAABP////////kAAAAAAAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAj//////////////////////////////7AAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAAAAAAAAAAAAAABP////////kAAAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////////////////////7AAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAL///////yWc3v/+y4UgAAAAAAAAAAAAAAAAAL/////////////////9ggAAAAAAAAAAAAAAAL///////////////////8QAAAAAAAAAAAAAAL////////////////////+hAAAAAAAAAAAAAL/////////////////////+QAAAAAAAAAAAAL//////////////////////9gAAAAAAAAAAAL///////////////////////1AAAAAAAAAAAL////////////////////////MAAAAAAAAAAL////////////////////////0AAAAAAAAAAL////////////////////////9wAAAAAAAAAL////2nUyEAE2r////////////gAAAAAAAAAL/+lRAAAAAAAAAY///////////2AAAAAAAAAKlAAAAAAAAAAAAALf/////////8AAAAAAAAAAAAAAAAAAAAAAAAAd//////////EAAAAAAAAAAAAAAAAAAAAAAAAD//////////QAAAAAAAAAAAAAAAAAAAAAAAAAr/////////gAAAAAAAAAAAAAAAAAAAAAAAAAX/////////oAAAAAAAAAAAAAAAAAAAAAAAAAD/////////sAAAAAAAAAAAAAAAAAAAAAAAAAD/////////wAAAAAAAAAAAAAAAAAAAAAAAAADf////////0AAAAAAAAAAAAAAAAAAAAAAAAAD/////////wAAAAAAAAAAAAAAAAAAAAAAAAAD/////////sAAAAAAAAAAAAAAAAAAAAAAAAAX/////////kAAAAAAAAAAAAAAAAAAAAAAAAAr/////////cAAAAAAAswAAAAAAAAAAAAAAAD//////////QAAAAAAA/7QAAAAAAAAAAAAAAe//////////AAAAAAAA///WEAAAAAAAAAAALf/////////6AAAAAAAA////+1EAAAAAAAAY///////////0AAAAAAAA///////JYxAAE2r////////////QAAAAAAAA//////////////////////////9AAAAAAAAA//////////////////////////kAAAAAAAAA/////////////////////////8AAAAAAAAAA/////////////////////////RAAAAAAAAAA////////////////////////wQAAAAAAAAAA///////////////////////5AAAAAAAAAAAA//////////////////////1AAAAAAAAAAAAAJr///////////////////FAAAAAAAAAAAAAAAABHrv/////////////YMAAAAAAAAAAAAAAAAAAAABRom93v//7cp0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN5ze//7tyoYxAAAAAAAAAAAAAAAAAAAAAAa/////////////6nMAAAAAAAAAAAAAAAAAGf////////////////9QAAAAAAAAAAAAAAAI//////////////////9QAAAAAAAAAAAAAAPf//////////////////9QAAAAAAAAAAAAAG////////////////////9QAAAAAAAAAAAACP////////////////////9QAAAAAAAAAAAAj/////////////////////9QAAAAAAAAAAAG//////////////////////9QAAAAAAAAAAA///////////////////////9QAAAAAAAAAADv//////////6mMgASNov///9QAAAAAAAAAAj//////////FAAAAAAAAAEnv9QAAAAAAAAAC//////////YAAAAAAAAAAAAEpQAAAAAAAAAK/////////jAAAAAAAAAAAAAAAAAAAAAAAAAv////////9AAAAAAAAAAAAAAAAAAAAAAAAACf////////cAAAAAAAAAAAAAAAAAAAAAAAAADv///////+AAAAAAAAAAAAAAAAAAAAAAAAAAX////////2AAAAAAAAAAAAAAAAAAAAAAAAAAn////////wAAAAAAAAAAAAAAAAAAAAAAAAAA7///////+gAAAAAAAAAAAAAAAAAAAAAAAAAC////////9gAAFYze/+24UAAAAAAAAAAAAAAF////////8gA6/////////7QAAAAAAAAAAAAI////////8Cz////////////UAAAAAAAAAAAL////////6P//////////////oAAAAAAAAAAN/////////////////////////RAAAAAAAAAO/////////////////////////+IAAAAAAAAP//////////////////////////0QAAAAAAAf//////////////////////////+wAAAAAAAf///////////////////////////2AAAAAAAv///////////////////////////+AAAAAAAv////////////xiABSf//////////cAAAAAAf///////////2AAAAABz/////////0AAAAAAf//////////9gAAAAAAHf////////8wAAAAAP//////////wAAAAAAABP////////9wAAAAAP//////////UAAAAAAAAN////////+wAAAAAO//////////AAAAAAAAAI/////////QAAAAAM/////////8AAAAAAAAAF/////////wAAAAAK/////////6AAAAAAAAAC/////////wAAAAAH/////////5AAAAAAAAAB/////////xAAAAAF/////////4AAAAAAAAAA/////////wAAAAAB/////////5AAAAAAAAAB/////////wAAAAAA3////////6AAAAAAAAAC/////////gAAAAAAn////////8AAAAAAAAAF/////////AAAAAAAT/////////AAAAAAAAAI////////+QAAAAAADv////////UAAAAAAAAN////////9QAAAAAACf////////sAAAAAAABP////////8QAAAAAAAv////////9gAAAAAADf////////sAAAAAAAAK/////////2AAAAABz/////////UAAAAAAAAC//////////xiABSf/////////9AAAAAAAAAAj////////////////////////0AAAAAAAAAADP//////////////////////+gAAAAAAAAAAAu//////////////////////0AAAAAAAAAAAAD/////////////////////+IAAAAAAAAAAAAAT////////////////////SAAAAAAAAAAAAAAA+//////////////////wQAAAAAAAAAAAAAAABr////////////////3AAAAAAAAAAAAAAAAAAA8//////////////kQAAAAAAAAAAAAAAAAAAAAOe//////////xxAAAAAAAAAAAAAAAAAAAAAAAAN6zu//7LhRAAAAAAAAAAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM////////////////////////////EAAAAAAM///////////////////////////+AAAAAAAM///////////////////////////4AAAAAAAM///////////////////////////xAAAAAAAAAAAAAAAAAAAAAAAAAJ////////+QAAAAAAAAAAAAAAAAAAAAAAAAAf////////8gAAAAAAAAAAAAAAAAAAAAAAAACP////////sAAAAAAAAAAAAAAAAAAAAAAAAADv////////MAAAAAAAAAAAAAAAAAAAAAAAAAf////////8AAAAAAAAAAAAAAAAAAAAAAAAAA7////////1AAAAAAAAAAAAAAAAAAAAAAAAAF/////////QAAAAAAAAAAAAAAAAAAAAAAAAAN////////9gAAAAAAAAAAAAAAAAAAAAAAAABP////////4AAAAAAAAAAAAAAAAAAAAAAAAAC/////////gAAAAAAAAAAAAAAAAAAAAAAAAAL/////////EAAAAAAAAAAAAAAAAAAAAAAAAAr////////5AAAAAAAAAAAAAAAAAAAAAAAAAB/////////yAAAAAAAAAAAAAAAAAAAAAAAAAI////////+gAAAAAAAAAAAAAAAAAAAAAAAAAf////////8wAAAAAAAAAAAAAAAAAAAAAAAAB/////////wAAAAAAAAAAAAAAAAAAAAAAAAADv////////UAAAAAAAAAAAAAAAAAAAAAAAAAb////////9AAAAAAAAAAAAAAAAAAAAAAAAAA3////////2AAAAAAAAAAAAAAAAAAAAAAAAAE/////////gAAAAAAAAAAAAAAAAAAAAAAAAAM////////+AAAAAAAAAAAAAAAAAAAAAAAAAA/////////8QAAAAAAAAAAAAAAAAAAAAAAAACv////////kAAAAAAAAAAAAAAAAAAAAAAAAAL/////////IAAAAAAAAAAAAAAAAAAAAAAAAAn////////6AAAAAAAAAAAAAAAAAAAAAAAAAB/////////zAAAAAAAAAAAAAAAAAAAAAAAAAH/////////AAAAAAAAAAAAAAAAAAAAAAAAAAO////////9QAAAAAAAAAAAAAAAAAAAAAAAABv////////0AAAAAAAAAAAAAAAAAAAAAAAAADf////////YAAAAAAAAAAAAAAAAAAAAAAAAAX////////+AAAAAAAAAAAAAAAAAAAAAAAAAAz////////3AAAAAAAAAAAAAAAAAAAAAAAAAD/////////xAAAAAAAAAAAAAAAAAAAAAAAAAK////////+QAAAAAAAAAAAAAAAAAAAAAAAAAv////////8gAAAAAAAAAAAAAAAAAAAAAAAACf////////oAAAAAAAAAAAAAAAAAAAAAAAAAH/////////MAAAAAAAAAAAAAAAAAAAAAAAAAj////////8AAAAAAAAAAAAAAAAAAAAAAAAAA7////////1AAAAAAAAAAAAAAAAAAAAAAAAAG/////////QAAAAAAAAAAAAAAAAAAAAAAAAAN////////9gAAAAAAAAAAAAAAAAAAAAAAAABf////////4AAAAAAAAAAAAAAAAAAAAAAAAADP////////cAAAAAAAAAAAAAAAAAAAAAAAAAT/////////EAAAAAAAAAAAAAAAAAAAAAAAAAv////////5AAAAAAAAAAAAAAAAAAAAAAAAAC/////////yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJorN7///7LljAAAAAAAAAAAAAAAAAAAAAAWu////////////+2EAAAAAAAAAAAAAAAAAGP////////////////+iAAAAAAAAAAAAAAAG7///////////////////kAAAAAAAAAAAAACv/////////////////////SAAAAAAAAAAAAv//////////////////////+IAAAAAAAAAAJ////////////////////////0AAAAAAAAABP////////////////////////+AAAAAAAAADP/////////////////////////xAAAAAAAAP//////////////////////////3AAAAAAAAj//////////5QQAUjv/////////7AAAAAAAAv/////////wQAAAAAK//////////AAAAAAAA7////////9AAAAAAAAv/////////EAAAAAAA/////////1AAAAAAAAL/////////IAAAAAAA/////////wAAAAAAAADf////////MAAAAAAA7////////gAAAAAAAAC/////////EAAAAAAAz////////gAAAAAAAAC/////////AAAAAAAAn////////wAAAAAAAADf///////9AAAAAAAAX////////1AAAAAAAAL////////4AAAAAAAADv///////9AAAAAAAAv////////yAAAAAAAAB/////////wQAAAAAK////////+gAAAAAAAAAM/////////5QQATjv////////4QAAAAAAAAAB3///////////////////////MAAAAAAAAAAAHP/////////////////////jAAAAAAAAAAAAAI////////////////////oQAAAAAAAAAAAAAAKf////////////////+zAAAAAAAAAAAAAAAAABbv/////////////4EAAAAAAAAAAAAAAAAAA57///////////////pAAAAAAAAAAAAAAAACr//////////////////8QAAAAAAAAAAAAAB/////////////////////+QAAAAAAAAAAAAv//////////////////////9EAAAAAAAAAAL////////////////////////0QAAAAAAAACP/////////5UgATfv/////////AAAAAAAAAP/////////oQAAAAAH/////////2AAAAAAAAz////////5AAAAAAAAX////////+AAAAAAAC/////////QAAAAAAAACv////////YAAAAAAI////////9QAAAAAAAAAv////////sAAAAAAM////////8AAAAAAAAAAN////////8AAAAAAP////////wAAAAAAAAAAJ////////8wAAAAAf////////sAAAAAAAAAAH////////9AAAAAAv////////oAAAAAAAAAAH////////9QAAAAA/////////sAAAAAAAAAAH////////9gAAAAA/////////wAAAAAAAAAAJ////////9gAAAAAv////////8AAAAAAAAAAN////////9QAAAAAP////////9QAAAAAAAAAv////////8wAAAAAN/////////QAAAAAAAACf////////8QAAAAAK/////////5AAAAAAAAX/////////0AAAAAAG//////////oQAAAAAH//////////kAAAAAAA///////////5QQATfv//////////MAAAAAAAn//////////////////////////8AAAAAAAAH//////////////////////////0AAAAAAAAB/////////////////////////+gAAAAAAAAAK////////////////////////0QAAAAAAAAAAv//////////////////////9EAAAAAAAAAAACf/////////////////////BAAAAAAAAAAAAAF7///////////////////cAAAAAAAAAAAAAAAB+////////////////+RAAAAAAAAAAAAAAAAAASe////////////+1AAAAAAAAAAAAAAAAAAAAAAJYrN7///7LhjAAAAAAAAAAAAAAAAAAAAAAAAFIrO//7slzAAAAAAAAAAAAAAAAAAAAAAAABs//////////+TAAAAAAAAAAAAAAAAAAAAAY//////////////xAAAAAAAAAAAAAAAAAAAb////////////////6EAAAAAAAAAAAAAAAAK//////////////////4wAAAAAAAAAAAAAAHf///////////////////1AAAAAAAAAAAAAB3/////////////////////QAAAAAAAAAAAAM//////////////////////8wAAAAAAAAAACP///////////////////////QAAAAAAAAAAP////////////////////////5AAAAAAAAAAz/////////+lEAJr//////////MAAAAAAAAD/////////+MAAAAAX/////////wAAAAAAAAK/////////yAAAAAABP////////8wAAAAAAAP////////9gAAAAAAAJ////////+gAAAAAABP////////8AAAAAAAAC/////////xAAAAAAB/////////oAAAAAAAAA3////////2AAAAAACv////////cAAAAAAAAAr////////7AAAAAADP////////QAAAAAAAAAf/////////AAAAAADv////////MAAAAAAAAAb/////////MAAAAADv////////IAAAAAAAAAb/////////cAAAAAD/////////MAAAAAAAAAb/////////kAAAAADv////////QAAAAAAAAAf/////////wAAAAADf////////cAAAAAAAAAr/////////4AAAAADP////////oAAAAAAAAA3/////////8AAAAACf////////4AAAAAAAAC//////////8QAAAABv////////9gAAAAAAAJ//////////8gAAAAAf/////////hAAAAAABP//////////8wAAAAAM/////////+IAAAAAX///////////8wAAAAAG//////////+lEAJb////////////9AAAAAAA7///////////////////////////9AAAAAAAX///////////////////////////8wAAAAAACv//////////////////////////8wAAAAAAAN//////////////////////////8gAAAAAAAB3/////////////////////////8AAAAAAAAAHP////////////////////////4AAAAAAAAAAJ//////////////+M////////0AAAAAAAAAAAPP///////////8MO////////oAAAAAAAAAAAAEr/////////tAAP////////cAAAAAAAAAAAAAAEi97/7clRAAA/////////QAAAAAAAAAAAAAAAAAAAAAAAAACP////////AAAAAAAAAAAAAAAAAAAAAAAAAADf///////7AAAAAAAAAAAAAAAAAAAAAAAAAAT////////3AAAAAAAAAAAAAAAAAAAAAAAAAAz////////xAAAAAAAAAAAAAAAAAAAAAAAAAF////////+wAAAAAAAAAAAAAAAAAAAAAAAAA/////////9AAAAAAAAAAAAAAAAAAAAAAAAALv////////wAAAAAAAAAArUAAAAAAAAAAAAF//////////QAAAAAAAAAA//pUAAAAAAAAAS//////////6AAAAAAAAAAA////8ljIQATWd///////////hAAAAAAAAAAA///////////////////////9AAAAAAAAAAAA///////////////////////gAAAAAAAAAAAA//////////////////////5AAAAAAAAAAAAA/////////////////////+QAAAAAAAAAAAAA/////////////////////cAAAAAAAAAAAAAA////////////////////kAAAAAAAAAAAAAAA///////////////////kAAAAAAAAAAAAAAAA/////////////////+SAAAAAAAAAAAAAAAAACau/////////////GEAAAAAAAAAAAAAAAAAAAAAE2is3u//7sp0AAAAAAAAAAAAAAAA" # big BPM number (DejaVuSans-Bold 78) + +def load_font(b64): + blob = a2b_base64(b64); count = blob[0]; p = 1; pixoff = 1 + count * 7; glyphs = {} + for _ in range(count): + cp = (blob[p] << 8) | blob[p + 1]; w = blob[p + 2]; h = blob[p + 3] + xoff = blob[p + 4]; xoff = xoff - 256 if xoff > 127 else xoff + top = blob[p + 5]; adv = blob[p + 6]; p += 7 + glyphs[cp] = (w, h, xoff, top, adv, pixoff); pixoff += (w * h + 1) // 2 + return (glyphs, blob) + +FONT_M = load_font(FONT_M_B64) +FONT_L = load_font(FONT_L_B64) # ============================== ST7796 DISPLAY ============================== class ST7796: @@ -86,7 +123,7 @@ class ST7796: self.cs = Pin(PIN_CS, Pin.OUT, value=1) self.dc = Pin(PIN_DC, Pin.OUT, value=0) self.rst = Pin(PIN_RST, Pin.OUT, value=1) - self._chunk = bytearray(1024) # scratch for fills (512 px) + self._chunk = bytearray(1024) self.reset(); self.init() def _cmd(self, c, data=None): @@ -100,29 +137,26 @@ class ST7796: def init(self): c = self._cmd - c(0x01); time.sleep_ms(120) # software reset - c(0x11); time.sleep_ms(120) # sleep out - c(0xF0, b'\xC3'); c(0xF0, b'\x96') # command set control (unlock) + c(0x01); time.sleep_ms(120) + c(0x11); time.sleep_ms(120) + c(0xF0, b'\xC3'); c(0xF0, b'\x96') c(0x36, bytes((MADCTL,))) - c(0x3A, b'\x55') # 16 bits/pixel (RGB565) - c(0xB4, b'\x01') # 1-dot inversion - c(0xB6, b'\x80\x02\x3B') # display function control + c(0x3A, b'\x55') + c(0xB4, b'\x01') + c(0xB6, b'\x80\x02\x3B') c(0xE8, b'\x40\x8A\x00\x00\x29\x19\xA5\x33') - c(0xC1, b'\x06') # power control 2 - c(0xC2, b'\xA7') # power control 3 - c(0xC5, b'\x18'); time.sleep_ms(120) # VCOM - c(0xE0, b'\xF0\x09\x0B\x06\x04\x15\x2F\x54\x42\x3C\x17\x14\x18\x1B') # +gamma - c(0xE1, b'\xE0\x09\x0B\x06\x04\x03\x2B\x43\x42\x3B\x16\x14\x17\x1B') # -gamma - c(0xF0, b'\x3C'); c(0xF0, b'\x69'); time.sleep_ms(120) # lock command set - c(0x21 if INVERT_COLORS else 0x20) # inversion on/off - c(0x29) # display on - time.sleep_ms(50) + c(0xC1, b'\x06'); c(0xC2, b'\xA7'); c(0xC5, b'\x18'); time.sleep_ms(120) + c(0xE0, b'\xF0\x09\x0B\x06\x04\x15\x2F\x54\x42\x3C\x17\x14\x18\x1B') + c(0xE1, b'\xE0\x09\x0B\x06\x04\x03\x2B\x43\x42\x3B\x16\x14\x17\x1B') + c(0xF0, b'\x3C'); c(0xF0, b'\x69'); time.sleep_ms(120) + c(0x21 if INVERT_COLORS else 0x20) + c(0x29); time.sleep_ms(50) def _window(self, x, y, w, h): x1, y1 = x + w - 1, y + h - 1 self._cmd(0x2A, bytes((x >> 8, x & 0xFF, x1 >> 8, x1 & 0xFF))) self._cmd(0x2B, bytes((y >> 8, y & 0xFF, y1 >> 8, y1 & 0xFF))) - self.cs(0); self.dc(0); self.spi.write(bytes((0x2C,))); self.dc(1) # leaves us mid-RAMWR + self.cs(0); self.dc(0); self.spi.write(bytes((0x2C,))); self.dc(1) def fill_rect(self, x, y, w, h, color): if w <= 0 or h <= 0: return @@ -137,49 +171,51 @@ class ST7796: def fill(self, color): self.fill_rect(0, 0, WIDTH, HEIGHT, color) - # text via the built-in 8x8 mono font, expanded to colour and integer-scaled - def text(self, s, x, y, fg, bg, scale=2): - if not s: return - w8 = len(s) * 8 - stride = w8 // 8 - mbuf = bytearray(stride * 8) - mfb = framebuf.FrameBuffer(mbuf, w8, 8, framebuf.MONO_HLSB) - mfb.fill(0); mfb.text(s, 0, 0, 1) - dw = w8 * scale - row = bytearray(dw * 2) - self._window(x, y, dw, 8 * scale) - for r in range(8): - base = r * stride - di = 0 - for col in range(w8): - bit = (mbuf[base + (col >> 3)] >> (7 - (col & 7))) & 1 - cpx = fg if bit else bg - for _ in range(scale): - row[di] = cpx[0]; row[di+1] = cpx[1]; di += 2 - for _ in range(scale): self.spi.write(row) - self.cs(1) + # ---- anti-aliased text (fg blended over bg via a 16-entry alpha LUT) ---- + def _lut(self, fg, bg): + fgv = (fg[0] << 8) | fg[1]; bgv = (bg[0] << 8) | bg[1] + fr = (fgv >> 11) & 0x1F; fg6 = (fgv >> 5) & 0x3F; fb = fgv & 0x1F + br = (bgv >> 11) & 0x1F; bg6 = (bgv >> 5) & 0x3F; bb = bgv & 0x1F + out = [] + for a in range(16): + t = a * 17 + r = (br*(255-t) + fr*t) // 255; gg = (bg6*(255-t) + fg6*t) // 255; b = (bb*(255-t) + fb*t) // 255 + v = (r << 11) | (gg << 5) | b; out.append((v >> 8, v & 0xFF)) + return out - def text_w(self, s, scale=2): return len(s) * 8 * scale + def text_w(self, s, font): + glyphs = font[0]; w = 0 + for ch in s: + g = glyphs.get(ord(ch)) + if g: w += g[4] + return w -# seven-segment digit renderer (for the big BPM) — no font, just rectangles -_SEG = { # a,b,c,d,e,f,g - '0': 0b1111110, '1': 0b0110000, '2': 0b1101101, '3': 0b1111001, - '4': 0b0110011, '5': 0b1011011, '6': 0b1011111, '7': 0b1110000, - '8': 0b1111111, '9': 0b1111011, ' ': 0b0000000, '-': 0b0000001, -} -def draw_digit(d, ch, x, y, W, H, T, on, off): - seg = _SEG.get(ch, 0); v = (H - 3 * T) // 2 - rects = [ - (x + T, y, W - 2*T, T, 6), # a top - (x + W - T, y + T, T, v, 5), # b top-right - (x + W - T, y + 2*T + v, T, v, 4), # c bottom-right - (x + T, y + H - T, W - 2*T, T, 3), # d bottom - (x, y + 2*T + v, T, v, 2), # e bottom-left - (x, y + T, T, v, 1), # f top-left - (x + T, y + T + v, W - 2*T, T, 0), # g middle - ] - for rx, ry, rw, rh, bitpos in rects: - d.fill_rect(rx, ry, rw, rh, on if (seg >> bitpos) & 1 else off) + def text_aa(self, s, x, y, fg, bg, font): + glyphs, blob = font; L = self._lut(fg, bg); pen = x + for ch in s: + g = glyphs.get(ord(ch)) + if not g: continue + w, h, xoff, top, adv, off = g + if w and h: + buf = bytearray(w * h * 2); di = 0; k = 0 + for _ in range(h): + for _ in range(w): + byte = blob[off + (k >> 1)] + e = L[(byte >> 4) if (k & 1) == 0 else (byte & 0xF)] + buf[di] = e[0]; buf[di+1] = e[1]; di += 2; k += 1 + self._window(pen + xoff, y + top, w, h); self.spi.write(buf); self.cs(1) + pen += adv + return pen + + def text_center(self, s, cx, cy, fg, bg, font): + tw = self.text_w(s, font); glyphs = font[0]; t0 = 999; b1 = 0 + for ch in s: + g = glyphs.get(ord(ch)) + if g and g[1]: + if g[3] < t0: t0 = g[3] + if g[3] + g[1] > b1: b1 = g[3] + g[1] + if t0 == 999: t0 = 0 + self.text_aa(s, cx - tw // 2, cy - (t0 + b1) // 2, fg, bg, font) # ============================== GT911 TOUCH ============================== class GT911: @@ -203,7 +239,7 @@ class GT911: b = self.i2c.readfrom_mem(self.addr, 0x8150, 4, addrsize=16) tx = b[0] | (b[1] << 8); ty = b[2] | (b[3] << 8) pt = self._map(tx, ty) - try: self.i2c.writeto_mem(self.addr, 0x814E, b'\x00', addrsize=16) # clear ready flag + try: self.i2c.writeto_mem(self.addr, 0x814E, b'\x00', addrsize=16) except OSError: pass return pt def _map(self, tx, ty): @@ -215,7 +251,7 @@ class GT911: return None # ============================== POLYMETER ENGINE ============================== -# program string: v1;t;[vol];[cd];[b];;;... +# program string: [v1;]t;;;... (globals like b/rmp/tr are ignored on-device) # lane = :[/[s]][=pattern][@db][~][!] # pattern chars: X=accent(2) x=normal(1) g=ghost(3) . - _ =mute(0) PAT = {'X': 2, 'x': 1, 'g': 3, '.': 0, '-': 0, '_': 0} @@ -228,7 +264,7 @@ def parse_program(s): if not tok: continue if tok[0] == 't' and tok[1:].isdigit(): bpm = int(tok[1:]); continue - if ':' not in tok: # skip v1, vol, cd, b and other globals we don't need on-device + if ':' not in tok: # skip v1, vol, cd, b, rmp, tr and other globals continue lane = _parse_lane(tok) if lane: lanes.append(lane) @@ -239,11 +275,8 @@ def _parse_lane(tok): poly = '~' in tok mute = '!' in tok tok = tok.replace('~', '').replace('!', '') - db = 0 if '@' in tok: - tok, _, rest = tok.partition('@') - try: db = int(rest) - except: db = 0 + tok = tok.partition('@')[0] sound, _, rest = tok.partition(':') pattern = None if '=' in rest: @@ -251,27 +284,23 @@ def _parse_lane(tok): sub = 1 if '/' in rest: rest, _, sd = rest.partition('/') - sd = sd.rstrip('s') # ignore swing flag on-device + sd = sd.rstrip('s') sub = int(sd) if sd.isdigit() else 1 - # grouping: "4" or "3+3+2" groups = [int(g) for g in rest.split('+') if g.isdigit()] or [4] beats = sum(groups) starts = set(); acc = 0 - for g in groups: starts.add(acc); acc += g + for gp in groups: starts.add(acc); acc += gp steps = beats * sub if pattern: - levels = [PAT.get(c, 0) for c in pattern] + levels = [PAT.get(ch, 0) for ch in pattern] if len(levels) < steps: levels += [0] * (steps - len(levels)) steps = len(levels) else: levels = [] for i in range(steps): - if i % sub == 0: - levels.append(2 if (i // sub) in starts else 1) - else: - levels.append(0) - return {'sound': sound, 'sub': sub, 'steps': steps, 'levels': levels, - 'poly': poly, 'mute': mute, 'db': db} + if i % sub == 0: levels.append(2 if (i // sub) in starts else 1) + else: levels.append(0) + return {'sound': sound, 'sub': sub, 'steps': steps, 'levels': levels, 'poly': poly, 'mute': mute} # ============================== APP ============================== class App: @@ -287,34 +316,28 @@ class App: self._aPrev = 1; self._bPrev = 1 self.jx = ADC(PIN_JOY_X); self.jy = ADC(PIN_JOY_Y) self._joyNext = 0 - self._touchLock = 0; self._unpressAt = 0; self._pending = None + self._unpressAt = 0; self._pending = None + self._touchDown = False; self._touchSeen = 0 self.running = False - self.bpm = 120 - self.idx = 0 - self.lanes = [] + self.bpm = 120; self.idx = 0; self.lanes = [] self.rgb = (0, 0, 0) - self.buttons = [] # touch hit zones: (x,y,w,h,key) + self.buttons = [] self.load(0) self.draw_static() - self.draw_bpm(force=True) - self.draw_status() - self.draw_dots(force=True) + self.draw_bpm(); self.draw_status(); self.draw_dots() # ---------- program ---------- def load(self, i): n = len(PROGRAMS); self.idx = i % n - name, prog = PROGRAMS[self.idx] - self.name = name + self.name, prog = PROGRAMS[self.idx] self.bpm, self.lanes = parse_program(prog) self.master = self.lanes[0] self.beat = -1 self._reset_clock() - def _reset_clock(self): now = time.ticks_us() for L in self.lanes: - L['next'] = now - L['step'] = -1 + L['next'] = now; L['step'] = -1 L['stepdur'] = int(60_000_000 / self.bpm / L['sub']) # ---------- audio + light ---------- @@ -322,19 +345,23 @@ class App: f = {2: 2300, 1: 1600, 3: 1050}.get(level, 1600) duty = {2: 42000, 1: 30000, 3: 14000}.get(level, 30000) self.buz.freq(f); self.buz.duty_u16(duty) - self.buz_off = time.ticks_add(time.ticks_us(), 22000) # 22 ms + self.buz_off = time.ticks_add(time.ticks_us(), 22000) def flash(self, level): self.rgb = LEVEL_RGB.get(level, (0, 150, 255)) if self.np: self.np[0] = self.rgb; self.np.write() + def led_off(self): + self.rgb = (0, 0, 0) + if self.np: self.np[0] = (0, 0, 0); self.np.write() # ---------- transport ---------- def toggle(self): self.running = not self.running if self.running: self._reset_clock(); self.beat = -1 - else: - self.buz.duty_u16(0) - if self.np: self.np[0] = (0, 0, 0); self.np.write() - self.draw_status(); self.draw_dots(force=True) + else: self.buz.duty_u16(0); self.led_off() + self.draw_status(); self.draw_dots(); self._redraw_play() + def _redraw_play(self): + for (x, y, w, h, key) in self.buttons: + if key == "play": self._draw_button(x, y, w, h, key); return def set_bpm(self, v): v = max(30, min(300, v)) if v != self.bpm: @@ -343,7 +370,7 @@ class App: self.draw_bpm() def goto(self, i): was = self.running; self.load(i) - self.draw_bpm(force=True); self.draw_status(); self.draw_dots(force=True) + self.draw_bpm(); self.draw_status(); self.draw_dots(); self._redraw_play() if was: self.running = True; self._reset_clock(); self.beat = -1 def tap(self): now = time.ticks_ms() @@ -354,7 +381,7 @@ class App: span = time.ticks_diff(self._taps[-1], self._taps[0]) / (len(self._taps) - 1) if span > 0: self.set_bpm(round(60000 / span)) - # ---------- scheduler (call often) ---------- + # ---------- scheduler ---------- def tick(self): now = time.ticks_us() if self.buz_off and time.ticks_diff(now, self.buz_off) >= 0: @@ -366,20 +393,18 @@ class App: L['step'] = (L['step'] + 1) % L['steps'] lvl = 0 if L['mute'] else L['levels'][L['step']] if lvl > 0: fired.append(lvl) - if L is self.master and L['step'] % L['sub'] == 0: - beat_hit = True + if L is self.master and L['step'] % L['sub'] == 0: beat_hit = True L['next'] = time.ticks_add(L['next'], L['stepdur']) if fired: - best = max(fired, key=lambda l: PRIO.get(l, 0)) # accent > normal > ghost + best = max(fired, key=lambda l: PRIO.get(l, 0)) self.click(best); self.flash(best) if beat_hit: - self.beat = (self.master['step'] // self.master['sub']) - self.draw_dots() - # fade the RGB between beats - if self.rgb != (0, 0, 0): - r, g, b = self.rgb; r = r*7//10; g = g*7//10; b = b*7//10 - self.rgb = (r, g, b) if (r + g + b) > 12 else (0, 0, 0) - if self.np: self.np[0] = self.rgb; self.np.write() + self.beat = (self.master['step'] // self.master['sub']); self.draw_dots() + # fade the RGB between beats (only while running) + if self.rgb != (0, 0, 0): + r, g, b = self.rgb; r = r*7//10; g = g*7//10; b = b*7//10 + self.rgb = (r, g, b) if (r + g + b) > 12 else (0, 0, 0) + if self.np: self.np[0] = self.rgb; self.np.write() # ---------- inputs ---------- def poll(self): @@ -389,31 +414,33 @@ class App: b = self.btnB.value() if b == 0 and self._bPrev == 1: self.tap() self._bPrev = b - # joystick: up/down = tempo, left/right = prev/next item (with repeat) now = time.ticks_ms() if time.ticks_diff(now, self._joyNext) >= 0: x = self.jx.read_u16() - 32768; y = self.jy.read_u16() - 32768 if JOY_INVERT_X: x = -x if JOY_INVERT_Y: y = -y - acted = False if abs(y) > JOY_DEADZONE: - self.set_bpm(self.bpm + (1 if y > 0 else -1) * (5 if abs(y) > 26000 else 1)); acted = True + self.set_bpm(self.bpm + (1 if y > 0 else -1) * (5 if abs(y) > 26000 else 1)) + self._joyNext = time.ticks_add(now, 70) elif abs(x) > JOY_DEADZONE: - self.goto(self.idx + (1 if x > 0 else -1)); acted = True - self._joyNext = time.ticks_add(now, 350); return - self._joyNext = time.ticks_add(now, 70 if acted else 20) - # touch — non-blocking: redraw a pressed button after its hold, debounce repeats + self.goto(self.idx + (1 if x > 0 else -1)); self._joyNext = time.ticks_add(now, 350); return + else: + self._joyNext = time.ticks_add(now, 20) if self._unpressAt and time.ticks_diff(now, self._unpressAt) >= 0: - x, y, w, h, key = self._pending; self._draw_button(x, y, w, h, key) - self._unpressAt = 0 - if time.ticks_diff(now, self._touchLock) >= 0: - pt = self.touch.read() - if pt: self.hit(pt[0], pt[1]) + x, y, w, h, key = self._pending; self._draw_button(x, y, w, h, key); self._unpressAt = 0 + # edge-detect the touch like the hardware buttons: act once on finger-DOWN, ignore held + pt = self.touch.read() + if pt: + self._touchSeen = now + if not self._touchDown: + self._touchDown = True; self.hit(pt[0], pt[1]) + elif self._touchDown and time.ticks_diff(now, self._touchSeen) > 140: + self._touchDown = False def hit(self, x, y): for bx, by, bw, bh, key in self.buttons: if bx <= x <= bx+bw and by <= y <= by+bh: - self.d.fill_rect(bx, by, bw, bh, C_BTNHI) # pressed flash + self.d.fill_rect(bx, by, bw, bh, C_BTNHI); self._btn_label(bx, by, bw, bh, key) if key == 'play': self.toggle() elif key == 'prev': self.goto(self.idx - 1) elif key == 'next': self.goto(self.idx + 1) @@ -422,70 +449,63 @@ class App: elif key == 'tap': self.tap() self._pending = (bx, by, bw, bh, key) self._unpressAt = time.ticks_add(time.ticks_ms(), 120) - self._touchLock = time.ticks_add(time.ticks_ms(), 280) # ignore held finger return # ---------- drawing ---------- def draw_static(self): d = self.d; d.fill(C_BG) - d.text("PM_K-1 KIT", 12, 12, C_CYAN, C_BG, 2) # VARASYS logo is on the case, not the screen - d.fill_rect(0, 34, WIDTH, 2, C_PANEL) - d.text("BPM", 12, 196, C_MUTE, C_BG, 2) - # build + paint the touch buttons + d.text_aa("PM_K-1 KIT", 12, 10, C_CYAN, C_BG, FONT_M) # VARASYS logo is on the case, not the screen + d.fill_rect(0, 42, WIDTH, 2, C_PANEL) + d.text_aa("BPM", 12, 120, C_MUTE, C_BG, FONT_M) self.buttons = [] - row1 = 300; bw = 96; bh = 54; gap = (WIDTH - 3*bw) // 4 - xs = [gap, gap*2 + bw, gap*3 + bw*2] - for x, key in zip(xs, ('prev', 'play', 'next')): - self.buttons.append((x, row1, bw, bh, key)); self._draw_button(x, row1, bw, bh, key) - row2 = row1 + bh + 16 - for x, key in zip(xs, ('minus', 'tap', 'plus')): - self.buttons.append((x, row2, bw, bh, key)); self._draw_button(x, row2, bw, bh, key) - d.text("joystick: tempo / item button A: play B: tap", 12, HEIGHT - 20, C_MUTE, C_BG, 1) + bw = 96; bh = 56; gap = (WIDTH - 3*bw) // 4; xs = [gap, gap*2+bw, gap*3+bw*2] + for x, key in zip(xs, ("prev", "play", "next")): + self.buttons.append((x, 300, bw, bh, key)); self._draw_button(x, 300, bw, bh, key) + for x, key in zip(xs, ("minus", "tap", "plus")): + self.buttons.append((x, 372, bw, bh, key)); self._draw_button(x, 372, bw, bh, key) def _draw_button(self, x, y, w, h, key): d = self.d; d.fill_rect(x, y, w, h, C_BTN) d.fill_rect(x, y, w, 2, C_PANEL); d.fill_rect(x, y+h-2, w, 2, C_PANEL) - label = {'prev':'<<','play':'>||','next':'>>','minus':'-','plus':'+','tap':'TAP'}[key] - col = C_GREEN if key == 'play' else C_TXT - sc = 3 if key in ('minus','plus') else 2 - tw = d.text_w(label, sc) - d.text(label, x + (w - tw)//2, y + (h - 8*sc)//2, col, C_BTN, sc) + self._btn_label(x, y, w, h, key) + def _btn_label(self, x, y, w, h, key): + sym = {"prev": "◀◀", "next": "▶▶", "minus": "-", "plus": "+", "tap": "TAP", + "play": "■" if self.running else "▶"}[key] + col = C_GREEN if key == "play" else C_TXT + bg = C_BTNHI if False else C_BTN + self.d.text_center(sym, x + w//2, y + h//2, col, bg, FONT_M) - def draw_bpm(self, force=False): - d = self.d - s = "%3d" % self.bpm - W = 64; H = 96; T = 12; gap = 12; x0 = WIDTH - 12 - (3*W + 2*gap); y0 = 92 - for i, ch in enumerate(s): - draw_digit(d, ch, x0 + i*(W+gap), y0, W, H, T, C_TXT, C_BG) + def draw_bpm(self): + d = self.d; s = str(self.bpm) + d.fill_rect(60, 84, WIDTH - 60, 96, C_BG) + tw = d.text_w(s, FONT_L) + d.text_aa(s, WIDTH - 14 - tw, 90, C_TXT, C_BG, FONT_L) def draw_status(self): - d = self.d - d.fill_rect(0, 240, WIDTH, 40, C_BG) - st = ">RUN" if self.running else "=STOP" - d.text(st, 12, 244, C_GREEN if self.running else C_MUTE, C_BG, 2) - nm = self.name[:18] - d.text(nm, WIDTH - d.text_w(nm, 2) - 12, 244, C_TXT, C_BG, 2) - d.text("%d/%d" % (self.idx+1, len(PROGRAMS)), 12, 266, C_MUTE, C_BG, 1) + d = self.d; d.fill_rect(0, 236, WIDTH, 58, C_BG) + d.text_aa("▶ RUN" if self.running else "■ STOP", 12, 240, + C_GREEN if self.running else C_MUTE, C_BG, FONT_M) + idx = "%d/%d" % (self.idx + 1, len(PROGRAMS)); tw = d.text_w(idx, FONT_M) + d.text_aa(idx, WIDTH - 12 - tw, 240, C_MUTE, C_BG, FONT_M) + d.text_aa(self.name[:26], 12, 268, C_TXT, C_BG, FONT_M) - def draw_dots(self, force=False): + def draw_dots(self): d = self.d; m = self.master bpb = max(1, m['steps'] // m['sub']) yy = 200; sz = 18; sp = 26 x0 = max(12, WIDTH - 12 - bpb * sp) - d.fill_rect(0, yy, WIDTH, sz, C_BG) # clear the dot row + d.fill_rect(0, yy, WIDTH, sz, C_BG) for i in range(bpb): - lvl = m['levels'][(i*m['sub']) % m['steps']] # accent (2) shows amber when lit + lvl = m['levels'][(i*m['sub']) % m['steps']] on = self.running and i == self.beat col = (C_AMBER if lvl == 2 else C_CYAN) if on else C_DIMDOT d.fill_rect(x0 + i*sp, yy, sz, sz, col) def run(self): if self.touch.addr is None: - self.d.text("touch: not found", 12, HEIGHT - 40, C_AMBER, C_BG, 1) + self.d.text_aa("touch: not found", 12, HEIGHT - 30, C_AMBER, C_BG, FONT_M) while True: - self.tick() - self.poll() - time.sleep_us(200) + self.tick(); self.poll(); time.sleep_us(200) # ============================== GO ============================== App().run()