Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 10:08 02 Dec 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Simple

Author Message
marcos_lm
Newbie

Joined: 13/11/2025
Location: Spain
Posts: 5
Posted: 12:46pm 01 Dec 2025
Copy link to clipboard 
Print this post

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: Australia
Posts: 3309
Posted: 01:06pm 01 Dec 2025
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 10660
Posted: 01:14pm 01 Dec 2025
Copy link to clipboard 
Print this post

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: Spain
Posts: 5
Posted: 01:19pm 01 Dec 2025
Copy link to clipboard 
Print this post

  Geoffg said  Wow!  That is so impressive on so many levels both artistic and technical.
Geoff


Thanks a lot, Geoff.
I really appreciate your comment.
 
marcos_lm
Newbie

Joined: 13/11/2025
Location: Spain
Posts: 5
Posted: 01:37pm 01 Dec 2025
Copy link to clipboard 
Print this post

  matherp said  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


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: Netherlands
Posts: 5495
Posted: 01:46pm 01 Dec 2025
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 10660
Posted: 02:09pm 01 Dec 2025
Copy link to clipboard 
Print this post

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: Spain
Posts: 5
Posted: 02:13pm 01 Dec 2025
Copy link to clipboard 
Print this post

matherp, V6.00.03
 
Martin H.

Guru

Joined: 04/06/2022
Location: Germany
Posts: 1317
Posted: 03:24pm 01 Dec 2025
Copy link to clipboard 
Print this post

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: Spain
Posts: 5
Posted: 03:48pm 01 Dec 2025
Copy link to clipboard 
Print this post

  Martin H. said  Unfortunately, I failed at vectorizing the frames (my contour tracing never worked properly., or rather, I didn't pursue it any further.


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 Kingdom
Posts: 10660
Posted: 03:56pm 01 Dec 2025
Copy link to clipboard 
Print this post

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
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 States
Posts: 3497
Posted: 04:22pm 01 Dec 2025
Copy link to clipboard 
Print this post

Anything but "Simple". Terrific job--congratulations.
PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed
 
twofingers

Guru

Joined: 02/06/2014
Location: Germany
Posts: 1691
Posted: 05:18pm 01 Dec 2025
Copy link to clipboard 
Print this post

Simply fantastic! Congratulations!
causality ≠ correlation ≠ coincidence
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 8332
Posted: 05:40pm 01 Dec 2025
Copy link to clipboard 
Print this post

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: Germany
Posts: 597
Posted: 10:16pm 01 Dec 2025
Copy link to clipboard 
Print this post

Cool.
Thx for the code
Plasma
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10660
Posted: 10:20pm 01 Dec 2025
Copy link to clipboard 
Print this post

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
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
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025