Jump to content
IGNORED

Camel99 Forth Information goes here


TheBF

Recommended Posts

I have developed a healthy respect for the authors of the Sprite control in X-BASIC.

I found this old XB Demo program and wondered how I could do it in "GOLLY GEE WHIZ" Forth.

Without Automotion I had to create a timer variable for each motion element and down count to zero, like the ROM code does.

 

Was able to gain some efficiency by creating words that only play with X or Y in a sprite independently.

 

Anyway for those studying Forth this might provide some ideas.

 

 

 

\ EXTENDED BASIC DEMO ported to CAMEL99 V2

INCLUDE DSK1.TOOLS.F
INCLUDE DSK1.CHARSET.F
INCLUDE DSK1.DIRSPRIT.F  \ This also loads GRAFIX.F

HEX
0103 070F 1F3F 7FFF PATTERN: CH40   \ LEFT-TRIANGLE
FFFF FFFF FFFF FFFF PATTERN: CH41   \ BLOCK1
80C0 E0F0 F8FC FEFF PATTERN: CH48   \ RIGHT-TRIANGLE
FFFF FFFF FFFF FFFF PATTERN: CH49   \ BLOCK2
FFAA AAAA AAAA AAAA PATTERN: CH56   \ VERT LINES (FENCE)
AAAA AAAA AAAA AAFF PATTERN: CH73   \ FENCE-BOTTOM
0000 0000 0000 0000 PATTERN: CH64   \ empty char
FF80 BFA0 A0A0 A0A0 PATTERN: CH57   \ TOP-LEFT CORNER
FF00 FF00 0000 0000 PATTERN: CH58   \ TOP-DOUBLE-LINE
FF01 FD05 0505 0505 PATTERN: CH59   \ TOP-RIGHT CORNER
0505 0505 0505 0505 PATTERN: CH60   \ VERT-DBL-LINE
0505 0505 05FD 01FF PATTERN: CH61   \ BOT-RIGHT-CORN
0000 0000 00FF 00FF PATTERN: CH62   \ BOT-DOUBLE-LINE
A0A0 A0A0 A0BF 80FF PATTERN: CH63   \ BOT-LEFT-CORN
A0A0 A0A0 A0A0 A0A0 PATTERN: CH72   \ LEFT-DBL-LINE
EEAA AAEE 2222 2222 PATTERN: CH80   \ '99'
8080 0077 5474 4474 PATTERN: CH81   \ 'er
7EFF FFFF FFFF FF7E PATTERN: CH88   \ ROCK
3C3C 3C3C 3C7E 7E99 PATTERN: CH104  \ STUMP
3038 7EFF FFFE 3E3C PATTERN: CH105  \ TOP-CLOUD
0000 0000 183C 3C18 PATTERN: CH106  \ TIRE
0003 0408 7FFF CF87 PATTERN: CH107  \ FRONT-CAR
00F0 8884 FEFF E7C3 PATTERN: CH108  \ BACK-CAR
0000 0000 3C5A 9981 PATTERN: CH109  \ WINGS-DOWN
0081 8142 3C18 1800 PATTERN: CH110  \ WINGS-UP
005C 7E7E FFFF 7600 PATTERN: CLOUD

DECIMAL
: DEF-CHARS
      CH40 40 CHARDEF    CH41 41 CHARDEF
      CH48 48 CHARDEF    CH49 49 CHARDEF
      CH56 56 CHARDEF    CH73 73 CHARDEF
      CH57 57 CHARDEF    CH58 58 CHARDEF
      CH59 59 CHARDEF    CH60 60 CHARDEF
      CH61 61 CHARDEF    CH62 62 CHARDEF
      CH63 63 CHARDEF    CH72 72 CHARDEF
      CH80 80 CHARDEF    CH81 81 CHARDEF
      CH88 88 CHARDEF    CH41 96 CHARDEF

     CH104 104 CHARDEF   CH105 105 CHARDEF
     CH106 106 CHARDEF   CH107 107 CHARDEF
     CH108 108 CHARDEF   CH109 109 CHARDEF
     CH110 110 CHARDEF   CLOUD 111 CHARDEF  ;

: MAKE-SPRITES
\       CHAR CLR COL ROW  SPR#
         104  9  104 130   1 SPRITE  \ treetop
         105 13  104 114   2 SPRITE  \ tree trunk
         106  2  101 116   3 SPRITE  \ BACK TIRE

         108  6  100 109   4 SPRITE  \ BACK CAR
         107  6   84 109   5 SPRITE  \ FRONT CAR

         106  2   82 116   6 SPRITE  \ FRONT TIRE
         109  2  100  35  11 SPRITE  \ bird
         111 16   10  31   8 SPRITE  \ cloud1
         111 16   50  13   9 SPRITE  \ cloud2
         111 16  200  50  10 SPRITE ; \ cloud3
         
