Kernal & BASIC sound API for the ROM - ideas?

Chat about anything CX16 related that doesn't fit elsewhere
BruceMcF
Posts: 1336
Joined: Fri Jul 03, 2020 4:27 am

Kernal & BASIC sound API for the ROM - ideas?

Post by BruceMcF »



On 12/8/2022 at 10:58 AM, Yazwho said:




Is it possible to patch in new 'commands' to Basic? Are there any examples?



You could patch into CBM Basic 2.0 on the C64 -- through the CHRIN routine, as @ZeroByte mentions, or through the Syntax error routine. The syntax error routine is why many Basic extensions in the 80s started the expanded Keywords with a special character which would trigger an error in stock Basic 2.0 -- the shim error routine first checked for the special character, and if not found called the original ROM syntax error routine. That way there was no slowdown in the execution of stock Basic programs when the shim was installed.

I haven't seen any documentation of it being available on the X16, but CBM Basic 2.0 was the starting point for the X16 Basic, so it seems likely both are still available -- there'd be no functional point to removing them.

TomXP411
Posts: 1760
Joined: Tue May 19, 2020 8:49 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by TomXP411 »



On 12/7/2022 at 12:34 PM, ZeroByte said:




To me, having something like a built-in music player (however simple) is well beyond the scope of this. It means that the Kernal must now have a function in the IRQ handler that checks the state of the music player at all times. Honestly, the code to play a song in BASIC on the YM is very simple to make. The worst thing about the YM is configuring the patches (instruments) on the voices. It's pretty easy on PSG as well using VPOKE except for having to figure out what frequency numbers to use. (having a table in the book / on a webpage to reference is one thing, but it's still a PITA)



Essentially, the goal here is to present the equivalent of a basic Casio keyboard - select an instrument and start plunking around. It doesn't have a built-in sequencer, or instrument creation functionality. It's meant to be a "plug-and-play" solution for beginners to have access to the stuff. If you wanna do fancy stuff, it's time to read the chips' reference materials and go down the rabbit hole yourself. Looping through notes from DATA statements and using them to issue PLAY commands (or whatever the BASIC commands end up being) isn't terribly complex, and makes for a good exercise in learning loops and data management in BASIC.



I do appreciate the feedback and strongly encourage more from you and from everyone else.



I haven't written any code for this yet. I'm taking a top-down approach so that I have a clear vision of the goal and what is needed to implement it before hitting the code.



 



On that front, to me, the decision point on the BASIC commands is this: should it be a list of chip-specific atomic commands (as in the list on the OP) or a slightly abstracted "music interface" that works like graphics tools where there's a currently-selected tool (paintbrush, line, flood fill bucket, etc) and color and "cursor location" - all commands acting relative to those?

This would make the list of commands shorter - needing only a command to select chip, voice, and instrument - then it's just NOTE NOTE NOTE NOTE

This seems to make sense to me, and shouldn't require a whole lot of state variables be added to the Kernal's memory area. I just wonder if it's TOO simplified.

 



Ideally, I think we'd have both a set of instrument patches and a way to adjust the patches once they're loaded. (ADSR, Waveform, etc.) 

Something like INSTRUMENT for picking a preset instrument. Something like YMINST 3,12 might load YM channel 3 with patch number 12.

Then a separate command would modify the currently loaded instrument: YMEDIT 3, 4, 8 would work on channel 3, setting parameter 4 to value 8. 

And finally, to set a user defined patch, we might do something like: YMPATCH 3, A$, where A$ is a sequence of bytes that represents all the registers for a patch. 

The PSG would be done in a similar way, with PSGINST, PSGEDIT, and PSGPATCH commands. 

And for both instruments, I'd do something like YMNOTE or PSGNOTE, with a MIDI note number, and a volume/velocity level. Setting a channel's Note number to zero would naturally end the note. 

I prefer note numbers to some system like PLAY "G4", since there's less overhead, and transposing is much easier. (just add or subtract 1 for each half-step.) And if  you just remember that 60 is C4 (Middle C), you can easily figure out any other note, just by knowing the musical scale. 

TomXP411
Posts: 1760
Joined: Tue May 19, 2020 8:49 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by TomXP411 »



On 12/8/2022 at 7:57 AM, ZeroByte said:





  • FMNOTE <channel>,<note>,<octave>[, <volume>]


  • PSGNOTE <channel>,<note>,<octave>[, <volume>]




I don't think octave is needed. In fact, it gets in the way. MIDI note numbers are simpler and faster. It requires fewer parameter lookups, so less time is spent parsing the command, and transposing keys is easier if you leave out octaves. For example, going from C to G is as simple as adding 7 to the note number. That works for any note, no matter where it is on the scale. And as a musician, I really don't think in terms of fixed octaves... I'm thinking more of the relative location of any two notes - especially on piano and guitar, where everything is relative, and shifting keys is just a matter of moving left or right a few spaces.

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by ZeroByte »



