The X16 Toolchain
The X16 Toolchain
The two things that seem to slow me down with working on my ridiculous little games are toolchain items:
(1) Sprite tools
(2) A PSG API
I am going to try to use this thread to unify my toolchain thoughts and efforts.
SPRITES
The X16-Demo repository has a very useful Python script in /tools: png2sprite.py, which converts a PNG image to a C-style array of bytes. Its most useful thing is how it does its darndest to match the PNG image's colors to the X16 default 8-bit palette.
I could use that. So I wrapped a Perl script around it. The script calls Python, and takes the output and generates a binary file that can be loaded straight into VERA.
This is useful, because there are plenty of image editors, and all of that work can be done on modern computers.
Both files are attached just because.
**
There's also a sprite editor on the X16 -- so you can draw sprites directly and it outputs BASIC statements. I've used it a bit, but it's not yet part of my toolchain. I'd like to edit it and add a routine that writes a binary file.
c2bin.pl
png2sprite.py
The X16 Toolchain
PSG API
Every six months or so I fret about this. I rotate between a few central topics to fret about -- this keeps me from burning out. Or maybe it keeps me from being productive. Whatever.
I think to myself that a "simple" (?) set of small (?) assembly routines, based of course on Dusan Strakl's tiny sound library, would be just the thing I need to put some kind of SIDlike sounds in my games.
It's one thing to want it, and quite another to do it. I've tinkered with BASIC sound code just to prototype something, anything at all, but I never get far because it's just not trivial. Of course, nothing easy is worth doing, so I come back to it and remember that if I want anything to get done, I should do it.
A PSG API is basically an ADSR envelope manager for the PSG. I think this means each Voice is assigned to an envelope and has a state. Then there's ADSR envelope definitions. The manager itself would have to run on an interrupt, just like Dusan's library.
ENVELOPE
Assume each Voice gets an envelope, defined exactly by the length of each stage: Attack, Decay, Sustain, and Release. It also gets a value identifying its current "position" along the total envelope.
The X16 Toolchain
Concerto is more or less what you're talking about as a front-end for the sound system. It has its own API that's arguably just as intricate as the raw interfaces to the chips, except it makes fancy sounds instead of beeps and boops. Any kind of API is going to be like this - removing some complexities and replacing them with its own.
The X16 Toolchain
As a coder, I'm not even thinking up at that level. I'm thinking on a much lower level. If you've seen SuperBASIC you'll know what I'm thinking of. ... Actually I'll attach the text tech documentation for it.
[SSND is simply fantastic. [DSPR is similarly just 100% useful.
SuperBASIC64.txt
- kliepatsch
- Posts: 247
- Joined: Thu Oct 08, 2020 9:54 pm
The X16 Toolchain
Are you more interested in an API for C or for BASIC?
Do I understand correctly, that you basically wish for a command where you can play a short one-shot sound by just specifying a couple of parameters at the time you are playing the sound - no prior "definition" of the sound needed?
The X16 Toolchain
Well, what I am working towards, slowly and painfully, is an interrupt in assembly which uses memory locations for holding ADSR data.
At the moment, I am first trying out some POCs, like this one, which is in C
- kliepatsch
- Posts: 247
- Joined: Thu Oct 08, 2020 9:54 pm
The X16 Toolchain
Ah I see you have got the ideas working already! Now you "only" need to put the ADSR handling into an interrupt handler.
After some reading I understand that writing an IRQ handler in C is kinda painful (largely following the info given in this thread).
I know that some (if not most of the) functions that come with CC65 are written in assembly, so there must be a way to combine assembly with C code, other than simple inline assembly commands. I would guess you compile assembly and C code separately, and then put the compiled object files together during linking. Greg King will likely know more about this.
"How to combine C and assembly" would be a valuable category in the "How To" section.
As an example for how to combine C and assembly, one might look at how cc65's library does it. Let's look at how "vpoke" is implemented:
There is an assembly source file in /libsrc/cx16 containing the code for the function, and the label is exported. Note that a leading underscore is used. This is because C will internally add an underscore to all of its symbols. (See here)
There is the C definition of vpoke in /include/cx16.h
From here on I am guessing: the assembly file and the C file are compiled independently from one another. But since they have matching names, the linker will match the C call to the ASM function during linking.
The open question is: how does the C compiler pass parameters and the result? The safest and easiest case would be to simply have no parameters and result (which is fine for an IRQ handler I think).
The X16 Toolchain
Thanks for engaging. Actually I'm by default thinking the envelope manager is going to be 100% assembly. Doing something in C moves me forward, gives me some success, and helps me understand the problem.
-
- Posts: 504
- Joined: Sat Jul 11, 2020 3:30 pm
The X16 Toolchain
I had a look at a bit of your code on github. I think you might be trying to make the X16 do a little too much of the work going from the abstract ADSR envelope to the volume of a channel at any given moment.
Just how granular does your ADSR envelope need to be? Is 1/60th of a second enough, or does it need to be modulating the volume on a shorter time scale than that? Because if your interrupt routine is relying on the VSYNC interrupt, that's all you've got for resolution.
And just how long would the total envelope last? surely not more than four seconds, right?
The point I'm getting at is maybe look at getting some C++ program to generate a table of values for an ADSR envelope, some volume between 0 and 63 for each 1/60 of a second of the envelope. Then you only need to increment a pointer into a lookup table at the VSYNC interrupt to find the volume for that channel. Multiple channels could be using the same ADSR lookup tables at different points, each having their own indices incremented every interrupt; perhaps mark the last byte of the sound with an FF or 00 in the lookup table. An envelope that lasts half a second would just be a 30+1 byte string of data. A 256 byte table would be an envelope over 4 seconds long.
So, for each channel you'd have two bytes for the starting point of the table, another offset byte incremented every VSYNC, and that's it. You'd just define those envelopes externally and turn them into some sort of ADSR.DAT file.
Actually, you could have separate tables for Attack, Decay, and Release, and just a regular counter for Sustain. Then the envelope is just pointers to Attack, Decay, a counter for Sustain, and a pointer for Release. The code is a little more involved than just a single ADSR table, but it could give a bit more flexibility in the sounds.