\ motion support
: --@    ( variable -- n )  -1 OVER +! @ ;
: SP.X+!  ( n spr# -- ) DUP >R  SP.X@ + R> SP.X! ;
: SP.Y+!  ( n spr# -- ) DUP >R  SP.Y@ + R> SP.Y! ;

VARIABLE CAR-TMR
: MOVE-CAR ( -- )
       CAR-TMR --@
       0= IF
         -1 3 SP.X+!  \ move 4 sprites together
         -1 4 SP.X+!
         -1 5 SP.X+!
         -1 6 SP.X+!
         20 CAR-TMR !
        THEN  ;

VARIABLE CLOUDTMR
: MOVE-CLOUDS
     CLOUDTMR --@
     0= IF
          -1  8 SP.X+!
          -2  9 SP.X+!
           1 10 SP.X+!
         120 CLOUDTMR !
      THEN ;

VARIABLE BIRDTMR
: MOVE-BIRD
     BIRDTMR --@
     0= IF
          1 11 SP.X+!
          25 BIRDTMR !
      THEN ;

VARIABLE FLAPTMR
: FLAP ( -- )
         FLAPTMR --@
         0= IF  11 ]SDT ->PAT VC@    \ read sprite pattern
                109 =
                IF   110 11 PATTERN
                ELSE 109 11 PATTERN
                THEN 75 FLAPTMR !   \ reset timer
         THEN ;

: SET-TIMERS
        20 CAR-TMR !
        75 FLAPTMR !
       120 CLOUDTMR !
        25 BIRDTMR !  ;

DECIMAL
: DEFAULTS          \ restore graphics to BASIC defaults
     4 19 2 8 COLORS
     CHARSET
     8 SCREEN
     DELALL ;

: ROAD  ( -- )
         96 SET# 16 16 COLOR
         0 15  96  64   HCHAR ;

: LOGRASS  ( -- )
         0 17   41   32 7 * HCHAR ;

: HIGRASS  ( -- )
         0 13   49   32 2* HCHAR ;

: FENCE
         56 SET# 2 12 COLOR
         73 SET# 2 12 COLOR
         20 13 56 12 HCHAR
         20 14 73 12 HCHAR ;

: GREEN-COLORS ( -- )
          40 SET# 13 8 COLOR
          48 SET#  4 8 COLOR ;

: .MOUNTAINS ( col row -- )
       AT-XY
       CR   ."                   (0"
       CR   ."    (0            ()10      (0"
       CR   ."   ()10    (0    ())110    ()10"
       CR   ."  ())110  ()10  ()))1110  ())110"
       CR   ." ()))1110())110())))11110()))1110" ;

: ROCK ( -- )
          88 SET#  14 11 COLOR
           7 14 88 1 HCHAR
           9 14 88 1 HCHAR ;

: SIGN-COLORS  ( -- )
          7  9 2 12 COLORS ;

: .SIGN  ( col row -- )
        [CHAR] P SET# 2 12 COLOR
        2DUP    AT-XY ." 9::;"
        1+ 2DUP AT-XY ." HPQ<"
        1+      AT-XY ." ?>>="  ;

DECIMAL
: RUN   ( -- )
        DELALL
        9 SCREEN
        CLEAR
        DEF-CHARS
        GREEN-COLORS
        32 SET# 8 8 COLOR
        0 7 .MOUNTAINS
        ROAD
        LOGRASS HIGRASS
        SIGN-COLORS
        17 11 .SIGN
        ROCK
        FENCE
        1 MAGNIFY
        MAKE-SPRITES
        SET-TIMERS
        BEGIN
           MOVE-CAR
           MOVE-CLOUDS
           MOVE-BIRD
           FLAP
           ?TERMINAL
        UNTIL
        DEFAULTS ;

 

 

 

 

xbdemo.mp4

post-50750-0-27962900-1526082681.jpg

  • Like 1
Link to comment
Share on other sites

Hah, that looks familiar. ;) I wrote that for the Summerland TI Users Group back when I was still in high school, based on a picture in 99er Magazine (was part of an explanation of sprites). ;)

 

(edit: the original XB version, that is ;) )

Edited by Tursi
  • Like 3
Link to comment
Share on other sites

Hah, that looks familiar. ;) I wrote that for the Summerland TI Users Group back when I was still in high school, based on a picture in 99er Magazine (was part of an explanation of sprites). ;)

 

(edit: the original XB version, that is ;) )

 

You know you are history when... :-)

 

That's really cool. Thanks for letting everybody know.

 

B

Link to comment
Share on other sites

So now there's 3 Forth systems for the TI?

 

I assume this one is based on Brad's Camel Forth?

 

But, by the look of it, you're not cross compiling from a PC?

 

I'm also using Brad's Camel Forth, but the 6809 variant and using his Chromium cross compiler.

 

Target is the Vectrex, probably a lot less work than you need to put it as it has the correct processor!

Link to comment
Share on other sites

So now there's 3 Forth systems for the TI?

 

I assume this one is based on Brad's Camel Forth?

 

But, by the look of it, you're not cross compiling from a PC?

 

I'm also using Brad's Camel Forth, but the 6809 variant and using his Chromium cross compiler.

 

Target is the Vectrex, probably a lot less work than you need to put it as it has the correct processor!

 

 

Yes I am the last of the noble group. I am cross-compiling actually. On a "real" PC, the DOSBOX. :-)

 

The cross-compiler is here. https://github.com/bfox9900/CAMEL99/tree/master/Compiler

It actually began with the TI-Forth assembler that I modified to assemble into a different memory segment.

 

It runs on an old DOS Forth from the '90s called HsForth which I upgraded to something a little closer to ANS Forth.

 

I stripped the comments from Brad's MSP430 Camel Forth Assembly Language source , which are Forth and wrote the compiler to compile the comments. :-) It still had to write all the primitives in 9900 assembler however.

 

Edit: The cross-compiler builds an 8K kernel that can extend itself with source code.

The latest version now can compile TI DV80 source files and I have the beginnings of ANS file word set support.

 

I had to do it the hard way. It's been on my bucket list for 35 years to make a cross-compiler so time was running out.

 

 

Brian

Edited by TheBF
Link to comment
Share on other sites

Improved Background Sound List Player

 

Spent some time working on what I think can be my goto sound list player.

I will test it a little harder and then put it up on GitHub.

But I notice that even though it does not use interrupts it plays very solid when I exercise the screen heavily.

 

The features are:

  1. It can Queue up to 16 sound lists
  2. Sound lists are stored in VDP RAM
  3. Code includes VDP byte directive so lists are as easy to code as Assembler
  4. Player task goes to sleep when sound Queue is empty
  5. Uses the TMS9901 timer for sound duration
  6. Task memory is allocated in Low RAM
  7. Size: Multi-tasker, VDP memory manager, VDP byte compiler, BG player adds 1218 bytes to the Forth dictionary
  8. >SNDQ command enqueues a sound list
  9. PLAYQ command plays whatever is the sound queue
  10. KILLQ command emptys the sound queue. (playing sounds will continue until finished)

Sound lists look like this:

VCREATE NOKIA
       VBYTE 01,9F,20
       VBYTE 03,90,85,05,09
       VBYTE 02,8F,05,09
       VBYTE 02,87,09,12
       VBYTE 02,87,08,12
       VBYTE 02,85,06,09
       VBYTE 02,81,07,09
       VBYTE 02,8E,0B,12
       VBYTE 02,8A,0A,12
       VBYTE 02,81,07,09
       VBYTE 02,8F,07,09
       VBYTE 02,8A,0C,12
       VBYTE 02,8A,0A,12
       VBYTE 02,8F,07,24
       VBYTE 01,9F,00
/VEND

 

 

\ BACKGROUND VDP sound list player in CAMEL99 Forth V2
INCLUDE DSK1.MTASK99.F
INCLUDE DSK1.VDPMEM.F

\ ========================================================
\ sound list player
HEX
: SILENT ( --)  9F SND!  BF SND!  DF SND! FF SND! ;  \ turn off all sounds