On 12/8/2022 at 4:25 PM, TomXP411 said:




I don't think octave is needed. In fact, it gets in the way. MIDI note numbers are simpler and faster. It requires fewer parameter lookups, so less time is spent parsing the command, and transposing keys is easier if you leave out octaves. For example, going from C to G is as simple as adding 7 to the note number. That works for any note, no matter where it is on the scale. And as a musician, I really don't think in terms of fixed octaves... I'm thinking more of the relative location of any two notes - especially on piano and guitar, where everything is relative, and shifting keys is just a matter of moving left or right a few spaces.



Combining note and oct into a single argument would make the command parsing a little simpler, definitely.

I was thinking along the lines of note=0-12 where 0 = off, and 1-12 = C .. B

Using a nybble-packed byte in a format similar to what the YM2151 uses seems to make the most sense to me, as it facilitates an easy 12-value LUT for the pitches (in octave 4) and then just << or >> the resulting value to shift it into the correct octave.

Plus, as the intended audience is the novice user / musician, I think keeping it all in terms of note names makes more sense than a raw continuum of intervals. I don't have to stop and think "what's A4 again?"



So I'm thinking xxNOTE <channel>,<value>[, <volume>] seems to work.



channel = 0-8 for FM, and 0-16 for PSG (where 0 = all channels)

value = $xy where x=octave, y=note

