CodeWalrus

Development => Calculators => Calc Projects, Programming & Tutorials => Topic started by: utz on April 05, 2016, 08:29:18 AM

Title: Heavy Metal on TI-92 Plus
Post by: utz on April 05, 2016, 08:29:18 AM
Been abusing my TI-92 Plus to make some Heavy Metal. Check it out, yo: https://soundcloud.com/irrlicht-project/the-aftermath-ti-92-plus (https://soundcloud.com/irrlicht-project/the-aftermath-ti-92-plus)

This is powered by my latest creation, the QED68 sound routine. QED68 mixes four channels of PCM WAV samples in realtime, at around 24 KHz. It can output 24 discrete volume levels with optional overdrive. The above tune is perhaps not ideal for showcasing the routine's power, as I downsampled quite heavily because I was afraid I would run out of RAM (which ultimately turned out not to be the case at all). Better sound quality is very much possible with better samples.

I guess there aren't many TI-92 Plus users around here, but just in case, here's a package with an XM converter (https://github.com/utz82/qed68/releases/latest) that you can use to make your own music using QED68. Last but not least, here's the source code (https://github.com/utz82/qed68).

In theory, the code will also work on TI-89 and V200. I haven't tested this however, because I don't have these models and emulators generally won't do the trick (so yeah, it's real HW only). If someone could test it on these calcs, that'd be great.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Caleb Hansberry on April 05, 2016, 08:35:33 AM
Well it's got my vote of approval~
Title: Re: Heavy Metal on TI-92 Plus
Post by: Dream of Omnimaga on April 05, 2016, 07:18:09 PM
Wow this sounds great @utz ! O.O It almost sounds like some SNES music actually or even some music from Sega CD games. I am surprised that the 92+ can produce such sound, let alone with different volume levels. Sadly, the only 68K calcs I got are the TI-89 Titanium and original TI-92 so I can't use this, but I wonder if it would be easy to port it to the 89 and 89T?
Title: Re: Heavy Metal on TI-92 Plus
Post by: p4nix on April 05, 2016, 07:55:37 PM
Really nice music. I hope you don't mind I included it as music fitting to a mood of greek's 'Medea' in a presentation for an playlist which fits to the plot ;)

Keep doing that awesome lowbit music, I really love pretty much everything uploaded for the Irrlicht Project on Soundcloud!
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 06, 2016, 08:08:22 AM
Aww, thanks you guys, and thanks @DJ Omnimaga or whoever it was for posting this on the front page! That's a great motivation to keep going with this "nonsense" ;)

In theory it would be trivial to port this to 89/89T. The frequencies in the converter will probably have to be adjusted, but otherwise it might very well run out of the box.
I'm attaching a test build (run with "digiplay()"), give it a try if you like.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Lionel Debroux on April 07, 2016, 06:20:14 AM
Good work :)

From a quick glance at the source code, I can see several things:

move.b #$cc,($600017) ;restore timer speed
This is incorrect for HW1 calculators, whose default initial timer value is 0xB2 - and anyway, this doesn't respect a user's custom initial timer value settings :)
I tend to use 0xCE instead of 0xCC on my 89 HW2 calculator, which yields 1024/51 Hz instead of 1024/53 Hz AUTO_INT_5 rate - an order of magnitude closer to 20 Hz. On my calculator running AMS 2.05, after changing the initial value in port 600017 and enabling AUTO_INT_3 until the next power off (port 600015, bit 2), the default APD time, as measured by AUTO_INT_3 ticks stored in the OS's internal variables, is ~299s, instead of ~310-311s.
You need to obtain the initial timer value through a waiting loop, then save and restore that. See PRG_getStart in intr.h, https://debrouxl.github.io/gcc4ti/intr.html#PRG_getStart .

Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 08, 2016, 08:59:01 AM
Wow Lionel, thanks for your detailed feedback and bughunting! Very much appreciate it.
I'm currently working on another project, but I'll get back to this asap. First,  I have a few questions/remarks however...

Quote from: Lionel Debroux on April 07, 2016, 06:20:14 AM
  • Bug report:
move.b #$cc,($600017) ;restore timer speed

Ah yes, I was simply too lazy to implement proper handling of $600017, and didn't really know how to do it either. Thanks for pointing me in the right direction.


Quote from: Lionel Debroux on April 07, 2016, 06:20:14 AM
  • Bug report: setting the SR to 0x0400

I gather AUTO_INT_3 fires at OSC2/2¹⁹ on HW2, but how often does it fire on HW3/4? Any int firing at more than ~30 Hz would cause a parasite tone, and thus be unacceptable. Also, what's the proper way to detect HW3/4?

