Shop OBEX P1 Docs P2 Docs Learn Events
Prop2Play - audio player for a P2-EC32 [0.33] - 1024x600 — Parallax Forums

Prop2Play - audio player for a P2-EC32 [0.33] - 1024x600

pik33pik33 Posts: 2,350
edited 2023-06-14 11:20 in Propeller 2

0.33: fixed several bugs including broken SID files playing, changed the screen resolution to 1024x600
0.32: better MP3 support (stil buggy)
0.31: preliminary MP3 support (stil buggy and not finished, but it plays)
0.30: Wuerfel's usbnew driver used for keyboard and mouse control
0.29: mouse control added, FLAC support started
0.27: 6502 added so .sid files can be played, .spc format added (the interface needs finishing, but files can be played)
0.26: .dmp SID files playing fully integrated, status panel added
0.25: A major rewrite: all what could be moved to PSRAM is now moved there. A bitmap based PSRAM driver is now used, 1024x576x8bpp, instead of text and HUB based one.
0.24a: new faster SD access allowed to rewrite .wav playing and release a lot of HUB space
0.23: new audio driver (0.92) with PSRAM cache, dmp files plays from PSRAM
0.22: Amiga modules plays from PSRAM: no more 300kB size limit.
0.21: starting to adapt the player for PSRAM use
0.19: a proper dmp playing procedure, a,d keys change SID frequency
0.18: first SIDCog sound got out from the player (dmp files) - still buggy
0.17: first working wav player (44k/16/stereo only) added
0.15: module player completed, started to modify SIDCog to fit into the player
0.13: real time volume and stereo separation control works, every channel can be switched on or off
0.12: selecting and loading module from SD now works, more visual effects added
0.11: oscilloscope now works
0.11: Playing loop runs in its own cog; user interface started to respond to serial terminal input
0.10: Code cleaned; a module, with still hardcoded name, is now loaded from SD
0.08: 2 of planned fields: channel display and a scrolling file info implemented
0.07: fine horizontal scroll implemented in HDMI driver and used for a help line at the bottom of the screen

Let it start in its own topic instead of audio or video driver one.

The player is intended to play wav, mod and sid (dmp first, real sid files needs 6502 which I have to write first) - and maybe other formats, I don't know which ones, the future will show.

The (new folder of) repository is here: player psram 2

Moved to gitlab:

0.06 is still a preliminary version which cannot access files on SD card and doesn't have any controls now. Although the video driver can do fine scrolling in text mode, the help line at the bottom is still coarse scrolled. The mod file has to be specified in "file" line in the main program.

