A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9
10 Copyright (C) 2024 William Wilgus
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 KIND, either express or implied.
17]]--
18--https://nullprogram.com/blog/2011/06/13/ [Infinite Parallax Starfield]
19
20-- Imports
21local _clr = require("color") -- clrset, clrinc provides device independent colors
22local _lcd = require("lcd") -- lcd helper functions
23local _draw = require("draw") -- draw all the things (primitives)
24local _poly = require("draw_poly") -- vector drawing with tables of coords
25require("actions")
26--'CONSTANTS' (in lua there really is no such thing as all vars are mutable)
27--------------------------------------------------------
28--colors for fg/bg ------------------------
29--first number of each quad is fallback for monochrome devices, excluded columns default to 0
30local WHITE = _clr.set(-1, 255, 255, 255)
31local BLACK = _clr.set(0, 0, 0, 0)
32local RED = _clr.set(WHITE, 100)
33local GREEN = _clr.set(WHITE, 0, 100)
34local BGREEN = _clr.set(WHITE, 0, 255)
35local BLUE = _clr.set(WHITE, 0, 0, 255)
36
37local STAR_SEED = 0x811C9DC5;
38local STAR_TILE_SIZE = math.max(rb.LCD_WIDTH, rb.LCD_HEIGHT) * 4;
39local bxor, band, rshift, lshift, arshift = bit.bxor, bit.band, bit.rshift, bit.lshift, bit.arshift
40local random, randomseed = math.random, math.randomseed
41local start_x, start_y, start_z, scale_x, scale_y
42
43-- load users coords from file if it exists
44local fname = rb.PLUGIN_DATA_DIR .. "/stars.pos"
45file = io.open(fname, "r")
46if file then
47 local v = 0
48 for line in file:lines() do
49 v = v + 1
50 if v == 1 then
51 start_x = tonumber(line) or 0
52 elseif v == 2 then
53 start_y = tonumber(line) or 0
54 elseif v == 3 then
55 start_z = tonumber(line) or 0
56 elseif v == 4 then
57 scale_x = tonumber(line) or 1
58 elseif v == 5 then
59 scale_y = tonumber(line) or 1
60 else
61 break;
62 end
63 end
64 io.close( file )
65end
66
67-- Robert Jenkins' 96 bit Mix Function.
68local function mix (a, b, c)
69 a=a-b; a=a-c; a=bxor(a, (rshift(c, 13)))
70 b=b-c; b=b-a; b=bxor(b, (lshift(a, 8)))
71 c=c-a; c=c-b; c=bxor(c, (rshift(b, 13)))
72 a=a-b; a=a-c; a=bxor(a, (rshift(c, 12)))
73 b=b-c; b=b-a; b=bxor(b, (lshift(a, 16)))
74 c=c-a; c=c-b; c=bxor(c, (rshift(b, 5)))
75 a=a-b; a=a-c; a=bxor(a, (rshift(c, 3)))
76 b=b-c; b=b-a; b=bxor(b, (lshift(a, 10)))
77 c=c-a; c=c-b; c=bxor(c, (rshift(b, 15)))
78
79 return c
80end
81
82-- given 32 bit number returns a table of 8 nibbles (4 bits)
83local function s_bytes_nib(bits, value)
84 -- bits must be multiples of 8 (sizeof byte)
85 local bbuffer = {}
86 local byte
87 local nbytes = bit.rshift(bits, 3)
88 for b = 1, nbytes do
89 if value > 0 then
90 byte = value % 256
91 value = (value - byte) / 256
92 else
93 byte = 0
94 end
95 bbuffer[#bbuffer + 1] = band(byte,0xF)
96 bbuffer[#bbuffer + 1] = band(rshift(byte, 2), 0xF)
97 end
98 return bbuffer
99end
100
101--[[ given table t and total elems desired uses random elems of t
102 and random numbers between 1 and max_v if #t < total_elems]]
103function randomize_table(t, total_elems, max_v)
104 local rand_t = {}
105 local i = 1
106 repeat
107 local v = t[random(i, total_elems)]
108 if v then
109 rand_t[i] = v
110 else
111 rand_t[i] = random(1, max_v)
112 end
113 i = i + 1
114 until i > total_elems
115 return rand_t
116end
117
118local function drawship(img, ship_t)
119 --_poly.polyline(img, x, y, ship_t, color, true, true)
120 _poly.polygon(img, ship_t.x, ship_t.y, ship_t.disp_t, ship_t.color, ship_t.fillcolor, true)
121end
122
123local function draw_astroid(img, x, y, shape, color, fillcolor, scale_x, scale_y)
124 --the random number generator gets seeded with the hash so we get the same figure each time
125 randomseed(shape)
126 local move_x, move_y
127 -- we also use the 4 bytes of the hash as 4 coord pairs and randomly generate 8 more (16) half the size (8)
128 local uniq_t = randomize_table(s_bytes_nib(32, shape), 16, 8)
129 move_x, move_y = _poly.polyline(img, 0, 0, uniq_t, color, true, true, scale_x or 1, scale_y or 1, true)
130 x = x - move_x / 2
131 y = y - move_y / 2
132 if fillcolor then
133 _poly.polygon(img, x, y, uniq_t, color, fillcolor, true, scale_x or 1, scale_y or 1) --filled figures
134 else
135 _poly.polyline(img, x, y, uniq_t, color, true, true, scale_x or 1, scale_y or 1)
136 end
137
138 return x, y, move_x, move_y
139end
140
141local function drawStars(img, drawFn, xoff, yoff, starscale, color, scale_x, scale_y)
142 local size = STAR_TILE_SIZE / starscale
143 local s_x, s_y = scale_x, scale_y
144 local w, h = rb.LCD_WIDTH, rb.LCD_HEIGHT
145
146 -- Top-left tile's top-left position
147 local sx = ((xoff - w/2) / size) * size - size;
148 local sy = ((yoff - h/2) / size) * size - size;
149
150 --Draw each tile currently in view.
151 for i = sx, w + sx + size*3, size do
152 for j = sy, h + sy + size*3, size do
153 local hash = mix(STAR_SEED, (i / size), (j / size))
154 for n = 0, 2 do
155 local px = (hash % size) + (i - xoff)
156 hash = arshift(hash, 3)
157
158 local py = (hash % size) + (j - yoff)
159 hash = arshift(hash, 3)
160 if px > 0 and px < w and py > 0 and py < h then
161 drawFn(img, px, py, color, n, hash)
162 end
163 end
164 end
165 end
166end
167
168local function update_lcd()
169 rb.lcd_puts(0,0, "[Infinite Starfield]")
170 _lcd:update()
171 rb.sleep(100)
172 update_lcd = _lcd.update
173end
174
175local backlight_on
176local function turn_on_backlight()
177 rb.backlight_force_on();
178 backlight_on = function() end
179end
180backlight_on = turn_on_backlight
181
182do
183 local act = rb.actions
184 local quit = false
185 --local last_action = 0
186 local x,y,z = start_x or 0, start_y or 0, start_z or 8
187 local s_x, s_y = scale_x or 1, scale_y or 1
188 local ship_t = {x = (rb.LCD_WIDTH - 0xF) / 2,
189 y = rb.LCD_HEIGHT - (rb.LCD_HEIGHT / 3),
190 color = BGREEN,
191 fillcolor = BLACK,
192 -- ship vector coords x,y, x,y,...
193 lt_t = {0,7, 15,0, 9,7, 15,15, 0,7},
194 rt_t = {0,0, 5,7, 0,15, 15,7, 0,0},
195 up_t = {0,15, 7,0, 15,15, 7,9, 0,15},
196 dn_t = {0,0, 7,15, 15,0, 7,5, 0,0}
197 }
198 ship_t.disp_t = ship_t.up_t
199
200 local fast = {x = 1, y = 1, count = 0, inc_x = rb.LCD_WIDTH / 16, inc_y = rb.LCD_HEIGHT / 16}
201
202 local last = {sx = s_x, sy = s_y, dx = 0, dy = 0, inc_x = 0, inc_y = 0}
203
204 local function draw_points(img, x, y, color, n, hash)
205 if s_x > s_y then
206 img:line(x, y, x + s_x, y, color, true)
207 elseif s_y > s_x then
208 img:line(x, y, x, y + s_y, color, true)
209 else
210 img:set(x, y, color, true)
211 end
212 end
213
214 function action_drift()
215 if last.dx > 0 then
216 last.dx = last.dx - 1
217 x = x + last.dx
218 elseif last.dx < 0 then
219 last.dx = last.dx + 1
220 x = x + last.dx
221 end
222 if last.dy > 0 then
223 last.dy = last.dy - 1
224 y = y + last.dy
225 elseif last.dy < 0 then
226 last.dy = last.dy + 1
227 y = y + last.dy
228 end
229 if last.dx == 0 and last.dy == 0 then
230 rockev.suspend("timer")
231 end
232 rockev.trigger("action", true, act.ACTION_REDRAW)
233 end
234
235 function action_event(action)
236 backlight_on()
237 if action == act.PLA_EXIT or action == act.PLA_CANCEL then
238 quit = true
239 start_x, start_y, start_z = x, y, z
240 scale_x, scale_y = last.sx, last.sy
241 elseif action == act.PLA_RIGHT_REPEAT then
242 fast.count = fast.count + 1
243 if fast.count % 10 == 0 then
244 fast.x = fast.x + fast.inc_x
245 if fast.count > 100 then s_x = s_x + 1 end
246 end
247 x = x + fast.x + last.inc_x
248 s_y = last.sy
249 last.dx = fast.x
250 ship_t.disp_t = ship_t.rt_t
251 elseif action == act.PLA_LEFT_REPEAT then
252 fast.count = fast.count + 1
253 if fast.count % 10 == 0 then
254 fast.x = fast.x + fast.inc_x
255 if fast.count > 100 then s_x = s_x + 1 end
256 end
257 x = x - fast.x + last.inc_x
258 s_y = last.sy
259 last.dx = -fast.x
260 ship_t.disp_t = ship_t.lt_t
261 elseif action == act.PLA_UP_REPEAT then
262 fast.count = fast.count + 1
263 if fast.count % 10 == 0 then
264 fast.y = fast.y + fast.inc_y
265 if fast.count > 100 then s_y = s_y + 1 end
266 end
267 y = y - fast.y + last.inc_y
268 s_x = last.sx
269 last.dy = -fast.y
270 ship_t.disp_t = ship_t.up_t
271 elseif action == act.PLA_DOWN_REPEAT then
272 fast.count = fast.count + 1
273 if fast.count % 10 == 0 then
274 fast.y = fast.y + fast.inc_y
275 if fast.count > 100 then s_y = s_y + 1 end
276 end
277 y = y + fast.y + last.inc_y
278 s_x = last.sx
279 last.dy = fast.y
280 ship_t.disp_t = ship_t.dn_t
281 elseif action == act.PLA_RIGHT then
282 last.inc_x = last.inc_x + 1
283 x = x + last.dx + 1
284 if last.inc_x < 0 then
285 last.inc_x = 0
286 end
287 last.dx = last.inc_x
288 ship_t.disp_t = ship_t.rt_t
289 elseif action == act.PLA_LEFT then
290 last.inc_x = last.inc_x - 1
291 x = x + last.dx - 1
292 if last.inc_x > 0 then
293 last.inc_x = 0
294 end
295 last.dx = last.inc_x
296 ship_t.disp_t = ship_t.lt_t
297 elseif action == act.PLA_UP then
298 last.inc_y = last.inc_y - 1
299 y = y + last.dy - 1
300 if last.inc_y > 0 then
301 last.inc_y = 0
302 end
303 last.dy = last.inc_y
304 ship_t.disp_t = ship_t.up_t
305 elseif action == act.PLA_DOWN then
306 last.inc_y = last.inc_y + 1
307 y = y + last.dy + 1
308 if last.inc_y < 0 then
309 last.inc_y = 0
310 end
311 last.dy = last.inc_y
312 ship_t.disp_t = ship_t.dn_t
313 elseif action == act.PLA_SELECT_REPEAT then
314 rockev.suspend("timer", true)
315 if s_x < 10 and s_y < 10 then
316 s_x = last.sx + 1
317 s_y = last.sy + 1
318 last.sx = s_x
319 last.sy = s_y
320 end
321 elseif action == act.PLA_SELECT then
322 s_x = last.sx + 1
323 s_y = last.sy + 1
324 if s_x > 10 or s_y > 10 then
325 s_x = 1
326 s_y = 1
327 end
328 last.sx = s_x
329 last.sy = s_y
330 elseif action == act.ACTION_NONE then
331 if fast.count > 100 then
332 z = (random(0, 400) / 100) * 4
333 end
334 fast.count = 0
335 fast.x = fast.inc_x
336 fast.y = fast.inc_y
337 s_x = last.sx
338 s_y = last.sy
339 backlight_on = turn_on_backlight
340 rb.backlight_use_settings()
341 if last.dx ~= 0 or last.dy ~= 0 then
342 rockev.suspend("timer", false)
343 else
344 last.inc_x = 0
345 last.inc_y = 0
346 end
347 end
348
349 _lcd:clear(BLACK)
350 for i = 0, z, 4 do
351 drawStars(_LCD, draw_points, x, y, i+1, RED, s_x, s_y)
352 drawStars(_LCD, draw_points, x, y, i+2, GREEN, s_x, s_y)
353 drawStars(_LCD, draw_points, x, y, i+3, BLUE, s_x, s_y)
354 drawStars(_LCD, draw_points, x, y, i+4, WHITE, s_x, s_y)
355 end
356
357 local hit_t = {}
358 local SHIP_X, SHIP_Y = ship_t.x + 8, ship_t.y + 8 --center the ship coords
359 local function draw_asteroids(img, x, y, color, n, hash)
360 if n > 0 then
361 local x0, y0, w0, h0
362 x0,y0,w0,h0 = draw_astroid(img, x, y, hash, color, false, s_x, s_y)
363 --check bounds
364 if s_x == s_y and x0 <= SHIP_X and x0+w0 >= SHIP_X and y0+h0 >= SHIP_Y and y0 <= SHIP_Y then
365 local r_t = {x = x0, y = y0, w = w0, h= h0, hash = hash, color = color}
366 hit_t[#hit_t + 1] = r_t
367 end
368 end
369 end
370
371 drawStars(_LCD, draw_asteroids, x, y, 1, RED, s_x, s_y)
372 drawStars(_LCD, draw_asteroids, x, y, 2, GREEN, s_x, s_y)
373 drawStars(_LCD, draw_asteroids, x, y, 3, BLUE, s_x, s_y)
374 drawStars(_LCD, draw_asteroids, x, y, 4, WHITE, s_x, s_y)
375 if fast.count < 10 and last.dx == last.dy then
376 local seen = {} -- might have multiple hits but only show unique hashes
377 for i, v in ipairs(hit_t) do
378 if i < 4 then
379 draw_astroid(_LCD, v.x + v.w / 2, v.y + v.h / 2, v.hash, WHITE, v.color, s_x, s_y)
380 end
381 end
382 for i, v in ipairs(hit_t) do
383 if not seen[v.hash] then
384 rb.lcd_puts(0, (i - 1), string.format("[%x]", v.hash))
385 end
386 seen[v.hash] = i
387 end
388 end
389
390 drawship(_LCD, ship_t)
391 update_lcd()
392
393 --last_action = action
394 end
395
396 function action_set_quit(bQuit)
397 quit = bQuit
398 end
399
400 function action_quit()
401 return quit
402 end
403end
404
405if not rb.backlight_force_on then
406 rb.backlight_force_on = function() end
407end
408
409if not rb.backlight_use_settings then
410 rb.backlight_use_settings = function() end
411end
412
413action_event(rb.actions.ACTION_NONE) -- we can call this now but not after registering..
414local eva = rockev.register("action", action_event)
415local evc = rockev.register("timer", action_drift, rb.HZ/7)
416
417while not action_quit() do rb.sleep(rb.HZ) end
418
419if start_x and start_y then
420 file = io.open(fname, "w")
421 file:write(start_x, "\n", start_y, "\n", start_z or 0, "\n", scale_x or 1, "\n", scale_y or 1, "\n")
422 io.close( file )
423end