Jump to content
IGNORED

May the FORTH Musical offering - FORTI Decompiled


Recommended Posts

 

May 4th Musical offering: FORTI Decompiled

 

1. Introduction

 

FORTI was a sound card for the TI-99/4A PEB, with software written in FORTH. It featured 4 of the TI sound chips, giving 12 programmable voices. Each chip's tone#3 generator was paired with a noise generator, for bass or percussion.

 

The FORTI software was best known in the form of its 3-voice demo of Bach's FWV 578 "Little" Fugue in G Minor (G-Moll), distributed with the TI FORTH Demo disk in 1983-84. The Bach demo used only 3 voices, on the sound chip in the 4A.

 

However, other compositions were made for 12 voices. I know of Chariots of Fire (Vangelis), Ricercar a 6 (Bach, Musical Offering, aka Prussian Fugue), and a newer Bach "Litle" Fugue (first 1K lost). On the plain 4A, these sound terrible but tantalizing, as 11 voices compete skilfully but hopelessly for 3 hardware sound channels.

 

What I did

 

Starting from a compiled FORTI demo of Chariots of Fire, I decompiled the FORTH words, read out data structures, and disassembled the code for the MUSIC word, which is in the ISR sound driver.

 

I benefitted from a small amount of leftover original source (from the corrupted Bach demo) and the FORTI user manual, but most of FORTI, especially the music compiling words, is lost to me and has to be reverse engineered.

 

From internal evidence, the Bach demo is older than Chariots of Fire. The FORTI user manual indicates features not supported in the Chariots of Fire MUSIC driver, which I may create again. I classify features into three versions of the FORTI software:

  • B. Bach Demo (TI FORTH graphics demo)
  • C. Chariots of Fire
  • D. Distribution with FORTI manual
Unless stated otherwise, I discuss features found in Chariots of Fire.

 

2. Capabilities of FORTI:

 

2.1 The Data Structures

  • Voiceline - a list of note numbers (byte) and duration in ticks (byte).
  • Envelope - a list of attenuation bytes, with looping index (high bit set).
  • Workspace - set of 16 TMS9900 workspace registers holding the context for one sound channel.
  • Pitchtable - arrays of frequency cmds for each note number.
2.2 The capabilities of the MUSIC ISR routine

 

For each of 12 voices, there is a separate voiceline, envelope, and pitchtable, assigned to a hardware sound channel.

 

 

The differential tempo counter:

 

Note durations in ticks (1/60th second) are multiplied by 32 and added into a "Next Note" countdown timer (per voice).

 

On each interrupt tick (1/60th second), the value DEL is subtracted from the countdown timer. DEL has an initial value of 32. When DEL is modified by the RIT, ACC, and TEMPO words, the effect is that the tempo is increased or decreased. The duration timer is never cleared so no time is lost. All voices move ahead at the same tempo.

 

When the duration timer reaches 0 or less (it is initialized to 0 to start music) a new note and duration are taken from the voiceline. If the duration is 0, the channel is silenced, and the ISR returns -1 on the stack (bug: and the ISR is removed?)

 

Looping inside the envelope: in terms of an ADSR model, the envelope will begin with an Attack-Decay section, a Sustain (looped) section, and an optional Release (tail) section which can apply during the last portion of each note. The Release feature is used in the Bach Demo but not Chariots of Fire (buggy).

 

Envelope bytes are processed every tick (1/60th second), not modified by tempo.

 

The 16 attenuation values are remapped through a dynamic offset DYN and a 48-byte dynamics table (DYT). This allows words like +VOLUME and -VOLUME to take effect

 

2.3 Pitchtables

 

A pitchtable entry is 2 data bytes to send to the sound chip for a note number (the channel number will be added later).

 

Note number 0 is used as a rest, but a rest is actually defined by having 0 for the sound chip period.

 