To be continued.



  • pik33pik33 Posts: 2,350

    It fine scrolls !

  • pik33pik33 Posts: 2,350

    Making a real program which uses the displaylisted driver allowed to remove several bugs from the driver. 482 longs - it needs cleaning. Maybe I will have to remove several functions and add another, more useful instead.
    File info field uses splitted screen - via DL - and then scrolls it, by modifying DL every vblank.
    Channel field displays current channel information (sample name and sample period) in the graphics part of the screen. The rest of this part will be a big oscilloscope, which has to be supported by the audio driver. The graphics mode is scalled vertically x2 to save several bytes, so 8x8 font has to be used there instead of 8x16.

  • pik33pik33 Posts: 2,350
    edited 2022-02-06 16:54

    All planned user interface panels are now in place. The code was cleaned and all unneeded experimental mess was removed.
    The module is no more "shared asm" compiled-in part of the program. Instead it is now loaded from SD card. The module name and path is still hardcoded in the program, but this is the first step needed to allow selecting and loading the module from a SD card.
    The text in "channels" panel, which is drawn on the graphic part of the screen using 8x8 font is now outputted by an inline asm procedure instead of driver's putpixel based one. This reduced the main loop time from unacceptable 9 ms to 500 us.

  • pik33pik33 Posts: 2,350

    The play loop and the user interface loop cannot exist in the same cog: playing now uses its dedicated cog, while the main cog started to respond to the user input from ANSI terminal (TAB now switches panels). When I return home, I will add responding to the RPi KBM interface too
    The serial input cog was set to 2 Mbps before, but as this is not stable in current Flexprop version, the speed was changed to 921600

  • pik33pik33 Posts: 2,350

    I managed to run the first working version of an oscilloscope effect.

  • I wonder how hard it would be to implement Opus audio codec. I think it's made to be a pretty small codec, but has great bitrate/quality tradeoff.

  • Opus should work, as long as it doesn't make flexC explode and actually runs fast enough (may need some handwritten ASM) - I'm too lazy to figure out how big the decoder struct actually is, but one of the API test cases says it should be between 2k and 64k, which I guess is manageable. Though there's a "why" question there. FLAC decoder seems more useful.

  • pik33pik33 Posts: 2,350
    edited 2022-02-13 20:11

    Oscilloscope is now in colors, color bars for channel levels added. Playing timer added.
    The player now can list directories and files, allowing to select and play a module from the SD. The module can be changed in any moment by selecting another one. Selecting can be done using ANSI serial terminal at 2 Mbps or my RPi Zero keyboard and mouse interface. Tab selects a panel, arrows and pgup/down select a file, Enter starts playing. There is no format control yet so opening something that is not a module can give strange audiovisual experience.

    Directory select windows is still very buggy while module select window works near 100% good (there are still some glitches). Now I have to debug this before experimenting with adding SIDCog. DMP will go first, then... I need a 6502... The simplest possible way is translating the Pascal version of it I use in RPi to Basic, the harder but better way is making this using LUT table and skipf loop.

    To speed things up, the program looks for dirlist.txt and filelist.txt in the directory it enters. If there is not such files it creates them and writes the file/directory lists to them. This means if you add a module later to the SD it will be invisible, until the directory is rescanned. To rescan, use "r" key. Without this working with big directories ( I have over 1000 modules in it) is near impossible and very annoing. Using the files means the program will hang up for several seconds when scanning the directory for the first time. This of course doesn't stop playing (by another cog).

    To compile this, you need the newest possible flexprop with hacked include/libsys/basic_dir.c. The modification allows directories to be listed separately, the original procedure treats them as normal files and in the result they were listed also in "Files" panel. To run, Edge or Eval with HDMI accessory at #0 and AV at #8 (audio pins are 14,15), SD with modules, HDMI monitor, PC with ANSI terminal at 2 MBps and/or (both can be connected) RPi Zero interface at 32,33

     * directory handling for BASIC
    #include <compiler.h>
    #include <dirent.h>
    #include <sys/stat.h>
    #include <string.h>
    #define fbReadOnly  0x01
    #define fbHidden    0x02
    #define fbSystem    0x04
    #define fbDirectory 0x10
    #define fbArchive   0x20
    #define fbNormal    (fbReadOnly | fbArchive)
    static int getlower(int c) {
        if (c >= 'A' && c <= 'Z') {
            return c + ('a' - 'A');
        return c;
     * simple pattern matching:
     *   * or *.* matches everything
     *   *xyz matches anything ending in xyz
     *   xyz* matches anything starting in xyz
    int _pat_match(const char *name, const char *pattern)
        int cname, cpattern;
        int name_len, pat_len;
        while (*pattern && *name && *pattern != '*') {
            cpattern = getlower(*pattern);
            cname = getlower(*name);
            if (cname != cpattern) return 0;
        if (*pattern == '*') {
            const char *tmp;
            int i;
            if (pattern[1] == '.' && pattern[2] == '*' && !pattern[3]) {
                return 1;
            /* check for tail */
            for (pat_len = 0; pattern[pat_len] != 0; pat_len++) ;
            for (name_len = 0; name[name_len] != 0; name_len++) ;
            if (name_len < pat_len) return 0;
            name += (name_len - pat_len);
            while (pat_len > 0) {
                if (getlower(*name) != getlower(*pattern)) return 0;
                name++; pattern++;
        if (*pattern == 0 && *name == 0) {
            return 1;
        return 0;
    char *_basic_dir(const char *pattern = 0, unsigned attrib = 0) {
        static DIR* olddir = 0;
        static unsigned oldattrib = 0;
        static const char *oldpattern = "";
        struct dirent *ent;
        struct stat sbuf;
        int r;
        unsigned mode;
        char *string;
        if (pattern && *pattern) {
            // start a new search
            if (olddir) closedir(olddir);
            olddir = opendir(".");
            oldattrib = attrib;
            oldpattern = pattern;
        if (!olddir) {
    #ifdef _DEBUG
            __builtin_printf("_basic_dir: olddir is NULL\n");
            return "";
        for(;;) {
            ent = readdir(olddir);
            if (!ent) {
                olddir = 0;
    #ifdef _DEBUG
                __builtin_printf("_basic_dir: entry is NULL\n");
                return "";
    #ifdef _DEBUG
            __builtin_printf("_basic_dir: entry name is %s\n", ent->d_name);
            /* see if the entry matches the pattern */
            if (!_pat_match(ent->d_name, oldpattern)) continue;
            /* now see if the entry matches the attributes */
            if (oldattrib != 0) {
                r = stat(ent->d_name, &sbuf);
                if (r) {
                    // error in stat, break
                    return "";
                mode = sbuf.st_mode & S_IFMT;
                attrib = 0;
                if (ent->d_name[0] == '.') {
                    attrib |= fbHidden;
                if (0 == (sbuf.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))) {
                    attrib |= fbReadOnly;
                } else {
                    attrib |= fbArchive;
                if ( mode == S_IFDIR ) {
                    attrib = fbDirectory;
                } else if ( mode == S_IFCHR || mode == S_IFBLK || mode == S_IFIFO) {
                    attrib = fbSystem;
                if ( 0 == (attrib & oldattrib) ) {
            string = _gc_alloc_managed(strlen(ent->d_name)+1);
            if (string) {
                strcpy(string, ent->d_name);
            return string;
  • pik33pik33 Posts: 2,350
    edited 2022-02-14 18:26

    It has still more bugs than program lines :) but it looks like this now. The audio goes from these headphones :) connected directly (via 470 uF) to P2 pins - maybe too much load, but it worked and didn't fry the P2 - normally I connect the amplifier there.

  • pik33pik33 Posts: 2,350

    Realtime controls: volume,stereo separation, active channels on/off

    Still don't work: directory up.

  • pik33pik33 Posts: 2,350
    edited 2022-02-19 18:23


    SD "explorer" seems to be complete and working. The module playing part seems to be complete now. Of course, debugging is still needed

    The next challenge: add SIDCog .

    I attached a binary for Eval/Edge. It is controllable with ANSI terminal (Flexprop) at 2 Mbps.

    if someone has a RPi Zero - it can also control the player via an USB keyboard. RPi Zero software: - connect RPi pin 8 (TxD) to P2 pin 32 and RPi pin 10 (RxD) to P2 pin 33, GND of course has to be connected too, and a Zero can be powered from Eval's 5V.

    HDMI is at pin 0, audio 14,15.

    Modules has to be placed on SD (standard Eval's slot pins). When first entry into the directory the program needs to scan it and this may be a long process if there is a lot of files - the program looks as if it hangs up. I will add a message box (scanning directory, please wait....) in next versions.

  • pik33pik33 Posts: 2,350

    Now, there is a problem. FlexBasic doesn't allow to open files using more than 1 cog. WAV and SID DMP files needs to be read in the playing loop in the independent cog while the file manager runs in the main loop also using the file system.
    I need to write a filesystem driver in another cog.

    The player now can play wav files, but it doesn't respond to anything until the wav file ends

  • pik33pik33 Posts: 2,350
    edited 2022-02-22 19:48

    I managed to place wave playing loop into main user interface loop and the program is responding now even while playing a wave file. As both wave play and user interface uses the filesystem, navigating the SD interferes with audio played :(

    First try to make a dedicated file cog was not successful. It works... but it works way too sloooooooooooooow. It seems string parameters passing and multiple function calls slowing it down way too much. FlexBasic's strings are slow due to dynamic, managed memory allocation. I tried to escape this using get/put, and while get works, put doesnt compile generating weird error message. It looks like there is a compiler error, i wrote about this in Basic subforum.

    In normal filesystem usage loading 4k bytes = 23 ms of wave data takes about 10 ms using the Sandisk Edge 32GB card.

  • Flexspin's filesystem is just really slow. In no world should a 4k block transfer take 10ms. For comparsion, I know I can load 4k of random blocks in under 4ms on P1 with optimized routines.

  • pik33pik33 Posts: 2,350
    edited 2022-02-22 21:59

    It is fast enough to play a wav, so things like Flac which I also hope to add should also work but one cog restriction is awful. The player seems to be a first program which uses this file system in Basic environment - a lot of flexbasic bugs was discovered and most of them fixed while writing this. Still, put# ant curdir$ don't work as expected, so I use print# and keep the current directory in the program.
    Strings have to be avoided in time critical code. They are dynamically allocated and managed, simple string assignment can be fast but sometimes can take over 20 ms. New declare... alias syntax gives the possibility to make something like static strings and a set of fast procedures working with them. I miss Pascal's sized static strings, like string[255],

    Edit: 0.17 pushed to github

  • pik33pik33 Posts: 2,350
    edited 2022-02-24 20:24

    While I didn't change the version number, the current player code has been debugged, cleaned and commented. I added, as an experiment, a feature which after the wav file ends, automatically plays the next file from the file list. This works but has to be written in the better way to avoid clicks and gaps between files. Stereo channels are not yet synchronized properly, the channels starts with delay (measured: 44 clocks) but the worse thing is the random and unknown time when the channel gets a new sample (which depends on what was played before) This synchronization has to be done inside the PASM driver code.

  • pik33pik33 Posts: 2,350
    edited 2022-02-25 15:12

    0.18. SID .dmp playing added. Still buggy, but it plays.It seems I managed to destroy something with constants while experimenting with SidCOG, but there is still the original version somewhere in this forum to restart with :)

    Sidcog needs something more than about 600 clocks per loop, P2 is running in the player at original SID frequency*360, so SID/2 is possible giving 720 clocks per loop without destroying anything in SIDCog, but then I got lost with ADSR and filter coefficienst and Mr Marvellous doesn't play as expected.

    Edit: Original SIDCog/P2 topic is hard to find so here it is: and I added all these versions to the Github repository - to not search them anymore

  • pik33pik33 Posts: 2,350

    Mr Marvelous problem fixed, this was neither a filter coefficients nor ADSR constants problem. Original SidCog has hardcoded at $10000 combined waveforms table, which of course didn't fit in the player. I remove hardcoded address but then I forgot do do something to tell the SIDCog where the table is. The result: combined waveforms were actually "code waveforms" as $10000 is the player code.
    I discovered where the problem is while playing another file using combined waveforms as its main instruments.

  • pik33pik33 Posts: 2,350
    edited 2022-03-02 15:31

    The SID .dmp loading from a file is now buffered (as in .wav) so n=moving the cursor on the user interface doesn't distort the sound. Also, "a" and "d" keys increase/decrease SID playing frequency in 50 to 400 Hz range with 50 Hz step. This enables, for example, Eskimonika (200 Hz stuff)

  • pik33pik33 Posts: 2,350

    As I now have a 4-bit PSRAM attached to the P2, I started to adapt the player to use this. A PSRAM can be useful for long module files and for a framebuffer to make better graphics and/or free HUB RAM from the framebuffer.
    The PSRAM chips attached to the Eval board via 12-bin connector, some wires and a breakout board are not stable at 354 MHz, so I had to slow down the CPU to 336 MHz. This is Paula95 and SID342

  • roglohrogloh Posts: 5,149
    edited 2022-03-09 06:34

    Sounds good, let me know how you go with this. I think the request list feature and/or block transfers will be beneficial to you and you may be able to integrate those into your player for higher performance.

    Using suitable QoS/burst settings for the PSRAM and making the video the highest priority followed by audio as the next priority and then the general writer COGs as all low priority round robin polled, it should be possible to share both video and audio with the PSRAM without impacts to either if there is enough bandwidth to go around such that you don't overload the scan line. That is what it has been designed to achieve. The other COGs can write to the PSRAM amongst themselves and will take whatever remaining bandwidth is left. You just have to limit the burst sizes from each COG such that an audio block transfer or a low priority COG burst plus the video scan line burst transfer can all fit within a single scan line period and then you are golden. My driver code will handle the rest.

  • pik33pik33 Posts: 2,350
    edited 2022-03-09 08:14

    One of ideas is make the thing like the real Amiga: get the audio samples by the video driver. Use line start address aligned to 1024, get 1024 bytes, use 960 bytes for graphics display and the rest as audio samples. In the reality however the current player uses text mode, except the oscilloscope window which is 4bpp graphic mode. Both modes need 112 longs=448 bytes for one line, so the idea is still valid: get a burst of 512 bytes.
    What I will do with this, I still don't know. I did the first step slowing the player down. 336 MHz seems to be safe, your video demo works with this frequency (I added an entry to the video driver to test this), problems starts somewhere over 340 MHz. The video and Paula can be slowed even more, for SID, this is the critical frequency, going lower needs to reduce the SID main frequency from 490 to 245 kHz (from real SID/2 to real SID/4). SIDCog has a lot of reserves to optimize. The LUT is empty and the combined waveforms are read from the hub - placing these into the LUT can save about 40 clocks, then, the real SID mapping and decoding in its main loop is not needed for the player. The player can decode the SID registers (at 50 Hz instead of 500 kHz) and feed the SIDCog internal registers instead.

  • roglohrogloh Posts: 5,149
    edited 2022-03-09 09:21

    I think it is good to not have the P2 at 350MHz in general, but of course you can do whatever you want with it. There's probably not a lot of timing margin left at that high a clock anyway and heat would be a big concern when lots of COGs are active unless you have some decent active cooling as well.

    I like your idea with the video scan line bytes for audio. You could potentially even still use a request list for doing this and perform two two transfers from disjoint external memory areas to disjoint hub areas as well. Video would use the first request list entry and audio the second entry (or vice versa). Then only one actual request list would need to be issued per scan line and the driver will perform both transfers and then notify your COG(s) with ATN/mailbox indications of completion.

  • pik33pik33 Posts: 2,350
    edited 2022-03-09 11:30

    I have a video cog, a serial transfer cog, an audio cog, a player loop cog , a main cog and now a PSRAM cog. This is 6 cogs running. There will be another cog as I want a graphic accelerator cog too. Eval is warm, but not hot. Edge is hotter - I glued a heatsink to one of my Edges to make it run cooler. The temperature doesn't exceed 50C as I can still keep a finger on the P2.

  • evanhevanh Posts: 15,168
    edited 2022-03-09 15:09

    Be wary of PLL not making set frequency as it warms up. The hotter the die is running the slower it runs. Might be good idea to have one smartpin toggling at sysclock/1000 to monitor for drooping sysclock frequency.

  • pik33pik33 Posts: 2,350
    edited 2022-03-09 18:19

    This is the player - if the frequency drops, it will be instantly hearable. This player worked many hours at 354 MHz without any trace of instability, on Eval and Edge boards. Now it is slowed down to 336 MHz which adds some more stability margin.

    I experimented with the sound (simple sine wave generator) and CPU frequency. The program was simple enough to run at "410" MHz, which in reality was something like 390 MHz, as the PLL gave up.

  • evanhevanh Posts: 15,168

    Just keep in mind, as the temperature rises that 390 lowers. The more work you're packing, the closer that limit becomes.

  • pik33pik33 Posts: 2,350
    edited 2022-03-13 21:03

    Modules now play from PSRAM - no more 300 kB size limit. Needs debug and switching to HUB when playing WAVs - to do

  • Sounds good pik33. Any working demo to post?

  • pik33pik33 Posts: 2,350
    edited 2022-03-14 09:17

    The project is on Github - PSRAM enabled player is player22.bas. This version will not play wav files as it needs switching the audio driver to HUB and this is not done yet, and I have to clean the audio driver code. player psram

    The video uses hub here. I didn't try to switch the video to PSRAM yet, as I still have no high level procedures for PSRAM graphics and adding PSRAM video may be difficult, as it will add too much delay. I will have to synchronize all things in the video cog.
    But... loading modules to PSRAM frees about 300 kB which I can use for a prettier video and more code. Maybe a flac decoder can be ported here.

    Edit: Paula frequency and SID speed was wrong, now fixed (constants left from 354 MHz version). Wav files plays again, switch between hub and psram implemented

    Edit2: To do: if PSRAM not found, use hub.

Sign In or Register to comment.