: PLAY$ ( $addr -- )         \ play 1 sound string, no time-sharing
        VCOUNT ( -- addr len)
        2DUP + VC@  >R       \ get duration at end of string, to milli-secs & Rpush
        BOUNDS               \ convert addr/len to end-addr. start-addr.
        DO  I VC@ SND! LOOP  \ feed bytes from VDP mem to sound chip
        R> JIFFS ;           \ use the delay from Rstack 1 jiff = 1/60 sec

\ play a TI sound list
: PLAYLIST  ( list-addr -- )
         BEGIN
           DUP VC@           \ read the string length byte
         WHILE ( tos <> 0)
            PAUSE
            DUP PLAY$        \ play a single string
            VCOUNT + 1+      \ advance to the next sound string
         REPEAT
         SILENT
         DROP ;              \ mom said always clean up after yourself

\ ========================================================
\ create a fifo to feed the sound player
HEX
                 \    head   tail      data
                 \    ----   ----   --------------
CREATE FIFO ( -- addr)  0 ,    0 ,  20 CELLS ALLOT

\ name the address fields for maximum speed
FIFO           CONSTANT QHEAD
FIFO    CELL+  CONSTANT QTAIL
FIFO 2 CELLS + CONSTANT QDATA

\ circular indexer in Forth replaced with machine code version
\ : []++    ( fifo head|tail -- addr) DUP @ 2+ 1F AND DUP ROT ! + ;

\ CODE []++ ( fifo head|tail -- addr) \ compute next fifo location
\         *TOS R0  MOV,    \ DUP @
\          R0     INCT,    \ 2+
\          R0 1F  ANDI,    \ 1F AND
\          R0 *TOS MOV,    \ DUP ROT !
\          R0  TOS MOV,    \
\         *SP+ TOS A,      \  +
\          NEXT,
\          ENDCODE

\ machine code version of above assembler code
CODE []++  ( fifo head|tail -- addr)
         C014 , 05C0 , 0240 , 001F , 
         C500 , C100 , A136 , NEXT, 
ENDCODE

: Q@     ( fifo -- n) QTAIL []++  @ ;  \ bump tail and fetch data
: Q!     ( n fifo --) QHEAD []++  ! ;  \ bump head and add to FIFO

\ Background Player program
: BGPLAYER ( -- )   \ play all lists in the Q then goto sleep
           BEGIN
             FIFO 2@ <>  \ fetch,compare head & tail
           WHILE
             QDATA Q@ PLAYLIST
           REPEAT
           MYSELF SLEEP
           PAUSE ;  \ hand-off to next task

\ ===============================================
\ CREATE player task
INIT-MULTI

USIZE MALLOC CONSTANT PLAYER  \ get memory for task
PLAYER FORK                   \ init the task
' BGPLAYER PLAYER ASSIGN      \ assign something to do

\ ===============================================
\ end user commands
\ Usage:  MUNCHMAN >SNDQ  PLAYQ
: >SNDQ   ( list -- ) QDATA Q! ;
: PLAYQ   ( list -- ) PLAYER RESTART ;
: KILLQ   ( -- )      QTAIL @ QHEAD ! ;

\ ===============================================
\ VDP list compiler
\ Lets us make sound lists like in assember
\ but the compile into VDP RAM
: ?BYTE ( n -- ) FF00 AND  ABORT" Not a byte" ;
: VBYTE ( -- )
         BEGIN
           [CHAR] , PARSE-WORD DUP 
         WHILE
            EVALUATE DUP ?BYTE
            VC,
         REPEAT
         2DROP ;

: /VEND   0 VC, 0 VC, ;  \ compile zeros

 

 

Edited by TheBF
  • Like 1
Link to comment
Share on other sites

Brian...

 

I would like to be more involved, but my focus for a few weeks is getting my left knee back to normal function after successful surgery. I am not looking for sympathy—just explaining why I have not been more responsive.

 

You would not, perchance, be coming to the Chicago Faire in November, would you?

 

...lee

Link to comment
Share on other sites

Brian...

 

I would like to be more involved, but my focus for a few weeks is getting my left knee back to normal function after successful surgery. I am not looking for sympathy—just explaining why I have not been more responsive.

 

You would not, perchance, be coming to the Chicago Faire in November, would you?

 

...lee

 

 

Hi Lee,

 

No worries. First things first is always a good strategy.

 

I have been feeling off myself with a flu like thing.

 

I have not given Chicago any thought. It would be a significant expense but would be fun I'm sure. I will give it some thought.

 

Brian

Link to comment
Share on other sites

 

 

Yes I am the last of the noble group. I am cross-compiling actually. On a "real" PC, the DOSBOX. :-)

 

The cross-compiler is here. https://github.com/bfox9900/CAMEL99/tree/master/Compiler

It actually began with the TI-Forth assembler that I modified to assemble into a different memory segment.

 

<<CHOP>>

 

Brian

 

Ah yes...actually I read your release notes etc. perhaps 4 or 5 weeks back, then I went on holiday for a week and forgot everything about it :-) My memory now of what I read is that you compiled (assembled?) the primitives and put that onto the '99 and then loaded the rest of Forth and any extensions using the '99 to compile. When you've got access to disk/flash, I guess that gives you a nice usable system to do that.

 

My CamelForth is running on 6809 and Brad R's "Chromium" cross-compiler is quite complete, so I'm compiling everything on the PC. As the target is the Vectrex with it's vector monitor and joystick, there is no keyboard or character-based display, and <1k of usable RAM, so compiling on the Vectrex is a non-starter without hardware mods. I do have a serial port/terminal added to mine for interactive testing, but a ~700 byte dictionary has some limitations!

 

My aim is to allow creation new cartridge programs/games by other developers, so hardware changes can only be minimal, thus PC is the IDE. There are some great emulators for the Vectrex with build-in debuggers and one has a full IDE for assembly language development, but it still works quite nicely with the output from the Chromium compiler. With some help from the Forth community, I have Camel Forth 6809 Chromium compiler now running on Gforth instead of F83 and with plain text files instead of block files, I saw this as mandatory to get any new users. One day I'll learn how to use Github...

 

I'm new to the TI processors,a quick look shows the 430 to be similar to the '99 processor, but with more capability, so it makes perfect sense to port 430 Camel Forth to the '99.

 

It looks like you've put a lot of effort into your Forth, just like the other two Forths for the TI, they all look excellent. I've really enjoyed reading about them and the '99 in general, especially after learning so much about the machine on the Floppy Days series of '99 podcasts. I owe it to myself to download Classic99 and give'em a go!

Edited by D-Type
Link to comment
Share on other sites

 