PT0 is the regular pitchtable. Note number 37 corresponds to low A or 110 Hz, which is the lowest musical note a tone generator can produce. Because of this limitation, PT0 fills the bottom 3 octaves (notes 1-36) with a copy of the octave 37-48. If a voice using PT0 plays notes in the range 1-36, they will still be heard, but in a higher octave.

 

PT1 notes load white noise for percussion.

 

PT4 is for channel 3 to drive the periodic noise channel and begins with note 0 corresponding to 13.75 Hz.

 

3. Examples:

 

Voiceline:

 

0 VARIABLE VOICE1 QU C D E F EI G A B C 0 ,
compiles a list of note-duration pairs into VOICE1 relying on definitions such as these:

 

49 VARIABLE XPOSE
: &* XPOSE @ + SWPB DURATION C@ + , ;

: A 0 &* ;
: A# 1 &* ;
: C 3 &* ;

: AA 12 &* ;
: CC 15 &* ;

: EI 24 DURATION C! ;   
: QU 48 DURATION C! ;  
: RE DURATION C@ , ;

: OCTAVE 12 * 49 + XPOSE ! ; ( n -- ) 

The FORTI manual says to compile a voiceline like so:

START
VOICE: VOICE1 
( 1 ) QU C D E F 
( 2 ) QU G A B C 
FINIS
My example based on C/ uses a lower level way:

( Verdi Requiem Mv2 trumpet )
0 OCTAVE
0 VARIABLE VOICE2 
 ( 131 ) 3E RE C E G E G RE C E G E G 
 ( 132 ) Q CC RE RE 3E C E$ A$ 
 ( 133 ) Q CC H RE 3E C E$ G 
 ( 134 ) Q C RE H RE
 0 ,

4. Internals

 

4.1 Workspace registers

 

FORTI takes advantage of the TMS9900 workspace pointer context. There is a block of 12 workspaces allocated in the dictionary, one for each voice. In MUSIC version C with dancing sprites, they must be contiguous so that a sprite table address can be found from (STWP-WS1)/8. In version B they are not contiguous.

 

In the Bach demo, (no sprites) Workspace WS1 might be set up like this:

 

VOICE2 VARIABLE WS1 PT0 , TRUMPET1 , 0 , 0 , 8000 , 9000 , 12 ALLOT
But with dancing sprites, all WS must be contiguous, in order to calculate the sprite#.

 

0 VARIABLE WS1 17E ALLOT
WS1 20 + WS2 CONSTANT
WS1 40 + WS3 CONSTANT
WS1 60 + WS4 CONSTANT
WS1 80 + WS5 CONSTANT
WS1 A0 + WS6 CONSTANT
WS1 C0 + WS7 CONSTANT
WS1 E0 + WS8 CONSTANT
WS1 100 + WS9 CONSTANT
WS1 120 + WSA CONSTANT
WS1 140 + WSB CONSTANT
WS1 160 + WSC CONSTANT
The word MUSIC is the core of the ISR and is written in assembler. The first instructions of MUSIC pulls the WP off the stack;

 

   ED10  0201  LI   R1,>ED18
   ED12  ED18  
   ED14  C019  MOV  *SP,R0           fetch WSP  ( wsp -- f )
   ED16  0400  BLWP R0               effect is R0->WP R1->PC. no LWP on 9900  
The full code is in Listing 1 at the end of this article.

 

