Jump to content
IGNORED

Sound list player in Forth. Audition tool for sounds?


TheBF

Recommended Posts

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

: DURATION@ ( snd$ -- ms) COUNT + C@ ; \ get time byte at end of string

: PLAY$ ( sound_string -- ) \ play 1 sound string
       DUP DURATION@ >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
       ?DO
          PAUSE            \ give time to other tasks
          I C@ SND!        \ feed each byte to sound chip
       LOOP
       ( 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
       BEGIN
         DUP C@
       WHILE               \ while the length is not 0
         DUP PLAY$         \ play a single string
         NEXTSND           \ advance to the next sound string
       REPEAT
       SILENT
       DROP ;              \ mom said always clean up after yourself

\ compiler addition to compile bytes into memory
: BYTES ( -- )
         BEGIN
            BL WORD COUNT $BUF PLACE
            $BUF C@       \ 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

HEX
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
/END

CREATE SMACK
        BYTES 4 F2 CC 01 E7 1
        BYTES 2 CC 03 1
        BYTES 2 CC 05 1
/END

CREATE PACMAN
        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
/END

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
/END

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
/END

: SHOTS  ( n -- )  0 ?DO  FIRESND PLAY  LOOP  EXPLODE PLAY ;

 

 

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

  • 2 months later...

So in the first post we saw how to make a Ti-99 sound list player in Forth .

 

In next few posts I will show you a few different ways that you could do this in Forth.

Forth is programmed by making new words to tame the problem.

 

One way to talk about sound is to use scientific words like Herz (Hz) and decibels (dB)

 

The following code takes all the problems of talking to the TMS9919 chip gives you

a handful of new words to make it easy.

 

After compiling this code into the system you could make the famous TI sounds like this:

: BEEP  ( -- ) GEN1  1398 Hz  -4 dB  170 MS  MUTE ;
: HONK  ( -- ) GEN1   218 Hz   0 dB  170 MS  MUTE ;

Does this code need any explanation? Not too much.

So although Forth can look very strange at the low levels, because after all it is not much higher than Assembly language at its core,

with very little effort you can make into something just right for the problem at hand.

 

Here is the code to accomplish this:

 

 

 

\ TMS9919 SOUND CHIP DRIVER and CONTROL LEXICON     Jan 2017 BJF

\ TMS9919 is a memory mapped device on the TI-99 @ >8400
\ SND! is in the CAMEL99 Kernel as  : SND!     8400 C! ;

\ frequency code must be ORed with these numbers to create a sound
HEX
  8000 CONSTANT OSC1      A000 CONSTANT OSC2
  C000 CONSTANT OSC3      E000 CONSTANT NOISE

\ Attenuation values are ORed with these values to change volume (0= max, 15 = off)
    90 CONSTANT ATT1         B0 CONSTANT ATT2
    D0 CONSTANT ATT3         F0 CONSTANT ATT4  ( noise volume adjust)

\ There are no 32 bit numbers in the CAMEL99 compiler
\ so we create a constant manually, as 2 literal numbers.
DECIMAL
: f(clk) ( -- d) 46324 1  ;   \ this is 111,860 as 32 bit int.

\ >FCODE re-arranges freq. value nibbles (4bits) for the TMS9919
HEX
: >FCODE   ( 0abc -- 0cab)  \ ASM would make this much faster
           DUP 0F AND SWAP      ( -- 000c 0abc)
           4 RSHIFT             ( -- 000c 00ab)
           SWAP ><  ( SWPB)     ( -- 00ab 0c00)
           + ;

\ we set the "ACTIVE CHANNEL" with these variables
 VARIABLE OSC               \ holds the active OSC value
 VARIABLE ATT               \ holds the active ATTENUATOR value

\ convert freq. to 9919 chip code
: >9919 ( freq -- fcode ) f(clk) ROT UM/MOD NIP >FCODE ;

 \ **for testing**  print sound data to screen
\ : SND!  ( c -- )  CR ." >"  HEX U. ;

\ Set the sound "GENerator that is active.
: GEN! ( osc att -- )  ATT !  OSC ! ;

\ ==================================================================
\ S C I E N T I F I C   S O U N D   C O N T R O L   L E X I C O N

\ sound generator selectors
: GEN1    ( -- )  OSC1  ATT1  GEN! ;
: GEN2    ( -- )  OSC2  ATT2  GEN! ;
: GEN3    ( -- )  OSC3  ATT3  GEN! ;
: GEN4    ( -- )  NOISE ATT4  GEN! ;

: (HZ)    ( f -- c c)   >9919  OSC @ OR  SPLIT ;   \ convert freq. add OSC
: (DB)    ( level -- c)  ABS 2/  0F MIN ATT @ OR ; \ convert DB to attenuation

: HZ      ( f -- ) (HZ) SND!  SND! ;
: DB      ( level -- ) (DB)  SND! ;   \ write level to 9919 chip. Usage: -6 DB
: MUTE    ( -- )  -30 DB ;
: SILENT  ( --)  9F SND!  BF SND!  DF SND!  FF SND! ;

 

 

 

In a future post we will talk about controlling the sound chip with musical words rather than scientific words.

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

You caught me again!

 

I was cleaning this file up and didn't notice that I renamed that word like that. I will fix it.

 

For the Forth curious, Forth has what is called in academic circles as a "hyper-static" name space.

Which means you can create a function, variable or whatever with the same name as something that

was define before and still use the previous definition while making the new one.

 

It's not recommended but it can be done! (as you see in my code)

 

B

 

*EDIT* It's gone now.

 

Thanks Lee

Edited by TheBF
Link to comment
Share on other sites

Having control the of the sound chip we can now move forward to create a Musical Lexicon.

 

This is some code that I wrote for the IBM PC internal speaker sound. With the SOUND words

we created before and the Forth word MS (milli-second delay) it was simple to make this

work on the TI-99.

 

The concept here is that musical notes need a duration and an off-time to separate each note.

We use music math and terminology here by setting beats per minute (BPM). This sets how long a Whole-note will last in mS.

A whole note fills an entire "measure" or Bar of music.

 

We also set a FEEL for the music which is fed to the EXPRESSION calculator and that sets the ON_TIME and OFF_TIME variables.

With that all we need to do to play a note is :

: PLAY      ( fcode -- )
             ?BREAK                     \ halts if function 4 pressed
             OSC @ OR  SPLIT SND! SND!    \ add osc# & write the freq code to the chip
             0 ATTENUATE  ON_TIME @ MS    \ open the channel
            15 ATTENUATE OFF_TIME @ MS  ; \ close the channel

You can see we check for a BREAK, then send the freq-code (fcode) to the correct Oscillator, open the channel for the ON_TIME.

Then we close the channel and wait for an OFF_TIME.

 

You may notice I don't use the words HZ and DB for control. This is because those words have overhead for human use.

We pre-compute the FCODE for notes in this system because the TI-99 is not a speed demon. :-)

 