note is 0-12, 0=off, else notes on the C .. B scale. (YM uses C# .. C for some odd reason, but that'd just be up to the driver to handle)

As for instruments on PSG - I'm going to nope out of that. It would be necessary to add an ADSR processor and states, and have it be called during IRQ in order to work, and putting that into the IRQ processing just seems like a waste for the majority case. I think the point here is to make it where you can just type a command and hear the tone coming out of the PSG. This does raise the issue of having a command (or syntax variant) for modifying the volume of an existing note on a channel... Hmmm.....

Remember, this isn't intended to be something super awesome, mostly just there to make simple access to sound. I don't think BASIC is well suited for playback of music that gets very intricate anyway, being as slow and not-well-timed as it is.... It's mostly going to be for playing ditties and or simple sound effects during BASIC programs. Heck, I've got a demo program in Zsound that makes a background ZSM player for BASIC. If you want real music in BASIC, you can always just use that. (or a more fleshed-out version of it - the one that's there is quite bare-bones)



As for the underlying code being added to the ROM bank, there's definitely room for discussion about some more advanced things to put in there... not sure if ADSR processing is in scope or not, but having some common tasks done in ROM means that everyone's programs get to be smaller for not having those routines in RAM. There could probably be a MID2PSG call that computes the PSG pitch values based on a MIDI note. How many MIDI pitch values are there in the standard?

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by ZeroByte »



On 12/7/2022 at 1:02 PM, neutrino said:




Just an idea.. perhaps a command like "Play" like that one in IBM BASIC ( ref ). But which specify which sound chip to be used and which plays in the background. Like many C64 SID tunes did by updating sound I/O during vertical blanking interval.



I've been giving this a little more thought... something that plays in the background is out of scope to what I'm thinking, but a command that plays the ditty and execution pauses while it does its thing might be worthwhile....


  • PLAY <song string>,<channel>,<tempo>


e.g.: PLAY "#F#G#ARFGA",4,1000

this sort of thing seems doable and useful and easy-to-use.... if not super fantastic. It would assume that you've already used FMINST and/or PSGINST to configure the voice as desired.

Designing the music string format would be another whole thing... I think I'll keep this in the back of my head in case this other stuff gets done and into the Kernal...

 

neutrino
Posts: 182
Joined: Wed Oct 19, 2022 5:26 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by neutrino »


Actually you could make FMINST, PSGINST as "notes" ie control codes inside the PLAY string too. Should anyone feel to change those settings on the fly.

 

rje
Posts: 1263
Joined: Mon Apr 27, 2020 10:00 pm
Location: Dallas Area

Kernal & BASIC sound API for the ROM - ideas?

Post by rje »


Hi Zero.


Quote




I think it would make sense to take this experience and create some simple utilities and BASIC commands that can at least get beeps, boops, and dings out of the X16 without having to dive into the arcane realm of audio.



1. If you target the audience of the X16 then you'll get it right.

2. Anything is better than nothing.

3. Code wins arguments.

 

Now having said that...

 

**

Who is the audience?  I'm asking because I'm not sure who that suggestion is for. People who have never programmed a computer before?   Don't 6-year olds write in Python now?

**

POKEing on the C64 was a bad experience.  Then Martin Kees produced THE MOST USEFUL BASIC commands for the SID back in 1985.  I used it exclusively with my BASIC programs.  But he had the SID.

 

Note his use of the "Syntax Error Shim" method by starting commands with the invalid character "["...


Quote







SID Chip Commands



[SSND voice, ad, sr, wave, freq, pwidth

[PLAY 256*wave + voice, freq, pwidth




[SSND (Set up sound) produces a sound from one of the three voices of the SID chip. Voice (1-3) selects the voice, ad and sr control the attack/decay and sustain/release registers of the selected voice. Wave controls the waveform, gating, and special effects functions of the sound chip. Wave, ad, and sr use the same values that would normally be POKEd to these registers. Freq controls the frequency of the voice but is a 16-bit value in the range 0-65535. Pwidth is the pulse width value for the pulse waveform and is needed only when wave = 65. Pwidth is an 11-bit value in the range 0- 12228. [SSND sets the volume register to 15.



[PLAY is a short form of [SSND that assumes AD/SR values have been set previously. Waveform and voice values are coded into the first parameter argument by wave*256 + voice. Freq and pwidth are used the same as in [SSND.



NOTE: [PLAY can be used to silence a voice. For instance, [PLAY 1,0 would silence voice one.






 

 

SUPERBASIC.asm SUPERBASIC.PRG
SuperBASIC v1 Documentation.pdf
rje
Posts: 1263
Joined: Mon Apr 27, 2020 10:00 pm
Location: Dallas Area

Kernal & BASIC sound API for the ROM - ideas?

Post by rje »


 


On 12/8/2022 at 10:29 AM, BruceMcF said:




You could patch into CBM Basic 2.0 on the C64 -- through the CHRIN routine, as @ZeroByte mentions, or through the Syntax error routine. The syntax error routine is why many Basic extensions in the 80s started the expanded Keywords with a special character which would trigger an error in stock Basic 2.0 -- the shim error routine first checked for the special character, and if not found called the original ROM syntax error routine. That way there was no slowdown in the execution of stock Basic programs when the shim was installed.



 

Using the error-shim routine doesn't slow down the rest of BASIC.  

Interrupting CHRIN would, if I remember right.

 

neutrino
Posts: 182
Joined: Wed Oct 19, 2022 5:26 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by neutrino »


Is it something with the number of commands X16 BASIC have and the CPU that makes it slow or is the the BASIC coded in such way that it runs inefficiently?

The former makes it something that is hard to get around. The latter would hint that a re-coded BASIC could be beneficial while still being compatible with existing code.

 

TomXP411
Posts: 1760
Joined: Tue May 19, 2020 8:49 pm

Kernal & BASIC sound API for the ROM - ideas?

Post by TomXP411 »



On 12/9/2022 at 9:21 AM, neutrino said:




Is it something with the number of commands X16 BASIC have and the CPU that makes it slow or is the the BASIC coded in such way that it runs inefficiently?



The former makes it something that is hard to get around. The latter would hint that a re-coded BASIC could be beneficial while still being compatible with existing code.



It's not about the number of commands. The token system means that you can have hundreds of commands without that being a slowdown. If you keep the lower 128 values as ASCII characters, you can have 128 token codes (commands) in a single-byte token system. With two byte tokens, you could have 64K commands.

The reason BASIC is so slow is a combination of things:


  • Numeric literals are not tokenized: parsing numbers is probably the biggest single time consumer in a BASIC program. So one of the biggest speedups is to turn frequently used numbers into variables, especially ones used as limits in FOR loops. 


  • The commands themselves are often poorly implemented, so cause even more slowdown. 


  • Variables are stored in lookup table and accessed via a linear lookup So if you have 23 variables, reading the 23'd variable takes longer than reading the first one.


As a result, the system spends most of its time parsing commands, rather than executing them, and a single command that has a numeric value with several digits can spend thousands of clock cycles just parsing the values. This is the biggest gain in BASIC compilers, since the first thing they do is convert literals to binary values, eliminating the time spent parsing the digits. This is also why using $hex literals on the CX16 is faster than using decimal literals, since the hex parser is much more efficient (each hex digit is exactly half a byte, and that parses very cleanly.)

As to expanding the language - the problem is that the token table is in ROM. So you can't modify it at runtime without pushing that out to RAM and decreasing the amount of memory available for programs. I can think of a few ways around that, however, and maybe I'll suggest something. The simplest way would be to create an "escape" token, which basically tells the interpreter "the next byte is a custom token" and hand that off to user code for parsing. 

 

Post Reply