Jump to content


+AtariAge Subscriber
  • Content Count

  • Joined

  • Last visited

Posts posted by TheBF

  1. I saw some super posts in the archives about sound ripppers and stuff related to sound so I thought I would take a run making a sound list player.


    All of this can be done in FB Forth or TF as well. However there are some words that will need adjustment.

    The missing words would be MS, which is just a quick delay loop.


    : MS 8 0 DO LOOP ; ( something like this, 8 is "about" the correct number for 1 MS)


    And PAUSE which is a noop.


    : PAUSE ; will fix that quickly.


    And ?NUMBER in CAMEL Forth is non-standard, but Lee created an equivalent in FB Forth for another one of my posts a while back.

    (Will find that and post here)


    However what a little player like this might be good for is trying out some data to listen to it for ALC programmers.


    You can feed it sounds lists from the keyboard and play them to see how you like the sound.


    Couple of things to be aware of:


    1. This player turns all 4 sound channels off when it gets to the end of a list with the word SILENT.

    If you use the TI player you will need to add the string of bytes to turn off your sounds.


    2. The BYTES word does not use commas, so sound lists tried here will have to have the commas removed.


    3. UN-comment SND! for other Forth systems or use the equivalent word to write a byte to the sound chip address.


    4. LSHIFT is the ANS Forth word, but TF uses << for the same thing I believe. Not sure about FB Forth .


    How it works:


    PLAY$ takes the address of a string of bytes with a count byte at the start and a time duration byte at the end.

    It converts the duration byte to milliseconds assuming a 1/60 of a second ISR time.

    It then computes the start and end address of the sound bytes and loops through them sending each one to SND! (the chip address)

    When the loop is completed it waits for the duration of milliseconds.


    PLAY Is the high level word to use for playing a sound list. It simply looks to see if there is a non-zero byte count in a sound string.

    While that is true is calls PLAY$.

    Then using the count byte it computes the address of the next string in the list and plays the next string until it hits a final ZERO.


    CREATE XXXX is Forth's way of making a new word in the Dictionary of words and will return the start of the data address of that new word.


    BYTES is a compiler directive that was created to parse the input stream, convert the data to a number and compile it into the next byte of memory.

    It could have been done with the C, primitive but it makes the lists harder to read ( example AA C, 7 C, DE C, ETC... )


    I believe the code here will run with the CAMEL99 version in GITHUB /BIN folder. (did not try it yet)


    I stole some sounds from PARSEC for FIRE and EXPLODE and I must confess it was a religious experience to hear those sounds come out my Forth system. ;-)


    If you get it working type:


    3 SHOTS at the console. That does 3 FIRE sounds and then runs the EXPLODE list. Memories...




    \ TI sound list player in CAMEL99 Forth
    \ 1. This player needs an extra 0  to mark the end of the sound data
    \ 2. It automatically turns off sound when the data is ended
    \ 3. Uses the TMS9901 timer to control sound duration 
    \    (Forth word MS is millisecond delay)
    \ : SND!  ( byte -- )  8400 C! ; ( in the CAMEL99 Kernel )
    : SILENT ( --)  9F SND!  BF SND!  DF SND! FF SND! ;  \ turn off all sounds
    : >MS    ( n -- n') 4 LSHIFT ;  \ n*16, converts ISR delay value to milliseconds
    : [email protected] ( snd$ -- ms) COUNT + [email protected] ; \ get time byte at end of string
    : PLAY$ ( sound_string -- ) \ play 1 sound string
           DUP [email protected] >MS   \ duration on stack for later leave for later
           SWAP COUNT          \ returns start address and length of string
           BOUNDS              \ convert addr/len to end-address, start-address
              PAUSE            \ give time to other tasks
              I [email protected] SND!        \ feed each byte to sound chip
           ( delay) MS ;       \ use the value we calculated that's on the stack
    : NEXTSND  ( snd$ -- )     \ next_string = startaddr + length + 1
                COUNT + 1+ ;
    : PLAY ( addr -- )         \ play a list of sound strings
             DUP [email protected]
           WHILE               \ while the length is not 0
             DUP PLAY$         \ play a single string
             NEXTSND           \ advance to the next sound string
           DROP ;              \ mom said always clean up after yourself
    \ compiler addition to compile bytes into memory
    : BYTES ( -- )
                BL WORD COUNT $BUF PLACE
                $BUF [email protected]       \ fetch 1st char (string lenght)
             WHILE            \ while the string len>0
                $BUF ?NUMBER 0= ABORT" BAD#"
                C,            \ compile into next byte of memory
             REPEAT ;
    : /END   0 , ;            \ compile a zero into memory
    CREATE SOUND1   ( Munchman )
           BYTES 08 85 2A 90 A6  08  B0  CC  1F  12
           BYTES 08 85 2A 90 A4  1C  B0  C9  0A  12
            BYTES 4 F2 CC 01 E7 1
            BYTES 2 CC 03 1
            BYTES 2 CC 05 1
            BYTES 6 86 0D 97 AC 1A B7 8
    	BYTES 2 8F 08 2
    	BYTES 2 AB 23 5
    	BYTES 2 86 0D 4
    	BYTES 1 BF 3
    	BYTES 2 8E 0B 8
    	BYTES 2 8A 0A 2
    	BYTES 3 AC 1A B7 8
    CREATE EXPLODE   ( from Parsec )
           BYTES 7 9F BF DF E7 F0 C0 07 5
           BYTES 1 F1 6
           BYTES 1 F2 7
           BYTES 1 F3 8
           BYTES 1 F4 9
           BYTES 1 F5 10
           BYTES 1 F6 11
           BYTES 1 F7 12
           BYTES 1 F8 13
           BYTES 1 F9 14
           BYTES 1 FA 15
           BYTES 1 FB 16
           BYTES 1 FC 17
           BYTES 1 FD 18
           BYTES 1 FE 30
           BYTES 1 FF 0
    CREATE FIRESND   ( from Parsec )
           BYTES 4 9A BF DF FF 1
           BYTES 3 80 0A 98 1
           BYTES 3 80 0C 96 1
           BYTES 3 80 10 94 1
           BYTES 3 80 14 92 1
           BYTES 3 80 18 90 1
           BYTES 3 80 1C 92 1
           BYTES 3 80 20 94 1
           BYTES 3 80 28 96 1
           BYTES 3 80 30 98 1
           BYTES 3 80 38 9A 1
           BYTES 3 80 3E 9C 1
           BYTES 1 9F 0



    • Like 3


    Are the horizontal lines slightly bent ?


    100 call clear::call screen(14)::call color(6,1,1,7,1,1)::c$="FF"&rpt$("80",15)&"FF"&rpt$("0",15)
    110 call char(72,c$,80,c$)::c$=rpt$("HJPR",&rpt$("IKQS",::for i=0 to 23
    120 display at(i+1,1):seg$(c$,3+32*(i and 1)+abs((i and 6)/2-2),28);
    130 next i::call color(6,1,2,7,1,16):: for i=0 to 9^9::next i


    This points to some deep insights into the architecture of our brains.

    I don't know what exactly, but the fact that we can't see the parallel lines clearly makes me wonder what else do I misperceive?

    I suspect that answer is a great deal!


    And if anybody here sees the parallel lines clearly, they are either a mutant or should maybe see their doctor :-)


    Thanks Sometimes99er!



    • Like 1

  3. array [0..999] of char, just like array[0.999] of boolean, still occupies 1000 words, as each character/boolean is stored in one word (16 bits). The system can't allocate a 10000 word array.


    Now if you declare a packed array[0.999] of char, then that's 500 words, and a packed array[0..999] of boolean is only 63 words. But then you have to add the overhead for packing and unpacking when accessing the array. So it works, but will take longer time.


    I'm not sure which boundaries you are referring to, when asking if the p-system can handle arrays across boundaries? There are no boundaries to cross in this case.


    I always like the way Wirth's languages have these different data types. Forth makes you work hard for that nice stuff.

    So in my Pascal Envy fever I thought I would see what it would take to make a "packed array of bits" and it is more code than I thought.


    The overhead just to set 1 bit, according to the CLASSIC99 9901 timer is 1.8 milli-seconds in my Forth system.


    EDIT: New times 1.5mS to set a bit.

    1.1mS to read a bit ([email protected])


    This could be really sped up with assembly language code words but it's still a lot of work to strip out bits.



    EDIT: Here is the revised code with fixed RSHIFT and uses CELL+ and CELLS to be less platform dependant.

    \ BOOLEAN array experiment
    \ BOOLEAN data is one CELL (16 bits on TMS9900)
    \ create & erase memory area for 'n' bits
    : BITS:  ( n -- ) CREATE    8 /  HERE OVER 0 FILL  CELL+  ALLOT  ;  \ added 2 bytes extra
    \ compute bit# in a cell & cell address
    : BITFLD     ( bit# addr[] -- bit#' addr)
                   SWAP 10 /MOD CELLS ROT +  ;
    : [email protected]      ( bit# addr -- ? )
                  BITFLD @               \ compute bit# & fetch bits in cell
                  SWAP RSHIFT            \ if bit#<>0 RSHIFT,
                  0001 AND ;             \ mask 1 bit
    : BIT#>MASK ( bit# -- n )  0001 SWAP LSHIFT ;
    : BSET      ( bit# addr[] -- )
                  BITFLD                   ( -- bit# addr)
                  SWAP BIT#>MASK >R        \ save the mask
                  DUP @                    \ -- addr bits
                  R> OR SWAP ! ;           \ or mask into bit, store in addr
    : BRST      ( bit# addr[] -- )
                  BITFLD                   ( -- bit# addr)
                  SWAP BIT#>MASK INVERT >R  \ invert and save mask
                  DUP @                     \ -- addr bits
                  R> AND SWAP ! ;           \ mask out bits, store back in addr
    \ test code
      300 BITS: ]X      \ make array X of 1000 bits
    : FILLBITS   300 0 DO  I ]X BSET   LOOP ;
    : CLRBITS    300 0 DO  I ]X BRST   LOOP ;
    : EVENBITS   ." Erasing..."  CLRBITS 300 0 DO  I ]X BSET   2 +LOOP ;
    : SHOWBITS   300 0 DO  I ]X [email protected] . LOOP ;

  4. Really how does the data get from GROM to RAM? It does use the GPL MOVE command after all.


    It was my understanding it uses the TI OS GPL MOVE to get data.


    I didn't need GPL to move data from GROM to RAM. The GROMs are memory mapped devices.

    I set the address and then read the GROM data and put it somewhere in RAM.


    I did it in Forth using the routine that reads a character from a memory location. ([email protected])

    Which is written in assembler. Then I can put it anywhere I want to.


    Am I missing something about GROMS? (very possible)

    : GROM   ( addr -- ) \ set GROM address
             SPLIT  9C02 C!  9C02 C! ;
    : [email protected]+   ( -- c)  9800 [email protected] ;  \ GROM char fetch, w/auto-incr

  5. I didn't expect it to work, but I tried making the array large enough to accomodate numbers up to 10000. But the p-system responded "Can't allocate global data", which is what I expected.


    It makes me wonder how big a "Boolean" type is in the P-Code system. Any idea?


    Oops re-read your earlier post completely. 16 bits for a Boolean.

    How big can you go before it hits the limit?


    Could you try ARRAY of CHAR ?

  6. Quick update:

    The version based on apersson850's array access method is fully functional now. Actually, it was never broken to start with, but rather the PIO throughput was much lower than I had expected and it was not being adequately picked up by my connected equipment. The culprit was the compare instruction when cycling through the array elements

           CLR  R3
           SWPB R3
    REDO   LI   R1,1
           INC  R1
           C    R1,R3
           JLE  LOOP
           JMP  REDO

    At Lee's suggestion, I changed it to a decrementing loop instead, thus eliminating the compare instruction alltogether

    REDO   CLR  R3
           SWPB R3
           LI   R2,VERTEX+1
           DEC  R3
           JNE  LOOP
           JMP  REDO

    and now the array is cycled through much faster. I had no idea Compare executed that slowly...

    At that point, I'm not going to even bother to use the NUMREF utility function given that I will have to issue a BLWP call for each array element in that case, which is going to be even slower.



    3 MHz clock, 14 cycles or more to do almost anything and you get a VERY slow machine. I would estimate 120,000 instructions per second or less.

    State of the art circa 1970s


    By comparison the modern MSP430 from TI which has a similar instruction set and register count to the TMS9900 does some instructions in 1 clock cycle but on average takes about 2.5

    So with the same 3MHz clock it would give you 1,200,000 instructions per second.


    This is part of the fun of programming the old devices. You must be smarter <oldfartrant> than these kids today, programming multi-core GHz processors in Javascript.</oldfartrant>


    • Like 1

  7. That's a pretty cool trick. You are the font master.


    I added these simply routines to my system to access GROM without the Assembly language

    : GROM  ( addr -- ) SPLIT 9C02 C! 9C02 C! ;
    : [email protected]+   ( -- char)  9800 [email protected] ;  \ read & auto-increment address
    : GVMOVE  ( grom_addr vdp_addr cnt -- ) \ GROM->VDP move
              ROT GROM
              BOUNDS DO  [email protected]+  I VC!  LOOP ;
    : BIGCAPS  ( -- ) 4B4  20 ]PDT  200 GVMOVE  ;

    Which let me write this demo

            8 9 AT-XY     ." TEXAS INSTRUMENTS"
           10 11 AT-XY       ." HOME COMPUTER"
            2 16 AT-XY     ." READY-PRESS ANY KEY TO BEGIN"
            4 22 AT-XY (c) ." 1981  TEXAS INSTRUMENTS"
            0 0 AT-XY 0 4 DO I . 1000 MS   -1 +LOOP
            0 0 BL 10 HCHAR
            BEGIN KEY? UNTIL
            BYE  ;


  8. Nice!


    Does the sieve just produce primes? If so there's a primes finder on the TF disk. Just type DEMOS after it boots and it'll tell you which block it's on.


    Run it with 1000 PRIMES and it should do its stuff.


    It can find primes to 1000 in around 2.5 seconds if I remember correctly.


    If I remember correctly I adapted it from a Brodie program in Starting Forth. It was one of the first programs TF ever ran :-)



    Yes this one just finds PRIME numbers. It look like it was done by Anton, but I can't be sure.

    So if we scale the Starting Forth version to 10000 it would take 25 seconds, so this one is quite a bit faster.


    With this version in TF it should search to over 20,000 because it uses all available memory in the expansion card.

  9. This is the way most Forths are implemented for both stacks for all the reasons you state.


    In CAMEL99 I tried a common optimization and CACHE the top of stack value in a register.

    This changes where you have to push and pop but on balance it speeds up a Forth system by about 10%.

    Simple Math operations become:


    A *SP+,R4


    A *SP+,*SP


    Fetching the contents of a variable is:


    MOV *R4,R4


    I like it. :)

  10. So I changed the benchmark to use a VALUE to hold the array address.

    It improved TF and CAMEL99 ITC version, but it actually slowed down the directed threaded version a little.

    The DTC version has HERE written in Assembler so this only proves that my code for CONSTANT is slower than fetching the contents of a variable in ASM.


    Anyway here is result.

    \ Sieve of Erathosenes in Forth using a VALUE as array pointer
    \ Tested with TurboForth and CAMEL99 Forth
    0 VALUE SIEVE[]           \ holds array address
    \ array calculators
    : SIEVE[]@ ( n addr -- ? ) SIEVE[] + [email protected]  ;
    : SIEVE[]! ( n addr -- )   SIEVE[] + C! ;
    : ERASE  ( addr n -- ) 0 FILL ;
    : FREEMEM  ( -- n) FF00 HERE - ;
    : ?MEM     ( n -- n )  FREEMEM OVER < ABORT" Out of memory" ;
    \ byte array uses unallocated memory at HERE
    : PRIMES ( n -- )
            HERE TO SIEVE[]       \ assign address to the array
            CR ." Running..."
            SIEVE[] OVER ERASE
            1 0 SIEVE[]!          \ mark as prime like 'C' version
            1 1 SIEVE[]!
            2                     \ start at 2
               2DUP DUP * >
               DUP SIEVE[]@ 0=
               IF  2DUP DUP *
                      1 I SIEVE[]!
                   DUP +LOOP
            CR ." Complete."
            ." Primes: " 2 DO  I SIEVE[]@ 0= IF I . THEN  LOOP
    10,000 primes    W/HERE   W/VALUE  
    TF               8.0       7.7
    CAMEL99 ITC     10.28      8.4
    CAMEL99 DTC      7.25      7.5

  11. I have no doubt about it Rich.


    Do you have an approximate number of secs?

    I did not make it clear, but I only time the loop that does the calculation.

    The print out of the results I ignored.


    I also did some testing and realized the Forth example is pretty silly in one other way.

    The HERE word reads an address from a variable and is used as the base address of the data array.

    So that's not at all like using a fixed memory address for the data array.

    When I replaced a Forth version of HERE with an Assembly language version of HERE the ITC benchmark improved 20%!


    "HERE" is a convenient memory location for throw away calculations where you don't need to create a variable, but not appropriate for a speed benchmark IMHO.


    So I will change that and republish the results for all the Forth examples.


    Well, on the TI-99/4A, I've always used the stack growing in the other direction. To follow your example ...


    A push is then done with

    MOV @DATA,,*SP+


    and a pop with






    A convention in my experience is to have stacks grow downwards from high RAM and memory usage to grow upwards.

    Not written in stone somewhere, just something I have seen.



  13. I thought I would expand this conversation by using a classic benchmark, the Sieve of Erathosenes.

    I know it's an old chestnut, but it is commonly used to see how a language does a few things.


    I tried it first with TI-BASIC. I used the MSX version from Rosetta code and fixed a few things so it would run on the TI-99

    100 REM  Eratosthenes Sieve 
    110 REM    for TI BASIC 
    120 REM  ****************** 
    140 INPUT "Search until: ":L
    150 DIM P(1500)
    160 FOR N=2 TO SQR(L+1000)
    170 IF P(N)<>0 THEN 210
    180 FOR K=N*N TO L STEP N
    190 LET P(K)=1
    200 NEXT K
    210 NEXT N
    220 FOR N=2 TO L
    230 IF P(N)<>0 THEN 250
    240 PRINT N;
    250 NEXT N

    I use 1000 for the input value because it takes along time. The big shock for me was that XB was slower than TI-BASIC. :?


    Then of course I did a version for Forth and tested it on Turbo Forth and Camel99 a couple of ways.

    Here is that code which is also from Rosetta Code but slightly modified by me.

    I don't agree with the way they did the looping in the WHILE statement. It's a slow thing to have multiplication to calculate a loop

    I will see if I can make it more like the C version...

    \ Sieve of Erathosenes in Forth
    \ Tested with TurboForth and CAMEL99 Forth
    \ array calculators
    : []@ ( n addr -- ? )  + [email protected]  ;
    : []! ( n addr -- )    + C! ;
    : ERASE  ( addr n -- ) 0 FILL ;
    : FREEMEM  ( -- n) FF00 HERE - ;
    : ?MEM     ( n -- )  FREEMEM OVER < ABORT" Out of memory" ;
    \ byte array uses unallocated memory at 'HERE'
    : PRIMES ( n -- )
            CR ." Running..."
            HERE OVER ERASE
            1 0 HERE []!       \ mark as prime like 'C' version
            1 1 HERE []!
            2                  \ start at 2
               2DUP DUP * >
               DUP HERE []@ 0=
               IF  2DUP DUP *
                      1 I HERE []!
                   DUP +LOOP
            CR ." Complete."
            ." Primes: " 2 DO  I HERE []@ 0= IF I . THEN  LOOP

    And here are the timings. BASIC can't allocate enough memory for more than about 1500 elements because of the big floating point numbers so I limited it to 1000.

    1000 primes
    X BASIC         14.3 secs
    TI BASIC        11.9
    10,000 primes
    TF               8.0
    CAMEL99 ITC     10.28
    CAMEL99 DTC      7.25

    I would be interested in seeing the GCC results using the Rosetta code version or something similar.




    #include <stdlib.h>
    #include <math.h>
    eratosthenes(int n, int *c)
    	char* sieve;
    	int i, j, m;
    	if(n < 2)
    		return NULL;
    	*c = n-1;     /* primes count */
    	m = (int) sqrt((double) n);
    	/* calloc initializes to zero */
    	sieve = calloc(n+1,sizeof(char));
    	sieve[0] = 1;
    	sieve[1] = 1;
    	for(i = 2; i <= m; i++)
    			for (j = i*i; j <= n; j += i)
    					sieve[j] = 1; 
      	return sieve;



  14. Nice. I am having more trouble that I thought I would have, re-creating the motion characteristics of XB.

    Ah the programmers life.

    I guess considering that I am standing on top of a cross-compiler/assembler that I wrote, on top of the Forth kernel that it generated on top of the multitasker, on top of the sprite library I should realize it's a house of cards. :-)


    I have 2 rules about software:


    1. Anything is possible. YEAH!

    2. Nothing is easy (oh yeah).



    • Like 1

  15. Very cool. Amazing what you can coax XB to do.

    I will do this one too.

    For this one I will have to load my sprite library and the multi-tasker.

    I don't use interrupts for auto-motion in this system.

    But I think I can do it...



    • Like 2

  16. The jump tables are fixed in all known revisions -- for the older bank switched copy cartridges I wrote a generic loader to load the character set from assembly, and it worked on 99/4, 99/4A, and 99/4A v2.2.


    Code is attached here. Also includes the subroutines used for setting address, copying bytes, etc.


    The main trick is in the "GPLVDP" function which parses the jump table and the first instruction of the GPL subroutine to determine the address in GROM of the character set. It does both uppercase and lowercase (small capitals) sets.




    * this function is meant to be called as "BL @GOGO"
    * it destroys all registers.
    * Load lower case charsets *
    * Note, if you still need space, you can remove support for the
    * 99/4 by deleting the code marked between * +++ 99/4 support +++ begin/end
    * blocks
     MOV R11,R9   * Save our return spot
    * +++ 99/4 support begin +++
    * load R3 with 6 for 99/4, or 7 for 99/4A
     CLR R0
     BL @GETGPL   * read GROM >0000
     LI R3,7
     CI R0,>AA01  * 99/4 is AA01, all versions of 99/4A seem to be AA02 (even 2.2!)
     JNE IS4A     * note we also assume unknown is 99/4A just to be safe
     DEC R3
    * make a copy of the capitals for the 99/4 to 'support' lowercase
    * this will be partially overwritten by the main set, but it works!
     LI R0,>0018  * GPL vector address
     LI R1,>4A00  * dest in VDP - must OR with >4000 for write
     LI R2,>0040  * how many chars
     BL @GPLVDP   * this function goes somewhere later in your ROM
    * +++ 99/4 support end +++
    * If you delete the above block, replace with
    * LI R3,7
    * so that the character size counter is still valid
    * 'lowercase' letters
     LI R0,>004A  * GPL vector address (not available for 99/4)
     LI R1,>4B00  * dest in VDP - must OR with >4000 for write
     LI R2,>001F  * how many chars
     BL @GPLVDP   * this function goes somewhere later in your ROM
    * main set
     LI R0,>0018  * GPL vector address
     LI R1,>4900  * dest in VDP - must OR with >4000 for write
     LI R2,>0040  * how many chars
     BL @GPLVDP   * this function goes somewhere later in your ROM
     B *R9        * RETURN TO CALLER
    * GROM routines *
    * Set GROM address
     MOVB R0,@>9C02
     SWPB R0
     MOVB R0,@>9C02
     B *R11
    * Get a word from GPL
     MOVB @>9800,R0
     SWPB R0
     MOVB @>9800,R0
     SWPB R0
     B *R11
    * Copy R2 characters from a GPL copy function vectored at
    * R0 to VDP R1. GPL vector must be a B or BR and
    * the first actual instruction must be a DEST with an
    * immediate operand. Set R3 to 6 for 99/4 (6 byte characters)
    * or 7 for a 99/4A (7 byte characters)
     MOV R11,R10    * save return address
     BL @GPLSET     * set GROM address
     BL @GETGPL     * Get branch instruction (not verified!)
     ANDI R0,>1FFF  * mask out instruction part
     AI R0,3        * skip instruction and destination
     BL @GPLSET     * set new GROM address
     BL @GETGPL     * get actual address of the table
     BL @GPLSET     * and set that GROM address - GROM is now ready!
     SWPB R1        * assume VDP is already prepared for write to save space
     MOVB R1,@>8C02
     SWPB R1
     MOVB R1,@>8C02 * VDP is now ready!
     CLR R0
     MOVB R0,@>8C00 * pad the top of the char with a space
     MOV R3,R0      * then copy 7 (or 6) bytes
    * +++ 99/4 support begin +++
     CI R3,6        * check for 99/4
     JNE LP9
     MOVB R0,@>8C00 * extra blank line for 99/4
    * +++ 99/4 support end +++
    * no changes needed if this block removed
     MOVB @>9800,@>8C00  * copy a byte (both sides autoincrement)
     DEC R0
     JNE LP9
     DEC R2         * next character
     JNE LP8
     B *R10



    Thanks Tursi.


    I will borrow these ideas for finding the character table ... one day. :)


    I did make the CHARSET word in high level Forth using the GVMOVE function.

    It is not as fast as the BIG chars but not a noticeable delay for most purposes.

    : ]GFONT ( ascii -- grom_adr) BL -  7 * 6B4 + ;  \ GROM array of TI Font data
    : CHARSET ( -- )
            [CHAR] ~ 1+ BL                    \ all ASCII chars
               I ]GFONT                       \ get GROM address for char I
               I ]PDT                         \ get PDT address for char I
               0 OVER VC!                     \ store 1st zero in VDP
                 1+                           \ inc PDT address
                 7 GVMOVE                     \ write 7 bytes GROM->VDP
            LOOP ;

  17. This is such cool trick I had to play with it in Forth.

    In CAMEL99 I used integers for character patterns so I had to play games with the numbers.


    This was made simpler by 2 little code words:

    1. SPLIT which splits an integer into 2 bytes
    2. FUSE which "fuses" to bytes into an integer


    Once it worked, I went nuts and made TYPE.BIG which takes the address and len of a string and prints it in these big characters.


    I would never have thought of doing this like this. Very clever.


    EDIT: Forgot to mention, by not using strings this goes much faster. The code in the spoiler runs in 1 second.




    \ big characters in TI BASIC by sometimes99er in CAMEL99
    \ CAMEL99 defines char patterns with integers
    \ so we had to get creative
    : STRETCH1 ( 00AA -- AAAA)  SPLIT DROP DUP FUSE ;   \ removes top pixels
    \ Chop Charpat into 2 patterns. Upper & lower
    : SPLITPAT  ( n n n n  -- n n n n  n n n n )
                 >R >R >R       \ push 3 args to rstack
                 STRETCH1       \ 1st arg is special
                 R> STRETCH     \ pop rstack & stretch
                 R> STRETCH
                 R> STRETCH
                 0000  ;        \ CHARDEF need 4 args.
    \ got fancy here and made a word to print strings BIG
    : UPPER?   ( char -- ?)  [CHAR] A [CHAR] Z 1+ WITHIN ;
    : LOWER    ( c -- c )    DUP UPPER? IF  020 OR  THEN ;
    : TYPE.LOW ( adr len -- ) OVER + SWAP DO   I [email protected] LOWER EMIT  LOOP ;
    : TYPE.BIG ( addr len -- ) 2DUP TYPE CR  TYPE.LOW  ;
    : RUN
         CR CR
         91 65 DO                   \ loop index I is a char
            I LOWER CHARDEF
            I CHARDEF
         BEGIN  KEY?  UNTIL  BYE ;





    • Like 2
  • Create New...