Quote from: Lionel Debroux on April 07, 2016, 06:20:14 AM
  • Bug report: the AUTO_INT_5 handler starts by clobbering d0 and a6.

This is not a problem, since neither register holds any relevant values at this point. D0 is loaded with A0 on sound loop restart, and A6 just holds a constant pointer to the base of the jump table. I'll still consider your code though, as you hinted that would be faster.

Quote from: Lionel Debroux on April 07, 2016, 06:20:14 AM
  • Minor optimization in the init code
move.w (a1)+,d0 ;skip ctrl word

Whoops, should've seen that one. Well, this is actually just my second 68k asm program (first was a simple PWM implementation quickly done when I got the calc a few months ago), so I'm still learning ;)
Title: Re: Heavy Metal on TI-92 Plus
Post by: Lionel Debroux on April 08, 2016, 10:18:35 AM
QuoteI gather AUTO_INT_3 fires at OSC2/2¹⁹ on HW2, but how often does it fire on HW3/4?
As I wrote, on the 89T, AUTO_INT_3 was repurposed for USB, so unless the user plugs something into the port, it shouldn't fire.
But you still shouldn't disable the 1 Hz AUTO_INT_3 through I >=3 in SR on non-89T models, due to AMS 2.07+ on the V200 and AMS 2.08/2.09 on 89/92+ - it's considered user-unfriendly :)

QuoteAlso, what's the proper way to detect HW3/4?
In general, the most portable way to detect the precise hardware version is FL_getHardwareParmBlock from flash.h, https://debrouxl.github.io/gcc4ti/flash.html#FL_getHardwareParmBlock . That ROM_CALL does not exist on 92+ AMS 1.00, so there's special-casing in flash.h if MIN_AMS=100.
But I'd suggest something simpler for detecting the 89T, especially nowadays, over 10 years and a half after TI abandoned the TI-68k series: using the fact that ROM_BASE == 0x800000, under some form.
The usual way to compute ROM_BASE is to and.l __jmp_tbl with 0xE00000, which the compiler usually translates to:
move.l 0xC8.w,dn; andi.l #E00000,dn; cmpi.l #800000,dn; beq(.s) / bne(.s).
We can do better in ASM by destroying the value and testing only on the relevant part:
move.l 0xC8.w,dn; swap dn; andi.w #E0,dn; cmpi.w / subi.w #80, dn; beq(.s) / bne(.s).
Or heck, if one doesn't care about any potential, highly unlikely future TI-68k models, why not the looser
move.l 0xC8.w,dn; swap dn; tst.b dn; bmi(.s)

But I'd say, don't bother: use I = 2 in SR, and just redirect AUTO_INT_3 and AUTO_INT_4 to a RTE instruction. You can even abuse the AUTO_INT_5 or AUTO_INT_6 handler's RTE.


Further review on your program's code:
Another uncommon thing in your program - even though it shouldn't be an issue for that particular use case, like clobbering registers in the interrupt handlers - is to leave bit 2 of 600001 clear for the entire program's duration.

A significant size optimization which you can use without interfering with timings: replacing these many 7-nop sequences wasting 28 clock cycles, which blow up your program's size immensely (14 bytes every time, as you know), by smaller time-wasters which take the same amount of time, e.g.
6 bytes: move.l dn/an,-(sp); move.l (sp)+,dn/an; nop (12 + 12 + 4)
6 bytes: move.l dn/an,-(sp); addq.l #2,sp; addq.l #2,sp (12 + 8 + 8 )
6 bytes: move.l dn/an,-(sp); addq.l #4,sp; or.l / and.l dn,dn (12 + 8 + 8 )
6 bytes: link an,#0; unlk an (16 + 12)
6 bytes: move.l d(an),d(an) (28), you can use d=0 and n=6 or n=7
6 bytes: move.l xxx.w,xxx.w (28), you can use e.g. 0x4200 (stack fence) or LCD_MEM
6 bytes: movep dn,-8(sp); nop (24 + 4) - not even sure the movep instruction works on the TI-68k series, and I haven't implemented it in the JS TI-68k emulator.
4 bytes: move.l (sp),-(sp); addq.l #4,sp (20 + 8 )
I haven't found 2-byte or 4-byte examples, but even with 6-byte sequences, you'll save hundreds of bytes.
The fact that the clock cycle count is low precludes e.g. bsr + rts (18 + 16) or jsr d(pc) + rts (both 18 + 16), which wouldn't be smaller than the above anyway, and even more so triggering a processor exception such as trap (2 bytes !) + rte (34 + 20). The fact that there's no spare register further reduces the options for wasting time, e.g. DBcc.
I thought about the 6-byte clr.l xxx.l (12 for operation, 16 for EA computation !), but either one uses a relocated address in the program and the size benefit largely disappears, or one uses a constant address, which is basically a no-no in RAM, and would need precise timing testing for other addresses (Flash, or simply outside the assigned space), though wait states are normally not used on the 89 / 92+ / V200 / 89T.