The word ATTENUATE is like DB but uses the native values of 0 .. 15 rather than making it look nice ( 0 .. -30 DB)

But they are doing the same things to the sound chip, just faster.

 

Paste the SOUND.FTH file into your emulator first then paste in MUSIC.FTH (the spoiler code here).

 

Then type in the examples:

 

TWINKLE

BAGPIPES

VARIATION

 

If you know a little music it's easy to program your own music.

 

Note names go first and the system uses that note length until you change it.

 

 

 

\ music lexicon to control TMS9919

\ INCLUDE SOUND.FTH

DECIMAL
\ duration control variables and values
VARIABLE ON_TIME
VARIABLE OFF_TIME
VARIABLE FEEL        \ controls the on/off time ratio
VARIABLE TEMPO

: TIMEBASE  ( -- d) 43392 3 ;           \ = 43392 3  ( double number)

: ATTENUATE ( n -- ) ATT @ OR SND! ;    \ faster than using DB

: BPM>MS    ( bpm -- ms)
            TIMEBASE ROT UM/MOD NIP ;   \ convert beats per minute to mS

: WHOLENOTE ( -- mS ) TEMPO @ BPM>MS ;  \ using tempo set the bpm for 1 whole note

: PLAY      ( fcode -- )
             ?BREAK                     \ halts if function 4 pressed
             OSC @ OR  SPLIT SND! SND!
             0 ATTENUATE  ON_TIME @ MS
            15 ATTENUATE OFF_TIME @ MS  ;

 \ Music needs notes to start and end in different ways.
 \ this word adjust the on:off ratio using n