Ah yes...actually I read your release notes etc. perhaps 4 or 5 weeks back, then I went on holiday for a week and forgot everything about it :-) My memory now of what I read is that you compiled (assembled?) the primitives and put that onto the '99 and then loaded the rest of Forth and any extensions using the '99 to compile. When you've got access to disk/flash, I guess that gives you a nice usable system to do that.

 

<<CHOP>>

 

Actually something I forgot to mention, I once owned a TI-99/4A. I can't remember where I got it from, but my eBay auction text from Jan 2002 when I listed it said "This is an auction for a Texas Instruments TI99/4A computer main unit only. I have no power supply or other leads so I do not know if it works."

 

No wonder I can't really remember much about it, except it seemed really nicely made. I'm a bit sad now that I didn't stick it in a box in the garage for later!

Edited by D-Type
  • Like 1
Link to comment
Share on other sites

 

Ah yes...actually I read your release notes etc. perhaps 4 or 5 weeks back, then I went on holiday for a week and forgot everything about it :-) My memory now of what I read is that you compiled (assembled?) the primitives and put that onto the '99 and then loaded the rest of Forth and any extensions using the '99 to compile. When you've got access to disk/flash, I guess that gives you a nice usable system to do that.

 

My CamelForth is running on 6809 and Brad R's "Chromium" cross-compiler is quite complete, so I'm compiling everything on the PC. As the target is the Vectrex with it's vector monitor and joystick, there is no keyboard or character-based display, and <1k of usable RAM, so compiling on the Vectrex is a non-starter without hardware mods. I do have a serial port/terminal added to mine for interactive testing, but a ~700 byte dictionary has some limitations!

 

My aim is to allow creation new cartridge programs/games by other developers, so hardware changes can only be minimal, thus PC is the IDE. There are some great emulators for the Vectrex with build-in debuggers and one has a full IDE for assembly language development, but it still works quite nicely with the output from the Chromium compiler. With some help from the Forth community, I have Camel Forth 6809 Chromium compiler now running on Gforth instead of F83 and with plain text files instead of block files, I saw this as mandatory to get any new users. One day I'll learn how to use Github...

 

I'm new to the TI processors,a quick look shows the 430 to be similar to the '99 processor, but with more capability, so it makes perfect sense to port 430 Camel Forth to the '99.

 

It looks like you've put a lot of effort into your Forth, just like the other two Forths for the TI, they all look excellent. I've really enjoyed reading about them and the '99 in general, especially after learning so much about the machine on the Floppy Days series of '99 podcasts. I owe it to myself to download Classic99 and give'em a go!

 

 

For 100% clarity it cross-assembles about 100 primitives into the binary image and then using cross-compiler magic and those primitives you cross-compile the ANS/ISO Forth CORE word set and quite a bit of the extended word set. And the system includes simple file access and the word "INCLUDED".

 

So with : ; and INCLUDED I can load and compile anything else that is needed.

 

I can also use the XFC99 Cross-compiler to create stand alone 99 programs but currently they are limited to 8K in size.

 

I don't know much about github either. I can get files up there but have not yet grokked version control. Not that interested to be honest, but I guess I should try harder.

 

Well done on moving Chromium to gForth.

I have dreams of porting my cross-compiler to gForth as well. So much code, so little time...

 

There are things about MSP430 that are nice like a real stack pointer and instructions that execute in 2 or 3 clocks instead 14 to 28. :mad:

The 9900 has the faster context switch, at least in terms of instructions. But yeah it's a pretty easy conversion for many primitives.

 

B

Link to comment
Share on other sites

Performance Enhancements in ANS Forth

 

I am going to release a pretty near final version of the instruction manual and it includes this chapter that might be interesting to the Forth experimenters here.

 

 

Performance Enhancements

As we have seen Forth operates like everything is a sub-routine. In many languages this would cause a big overhead but Forth was built to minimize sub-routine overhead.

One way it does this is by using separate stacks for data and return addresses. Even so there are times when you need every bit of speed you can get.

This can mean that calling a routine in a time critical loop makes things too slow.

Text Macros

The solution to removing some calling overhead is to use a feature that became part of Forth in the ANS Forth 94 Standard.

The name of the feature is the “Text Macro” and it combines a literal text string and the word EVALUATE.

Consider the following code:

INCLUDE DSK1.ELAPSE.F   \ elapsed timer utility

\ Calculate the address of an array at index ‘n’
: []  ( ndx addr – addr[n])  SWAP CELLS + ;

\ store n in array at ndx
: []! ( n ndx addr -- ) [] ! ;

\ fetch n from array at ndx
: []@ ( ndx addr – n)   [] @ ;

DECIMAL 
2000 CONSTANT SIZE

CREATE Q  SIZE CELLS ALLOT    \ make some space called Q
: FILL-Q    SIZE 0 DO  I  I Q []!  LOOP ;
: SEE-Q     SIZE 0 DO     I Q []@ . LOOP ;
: EMPTY-Q   SIZE 0 DO     I Q [] OFF  LOOP ; 

This code will work as expected and you can fill the array, see-the-array and empty the array.

If we run this we get the following results:

 

(Example 1 jpeg)

 

 

Now let’s replace the array index calculator with a TEXT Macro like this:

: [] S” SWAP CELLS +” EVALUATE ; IMMEDIATE

 

What’s the difference? When word [] runs now it will take the text string and interpret it. That’s normal, but now consider what happens when we do this:

\ store n in array at ndx

: []! ( n ndx addr -- ) [] ! ;

 

Notice that now [] is an IMMEDIATE word. This means that the text string will be interpreted while we are compiling the definition of []! The effect of this we be that the compiler will compile SWAP CELLS + as separate words into the routine []!. This therefore means that there will no longer be a sub-routine call to [], but rather a call to each of the separate words. In other words we have compiled the definition of [] “inline”.

Let’s see what it does to the speed of our loops.

 

​(Text macros jpeg)

 

We get a 15% speed improvement in the first case and a 21% improvement in the second case. The downside of using text macros is that every time we use [] we consume 3 CELLS ( 6 bytes) of space rather than only 1 CELL with a colon definition. So use TEXT macros wisely and you can get some improvements in your code speed with very little effort.

INLINE CODE WORDS

CAMEL99 Forth is implemented as something called an "indirect threaded code". This is a very clever way to make a language that fits a lot of stuff in a small space. The secret to this code density is that every routine is identified by just one address. The secret to making it run quickly is creating a little routine to read those addresses and do the code associated with them as fast as possible. On the TMS9900 that little routine is 3 instructions of assembly language so it's pretty fast but there is a price penalty with threaded code that can range from 2.5 times to as much as ten times in the worst cases.