Your 2-nop sequences followed by an instruction which resets the flags can be replaced by or.l dn,dn / and.l dn,dn. Likewise, if you're feeling fancy, single nops can be replaced by e.g. or.w dn,dn, and.w dn,dn, tst.w dn, cmp dn,dn. And three nops can be replaced by e.g. two not.l dn, neg.l dn, or one move to ccr.

Three generic optimizations which might be useful to you for another program, and you probably already know about them, but which need extra care not to interfere with your carefully crafted timings in this program:
* move.b #$ff,dn -> st.b dn: smaller and faster;
* move.b   #$0,dn -> clr.b dn: smaller and faster. Actually, a set of 4 move.b #0,dn could be replaced by 4 clr.b dn + subq.l #4,sp + addq.l #4, sp, which would still be 4 bytes smaller.
* make sure all of your branches which could be short (without interfering with timings) are actually short.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Dream of Omnimaga on April 09, 2016, 05:31:29 AM
Quote from: utz on April 06, 2016, 08:08:22 AM
Aww, thanks you guys, and thanks @DJ Omnimaga or whoever it was for posting this on the front page! That's a great motivation to keep going with this "nonsense" ;)

In theory it would be trivial to port this to 89/89T. The frequencies in the converter will probably have to be adjusted, but otherwise it might very well run out of the box.
I'm attaching a test build (run with "digiplay()"), give it a try if you like.
Yay! Glad to see a 89T version. You should put download links in your first post too. :)
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 09, 2016, 12:16:31 PM
Thanks again so much, Lionel. I'll definately incorporate your suggestions.

DJ, the version I posted was just a test to check if it would work in principle. For a proper 89T version, I'll need to make some changes based on what Lionel said, and possibly also adjust the converter to account for the faster clock speed of the HW3/4 models. I'll get back to it when I've finished my current project, which will take a few more days to complete.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Dream of Omnimaga on April 10, 2016, 05:52:58 AM
Oh I see. I was wondering if it would actually run off the bat on my calc. That reminds me, I need to find my 2.5mm converter again.
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 20, 2016, 03:16:06 PM
@Lionel Debroux: I'm in the process of implementing your suggestions. Two quick questions though.

Quote from: Lionel Debroux on April 07, 2016, 06:20:14 AM
I tend to use 0xCE instead of 0xCC on my 89 HW2 calculator, which yields 1024/51 Hz instead of 1024/53 Hz AUTO_INT_5 rate - an order of magnitude closer to 20 Hz. On my calculator running AMS 2.05, after changing the initial value in port 600017 and enabling AUTO_INT_3 until the next power off (port 600015, bit 2), the default APD time, as measured by AUTO_INT_3 ticks stored in the OS's internal variables, is ~299s, instead of ~310-311s.
You need to obtain the initial timer value through a waiting loop, then save and restore that. See PRG_getStart in intr.h, https://debrouxl.github.io/gcc4ti/intr.html#PRG_getStart .

So I can't just read out the value from port 600017? My limited (read: non-existant) understanding of C suggests that PRG_getStart does just that.

Second question is about AUTO_INT_3. In your last post you suggest redirecting it to a RET, but earlier you said I should only do that on HW3+. Which is correct? I mean if it speeds up things I'll gladly redirect it, but if that kills the clack feature I can probably live with not touching them, provided the original INTs don't clobber any registers.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Lionel Debroux on April 20, 2016, 08:03:52 PM
1) PRG_getStart does indeed read port 600017, but it also fiddles with port 600015. See https://github.com/debrouxl/gcc4ti/blob/next/trunk/tigcc/archive/prgstart.s and http://tict.ticalc.org/docs/J89hw.txt .
2) yup, I meant that you should redirect AUTO_INT_3 to a RTE on the HW3/HW4 89T (ROM_base == 0x800000), in order to reduce as much as possible the potential USB interference with your timings (if someone leaves the calculator connected to an USB host), but you should leave non-89T (HW1/2) alone :)
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 21, 2016, 12:58:10 PM
Man, things aren't going well today at all.
I've implemented the detection loop as follows:

PRG_getStart
lea.l ($600017),a0
bset #3,-2(a0)
\ne_loop
tst.b (a0)
bne.s \ne_loop
\eq_loop
move.b (a0),d2
beq.s \eq_loop


The result is that the sound routine will crash with an Illegal Address Error at some point. Mind you, I just have that bit of code in there, not actually using the result. Any ideas why? Also, why does tprbuilder refuse the "move.l xxx.w,dn" mnemonic?

I'm a bit unhappy because I'm spending hours after hour on this thing, when I could certainly do more useful things.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Dream of Omnimaga on April 21, 2016, 02:38:16 PM
Sorry to hear utz. I know the feeling about such debugging. >.<

Maybe Lionel might be able to help.
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 21, 2016, 07:09:23 PM
I'm afraid at this point even Lionel can't help me... that bit of code by itself can't be causing the problem, as in effect it does nothing at all. I'm sure the problem must be elsewhere. I mainly needed to vent my frustration, so thanks for the consolidating words @DJ Omnimaga.

I've got a hunch about what's going on by now. My guess is that those stack-based size optimizations that Lionel proposed need some modification, because in their current form they will mess up the registers when an interrupt triggers between those 2-step instructions. But that I will investigate... tomorrow, maybe.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Lionel Debroux on April 22, 2016, 11:54:57 AM
Most of the size optimizations I proposed are stack-based, yeah. But if the mere fact of switching to e.g. move.l xxx.w,xxx.w fixes the crash, then that would be a sign that your code is really unsafe, and you're just papering over the real issue.
To prevent reentrance from the same interrupt, the whole critical section of code should be executed from within the interrupt handler, i.e. before the RTE. Which means you wouldn't need to play games with the return address, either.

[EDIT: fixed typo]
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 22, 2016, 02:15:10 PM
Yeah, you're right. And those optimizations were not the problem, after all. Also I agree, not fiddeling with the return addresses would be nice, but I don't see how to do it without a major (unacceptable) increase in step-to-step transition noise.

So what was the problem? Well, I didn't realize that after running the timer detection loop, AUTO_INT_5 would fire immediately (ie. during TRAP #1). Which of course causes trouble when the AUTO_INT_5 vector has been modified  :banghead:

Aaaaanyway... before I package a new release, would one of you be so kind to test the TI-89 build? (upload both attached files and boot with "qed68()")
Also, updated source is available at https://github.com/utz82/qed68 (https://github.com/utz82/qed68) for further examination.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Lionel Debroux on April 22, 2016, 03:06:10 PM
I haven't tested your program, but I can see other optimizations:
* the branches in the rdch area should be short (smaller);
* bclr #0,($60000E); bclr #1,($60000E) should be replaced by a single and (smaller, faster).
* bra \nx[1234] should be short (smaller): unconditional branches are 10 cycles, whether they're short or word;
* in rdseq, movea.l (seqpntr),a0 should be PC-relative (smaller). This one doesn't seem to be part of an area with carefully crafted timings;

For maintainability, you should have used appropriately named macros, instead of open-coding so many
* or.l d0,d0
* move.l (a7),-(a7); addq.l #4,a7
* move.l 1(a<n>),a<n> immediately followed by bra \nx(<n-1>) (I have noticed the occasional move in-between, for timing purposes).
* cmp.b (a<n>),d0 immediately followed by beq \resetsmp<n-1>;
* the 4 5-instruction sequences at the beginning of every core* subroutine, unless writing to the link port requires otherwise;
* the 6-instruction sequence at every \nx4 label, only core2[234] seem to be different due to writing to the link port.

A pessimization: disabling vector table protection only for short periods of time, when you're actually modifying its contents, instead of disabling it for the whole duration of your program :)
Title: Re: Heavy Metal on TI-92 Plus
Post by: utz on April 22, 2016, 04:00:51 PM
Thanks Lionel, I'll look into these.

Branches are auto-optimized via the .tpr, so they should always be short. But I can add the .s to make the code more portable. Forgot about re-enabling vector table protection, will take care of that.

I very much dislike macros. I do see why others find them convenient, but for me, they decrease readability of the code too much.  So since I'm the one who has to maintain this code, I prefer to keep the text bloat, sorry.

Edit: Changes done, latest test build for TI-89 attached.
Title: Re: Heavy Metal on TI-92 Plus
Post by: Dream of Omnimaga on April 22, 2016, 09:50:52 PM
I'll give this a try on a calculator later if I can find my adapter and some batteries. :)