: EXPRESSION ( note_dur n --)
           OVER SWAP -  TUCK -   ( -- on-ms off-ms )
           1 MAX OFF_TIME !  
           1 MAX ON_TIME ! ;                       \ store times in variables

: NOTE      ( -- MS ) ON_TIME @ OFF_TIME @ + ;           \ return full duration of current note

: DURATION!    ( MS -- )  FEEL @ EXPRESSION ;

: 5%       ( -- ) 5 / ;
: 10%      ( n -- n ) 10 / ;
: 20% ( n -- n ) 20 / ;
: 50%       ( N -- N/2)    2/ ;
: %         ( N N2  -- N%) 100 */ ;       \ calculate n2% of n
: 50%+      ( N -- N+50%)  DUP 50% + ;    \ dotted notes have 50% more time

: BPM       ( BPM -- )  \ set tempo in beats per minute
            TEMPO !  
            WHOLENOTE DURATION! ;

: NORMAL      NOTE 3 % FEEL ! ;
: LEGATO      NOTE 1 % FEEL ! ;     \ notes run together
: STACCATTO   NOTE 10 % FEEL ! ;  \ short notes
: MARCATO     NOTE  5 % FEEL ! ;  \ march feel

: RIT.     NOTE DUP 20% + DURATION! ;

: 1/1      WHOLENOTE      DURATION! ;
: 1/2      WHOLENOTE 50%  DURATION! ;
: 1/2.     1/2  NOTE 50%+ DURATION! ;
: 1/4      1/2  NOTE 50%  DURATION! ;
: 1/4.     1/4  NOTE 50%+ DURATION! ;
: 1/8      1/4  NOTE 50%  DURATION! ;
: 1/8.     1/8  NOTE 50%+ DURATION! ;
: 1/16     1/8  NOTE 50%  DURATION! ;
: 1/32     1/16 NOTE 50%  DURATION! ;
: REST     NOTE MS ;

\ note object creator
: NOTE:   ( freq -- )
           CREATE         \ compile time: create a name in the dictionary
                  >9919 , \ compile the 9919 code into the note

           DOES> @ PLAY ; \ run time:  fetch the number, play the note

\ FREQ  NATURAL    FREQ  ACCIDENTAL    EN-HARMONIC
\ -------------    ----------------   ----------------
  110 NOTE: A2
  131 NOTE: C3     139 NOTE: C#3       : DB3 C#3 ;
  147 NOTE: D3     156 NOTE: D#3       : Eb3 D#3 ;
  165 NOTE: E3
  175 NOTE: F3     185 NOTE: F#3       : Gb3 F#3 ;
  196 NOTE: G3     208 NOTE: G#3       : Ab3 G#3 ;
  220 NOTE: A3     233 NOTE: A#3       : Bb3 A#3 ;
  247 NOTE: B3
  262 NOTE: C4     277 NOTE: C#4       : Db4 C#4 ;
  294 NOTE: D4     311 NOTE: D#4       : Eb4 D#4 ;
  329 NOTE: E4
  349 NOTE: F4     370 NOTE: F#4       : Gb4 F#4 ;
  392 NOTE: G4     415 NOTE: G#4       : Ab4 G#4 ;
  440 NOTE: A4     466 NOTE: A#4       : Bb4 A#4 ;
  494 NOTE: B4
  523 NOTE: C5     554 NOTE: C#5       : Db5 C#5 ;
  587 NOTE: D5     622 NOTE: D#5       : Eb5 D#5 ;
  659 NOTE: E5
  698 NOTE: F5     740 NOTE: F#5       : Gb5 F#5 ;
  784 NOTE: G5     831 NOTE: G#5       : Ab5 G#5 ;
  880 NOTE: A5     932 NOTE: A#5       : Bb5 A#5 ;
  988 NOTE: B5
 1047 NOTE: C6