The workspace registers are initialized before MUSIC first runs, and have the following meaning:

 

 

 FORTI WS usage. WS is a register workspace
 ------------------------
 R0 voice or cascade addr. List of byte pairs (note#,dur)
 R1 pitch-table: e.g. PT0 or PT1
 R2 envelope. e.g. PLUCK3 or ORGAN2
 R3 index into envelope
 R4 countdown timer to next note. init to 0 to begin.
 R5 const pitch cmd byte
 R6 const attn  cmd byte (for a noise, pitch is tone#3 and attn is tone#4/noise)
 R7 pitch cmd word for current note or 0 for rest
 R8  temp. compute vdp address (for sprite) or envelope address
 R9  vdp addr of sppat#
 R10 copy of SGCA, sound card address (copied from SGCA at each ISR entry, sic)
 R11 not used
 R12 temp. compute vdp data byte (for sprite)
 R13 ... R15 after blwp, not used

( Register numbers for use in assembling MUSIC - EO )
0 CONSTANT NTP ( Note table / voice pointer )
1 CONSTANT PTS ( pitch table start )
2 CONSTANT ATS ( attenuation table start / envelope )
3 CONSTANT ATP ( attenuation table offset )
4 CONSTANT DUR ( countdown timer duration )
5 CONSTANT PVID ( pitch voice ID )
6 CONSTANT VVID ( volume voice ID )
A CONSTANT SG   ( sound card address )
C CONSTANT PNDX ( pattern number vdp address )
( D E F are clobbered on entry to MUSIC but could be temps )

4.2 Player words

Words PLA1, PLA3, PLA12 begin playing 1, 3, or 12 voices, resp.

 

: PLA12 ' BMUSIC XMUS ; ( play 
: PLA3  ' CMUSIC XMUS ; ( play only WS1-3 on internal snd only )
: PLA1  ' DMUSIC XMUS ; ( play only WS1 on internal snd only )
where

: MUSIC0 85EE SGCA ! MUSIC ; ( 1110 1110 only chip #0 with enable low )
: MUSIC1 85F6 SGCA ! MUSIC ; ( 1111 0110 only chip #1 )
: MUSIC2 85FA SGCA ! MUSIC ; ( 1111 1010 only chip #2 )
: MUSIC3 85FC SGCA ! MUSIC ; ( 1111 1100 only chip #3 )
: MUSIC4 84FE SGCA ! MUSIC ; ( 1111 1110 internal only )
: MUSIC5 857E SGCA ! MUSIC ; ( 0111 1110 say chip 4 )
: MUSIC6 85BE SGCA ! MUSIC ; ( 1011 1110 say chip 5 )
: MUSIC7 85DE SGCA ! MUSIC ; ( 1101 1110 say chip 6 )
3424 VARIABLE PSTART
: XMUSIC WS1 MUSIC0 OR IF 83C4 @ PSTART ! 0 83C4 ! OFF ENDIF ;
: DMUSIC 0 XMUSIC ;
: YMUSIC WS2 MUSIC0 WS3 MUSIC0 OR OR XMUSIC ;
: BMUSIC WS4 MUSIC1 WS5 MUSIC1 WS6 MUSIC1 
         WS7 MUSIC2 WS8 MUSIC2
         WSA MUSIC3 WSB MUSIC3 WSC MUSIC3 OR OR OR OR OR OR OR YMUSIC ;
: CMUSIC 0 YMUSIC ; ( for PLA3 )
: XMUS CFA ISR ! INTLNK @ PSTART ! ;
When playing just one voice:

 

: MUSIC0 85EE SGCA ! MUSIC ; ( 1110 1110 only chip #1 with enable low )
: XMUSIC WS1 MUSIC0 OR IF 83C4 @ PSTART ! 0 83C4 ! OFF ENDIF ; ( f/finished? -- )
: DMUSIC 0 XMUSIC ; 
: PLA1  ' DMUSIC XMUS ; ( play only WS1 on chip#1 or internal snd  )
In PLA1, DMUSIC is the ISR which calls MUSIC on one workspace pointer. (PLA12 and BMUSIC make a player that calls each of 12 voices.)

 

 

4.3 Sprites

 

The version of MUSIC in Chariots of Fire draws dancing sprites on the screen (Bach demo does not have this.) For each of 12 voices, there is a sprite. The note number translates to a column on the screen, while the attenuation is shown by a colored bar where height indicates loudness.

 

4.4 Misc

Chariots of Fire utilizes 11 sound channels across 4 chips. Interestingly, it defines addresses for MUSIC5,6,7 as if the author imagined having more chips than 4.

 

5. Optimizations

 

It's quite likely that I'm not analyzing the final version of the MUSIC word. That said, I see the following optimizations that could be made:

 

* SGCA is placed on the stack and copied to the workspace R10 on each tick. This should be configured before music starts playing, just like other registers that don't change.

 

* The envelope release (or tail) segment looks buggy in Chariots of Fire (whose envelopes don't rely on it.)

 

* Byte swapping and SRL by 8.

movb *r8,r8
ab   @DYN+1,r8
srl   r8,8
movb @DYT(r8),r8
movb  r8,*r10
could be

clr   r11
movb *r8,r11
swpb  r11
a    @DYN,r11
movb @DYT(r11),*r10
6. Missing Words

 

Parts of the music entry system were not present in the compiled demo:

  • Defining words like VOICE: FINIS and <ENV: ENV> were not recovered. I speculate that the demos B and C were created in a lower level way, and that these defining words were invented for the final distributon.
  • Envelope word =REPEAT to encode the sustain loop
  • 3 SHARPS established a key signature, modifying the behavior of note words like F. In the demo, F pushes a constant note number 8 (plus octave). This implies
  • T for Tie or slur is not present. I don't see that MUSIC implements any means to vary frequency.
  • R: and :R to repeat a section in a voiceline. These save memory, but would require runtime implementation in MUSIC.
  • convenience word =TEMPO
  • counting words =MEAS and +MEAS
  • Envelope defining words <ENV: ENV> produce code that copies the envelope PFA into a WS. This requires a <BUILDS DOES> construct. In the demo, envelope words are just arrays.
  • The words +VOLUME and -VOLUME and dynamic markings from PPP to FFF.
  • CONDUCTOR is the top defining word and is not present
  • SILENT is not present
  • +FIFTH and words that transpose voices at runtime, not compile time, will require support in MUSIC.
  • <ALBUM and ALBUM> words
  • =GR and GR that subtracts time (for a grace note) from the previous note duration. In the demo, GR sets DURATION to 1 tick.
  • =FERMATA and FERMATA
  • =DRUM to set a WS to play noise
  • <PHRASE: PHRASE>
7. Future Innovations

 

I was disappointed to find nothing about tremolo in this demo. Envelopes are just attenuation changes, which is how an organ tremolo operates, but on other instruments, tremolo means to vary the frequency, or play two frequencies for the overtones. (T or Tie/Slur effect on frequency is unknown; perhaps it just skips the tail.) An envelope for pitch would allow many more effects.

 

The FORTI manual lacks a dictionary.

 

The pitch table is flexible enough to allow different temperament. The pitch table we take for granted is the equal tempered system in which music can be transposed to another key without changing its character.

 

8. Acknowledgments

 

David Olson retrieved the Chariots of Fire demo disk.

 

Rene LeBlanc for his FORTH disassembler, which I obtained in 1985 and never used until now.

 

Gene Hitz and Owen Brand provided the MATIUG disk library, which I converted to DSK format, finding the TI FORTH source.

 

The BERG/WERNECKE FORTH decompiler found in the MATIUG disk library.

 

Lee Stewart's fbFORTH and TI FORTH manuals which I used as a reference throughout, especially on the dictionary entry structure.

 

Tursi for Classic99 in which I did most of the work.

 

Kryoflux hardware and software was used to efficiently acquire hundreds of legacy disks.

 

CANTRELL, the TI engineer named on the FORTI "stereo card" schematic. Who is CANTRELL?

 

9. References

 

FORTI Music Card Users Manual and Schematic at WHTech

 

ADSR definition

 

 

Listing 1

 

* code field of MUSIC
* Load workspace from stack arg
   ED10  0201  LI   R1,>ED18
   ED12  ED18  
   ED14  C019  mov  *SP,R0           fetch WSP  ( wsp -- f )
   ED16  0400  blwp R0               effect is R0->WP R1->PC. no LWP on 9900  
   ED18  C2A0  mov  @SGCA,R10        ECCE is pfa of variable SGCA. typ  85EE(#1 chip)
         ECCE
   ED1C  04CC  clr  R12              
   ED1E  C104  mov  R4,R4            duration timer. clear to start         
   ED20  1531  jgt  LABEL2                 
* next note
   ED22  D330  movb *R0+,R12         get note number in R12    
   ED24  098C  srl  R12,8                 
   ED26  C1CC  mov  R12,R7           get note number in R7    
   ED28  0A9C  sla  R12,9            sprite col = note# * 2
   ED2A  0209  li   R9,>d844         WS1        
         D844
   ED2E  02A8  stwp R8                    
   ED30  6209  s    R9,R8            (WSx - WS1) is a multiple of 32     
   ED32  0938  srl  R8,3             divide by 8
   ED34  0228  ai   R8,>4301         address to write, in SAT 
         4301
   ED38  06C8  swpb R8                    
   ED3A  D808  movb R8,@>8c02        set up VDP address     
         8C02
   ED3E  06C8  swpb R8                    
   ED40  D808  movb R8,@>8c02             
         8C02  
   ED44  D80C  movb R12,@>8c00       sprite col = note# * 2
         8C00
   ED48  0588  inc  R8                     
   ED4A  C248  mov  R8,R9            vdp addr of sppat#      
   ED4C  D230  movb *R0+,R8          duration     
   ED4E  0988  srl  R8,8                  
   ED50  0A58  sla  R8,5             times 32 .  32 is the neutral value of DEL 
   ED52  A108  a    R8,R4            add, because need to pay time debt
   ED54  1509  jgt  LABEL1                 
* silence
* bug: if R4 is at worst 1-DEL, R8 is at worst 32, if R8<DEL-1 then music may stall
FINIS
   ED56  0640  dect R0               leave R0 pointing to the last (valid) note
   ED58  C206  mov  R6,R8            attn cmd byte     
   ED5A  0228  ai   R8,>0f00         silence
         0F00
   ED5E  D688  movb R8,*R10          write to sound chip
   ED60  02E0  lwpi >8300                 
         8300
   ED64  0719  seto *SP              put -1 on the stack ( wsp -- f )
   ED66  045F  b    *NEXT                  
LABEL1
   ED68  A1C7  a    R7,R7            note# * 2
   ED6A  A1C1  a    R1,R7            pitch table address     
   ED6C  C1D7  mov  *R7,R7                
   ED6E  1605  jne  NOTE             else, turn off voice:    
REST
   ED70  C206  mov  R6,R8            attn cmd byte     
   ED72  0228  ai   R8,>0f00         silence
         0F00
   ED76  D688  movb R8,*R10          write to sound chip
   ED78  1005  jmp  LABEL2           why not LABEL6      
NOTE
   ED7A  A1C5  a    R5,R7            pitch cmd byte     
   ED7C  D687  movb R7,*R10          write to sound chip
   ED7E  06C7  swpb R7                    
   ED80  D687  movb R7,*R10          write to sound chip
   ED82  04C3  clr  R3    
* note in progress
LABEL2
   ED84  C1C7  mov  R7,R7            pitch cmd or 0 if none     
   ED86  132E  jeq  LABEL6           nothing to do
   ED88  D212  movb *R2,R8           envelope first byte (N in sustain)
   ED8A  0938  srl  R8,3                  
   ED8C  8108  c    R8,R4            R4 is counting down from duration*32      
   ED8E  1501  jgt  SSTAIN                
   ED90  1003  jmp  LABEL3
SSTAIN
   ED92  0203  li   R3,>003f         start envelope at 3fh-R8*32    
         003F
   ED96  60C8  s    R8,R3                 
LABEL3
   ED98  0283  ci   R3,>003f             
         003F
   ED9C  1102  jlt  LABEL4                 
   ED9E  0203  li   R3,>003f         R3 max 3F
         003F
LABEL4
   EDA2  0583  inc  R3               at least 1     
   EDA4  C203  mov  R3,R8                 
   EDA6  A202  a    R2,R8            envelope  pointer in R8         
   EDA8  D218  movb *R8,R8           
   EDAA  0988  srl  R8,8                  
   EDAC  0288  ci   R8,>0080         0-F attentuation, 8x cmd byte     
         0080
   EDB0  1501  jgt  ENVJMP                 
   EDB2  1006  jmp  LABEL5            
* envelope cmd byte  
ENVJMP   
   EDB4  0228  ai   R8,->80              
         FF80
   EDB8  C0C8  mov  R8,R3            new envelope index in R3     
   EDBA  A202  a    R2,R8                 
   EDBC  D218  movb *R8,R8           they seem to have run out of registers     
   EDBE  1001  jmp  LABEL6   
* R8 lsb is attn    
LABEL5          
   EDC0  06C8  swpb R8
* adjust attn by dynamics table
LABEL6
   EDC2  B220  ab   @DYN+1,R8             
         ECD9
   EDC6  0988  srl  R8,8             ugh     
   EDC8  D228  movb @DYT(R8),R8         
         D1BA
   EDCC  06C9  swpb R9               vdp address of sppat#     
   EDCE  D809  movb R9,@>8c02             
         8C02
   EDD2  06C9  swpb R9                    
   EDD4  D809  movb R9,@>8c02             
         8C02
   EDD8  0A28  sla  R8,2             mpy by 4
   EDDA  D808  movb R8,@>8c00        set sppat# to attn
         8C00
   EDDE  0928  srl  R8,2                  
   EDE0  A206  a    R6,R8            add attn cmd byte             
   EDE2  D688  movb R8,*R10          write to sound chip
LABEL6
   EDE4  6120  s    @DEL,R4        Subtract DEL from timer     
         CC60
   EDE8  02E0  lwpi >8300                 
         8300
   EDEC  04D9  clr  *SP                   put 0 on the stack
   EDEE  045F  b    *NEXT                  
Edited by FarmerPotato
  • Like 7
Link to comment
Share on other sites

Download the binaries here:

 

Bach Demo

for "Little" Fugue in G Minor

6 LOAD

BPLAY

 

Chariots of Fire

for Chariots of Fire:

59 BLOAD

PLAY

 

or for Ricercar a 6

 

49 BLOAD

PLAY

 

 

This really cool. I have the beginnings of this kind of music language for Camel Forth but this is fully developed.

It inspires me to continue development in this direction.

 

Reading through the manual I get lots of ideas. I am curious about the notation's use of flats.

I saw that they use A$ B$ etc for flats if I read it correctly. To me the obvious labels are

 

Ab Bb Cb A# B# D# etc.

 

I suspect it has something to do with the search mechanism being case insensitive so their Forth could not differentiate between BB and Bb but's that's just a guess.

 

Thanks for this excellent reverse engineering work.

 

B

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Thank you for the comments. I am continuing to work on this. I will have a source code update next weekend.

In the absence of a copy of the actual FORTI distribution disk (D), here is my plan:

Road Map for FORTI

Clean source (compilable) for (B) Bach Demo and © Chariots of Fire, Ricercar a 8. Including some defining word features implemented to match (D)

FORTH assembler version of MUSIC, as well as CODE version (machine code)

Full implementation of features of (D) from user manual, with some innovations.

Collection of Bach inventions and sinfonias for 2-3 voices

MIDI import tool

TRON arcade game music (originally used AY-8910)

Well-known TI game songs remixed

Tool to export compiled music as A/L source for use outside FORTH

Reference release (E) (Erik) with innovations

4-chip FORTI card PCB (not made yet)

Extra special music card project compatible with FORTI (hardware built! waiting while I do software)

Major musical work demo

MIDI player

Speech synthesizer integration

Animatronic art installation

  • Like 4
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...