It turns out that ITC Forth spends about 50% of the time running those three instructions that read the address which sometimes is called the [1]inner interpreter and is given the name NEXT.

At the bottom of every Forth system are a pile of little routines written in assembler (CODE words) that do all the real work. They are simple things that read the contents of a memory address or add two numbers together. The code is all there and it is normally just called by the inner interpreter. Each routine ends with a call back to the inner interpreter.

Wouldn't it be handy if we could use all that code the way a conventional compiler does and copy the routines into memory all in row and eliminate the inner interpreter between each CODE word?

CAMEL99 has INLINE[ ] to do just that. It reads a string of Forth words that are assembly language words and strings the code together as one long routine. This is not as space efficient, but it can as much as 2.4 times faster! Below is an example of how we could use CODE words to improve the speed of our previous example but not write one instruction of Assembly language. Notice we have only changed the array indexing words to CODE words.

The Magic is done with INLINE[ ] which compiles each code word but strips off the call to NEXT and lets each CODE word run one after the other. This is ONLY possible with system CODE words. If you attempt to INLINE[] Forth words the compiler will ABORT with an error message.

INCLUDE DSK1.ELAPSE.F   \ elapsed timer utility

CODE []  ( i addr -- addr’) INLINE[ SWAP 2* + ] NEXT, ENDCODE
CODE []! ( n i addr -- )  INLINE[ SWAP 2* + ! ] NEXT, ENDCODE
CODE []@ ( i addr – n)    INLINE[ SWAP 2* + @ ] NEXT, ENDCODE

DECIMAL 
2000 CONSTANT SIZE

CREATE Q  SIZE CELLS ALLOT    \ make some space called Q
: FILL-Q    SIZE 0 DO  I  I Q []!  LOOP ;
: SEE-Q     SIZE 0 DO     I Q []@ . LOOP ;
: EMPTY-Q   SIZE 0 DO     I Q [] OFF  LOOP ; 

Now let’s see how our new programs perform.

 

( Inline jpeg)

 

Performance with INLINE[] CODE Words

As we can see our little programs now run 40% faster because the core routines inside the loop are much faster.

 

But look at the last result. This uses the assembly language word called FILL.

This line is doing the same thing as EMPTY-Q but using every trick available for the TMS9900 CPU. It is three times faster than using the DO/LOOP structure. So the lesson is that we can do many things to increase the speed of Forth and normally that’s fast enough. However when maximum speed is required Assembler is still fastest.

 

 


[1] This is NOT the "outer" text interpreter that we communicate with from keyboard, but an internal routine that reads addresses and runs machine code

post-50750-0-73381800-1527217196.jpg

post-50750-0-72210700-1527217203.jpg

post-50750-0-41226200-1527217214.jpg

Edited by TheBF
  • Like 2
Link to comment
Share on other sites

At some point in my Vectrex 6809 Camel Forth adventure, I'm going to need these optimisations, thanks for posting.

 

Originally, Vectrex carts were 4k, later ones were 8k. More recent homebrew carts have been up to 64k via simple 32k bank switching.

 

Memory size isn't a problem any more, but program speed always is. The Vectrex runs at 1.5MHz and so to get a flicker-free 50Hz screen update you need to get each update done in 30,000 cycles.

 

There are no frame buffers or separate vector generator hardware available, it's all got to be done in real time by the 6809!

Link to comment
Share on other sites

At some point in my Vectrex 6809 Camel Forth adventure, I'm going to need these optimisations, thanks for posting.

 

Originally, Vectrex carts were 4k, later ones were 8k. More recent homebrew carts have been up to 64k via simple 32k bank switching.

 

Memory size isn't a problem any more, but program speed always is. The Vectrex runs at 1.5MHz and so to get a flicker-free 50Hz screen update you need to get each update done in 30,000 cycles.

 

There are no frame buffers or separate vector generator hardware available, it's all got to be done in real time by the 6809!

 

 

Glad you found it helpful. The one thing you have going for you with 6809 I believe is that the 6809 does in-direct threading in 1 instruction.

( if memory serves me right) Contrast that to TMS9900 which takes 3 instructions and each instruction is sucking up 20 or so cycles.

 

Some other things I have noticed in Camel Forth.

 

There are quite a few stack operations code in Forth and many times they can be made much faster as CODE words.

 

The word HERE is defined as DP @ and this can make the system snappier if written as a CODE word.

 

The 99 really shows up slow code and dictionary searches were really slow. The test for that is type the numbers 1 to 10 and hit return.

This causes 10 searches through the entire dictionary. Stock Camel Forth took about 3.5 seconds.

 

I had to re-write FIND as a code primitive called (FIND) and then the final definition is

: FIND   LATEST @ (FIND) ;

The same test now takes less than 1 second. These changes may be very 9900 specific however, so testing is essential.

 

B

Link to comment
Share on other sites

The 99 really shows up slow code and dictionary searches were really slow. The test for that is type the numbers 1 to 10 and hit return.

This causes 10 searches through the entire dictionary. Stock Camel Forth took about 3.5 seconds.

 

I had to re-write FIND as a code primitive called (FIND) and then the final definition is

: FIND   LATEST @ (FIND) ;

The same test now takes less than 1 second. These changes may be very 9900 specific however, so testing is essential.

 

B

 

In fbForth 2.0, typing 0 1 2 3 4 5 6 7 8 9 happens in 0.67 s, while typing 4 5 6 7 8 9 8 7 6 5 takes 0.88 s. The reason for the difference is that 0 1 2 3 are defined as constants and are found in the dictionary, so are faster than number conversion. I am a little surprised that Camel99 Forth is so much slower because the dictionary search for words not found in fbForth 2.0 is 505 words!

 

INTERPRET uses -FIND :

: -FIND     ( --- false | pfa len true )  ( IS:string )
    BL WORD                       \ get next word in input stream to HERE
    HERE CONTEXT @ @ (FIND)       \ search CONTEXT vocabulary for the word
    DUP 0=                        \ did we find it?
    IF                            \ no
        DROP HERE LATEST (FIND)   \ search CURRENT vocabulary
    THEN  ;

(FIND) is indeed a code primitive. Because of the way I wrote the ROM code for fbForth 2.0, the code for (FIND) in the spoiler may not be terribly helpful, but here it is:

 

 

_PFIND MOV  *SP,R1          ; nfa to R1
       JEQ  PFIND4          ; top of dictionary?
PFIND1 MOV  R1,R0           ; no; copy nfa to R0
       MOV  @2(SP),R3       ; str addr ptr to R3
       MOVB *R0+,W          ; copy nfa count byte to W; inc R0 to 1st char
       ANDI W,>3F00         ; mask out non-count bits, but not smudge bit
       CB   W,*R3+          ; compare char counts; inc R3 to 1st str char
       JNE  PFIND3          ; counts the same?
PFIND2 MOVB *R0+,W          ; yes; next nfa char to W, incrementing nfa
       JLT  PFIND5          ; char with terminator bit?
       CB   W,*R3+          ; no; compare chars, incrementing str
       JEQ  PFIND2          ; if =, compare next chars
PFIND3 MOV  @-2(R1),R1      ; not =; counts different; get prev word's nfa
       JNE  PFIND1          ; if not top of dictionary, try again
PFIND4 INCT SP              ; no match; top of dictionary; pop top of stack
       CLR  *SP             ; return false on top of stack
       JMP  PFINDX          ; return to inner interpreter
PFIND5 ANDI W,>7F00         ; mask out terminator bit from nfa char
       CB   W,*R3           ; last nfa char same as last str char?
*                             ...should match space at end of word with even count
       JNE  PFIND3          ; no; head out
       INCT R0              ; yes; increment R0 to pfa or pfa ptr+2
*  Change to accommodate headers not in bank 0 (pfa ptr in R0)
       BL   @GETPFA         ; check for ptr in ROM
PFIND6 MOV  R0,@2(SP)       ; leave pfa on stack at same position as addr
       CLR  *SP             ; clear top stack position
       MOVB *R1,@1(SP)      ; copy length byte to right byte of top of stack
       DECT SP              ; make room on stack for 3rd cell
       SETO *SP             ; put -1 on stack
       NEG  *SP             ; make it 1 (true)
PFINDX B    @RTNEXT         ; return to inner interpreter 

 

 

 

...lee

Link to comment
Share on other sites

 

In fbForth 2.0, typing 0 1 2 3 4 5 6 7 8 9 happens in 0.67 s, while typing 4 5 6 7 8 9 8 7 6 5 takes 0.88 s. The reason for the difference is that 0 1 2 3 are defined as constants and are found in the dictionary, so are faster than number conversion. I am a little surprised that Camel99 Forth is so much slower because the dictionary search for words not found in fbForth 2.0 is 505 words!

 

INTERPRET uses -FIND :

: -FIND     ( --- false | pfa len true )  ( IS:string )
    BL WORD                       \ get next word in input stream to HERE
    HERE CONTEXT @ @ (FIND)       \ search CONTEXT vocabulary for the word
    DUP 0=                        \ did we find it?
    IF                            \ no
        DROP HERE LATEST (FIND)   \ search CURRENT vocabulary
    THEN  ;

(FIND) is indeed a code primitive. Because of the way I wrote the ROM code for fbForth 2.0, the code for (FIND) in the spoiler may not be terribly helpful, but here it is:

 

 

_PFIND MOV  *SP,R1          ; nfa to R1
       JEQ  PFIND4          ; top of dictionary?
PFIND1 MOV  R1,R0           ; no; copy nfa to R0
       MOV  @2(SP),R3       ; str addr ptr to R3
       MOVB *R0+,W          ; copy nfa count byte to W; inc R0 to 1st char
       ANDI W,>3F00         ; mask out non-count bits, but not smudge bit
       CB   W,*R3+          ; compare char counts; inc R3 to 1st str char
       JNE  PFIND3          ; counts the same?
PFIND2 MOVB *R0+,W          ; yes; next nfa char to W, incrementing nfa
       JLT  PFIND5          ; char with terminator bit?
       CB   W,*R3+          ; no; compare chars, incrementing str
       JEQ  PFIND2          ; if =, compare next chars
PFIND3 MOV  @-2(R1),R1      ; not =; counts different; get prev word's nfa
       JNE  PFIND1          ; if not top of dictionary, try again
PFIND4 INCT SP              ; no match; top of dictionary; pop top of stack
       CLR  *SP             ; return false on top of stack
       JMP  PFINDX          ; return to inner interpreter
PFIND5 ANDI W,>7F00         ; mask out terminator bit from nfa char
       CB   W,*R3           ; last nfa char same as last str char?
*                             ...should match space at end of word with even count
       JNE  PFIND3          ; no; head out
       INCT R0              ; yes; increment R0 to pfa or pfa ptr+2
*  Change to accommodate headers not in bank 0 (pfa ptr in R0)
       BL   @GETPFA         ; check for ptr in ROM
PFIND6 MOV  R0,@2(SP)       ; leave pfa on stack at same position as addr
       CLR  *SP             ; clear top stack position
       MOVB *R1,@1(SP)      ; copy length byte to right byte of top of stack
       DECT SP              ; make room on stack for 3rd cell
       SETO *SP             ; put -1 on stack
       NEG  *SP             ; make it 1 (true)
PFINDX B    @RTNEXT         ; return to inner interpreter 

 

 

 

...lee

 

Brad wrote Camel Forth to use more Forth definitions and less CODE words so it would be easier to port to new machines.

The poor old 9900 really struggles with that approach.

 

Here is the original FIND word . N= is string comparison CODE word.

Running the search loop in Forth is much slower than doing the whole thing in code as you have and CAMEL99 now has.

: FIND   ( c-addr -- c-addr   0      if not found )
\                         xt  1      if immediate
\                         xt -1      if "normal"
                   LATEST @                    \ -- adr
                   BEGIN                       \ -- adr nfa
                    2DUP OVER C@ CHAR+         \ -- adr nfa adr nfa n+1
                    N=                         \ -- adr nfa f
                    DUP IF
                          DROP
                          NFA>LFA @ DUP        \ -- adr link link
                        THEN
                   0= UNTIL                    \ -- adr nfa  OR  adr 0
                   DUP IF
                         NIP DUP NFA>CFA       \ -- nfa xt
                         SWAP IMMED?           \ -- xt iflag
                         0= 1 OR               \ -- xt 1/-1
                       THEN ;

Here is my new (FIND) word. I made use of the large register set to feed the machine more efficiently.

 

 

 

\ Register Usage
\ Inputs:  R3 = traverses NFAs in the Forth dictionary
\          R8 = address of the counted string we are looking for
\          R5 = length of the counted string in R8 + 1

\ string compare loop
\          R0 = number of characters to compare(search string length+1)
\          R1 = address of the 1st string to compare
\          R2 = address of the second string to compare

\ Outputs: R2 = address of found string -OR- address of search string
\          R4 = Forth TOS register. Holds the true/false result flag


CODE: (FIND) ( Caddr NFA -- XT ? )
             TOS R3 MOV,                  \ R3 = NFA which is a counted string
             *SP R8 MOV,                  \ R8 = caddr which is a counted string
              TOS CLR,                    \ TOS is the output flag, init to zero

\ get the length byte of Caddr
             *R8 R5 MOVB,                 \ caddr C@ -> R5
              R5 8 SRL,                   \ get the byte on the correct side right
              @@7 JEQ,                    \ if count=0 jump back to Forth
              R5 INC,                     \ 1+ to account for length byte
\ OUTER loop
             \ load char compare registers
@@1:          R5 R0 MOV,                  \ load R0 with length of caddr string
              R8 R1 MOV,                  \ load R1 with caddr string address
              R3 R2 MOV,                  \ load R2 with the NFA to compare

             \ inner character comparator loop
@@3:         *R1+ *R2+ CMPB,              \ compare char by char including the length byte
              @@5 JNE,                    \ ANY mismatch found, goto @@5
              R0 DEC,                     \ decr. loop counter
              @@3 JNE,                    \ loop while R0 > 0
              @@6 JMP,                    \ WE FOUND IT!! exit the loop

             \ traverse link list to next NFA
@@5:          R3  -3 ADDI,                \ convert nfa>lfa
             *R3  R3 MOV,                 \ do a fetch, R3 now has new NFA
              @@1 JNE,                    \ if <> 0 let's try the next word in the dictionary!
              NEXT,                       \ we got zero. End of the list! Go back to Forth
\ end Outer loop

\ convert NFA in R3 to CFA -> R2
@@6:          R3 R2 MOV,                  \ if found R3 has a name field address (NFA), copy ro R2
             *R3 R0 MOVB,                 \ get the length of the name to R0
              R0 SWPB,                    \ fix the #$%!@$ byte order again
              R0 R2 ADD,                  \ add length to R2, gets past the string to the CFA
              R2 INCT,                    \ inc 1 for the count byte and 1 more for even address evaluation
              R2 -2 ANDI,                 \ align R2 to even address boundary

\ test for immediate or normal word -> TOS
              TOS SETO,                   \ we found a word so set TOS to true
              R3 DEC,                     \ R3 still has the NFA. NFA-1 is the immediate field
             *R3 R0 MOVB,                 \ read contents of the immediate field
              @@9 JEQ,                    \ IF it's zero we're done
              TOS NEG,                    \ else if non zero negate the TOS from -1 to 1
                                          \ and head for home

\ bug in my forward jumps, needs multiple labels (assembler *TODO list)
@@7: @@9:     R2 *SP MOV,                 \ replace Caddr with the found XT in R2
              NEXT,                       \ Return to Forth
              END-CODE                    \ 42 BYTES

 

 

Edited by TheBF
Link to comment
Share on other sites

FIND is only used when compiling, correct? If yes, I guess using a cross compiler on a PC almost exclusively, which compiles my 5k binary in a couple of seconds, means this doesn't really make a lot of difference to me.

 

I'm still interested, though, to try the 1 2 3...9 10 Enter test the see how fast the Vectrex 6809 does it. When I'm back from holiday...

Link to comment
Share on other sites

FIND is only used when compiling, correct? If yes, I guess using a cross compiler on a PC almost exclusively, which compiles my 5k binary in a couple of seconds, means this doesn't really make a lot of difference to me.

 

I'm still interested, though, to try the 1 2 3...9 10 Enter test the see how fast the Vectrex 6809 does it. When I'm back from holiday...

 

 

It is used for compiling and can also be invoked by the word tick. ( ' interpreting mode version AND ['] compiling mode version )

 

So yes, using the cross-compiler means you are running the cross-compiler's FIND in the PC. But if you ever send text source code to the vectrex the speed of the downloads will be limited by FIND's lookup speed.

Link to comment
Share on other sites

It is used for compiling and can also be invoked by the word tick. ( ' interpreting mode version AND ['] compiling mode version )

 

I want to be careful not to muddy the waters too much for readers, but I wish to point out that, in TI Forth and fbForth (both fig-Forth based), ' (tick) works in both interpreting and compiling modes because it is state smart. As of the Forth-83 standard, there began a move to minimize the use of state-smart words. Consequently, tick’s state-smart capacity was removed in favor of reducing ' to interpreting mode only and creating ['] strictly for compiling mode. And, now, back to your regularly scheduled program...

 

...lee

  • Like 1
Link to comment
Share on other sites

Indexed addressing mode in Forth Arrays

 

​After playing with arrays in Forth I realized the 9900 was designed to do this in the instruction set.

​Indexed addressing is the way to do it so here is one way to use it in Forth Arrays. It is not as general purpose because the base address of the array must be embedded in the assembler code.

​This means that you must write special words for each type of access but as you can see the performance is double the Forth only solutions shown earlier.

 

Edit: removed some cut and paste mistakes

\ fastest array access using indexed address mode

INCLUDE DSK1.ASM9900.F
INCLUDE DSK1.ELAPSE.F   \ elapsed timer utility

DECIMAL
2000 CONSTANT SIZE

CREATE Q   SIZE CELLS ALLOT    \ make some space called Q

\ fetch contents of any cell in ARRAY
CODE ]Q@  ( i -- array[i]@)
        TOS  1 SLA,               \ shift R1 1 bit left (mult. By 2)
        Q (TOS) TOS MOV,          \ fetch contents of Q (TOS) to TOS
	NEXT,
ENDCODE

\ store 'n' on stack to any cell in ARRAY
CODE ]Q!  ( n index --)
       TOS  1 SLA,               \ shift TOS 1 bit left (mult. By 2)
      *SP+ Q (TOS) MOV,          \ POP 2nd stack item into address Q (TOS)
       TOS POP,                  \ refill TOS register
       NEXT,
ENDCODE

\ clear any cell in ARRAY
CODE ]Q0!  ( index --)
       TOS  1 SLA,               \ shift TOS 1 bit left (mult. By 2)
       Q (TOS) CLR,              \ Clear Q (TOS)
       TOS POP,                  \ refill TOS register
       NEXT,
ENDCODE

: FILL-Q      SIZE 0 DO  I  I ]Q!  LOOP ;
: SEE-Q       SIZE 0 DO     I ]Q@ . LOOP ;
: EMPTY-Q     SIZE 0 DO     I ]Q0!  LOOP ;

post-50750-0-31450700-1527434248.jpg

Edited by TheBF
Link to comment
Share on other sites

Adding LINPUT to CAMEL99 Forth

 

​As I write my instruction manual designed to assist the BASIC programmer who wants to try CAMEL99 Forth I got to the ANS Forth 94 file words. I know that new programmers sometimes struggle with using TI-BASIC's numerous file options so when I started explaining READ-FILE which takes 3 input parameters and returns 3 output parameters I wondered "What would it take to add LINPUT to the system?"

 

​Turns out not too much and I think it will be a nicer interface for a BASIC programmer to use.

 

​Here is the code that creates LINPUT to read a file record into a counted string.

: LINPUT  ( $ handle -- )
            >R                            \ push handle onto return stack 
            DUP CHAR+   ( -- $ $+1 )      \ skip past the string's length byte
            R> SELECT                     \ pop handle, select PAB for this file
            2 FILEOP ?FILERR              \ read operation, test error#
            [PAB FBUFF] V@  SWAP [PAB RECLEN] VC@  VREAD   \ VDP data -> $
            [PAB CHARS] VC@ SWAP C!       \ update string length
;

And a little DEMO file that shows how to use LINPUT went into the manual as well.

Turns out LINPUT is a little faster than READ-FILE because it does not try to do so much.

\ Print the contents of a DV80 file
INCLUDE DSK1.ANSFILES.F    \ ANS Forth file words
INCLUDE DSK1.LINPUT.F

DECIMAL
VARIABLE #1                 \ this variable will hold the file handle
VARIABLE A$ 80 ALLOT        \ variable with 80 bytes of space

: PRINT$  ( $ -- ) CR COUNT TYPE ; 

: SEEFILE ( addr len -- )   \ Usage: S" DSK1.MYFILE" SEEFILE
       DISPLAY 80 VARI SEQUENTIAL R/O OPEN-FILE ?FILERR #1 !
       BEGIN
         A$ #1 @ LINPUT 
         A$ PRINT$
       EOF UNTIL
       #1 @ CLOSE-FILE ?FILERR
;

Edited by TheBF
  • Like 2
Link to comment
Share on other sites

I gotta stop playing with this thing! :)

 

​Sound List Assembler in Forth

​Now that I have a sound list player I wanted a better way to create sound lists. The Forth way of doing this is to create some new words that do the job, a meta-language as it is sometimes called.

 

I had created a set of words to create sound bytes called HZ DB and NOISE to control the 9919 chip.

There are also output selector words called GEN1, GEN2, GEN3, GEN4

I already had a VDP memory manager in the library so I made some different versions of HZ, DB, and NOISE, that put the bytes into VDP RAM sequentially rather than sending them to the chip.

 

I created a way to count the VDP bytes that were used as they went into memory and fill in a count byte at the beginning of the string as required by the sound list format.

These are called [[ and ]]

 

Timing is put into the list by taking a millisecond value and dividing by 16 and compiling that number at the end of the string. The word is called MS,

 

Then all we need is a way to give sound lists a name in the Forth dictionary and what you get is a simple way to make a sound list.

 

For an example I took the PARSEC Explode sound list and translated it to SOUND assembler code.

Here is the way it looked before, where I made some Forth words to emulate Assembler byte data, but for VDP RAM.

HEX
VCREATE EXPLODE
       VBYTE 7,9F,BF,DF,E7,F0,C0,07,5
       VBYTE 1,F1,6
       VBYTE 1,F2,7
       VBYTE 1,F3,8
       VBYTE 1,F4,9
       VBYTE 1,F5,10
       VBYTE 1,F6,11
       VBYTE 1,F7,12
       VBYTE 1,F8,13
       VBYTE 1,F9,14
       VBYTE 1,FA,15
       VBYTE 1,FB,16
       VBYTE 1,FC,17
       VBYTE 1,FD,18
       VBYTE 1,FE,30
       VBYTE 1,FF,0
/VEND

And here is Sound Assembler that generates the same data

DECIMAL
SOUND: EXPLODE2            \ GEN3 controls Noise Generator Frequency
   [[ GEN1 MUTE, GEN2 MUTE, GEN3 MUTE, 
​      7 NOISE, 0 DB,
      GEN3 999 HZ, ]] 80 MS, \ Parsec used "7". Sounds same as 999 Hz. ??
   GEN4                      \ control noise generator volume
   [[  -2 DB, ]]  96 MS,
   [[  -4 DB, ]] 112 MS,
   [[  -6 DB, ]] 128 MS,
   [[  -8 DB, ]] 144 MS,
   [[ -10 DB, ]] 256 MS,
   [[ -12 DB, ]] 272 MS,
   [[ -14 DB, ]] 288 MS,
   [[ -16 DB, ]] 304 MS,
   [[ -18 DB, ]] 320 MS,
   [[ -20 DB, ]] 336 MS,
   [[ -22 DB, ]] 352 MS,
   [[ -24 DB, ]] 368 MS,
   [[ -26 DB, ]] 384 MS,
   [[ -28 DB, ]] 768 MS,
   [[ -30 DB, ]]   0 MS,
;SOUND

I kind of like sound lists now.

 

B

 

For those who might be curious here is the code. I have to update GITHUB. All of this is not there yet.

 

 

\ TI sound list assembler.
\ Assembles TI sound lists in VDP RAM that are
\ compatible with VDP Background sound player  
\ (VDPBGSND.F)

INCLUDE DSK1.TOOLS.F
INCLUDE DSK1.VDPBGSND.F
INCLUDE DSK1.SOUND.F

\ sound byte "assembler" commands compile values for the
\ currently selected generator. (GEN1 GEN2 GEN3 GEN4)
DECIMAL
: HZ,     ( f -- )     (HZ) SPLIT VC, VC,   ;
: DB,     ( level -- ) (DB) VC,  ;
: MUTE,   ( -- )       -30 DB,   ;
: MS,     ( n -- )     4 RSHIFT VC, ;  \ ms/16 = 1/60

\ turn all sound off
HEX
: SILENT, ( -- )  9F VC,  BF VC,  DF VC,  FF VC, ;

\ noise channel selects generator 4 by default
: NOISE,  ( n -- )  0F AND  GEN4 OSC @ OR  VC,  ;

DECIMAL
: SOUND: ( <text> -- ) VCREATE  !CSP ;

\ stack starting Vaddress & make space for string length
: [[     ( -- vaddr)   VHERE 0 VC, ;

\ back-fill string length into vaddr
: ]]     ( vaddr -- )  VHERE OVER - 1- SWAP VC! ;

\ mark end of sound list, check for clean stack
: ;SOUND ( -- )  /VEND ?CSP ;

 

 

Edited by TheBF
  • Like 1
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...