: TUNER ( -- ) GEN3 440 HZ  0 DB ;
: TUNEROFF     GEN3 MUTE ;

: DMAJOR      122 BPM NORMAL
        GEN1  1/8  D3 E3  F#3 G3 A3 B3  C#4 1/2. D4
              1/4  REST
              1/8  D4 C#4 B3  A3 G3 F#3 E3  1/1  D3 ;

: CHROMATIC   100 BPM  NORMAL
        GEN2  1/8 C3 C#3 D3 D#3 1/16 E3 F3 F#3 G3 G#3 A3 A#3 B3
              1/8 C4 C#4 D4 D#4 1/16 E4 F4 F#4 G4 G#4 A4 A#4 B4
        NORMAL 1/4 C5 ;

: TWINKLE
     NORMAL
     GEN1   1/8  A3 A3 E4 E4 F#4 F#4   1/4 E4
            1/8  D4 D4 C#4 C#4  B3 B3  1/4 A3
            STACCATTO
            1/8  E4 E4 D4 D4 C#4 C#4   1/4 B3
            NORMAL
            1/8  E4 E4 D4 D4 C#4 C#4   1/4 B3
            MARCATO
            1/8  A3 A3 E4 E4 F#4 F#4   1/4 E4
            1/8  RIT. D4 D4  RIT. C#4  C#4
                 RIT. B3  RIT. B3   RIT. 1/2. A3 ;

: BAGPIPES
          GEN2 165 HZ -6 DB
          GEN3 110 HZ -9 DB
          NOTE MS
          TWINKLE
          SILENT ;


: VARIATION
   STACCATTO
   GEN1     1/8  A3  C#4  B3  A3  E4  A3  C#4 E4
                 F#4 A4   G#4 F#4 E4  A3  C#4 E4
                 D4  F#4  E4  D4  C#4 E4  D4 C#4
                 B3  A4   B4  F#4 E4  F#4 E4 F#4

                 C#4 E4   C#4 E4  D4  E4  D4  E4
                 C#4 E4   C#4 E4  D4  B3  D4  E4
                 C#4 E4   C#4 E4  D4  E4  D4  E4
                 C#4 E4   C#4 E4  D4  B3  D4  E4

                 A3  C#4  B3  A3  E4  A3  C#4 E4
                 F#4 A4   G#4 F#4 E4  A3  C#4 E4
                 D4  F#4  E4  D4  C#4 E4  D4 C#4
                 RIT. B3  E4  RIT.  F#4 G#4
                 RIT. A4  G#4 RIT. 1/2 A4 ;

92 BPM
NORMAL

 

 

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

This looks good. I am going to have to work through all this early next month.

 

...lee

 

I don't want to stress you out. I am just having fun making this system do tricks.

 

Now I need to put my thinking cap on and get DSRLINK working... it's not simple.

 

:-)

Link to comment
Share on other sites

 

I don't want to stress you out. I am just having fun making this system do tricks.

 

Now I need to put my thinking cap on and get DSRLINK working... it's not simple.

 

:-)

 

No worries.

 

Have you looked at how I implemented DSRLNK in fbForth? I used MG’s (Millers Graphics) version, which invokes the GPL DSRLNK in the console. @Willsy also used the MG version in TurboForth. If you don’t care about tape or GROM DSRs, the TI Forth version will do. It is essentially a duplicate of the E/A cartridge’s DSRLNK.

 

...lee

Link to comment
Share on other sites

 

No worries.

 

Have you looked at how I implemented DSRLNK in fbForth? I used MG’s (Millers Graphics) version, which invokes the GPL DSRLNK in the console. @Willsy also used the MG version in TurboForth. If you don’t care about tape or GROM DSRs, the TI Forth version will do. It is essentially a duplicate of the E/A cartridge’s DSRLNK.

 

...lee

 

 

I looked at it but will it work for EA5 Binary format programs?

Link to comment
Share on other sites

 

 

I looked at it but will it work for EA5 Binary format programs?

 

Certainly—but DSRLNK is not a program loader. You would need to manage that additional feature yourself with the information available in the file(s) to be loaded. It is all there: address to load, number of bytes to load and where to branch to start the program. You would need to use I/O opcode 5 (LOAD) and a VRAM buffer large enough to store the largest file. You could also load the files in chunks. TI Forth loads the FORTHSAVE binary in two chunks—one for high RAM and one for low RAM. FORTHSAVE is not an E/A5 file, however—just a binary memory image. Of course, then you need to parse the loaded binary to get the load address, number of bytes to load there and whether there is another file to load. I think the first six bytes has this information, though I don't remember the order. I believe the start address is at the end of the last file.

 

LOAD, by the way, does not require OPEN before it nor CLOSE after it. Sorry if most of this is old news.

 

...lee

  • Like 1
Link to comment
Share on other sites

Ok thanks for the pointers. I will take another run at it.

 

I want to make use of Forth functions for the mundane stuff like file name massaging and such.

I have some cool little code routines called SKIP and SCAN that let me fly through strings.

So I want to set things up with Forth to reuse code as much as possible and then kick off the OS

to do its magic and come back to me.

 

We shall see if I can figure it out.

 

B

Link to comment
Share on other sites

  • 2 weeks later...

Looking back at post #1, there is a bit that needs to be changed for it to work in fbForth 2.0. fbForth 2.0 already has processing of sound tables built into its ISR. The part of the code required for SHOTS can be reduced to the code in the following spoiler:

 

 

 

HEX 
: EXPLODE   ( from Parsec )
   DATA[
        079F BFDF E7F0 C007 0501 F106 01F2 0701 F308 01F4
        0901 F510 01F6 1101 F712 01F8 1301 F914 01FA 1501
        FB16 01FC 1701 FD18 01FE 3001 FF00
   ]DATA  DROP  ;
: FIRESND   ( from Parsec )
   DATA[
      049A BFDF FF01 0380 0A98 0103 800C 9601 0380 1094 0103
      8014 9201 0380 1890 0103 801C 9201 0380 2094 0103 8028
      9601 0380 3098 0103 8038 9A01 0380 3E9C 0101 9F00 
   ]DATA  DROP  ;
 
: SHOTS  ( n -- )  0 DO  FIRESND 0 PLAY  LOOP  EXPLODE 0 PLAY ; 

 

 

The other sound tables can be managed similarly as EXPLODE and FIRESND with, of course, the inconvenience of converting byte arrays to word arrays for DATA[ ... ]DATA . <address> 0 PLAY will push sound table addresses to the sound stack if a sound table is already playing. The fbForth 2.0 ISR processing has the advantage of not holding up program execution waiting for a sound table to finish. I do like BYTES for sound tables because they are much easier to read that way! In fact, when I decided on DATA[ ... ]DATA , I had debated on having a similar construct for byte data.
All that said, it will still be a useful exercise to convert CAMEL99 to fbForth 2.0 code. Here are a few things that will need to be done:
  • For BYTES , I will need to know the function of $BUF and PLACE , (I probably forgot—remember I'm old!).
  • PLAY will need to be called something else.
  • SND! should be OK.
  • LSHIFT is SLA .
  • CREATE works differently in fbForth 2.0 than CAMEL99 or TurboForth. It is intended for use within other defining words ( : VARIABLE CONSTANT <BUILDS CODE: ASM: ). It creates a header just like the other two Forths, but the code field points to the parameter field instead of code that leaves the parameter field address on the stack. Its smudge bit is also set to prevent the word being defined from being found until its definition is finished. We would need to define a similar creation word with a different name: : SPAWN <BUILDS DOES> ; . This is exactly the definition of VARIABLE but for the comma between <BUILDS and DOES> , which requires a number on the stack before defining a new variable.
  • PAUSE is a very different word in fbForth as well as TI Forth. It is used to detect whether a user has tapped the Any Key to pause an action (usually display) or FCTN+4 to break out of the routine. Once paused, the user can resume with the Any Key or break out with FCTN+4. When it finally finishes, it leaves a flag on the stack, 0 (false) for no key or the second tap of the Any Key and 1 (true) for FCTN+4. Anyway, fbForth already has a no-op word: NOP
That is all I can think of at the moment. I will work on it.
...lee
Link to comment
Share on other sites

 

Looking back at post #1, there is a bit that needs to be changed for it to work in fbForth 2.0. fbForth 2.0 already has processing of sound tables built into its ISR. The part of the code required for SHOTS can be reduced to the code in the following spoiler:

 

 

 

HEX 
: EXPLODE   ( from Parsec )
   DATA[
        079F BFDF E7F0 C007 0501 F106 01F2 0701 F308 01F4
        0901 F510 01F6 1101 F712 01F8 1301 F914 01FA 1501
        FB16 01FC 1701 FD18 01FE 3001 FF00
   ]DATA  DROP  ;
: FIRESND   ( from Parsec )
   DATA[
      049A BFDF FF01 0380 0A98 0103 800C 9601 0380 1094 0103
      8014 9201 0380 1890 0103 801C 9201 0380 2094 0103 8028
      9601 0380 3098 0103 8038 9A01 0380 3E9C 0101 9F00 
   ]DATA  DROP  ;
 
: SHOTS  ( n -- )  0 DO  FIRESND 0 PLAY  LOOP  EXPLODE 0 PLAY ; 

 

 

The other sound tables can be managed similarly as EXPLODE and FIRESND with, of course, the inconvenience of converting byte arrays to word arrays for DATA[ ... ]DATA . <address> 0 PLAY will push sound table addresses to the sound stack if a sound table is already playing. The fbForth 2.0 ISR processing has the advantage of not holding up program execution waiting for a sound table to finish. I do like BYTES for sound tables because they are much easier to read that way! In fact, when I decided on DATA[ ... ]DATA , I had debated on having a similar construct for byte data.
All that said, it will still be a useful exercise to convert CAMEL99 to fbForth 2.0 code. Here are a few things that will need to be done:
  • For BYTES , I will need to know the function of $BUF and PLACE , (I probably forgot—remember I'm old!).
  • PLAY will need to be called something else.
  • SND! should be OK.
  • LSHIFT is SLA .
  • CREATE works differently in fbForth 2.0 than CAMEL99 or TurboForth. It is intended for use within other defining words ( : VARIABLE CONSTANT <BUILDS CODE: ASM: ).It creates a header just like the other two Forths, but the code field points to the parameter field instead of code that leaves the parameter field address on the stack. Its smudge bit is also set to prevent the word being defined from being found until its definition is finished. We would need to define a similar creation word with a different name: : SPAWN <BUILDS DOES> ; . This is exactly the definition of VARIABLE but for the comma between <BUILDS and DOES> , which requires a number on the stack before defining a new variable.
  • PAUSE is a very different word in fbForth as well as TI Forth. It is used to detect whether a user has tapped the Any Key to pause an action (usually display) or FCTN+4 to break out of the routine. Once paused, the user can resume with the Any Key or break out with FCTN+4. When it finally finishes, it leaves a flag on the stack, 0 (false) for no key or the second tap of the Any Key and 1 (true) for FCTN+4. Anyway, fbForth already has a no-op word: NOP
That is all I can think of at the moment. I will work on it.
...lee

 

 

Sounds good Lee,

 

I will get you the code for the words you are looking for. Yes I quite like the BYTES format for these sound strings and I have since changed it to BYTE just to be more like TI Assembler. This would mean then you could explore making sound lists interactively from the Forth console and then take the code and put it in an ASM program by just adding the '>" and commas.

 

I am also considering making a sound list compiler. This would let you use language like Hz , dB and mS to describe you sounds but the result will create a sound list data structure. That might be cool for FB Forth users to have in the tool box.

Link to comment
Share on other sites

PLACE is a word in GForth and I think was first used by Wil Baden (RIP) who's real name was Neil Baud. He has a great legacy of Forth utilities for managing text.

It's all ANS 94 Forth, but still full of great ideas.

So PLACE is not a "standard" word, but very handy to PLACE a stack string (addr len --) into a buffer as a counted string.

 

Here is the CAMEL Forth version by Brad Rodriguez. I found you can replace the MOVE word with CMOVE for most circumstances, because we seldom have overlapping string buffers in Forth, however the use of MOVE is technically correct.

: MOVE        ( src dst n -- )  \ version for 1 address unit = 1 char
              >R
              2DUP SWAP DUP
              R@ +                \ -- ... dst src src+n
              WITHIN
              IF    R> CMOVE>         \ src <= dst < src+n
              ELSE  R> CMOVE  THEN ;  \ otherwise

\ CAMEL Forth calls this ">COUNTED"
: PLACE       ( src n dst -- ) 2DUP C! CHAR+ SWAP MOVE ;

$BUF in my system was a temp buffer that I was using all the time. I have since retired it and will create buffers as needed.

You could use PAD I think depending on how PAD is implemented in FB Forth.

 

 

(albeit slower)

Link to comment
Share on other sites

PLACE is a word in GForth and I think was first used by Wil Baden (RIP) who's real name was Neil Baud. He has a great legacy of Forth utilities for managing text.

It's all ANS 94 Forth, but still full of great ideas.

So PLACE is not a "standard" word, but very handy to PLACE a stack string (addr len --) into a buffer as a counted string.

 

Here is the CAMEL Forth version by Brad Rodriguez. I found you can replace the MOVE word with CMOVE for most circumstances, because we seldom have overlapping string buffers in Forth, however the use of MOVE is technically correct.

: MOVE        ( src dst n -- )  \ version for 1 address unit = 1 char
              >R
              2DUP SWAP DUP
              R@ +                \ -- ... dst src src+n
              WITHIN
              IF    R> CMOVE>         \ src <= dst < src+n
              ELSE  R> CMOVE  THEN ;  \ otherwise

\ CAMEL Forth calls this ">COUNTED"
: PLACE       ( src n dst -- ) 2DUP C! CHAR+ SWAP MOVE ;

$BUF in my system was a temp buffer that I was using all the time. I have since retired it and will create buffers as needed.

You could use PAD I think depending on how PAD is implemented in FB Forth.

 

(albeit slower)

 

When I saw the above code, I saw where I could cut the ALC for MOVE short by 12 bytes by popping the count, shifting it left 1 bit (doubling it) and jumping into the ALC for CMOVE . This doubles the number of times through the loop, but it works easily enough. I don't know whether the 12 bytes saved is worth the extra time through the copy loop, however.

 

The-e-e-n I got creative when I saw CMOVE> and realized that, with 32 more bytes (?) for a net loss of 20 bytes, I could make CMOVE overlap safe! That is when all hell broke loose!! :evil: :-o That is when I remembered that FILL and ERASE depend on the unsafe overlap behavior of CMOVE to work. :dunce: That is when I remembered I had gone through this exercise before and it is probably documented somewhere in the fbForth development thread—did I tell you I am old? That was a little discouraging. :( Maybe I should add CMOVE> (I think I have a little room left) and make MOVE (always copies words, i.e., 2 bytes, each time through the loop) overlap safe (I think I am safe there). I'm rambling. I will stop talking now...

 

...lee

  • Like 1
Link to comment
Share on other sites

LOL. I have gone done some of those rabbit holes too. I limit my spinning on these things if they get too long.

 

I wrote a version of MOVE that does 16 bit cells and so is 2X faster than CMOVE, but I could not make it work with PLACE.

It was 1 byte off probably due to 9900 byte order, but I didn't waste much time on it. I am on my laptop at the moment and the code is on my desktop machine I will get it up here shortly and also some new words for controlling sound without sound lists for anyone who wants to try it that way.

Link to comment
Share on other sites

LOL. I have gone done some of those rabbit holes too. I limit my spinning on these things if they get too long.

 

I wrote a version of MOVE that does 16 bit cells and so is 2X faster than CMOVE, but I could not make it work with PLACE.

It was 1 byte off probably due to 9900 byte order, but I didn't waste much time on it. I am on my laptop at the moment and the code is on my desktop machine I will get it up here shortly and also some new words for controlling sound without sound lists for anyone who wants to try it that way.

 

I am still not sure what I will do re MOVE , CMOVE and CMOVE> , but, as far as I can tell, from figForth to ANS proposed Forth-2016, MOVE always copies cells, i.e., 16-bit, 2-byte words. To make an overlap-safe, byte move, I would call it something else— BMOVE or CMOVESF , perhaps?

 

...lee

Link to comment
Share on other sites

Yes that is my observation too. And I'd like have a way to MOVE data 2 bytes at a time in the kernel.

 

I been struggling with naming as well. Perhaps for CAMEL Forth I will move it closer to 2012 standard and add MOVE (16 bit)

I have already done some of that anyway.

I can simply put the "smart" code into PLACE itself or remove it completely and include a warning about overlapping strings in the docs. I lean towards simplicity versus overly "smart" words.

 

But I still need to figure out how make MOVE work inside PLACE rather than using CMOVE. The TI machine still baffles me at times.

Link to comment
Share on other sites

I have had some fun with minimalist sound chip control words that don't need the ISR to work.

 

*These words work on FB-Forth, Turbo Forth and CAMEL99 Forth.*

 

It all starts with a word to write a byte to the 9919 chip. I call it SND!. (sound store in Forth speak)

HEX
: SND!    ( c -- ) 8400 C! ;

Then you need a little delay. CAMEL99 uses the 9901 chips timer to make MS, a delay in milliseconds.

It is simple to emulate for another TI Forth system.

: MS      ( n -- )  0 DO   8 0 DO LOOP   LOOP ;

Controlling the noise channel is the simplest. The word NOISE takes 1 parameter.

 

Thierry Nouspikel shows it this way:

1 1 1 0 0 w r r 
  >E      | | |
          | 0 0 : 6991 Hz 
          | 0 1 : 3496 Hz 
          | 1 0 : 1748 Hz 
          | 1 1 : freq of generator 3 
          | 
          0 : Periodic noise 
          1 : White noise

The Forth word to do this:

: NOISE   ( n -- ) E0 OR SND! ; \ n selects the noise type

We just take the parameter n, OR it with >E0 and send it off to the chip.

In real terms it means this:

  • 0, 1 or 2 give you 1 of the three frequencies listed as very high harmonic content waveforms. (not very useful)
  • 4 , 5 or 6 gives you 3 flavours of noise.
  • 7 as a parameter lets you control this oscillator with the frequency of channel 3
    (this mode is fun cuz it let's get real BASS notes out of the chip. More later ...)

 

If it's easier for you to work in Binary, you can program it like this.

2 BASE ! ( switch to BASE 2)
000 NOISE
001 NOISE
010 NOISE
011 NOISE
100 NOISE
101 NOISE
110 NOISE
111 NOISE

Once the noise chip is given the parameter you need to set the attenuator to control the volume of the sound.

In DECIMAL arithmetic we have 0 to 14 as the volume range, Level 15 is OFF.

0 NOISE-DB has the least attenuation so that is full volume.

 

Our word for controlling the attenutator is NOISE-DB and with it we can make a quick NOISE-OFF word to kill the noise.

: NOISE-DB   ( db --) F MIN F0 OR SND! ;
: NOISE-OFF  ( -- )   F NOISE-DB ;

But to really make synthetic sounds you need control of the "sound envelope"; how fast the sound comes on and goes off.

So we created NOISE-UP & NOISE-DOWN. They each take a delay number to control the rise time and decay time.

(They could be made more interesting by take the start and end volume as parameters as well)

HEX
: NOISE-UP    ( speed  -- ) 2  F  DO  I NOISE-DB  DUP MS   -1 +LOOP DROP ;
: NOISE-DOWN ( speed -- )   F  2  DO  I NOISE-DB  DUP MS     LOOP DROP NOISE-OFF ;

With those you can make a nice SWOOSH sound this simply.

: SWOOSH    ( -- )
            NOISE-OFF
            5 NOISE
            8 NOISE-UP
            20 NOISE-DOWN ;
  • Like 2
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...