|
Forum Index : Microcontroller and PC projects : Simple
| Author | Message | ||||
| marcos_lm Newbie Joined: 13/11/2025 Location: SpainPosts: 5 |
Hi all, My name is Marcos, I write from Spain. Sorry for my English, I don't practice it very often. I am very new to MMBasic and to the PicoMite HDMI/USB. To learn, I made a small "Bad Apple" demo that plays video with audio from the SD card. Almost everything is written in plain MMBasic (only a small CSUB), so the code is not very optimal, but it works good enough for me now. I am just a hobbyist. If someone wants to test it or look at the code, the project is here: https://github.com/marcoslm/BadApple-PicoMite And here is a short video of the demo running: https://www.youtube.com/watch?v=hlqbMI7XZ-w I’m sure the code is far from perfect, so any suggestions to improve performance, structure or style are very welcome. Thanks a lot for MMBasic and the PicoMite, it is a very fun system. |
||||
| Geoffg Guru Joined: 06/06/2011 Location: AustraliaPosts: 3309 |
Wow! That is so impressive on so many levels both artistic and technical. Geoff Geoff Graham - http://geoffg.net |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10660 |
Truly excellent Can you explain the format of the converted video file ba.vid. I'd love to find a way to make it work at full speed purely in Basic |
||||
| marcos_lm Newbie Joined: 13/11/2025 Location: SpainPosts: 5 |
Thanks a lot, Geoff. I really appreciate your comment. |
||||
| marcos_lm Newbie Joined: 13/11/2025 Location: SpainPosts: 5 |
Thanks! Sure, I can explain the format. The video file is basically a stream of pixels (just black and white) compressed with RLE. There is no header in the file. The width, height, number of frames and framerate are all fixed in the player code. To make it smaller and avoid extra reads, every line always starts with 'black'. After each run, the decoder automatically toggles the colour (black/white). When the encoder needs to change the colour without drawing or moving X, it writes a run-length of zero. Right now my Basic version uses a normal string as buffer, so I’m limited to reading 256-byte chunks. I saw in the manual that longstrings exist, so maybe they could help load a whole frame at once, but I still need to test that. The CSUB and the pure Basic decoder both run at a “minimum guaranteed fps”. If I disable the fps limiter, the frame time varies a lot (peaks and drops), so sync with audio becomes messy. ba.vid is 320x240 at 22fps. balq.vid is 160x120 at 6fps (Basic alone can’t go much faster on my code). If you want, I can try generating a 30fps file just for testing (it would be amazing if it ran at full speed only in Basic). Edited 2025-12-01 23:46 by marcos_lm Footnote added 2025-12-01 23:51 by marcos_lm If you want to test it, in my code I have these variables acting as flags to enable/disable the FPS limiter and other options: CONST SHOW_FPS = 0 ' Show current FPS CONST SHOW_FPS_MIN = 0 ' Show the lowest FPS CONST LIMIT_FPS = 1 ' Enable FPS limit to the target value |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5495 |
Hi Marcos, I have to check audio-video sync, but the LQ also runs on a 252MHz RP2040 VGA in mode 2 VGA. I simply commented out the CPUSPEED check. This is on V6.01.00rc21. The 22fps version does not run on a 2040 (probably needs re-compile of the CSUB). Volhout Edited 2025-12-01 23:50 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10660 |
Marcos, which version of the firmware are you using? The csub doesn't work for me on 6.01.00RC20 |
||||
| marcos_lm Newbie Joined: 13/11/2025 Location: SpainPosts: 5 |
matherp, V6.00.03 |
||||
| Martin H. Guru Joined: 04/06/2022 Location: GermanyPosts: 1317 |
Hi Marcos, Wow, that's really excellent. I also had the idea of animating the BA video, but with the approach of drawing the images using the fast "polygon" command, which would compress the images even more, of course. Unfortunately, I failed at vectorizing the frames (my contour tracing never worked properly., or rather, I didn't pursue it any further. 'no comment |
||||
| marcos_lm Newbie Joined: 13/11/2025 Location: SpainPosts: 5 |
Thanks Martin! Funny thing: my first idea was also to try polygons, especially after seeing the 'polygon' command in the manual. Something like the old “State of the Art” demo on the Amiga. But some frames have so many separate “objects” at the same time that vectorizing all that looked too complex for me (and I wanted quick results), so in the end I trusted the PicoMite to draw lines fast enough and went for the method I used. I also thought about using predefined tiles and encoding each frame with tile indexes. That would probably be very fast too, but the image quality would be much lower. Thanks again! |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10660 |
Marco, I'm up to about 8FPS average Basic only 320x240. I haven't tried to sync the audio yet. Having the audio and video files on different devices as in my source makes a small difference but the big change is buffering all the writes. Apologies for mangling your elegant code. '============================================================ ' Bad Apple Demo para PicoMite HDMI/USB ' Autor: Marcos LM (2025) ' ' - Version HQ: decodificador CSUB en C (320x240 @ 22 FPS) ' - Version LQ: decodificador MMBasic (160x120 @ 6 FPS) ' ' Requisitos: ' - Firmware PicoMite HDMI/USB >= 6.00.03 ' - Archivos: ba.vid, balq.vid, ba.aud, ba.jpg en la SD ' ' Controles: ' - Cursores arriba/abajo: seleccionar opcion ' - ENTER : aceptar ' - ESC : parar reproduccion / salir '============================================================ Option EXPLICIT Option console serial '------------------------ ' Configuracion de video '------------------------ Dim INTEGER vidWidth, vidHeight, vDelay,l Dim FLOAT fps, totalFrames Dim STRING vidFile, audFile '------------------------ ' Configuracion extra '------------------------ Const HQ_DECODE = 0, LQ_DECODE = 1 Const T_ESC=27, T_ENTER=13, T_UP=128, T_DOWN=129 Const F_HANDLER = 1 ' canal para la apertura del archivo Const C_WHITE = RGB(255,255,255) Const C_GRAY = RGB(128,128,128) Const C_BLACK = RGB(0,0,0) Const SHOW_FPS = 0 ' Muestra FPS actuales Const SHOW_FPS_MIN = 0 ' Muestra el FPS mas bajo Const LIMIT_FPS = 1 ' Activa el limite a FPS objetivo '------------------------ ' Variables generales '------------------------ Dim FLOAT frameIndex Dim INTEGER x, y Dim INTEGER runLen Dim INTEGER colorIsWhite Dim STRING chunk ' buffer de datos comprimidos Const CHUNK_SIZE = 255 ' 255 como maximo (limitacion de bytes para string) Dim INTEGER bytesInChunk Dim INTEGER chunkPos Dim FLOAT targetTime Dim FLOAT frameDuration Dim FLOAT fpsCurrent Dim FLOAT frameStartTime Dim FLOAT fpsMin = 1000 Dim INTEGER selection ' Menu Dim integer x1(799),x2(799),y1(799),ch(1023) '------------------------ ' Comprobacisn inicial '------------------------ ' Comprobar velocidad de CPU (debe ser 315000 KHz para sincronizar A/V) Dim FLOAT cpuhz cpuhz = Val(MM.Info(CPUSPEED$)) If cpuhz <> 315000000 Then Print "Bad Apple demo expects CPUSPEED = 315000 (315 MHz)" B Print "Current: "; MM.Info(CPUSPEED$); " Hz" Print "Adjust with: OPTION RESOLUTION 640x480,315000" End EndIf Do '------------------------ ' Menu de Seleccion ' E inicializacion '------------------------ selection = MenuSel() ' Ajustamos configuracion ' Demo con decoder BASIC vidWidth = 320 vidHeight = 240 fps = 8 vidFile = "a:/ba.vid" audFile = "b:/ba.aud" totalFrames = 4754 targetTime = 1000000/fps ' ms por frame objetivo vDelay = 0 ' Salir ' Configuracion de pantalla y framebuffers MODE 2 : Font 8 Color C_WHITE, C_BLACK CLS ' Creamos un framebuffer donde dibujaremos y despues copiaremos al visible ' para evitar parpadeo (double buffering) FRAMEBUFFER CREATE FRAMEBUFFER WRITE N : CLS FRAMEBUFFER WRITE F : CLS ' Abrir archivo de video Open vidFile For INPUT As F_HANDLER loadchunk 'prime the chunk buffer ' Lanzar audio Play WAV audFile Pause vDelay ' ajuste de tiempo para cuadrar audio con video SYNC targettime '------------------------ ' Bucle de reproduccion '------------------------ Timer = 0 fpsCurrent = 0 frameIndex = 0 ' Dibujamos en F FRAMEBUFFER WRITE F Do While frameIndex < totalFrames CLS Math set 0,x1() Math set 0,x2() Math set 0,y1() l=0 ' Carga de 'chunk' y decodificar frame completo For y = 0 To vidHeight - 1 ' Inicializar linea para nuevo frame x = 0 colorIsWhite = 0 ' RLE personalizado: cada linea empieza en negro Do While x < vidWidth runLen = Byte(chunk, chunkPos) ' Si es tramo blanco y longitud > 0, dibujar la linea If colorIsWhite And runLen > 0 Then x1(l)=x x2(l)=x+runLen-1 y1(l)=y Inc l EndIf Inc x,runlen colorIsWhite = 1 - colorIsWhite ' alternar negro/blanco Inc chunkPos If chunkPos > bytesInChunk Then loadchunk Loop Next Line x1(),y1(),x2(),y1() Print Timer SYNC Timer =0 FRAMEBUFFER COPY F, N Inc frameIndex = frameIndex If Asc(Inkey$) = T_ESC Then Play STOP : Exit Do Loop Close F_HANDLER Do While MM.Info(SOUND)="WAV" Pause 20 Loop Play STOP FRAMEBUFFER CLOSE Loop '------------------------ ' SUBs y Funciones '------------------------ ' Menu de seleccion Function MenuSel() As INTEGER Const NOPC = 2 Local STRING opciones(NOPC-1) = (" Bad Apple Demo ", " Exit ") Local STRING ayudas(NOPC-1) = ("Lanzar version optimizada in Basic","Salir de la demo ") Local INTEGER estado=0, tecla, i Local INTEGER x=20, y=110 MODE 4 : CLS Load JPG "ba.jpg" Do ' Pintar opciones Font 7 For i = 0 To NOPC-1 If i = estado Then Color C_WHITE, C_BLACK Else Color C_BLACK, C_WHITE EndIf Text x, y + i * 10, opciones(i) Next i ' Texto de ayuda Font 8 Color C_GRAY, C_WHITE Text 20, 230, ayudas(estado) ' Esperar tecla Do tecla = Asc(Inkey$) Loop While tecla = 0 If tecla = T_ENTER Then Exit Do Play TONE 500, 550, 20 Select Case tecla Case T_ESC estado = -1 Exit Do Case T_UP If estado > 0 Then estado = estado-1 Case T_DOWN If estado < NOPC-1 Then estado = estado+1 End Select Loop Play TONE 1000,1000,20 ' Beep de confirmacion Do While MM.Info(SOUND)="TONE" Pause 5 Loop MenuSel=estado End Function ' Lee un nuevo bloque del video comprimido (RLE) Sub LoadChunk chunk = Input$(CHUNK_SIZE, F_HANDLER) bytesInChunk = Len(chunk) chunkPos = 1 If bytesInChunk = 0 Then FRAMEBUFFER CLOSE : Play STOP : Close F_HANDLER Print "EOF inesperado en frame"; frameIndex End ' Abortamos EndIf End Sub Edited 2025-12-02 02:27 by matherp |
||||
| lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3497 |
Anything but "Simple". Terrific job--congratulations. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
| twofingers Guru Joined: 02/06/2014 Location: GermanyPosts: 1691 |
Simply fantastic! Congratulations! ![]() causality ≠ correlation ≠ coincidence |
||||
| Mixtel90 Guru Joined: 05/10/2019 Location: United KingdomPosts: 8332 |
But you can't run "Bad Apple" on a microcontroller... ;) This is brilliant! :) Mick Zilog Inside! nascom.info for Nascom & Gemini Preliminary MMBasic docs & my PCB designs |
||||
| Plasmamac Guru Joined: 31/01/2019 Location: GermanyPosts: 597 |
Cool. Thx for the code Plasma |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10660 |
Marcos This is my best effort using every trick I can think of. The printout on the console shows the average frame write time. 320x240 basic gives 11FPS average '============================================================ ' Bad Apple Demo para PicoMite HDMI/USB ' Autor: Marcos LM (2025) ' ' - Version HQ: decodificador CSUB en C (320x240 @ 22 FPS) ' - Version LQ: decodificador MMBasic (160x120 @ 6 FPS) ' ' Requisitos: ' - Firmware PicoMite HDMI/USB >= 6.00.03 ' - Archivos: ba.vid, balq.vid, ba.aud, ba.jpg en la SD ' ' Controles: ' - Cursores arriba/abajo: seleccionar opcion ' - ENTER : aceptar ' - ESC : parar reproduccion / salir '============================================================ Option EXPLICIT Option console serial '------------------------ ' Configuracion de video '------------------------ Dim INTEGER vidWidth, vidHeight, vDelay,l,total Dim FLOAT fps, totalFrames Dim STRING vidFile, audFile '------------------------ ' Configuracion extra '------------------------ Const HQ_DECODE = 0, LQ_DECODE = 1 Const T_ESC=27, T_ENTER=13, T_UP=128, T_DOWN=129 Const F_HANDLER = 1 ' canal para la apertura del archivo Const C_WHITE = RGB(255,255,255) Const C_GRAY = RGB(128,128,128) Const C_BLACK = RGB(0,0,0) Const SHOW_FPS = 0 ' Muestra FPS actuales Const SHOW_FPS_MIN = 0 ' Muestra el FPS mas bajo Const LIMIT_FPS = 1 ' Activa el limite a FPS objetivo '------------------------ ' Variables generales '------------------------ Dim FLOAT frameIndex Dim INTEGER x, y Dim INTEGER runLen Dim INTEGER colorIsWhite Dim STRING chunk ' buffer de datos comprimidos Const CHUNK_SIZE = 192 ' 255 como maximo (limitacion de bytes para string) Dim INTEGER bytesInChunk Dim INTEGER chunkPos Dim FLOAT targetTime Dim FLOAT frameDuration Dim FLOAT fpsCurrent Dim FLOAT frameStartTime Dim FLOAT fpsMin = 1000 Dim INTEGER selection ' Menu Dim integer x1(1999),x2(1999),y1(1999) 'get the address of the dimension size of y1 Dim integer y1add=Peek(varheader y1())+36 '------------------------ ' Comprobacisn inicial '------------------------ ' Comprobar velocidad de CPU (debe ser 315000 KHz para sincronizar A/V) Dim FLOAT cpuhz cpuhz = Val(MM.Info(CPUSPEED$)) If cpuhz <> 315000000 Then Print "Bad Apple demo expects CPUSPEED = 315000 (315 MHz)" B Print "Current: "; MM.Info(CPUSPEED$); " Hz" Print "Adjust with: OPTION RESOLUTION 640x480,315000" End EndIf Do '------------------------ ' Menu de Seleccion ' E inicializacion '------------------------ selection = MenuSel() ' Ajustamos configuracion ' Demo con decoder BASIC vidWidth = 319 vidHeight = 239 fps = 10 vidFile = "a:/ba.vid" audFile = "b:/ba.aud" totalFrames = 4754 targetTime = 1000000/fps ' ms por frame objetivo vDelay = 0 ' Salir ' Configuracion de pantalla y framebuffers MODE 2 : Font 8 Color C_WHITE, C_BLACK CLS ' Creamos un framebuffer donde dibujaremos y despues copiaremos al visible ' para evitar parpadeo (double buffering) FRAMEBUFFER CREATE FRAMEBUFFER WRITE N : CLS FRAMEBUFFER WRITE F : CLS ' Abrir archivo de video Open vidFile For INPUT As F_HANDLER loadchunk 'prime the chunk buffer ' Lanzar audio Play WAV audFile Pause vDelay ' ajuste de tiempo para cuadrar audio con video SYNC targettime '------------------------ ' Bucle de reproduccion '------------------------ Timer = 0 fpsCurrent = 0 frameIndex = 0 ' Dibujamos en F FRAMEBUFFER WRITE F Do While frameIndex < totalFrames CLS Math set 0,x1() Math set 0,x2() Math set 0,y1() l=0 ' Carga de 'chunk' y decodificar frame completo For y = 0 To vidHeight ' Inicializar linea para nuevo frame x = 0 loadchunk Do While x <= vidWidth runlen =Byte(chunk,chunkpos) Inc x,runlen Inc chunkPos runlen =Byte(chunk,chunkpos) If x>vidWidth Then Exit If runLen > 0 Then x1(l)=x x2(l)=x+runLen-1 y1(l)=y Inc l EndIf Inc x,runlen Inc chunkPos If x>vidWidth Then Exit runlen =Byte(chunk,chunkpos) Inc x,runlen Inc chunkPos runlen =Byte(chunk,chunkpos) If x>vidWidth Then Exit If runLen > 0 Then x1(l)=x x2(l)=x+runLen-1 y1(l)=y Inc l EndIf Inc x,runlen Inc chunkPos Loop Next 'set the array size to the number of lines Poke word y1add,l-1 If l Then Line x1(),y1(),x2(),y1() Poke word y1add,1999 Inc total,Timer SYNC Timer =0 FRAMEBUFFER COPY F, N Inc frameIndex = frameIndex Print total\frameindex If Asc(Inkey$) = T_ESC Then Play STOP : Exit Do Loop Close F_HANDLER Do While MM.Info(SOUND)="WAV" Pause 20 Loop Play STOP FRAMEBUFFER CLOSE Loop '------------------------ ' SUBs y Funciones '------------------------ ' Menu de seleccion Function MenuSel() As INTEGER Const NOPC = 2 Local STRING opciones(NOPC-1) = (" Bad Apple Demo ", " Exit ") Local STRING ayudas(NOPC-1) = ("Lanzar version optimizada in Basic","Salir de la demo ") Local INTEGER estado=0, tecla, i Local INTEGER x=20, y=110 MODE 4 : CLS Load JPG "ba.jpg" Do ' Pintar opciones Font 7 For i = 0 To NOPC-1 If i = estado Then Color C_WHITE, C_BLACK Else Color C_BLACK, C_WHITE EndIf Text x, y + i * 10, opciones(i) Next i ' Texto de ayuda Font 8 Color C_GRAY, C_WHITE Text 20, 230, ayudas(estado) ' Esperar tecla Do tecla = Asc(Inkey$) Loop While tecla = 0 If tecla = T_ENTER Then Exit Do Play TONE 500, 550, 20 Select Case tecla Case T_ESC estado = -1 Exit Do Case T_UP If estado > 0 Then estado = estado-1 Case T_DOWN If estado < NOPC-1 Then estado = estado+1 End Select Loop Play TONE 1000,1000,20 ' Beep de confirmacion Do While MM.Info(SOUND)="TONE" Pause 5 Loop MenuSel=estado End Function ' Lee un nuevo bloque del video comprimido (RLE) Sub LoadChunk chunk=Right$(chunk,Len(chunk)-chunkpos+1) bytesinchunk=Len(chunk) chunkPos = 1 If bytesinchunk<64 Then Inc chunk,Input$(CHUNK_SIZE,F_HANDLER) bytesinchunk=Len(chunk) EndIf If bytesInChunk = 0 Then FRAMEBUFFER CLOSE : Play STOP : Close F_HANDLER Print "EOF inesperado en frame"; frameIndex End ' Abortamos EndIf End Sub |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |