* README -*- outline -*-

* Introduction

The package GFM Gforth Music provides an implementation of Bill
Schottstaedt's CLM, written in Forth, especially in Gforth 0.6.x.
Gforth version 0.6.2 comes along with a passable FFI so that it's
possible to integrate the shared sound library libsndlib.so which can
be found on ftp://ccrma-ftp.stanford.edu/pub/Lisp/sndlib.tar.gz.  If
you haven't a working libsndlib.so, GFM Gforth Music provides all of
the sndlib-generators written in Forth in fsndlib.fs, though they
consume about twice the time than the corresponding csndlib.fs
generators.

If libsndins.so is available in the ld-library-path fcomb, fm-violin,
jc-reverb, freeverb, and nrev are taken from there, otherwise a Forth
version is used.

A quick overview over generators, user functions and their usage can
be found in file gfm.fs after CLM-INIT up to the end of file.

* News (January 2005)

-- With a Gforth patch one can use CLM's src, convolve, granulate, and
   phase-vocoder generators.

-- String, Array, Vct, Hash, and other objects.

-- each/end-each and map/end-map for String, Array, Vct, and Hash
   objects.

-- Keyword arguments for nearly all MAKE-generators, the keywords are
   the same as CLM's.

-- An online information to get help on all generator functions and
   properties as well as on utility functions and objects.

-- fsndlib.fs and csndlib.fs have the same generators and functions.

-- Fontifying and indenting for Emacs users enhanced (at the end of
   this file).

* Installation

There are several needs for using this package.
 
If you want to use libsndlib.so, you have to install (before
configuring Gforth!) the ffcall libraries found at

ftp://ftp.santafe.edu/pub/gnu/ffcall-1.8.tar.gz (USA) 
ftp://ftp.ilog.fr/pub/Users/haible/gnu/ffcall-1.8.tar.gz (Europe) 

Gforth itself can be found at

ftp://ftp.complang.tuwien.ac.at/pub/forth/gforth/gforth-0.6.2.tar.gz

Gforth comes with a nice Forth tutorial!

Using csndlib.fs you need a working libsndlib.so library in your
ld-library-path.  Important: I compiled libsndlib.so with configure
option --with-float-samples and C-type Float as float (not double).
If you have other configurations, the whole csndlib.fs file may be
changed as well as the vct- and sound-data-definitions in gfm-defs.fs.

Using SRC, CONVOLVE, GRANULATE, or PHASE-VOCODER and csndlib.fs you
must install the patch described below (* Scr, Convolve, ...).

After installing ffcall, gforth and sndlib, unzip and untar this
package and a new directory will be created where the *.fs and *.gfm
files can be found.  You can test the executable *.gfm scripts
directly.  You may replace the value of the global variable
`*clm-player*' with `sndplay' in .gfmrc (see below):

	$" sndplay" to *clm-player*

You can install *.fs files in $(prefix)/share/gforth/site-forth:

    	make install (defaults to prefix=/usr/local)
or
	make install prefix=/usr/gnu

Using the audio definitions of fsndlib it's important to watch the
constants in the `=== Audio ===' part of fsndlib.fs.  They work here
but may need adjustments on your machine.  The program `audinfo' and
the header file `soundcard.h' may help.
 
It seems to be a good idea to me to increase the local stack buffer
(hardcoded in gforth-0.6.2/glocals.fs).  I increased it from 1000 to
100000 (gforth-0.6.2/glocals.fs, line 136).  Some instruments need a
big amount of local variables.  I circumvented it in clm-ins.fs by
using hashs, arrays or structs to fit in default local buffer size,
but that's not very readable.

If something goes wrong, please don't hesitate to ask me for help.  I
will try to do my best. (scholz-micha@gmx.de)

* Gforth's floating point numbers

Gforth has two stacks, a data stack which handles integers (and
pointers) and a float stack.  Integers are normal numbers like 1 10
-20 etc. and pointers are like make-oscil (returning a pointer of type
Oscil) or 10 make-vct.  These data can be stored on the data stack:

      make-oscil

brings the oscil object on top of stack.  In Forth context every
integer takes one cell (4 bytes on my machine).  A so called DOUBLE is
a long integer of two cells (8 bytes) and needs two stack entries of
the data stack:

      2.0

is NOT float 2.0 but (Forth) double 20 and takes two cells!  One can
see the value of double with `D.' in contrary to `.' for integer.

     20   . ==> 20
     2.0 d. ==> 20

To bring floats on the floating point stack we use:

     2e
     pi
     two-pi
     2.334e

Forth's double and float may lead to confusion but after a certain
time one get used with it...
     
* Src, Convolve, Granulate and Phase-Vocoder

** Patch needed with callbacks (csndlib.fs)

Before using callbacks with csndlib.fs you have to change function
engine_callback() in gforth-0.6.2/engine/main.c:

Original
========

void engine_callback(Xt* fcall, void * alist)
{
  clist = (va_alist)alist;
  engine(fcall, SP, RP, FP, LP);
}

Patched
=======

void engine_callback(Xt* fcall, void * alist)
{
  /* save global valiables */
  Cell *rp = RP;
  Cell *sp = SP;
  Float *fp = FP;
  Address lp = LP;

  clist = (va_alist)alist;
  engine(fcall, sp, rp, fp, lp);
  
  /* restore global variables */
  RP = rp;
  SP = sp;
  FP = fp;
  LP = lp;
}

Important: Don't allocate memory in callback functions if you need it
later outside CBs (i.e. NO make-vct)!  Provide allocated objects to
CBs (see PV-ANALYZE-CB in sndtest.gfm).  Test stack effects carefully
with Forth's utility functions F.S and .S within CBs.  After returning
from CBs the stacks are in the same state as before calling CBs,
CB-stack mistakes can't be seen outside CBs.

** What is a callback in Forth?

Callbacks are simply execution tokens or XTs.  The XT of function F+
is ' F+ (tick f-plus) outside functions and ['] F+ in function
definitions.  An XT can be called with EXECUTE.

1e 2e ' f+ execute f. ==> 3.

or even

1e 2e f+ f. ==> 3.

: foo ( r1 r2 -- r3 ) ['] f+ execute ;

1e 2e foo f. ==> 3.

Callbacks should be more flexible because often local variables are
needed.  A solution may be the NONAME-CREATE-DOES> construct.  An
alias for NONAME CREATE is LAMBDA-CREATE.

: make-simple-callback { vct -- xt; idx self -- r }
    lambda-create vct , latestxt
  does> { idx self -- r }
    idx self @ vct@
;

: bar ( -- )
    make-oscil { os }
    10 make-vct map  os oscil-0  end-map { v }
    v make-simple-callback { cb }
    10 0 do i cb execute f. loop
    v gen-free
    os gen-free
;

make-simple-callback stores the vct on a place in the dictionary where
it can be received in the DOES> part via `self @'.  The create-part is
terminated by the latestxt variable which returns the XT of the last
function.  In BAR the create-part returns the XT to the local variable
CB.  This local variable can be executed, in our case prepended with
an index which is needed by the DOES>-part.  The result of the last
loop is printing the contents of the vct.

Now a more useful example.  A LAMBDA-CREATEed XT is the :input
parameter for MAKE-SRC.  For us SRC takes the execute part.

: make-input-cb { rd -- xt; dir self -- r }
    lambda-create rd , latestxt
  does> { dir-ignored self -- r }
    self @ { rd }
    rd readin
;

: read-test-1 { f: start f: dur f: amp fname -- }
    :file fname make-readin { rd }
    rd make-input-cb { cb }
    :input cb make-src { sr }
    start dur run-instrument
        0e src amp f*
    end-run
    rd mus-close
    rd gen-free
    sr gen-free
;

0e 2e 0.5e $" oboe.snd" ' read-test-1 with-sound

The case above is very often needed so it exists a function
MAKE-READIN-CB which one can use.  It's the same definition like
make-input-cb.  MAKE-CONVOLVE, MAKE-GRANULATE as well as
MAKE-PHASE-VOCODER can also take a readin-generator as :input
parameter.  The MAKE-generators call make-readin-cb for us if the
:input parameter is a readin generator.  So the example above can be
shortened to:

: read-test-2 { f: start f: dur f: amp fname -- }
    :file fname make-readin { rd }
    :input rd make-src { sr }
    start dur run-instrument
        0e src amp f*
    end-run
    rd mus-close
    rd gen-free
    sr gen-free
;

0e 2e 0.5e $" oboe.snd" ' read-test-2 with-sound

* Usage

GFM Gforth Music can be started on command line:

    gforth gfm.fs

or as an executable script (see below).

A simple session (I added an empty line after input):

% gforth gfm.fs
Gforth 0.6.2, Copyright (C) 1995--2003 Free Software Foundation, Inc.
CLM/Sndlib, Copyright (C) 1996--2005 Bill Schottstaedt
GFM Gforth Music of 12-01-2005, Copyright (C) 2003--2005 Michael Scholz
order: CLM-Sndlib GFMusic Utils Forth Forth Root     Forth 
stack: float <0>  data <0> 
using csndlib
' fm-violin-test with-sound 

\    event: fm-violin-test
\ <fm-violin>
\ filename: "/home/mike/.snd.d/zap/forth.snd"
\    chans: 1, srate: 22050 
\   format: little endian float (32 bits) [Sun]
\   length: 1.000 (22050 frames)
\     real: 0.058  (utime 0.036, stime 0.008)
\    ratio: 0.06  (uratio 0.04)
\ maxamp A: 0.500 (near 0.277 secs)

help fm-violin-test no description available  ok

help fm-violin 
fm-violin ( f: start f: dur f: freq f: amp keyword-args -- )
\ keywords and default values
\ :fm-index              -- 1e
\ :amp-env               -- vct[ 0e 0e 25e 1e 75e 1e 100e 0e ]
\ :periodic-vibrato-rate -- 5e
\ :periodic-vibrato-amp  -- 0.0025e
\ :random-vibrato-rate   -- 16e
\ :random-vibrato-amp    -- 0.005e
\ :noise-freq            -- 1000e
\ :noise-amount          -- 0e
\ :ind-noise-freq        -- 10e
\ :ind-noise-amount      -- 0e
\ :amp-noise-freq        -- 20e
\ :amp-noise-amount      -- 0e
\ :gliss-env             -- vct[ 0e 0e 100e 0e ]
\ :gliss-amount          -- 0e
\ :fm1-env               -- vct[ 0e 1e 25e 0.4e 75e 0.6e 100e 0e ]
\ :fm2-env               -- vct[ 0e 1e 25e 0.4e 75e 0.6e 100e 0e ]
\ :fm3-env               -- vct[ 0e 1e 25e 0.4e 75e 0.6e 100e 0e ]
\ :fm1-rat               -- 1e
\ :fm2-rat               -- 3e
\ :fm3-rat               -- 4e
\ :fm1-index             -- 0e
\ :fm2-index             -- 0e
\ :fm3-index             -- 0e
\ :base                  -- 1e
\ :degree                -- 90e random
\ :distance              -- 1e
\ :reverb-amount         -- 0.01e
\ :index-type            -- :violin (or :cello)
\ :no-waveshaping        -- false
0e 3e 440e 0.5e :fm-index 0.5e ' fm-violin with-sound ok

help make-src 
make-src ( keyword-args -- gen )
\ === Sampling-Rate Conversion ===
\ keywords and default values
\ :input -- nil (xt)
\ :srate -- 1e
\ :width -- 10
\ INPUT can be a readin generator or an XT with stack effect ( dir -- f: value )
\ To convert a readin generator in an XT one can use make-readin-cb.
\ The run function SRC can only take an XT.
:file $"pistol.snd" make-readin value rd
:input rd make-src value sr   0e sr src f.
\ or
rd make-readin-cb value cb
make-src value sr
:input cb 0e sr src f. ok

bye 
%

GFM has a simple online help.  Most CLM-functions have at least a help
string showing the stack effect, some have simple examples and
describing text.

help make-oscil

shows the help string of make-oscil, the output is:

help make-oscil 
make-oscil ( keyword-args -- gen )
\ keywords and default values
\ :frequency     -- 440e
\ :initial-phase -- 0e
make-oscil value os
:frequency 330e make-oscil value os ok

The main Forth script is gfm.fs.  Global variables and
sndlib-constants (from sndlib.h) can be found here.  This file must be
included in your script file, it loads the other files in correct
order.  If your Gforth implementation is capable of using FFI, i.e. if
you have installed ffcall and gforth >= 0.6.2, it automatically loads
csndlib.fs which contains the connection to libsndlib.so, otherwise it
loads the pure Forth version in fsndlib.fs.  If you define the
variable *clm-use-csndlib* and set it to false before loading gfm.fs,
you can force to load fsndlib.fs (e.g. for testing).

All *.gfm scripts have at least these command line options (or better
gfm.fs have these options and derives them to scripts):

Usage: gfm.fs [ options ]
   -c, --csndlib	       use csndlib.fs (default)
   -f, --fsndlib	       use fsndlib.fs
   -d, --dac		       write to dac

   -V, --version	       display version information and exit
   -h, --help		       display this help message and exit

After initializing global variables but before using them gfm.fs loads
a file `.gfmrc' or `$HOME/.gfmrc' if one of it exists, where you can
set global *clm-...-variables.

If you have set the environment variable $SFDIR, the global Forth
value *clm-search-list* is predefined with that directory, otherwise
with the current working directory.  This variable is similar to
CLM's *clm-search-list* but contains only one path.

GFM sets the file-buffer-size to 1MB, you can change this value in
.gfmrc by e.g.

       1024 8 * file-buffer-size!

There are two types of global *clm-...* variables (or better values in
Forth's context):

              gfm.fs (definition)                    ~/.gfmrc (resetting)
              ===================                    ====================
integer       true value *clm-play*                false to *clm-play*
float         1e  fvalue *clm-decay-time*          0.5e fto *clm-decay-time*

FTO (float-to) sets floats and TO sets integers and pointers.  This is
important if you set global predefined values in your ~/.gfmrc or in a
script (see *.gfm files for examples).

** Scripting

Gforth provides scripting mechanism.  The first line in a script may
be:

#! /usr/bin/env gforth  or  #! /usr/local/bin/gforth

Note the space between #! and the path!

Before loading gfm.fs in your script you can set several variables.

       true  value *clm-use-csndlib*  \ using csndlib.fs
       false value *clm-use-csndlib*  \ using fsndlib.fs
       true  value *clm-dac-output*   \ writing to DAC

Instruments using RUN-INSTRUMENT/END-RUN work with file output as well
as with dac output.  You must set the global variable *clm-dac-output*
to true before loading gfm.fs if you want to write to dac, default is
false.

If your testing is finished, you can disable the assertions on some
generators and arrays by

0 assert-level !

The default value of ASSERT-LEVEL is 1.  Speeding up computation you
can replace gforth by gforth-fast.

The head of a testing phase script may look like this:

#! /usr/bin/env gforth
require gfm.fs
[...]

and after testing:

#! /usr/bin/env gforth-fast
0 assert-level !
require gfm.fs
[...]

** Keyword Arguments

All make-generator functions (except make-one|two-pole|zero) have the
same keyword arguments like Scheme ones.  The behavior is slightly
different.  One can't write `330e :INITIAL-PHASE PI MAKE-OSCIL' but
one must write `:FREQUENCY 330e :INITIAL-PHASE PI MAKE-OSCIL'.  Not
given arguments are replaced by default values.  To see all default
values of a given function use the online help (e.g. HELP MAKE-OSCIL).

:frequency 330e :initial-phase pi make-oscil
:initial-phase pi :frequency 330e make-oscil
make-oscil                 \ default frequency 440e and default initial phase 0e
:frequency 220e make-oscil \ default initial phase 0e

MUS-MIX, SRC, SRC-20, SRC-50, CONVOLVE, GRANULATE, PHASE-VOCODER,
WITH-SOUND, WITH-PSOUND, WITH-DAC, CLM-LOAD, WITH-REVERB,
WITH-MOVE-SOUND, MOVE-SOUND, MAKE-*PATH have also keyword arguments.

** Note list

GFM Gforth Music defines some convenient functions.  To define an
instrument you can use

        instrument: inst-name
	  ...
	;instrument

instead of

        : inst-name
	  ...
	;

If the hook variable *clm-notehook* is set, this XT is performed on
every instrument call:

        lambda: ( str -- ) ." \ <" .$ ." >" cr ; to *clm-notehook*

The hook variable is called with the instrument name (of type String).
The above example prints on every instrument call

\ <instrument-name-here>

Furthermore the variable *clm-instruments* is filled on start up; the
value of *clm-instruments* can be seen by

        show-instruments

A named piece of note list can be written by

        event: event-name
	  ...
	;event

instead of

        : event-name
	  ...
	;

If the word `event-name' is called later, it prints its name if the
global variable *clm-verbose* is true (see *.gfm files).

Writing instruments you can use as a shorthand

        start dur run   os oscil-0   i loc locsig   loop

instead of

        start dur times>samples do   os oscil-0   i loc locsig   loop

or better

	start dur run-instrument   os oscil-0   end-run

RUN-INSTRUMENT makes an locsig-generator and END-RUN writes a float
after every loop in the corresponding locsig-place and frees the
generator after finishing the loop.  The locsig-generator sets the
global variable *LOCSIG* which can be used in the run-loop for
MOVE-LOCSIG etc.  Degree, distance, and reverb-amount may be set
before RUN-INSTRUMENT with :LOCSIG-DEGREE, :LOCSIG-DISTANCE, and
:LOCSIG-REVERB-AMOUNT.

instrument: simp { f: start f: dur -- }
    440e 0e make-oscil { os }
    90e random :locsig-degree
    1e :locsig-distance
    start dur run-instrument   os oscil-0  0.1e f*   end-run
    os mus-free
;instrument

lambda: 0e 2e simp ; with-sound

See clm-ins.fs and sndtest.fs for more examples.

To compute and play sound files it exists the WITH-SOUND function.  It
must be called at least with an (note-list-)XT (e.g. ' fofins-test
with-sound) and can take additional keyword arguments.

        ' fofins-test with-sound
        ' fofins-test :reverb ' jc-reverb with-sound
        ' fofins-test :channels 2 :output $" fofins.snd" :play false with-sound

If *clm-dac-output* is true with-sound does not write to :output but
to dac.  Furthermore there exist more functions like with-sound:
WITH-REVERB, CLM-LOAD, SOUND-LET, WITH-MOVE-SOUND and the experimental
WITH-PSOUND.  All have the following keyword arguments; the global
variables are the default values which you can change in ~/.gfmrc.

        :play              *clm-play*             true
        :statistics        *clm-statistics*       true
        :verbose           *clm-verbose*          true
        :continue-old-file                        false
        :delete-reverb     *clm-delete-reverb*    true
        :reverb            *clm-reverb*           nil
	:reverb-data       *clm-reverb-data*      nil
        :channels          *clm-channels*         1
        :reverb-channels   *clm-reverb-channels*  1
        :clm-srate         *clm-srate*            22050
        :rt-bufsize        *clm-rt-bufsize*       8192
        :table-size        *clm-table-size*       512
        :locsig-type       *clm-locsig-type*      mus-interp-linear
        :header-type       *clm-header-type*      mus-next
        :data-format       *clm-data-format*      mus-lfloat
        :audio-format      *clm-audio-format*     mus-lshort
        :notehook          *clm-notehook*         nil
        :output            *clm-file-name*        $" test.snd"
        :reverb-file-name  *clm-reverb-file-name* $" test.reverb"
        :player            *clm-player*           $" intern"
        :comment           *clm-comment*          make-default-comment
        :device            *clm-device*           mus-audio-default
        :decay-time        *clm-decay-time*       1.0e

** Data types

String, Array, Hash, and Vct objects are based on the same struct, so
they inherit common properties and functions.

length            ( obj -- n )
data-ptr          ( obj -- addr )
cycle             ( obj -- val )    (not Hash)
?range            ( idx obj -- f )
?empty            ( obj -- f )
each/end-each     ( obj -- )
map/end-map       ( obj -- obj' )
[each]/[end-each] ( obj -- )
[map]/[end-map]   ( obj -- obj' )

These functions and properties can be found on all types mentioned
above.

Furthermore there exist the following base functions on String, Array
and Vct, which have aliases with data-type names:

object@  ( idx obj -- val )     string@     array@      vct@
object!  ( val idx obj -- )     string!     array!      vct!
object+! ( val idx obj -- )                 array+!     vct+!

In addition the following functions exist for all mentioned data
types: ?STRING, ?ARRAY, ?HASH, ?VCT, STRING=, ARRAY=, HASH=, VCT= as
well as the inspect functions .STRING, .ARRAY, .HASH and .VCT.  The
inspect and comparison functions can all be replaced by GEN= and .GEN
(see below ** Generators).

EACH/END-EACH and MAP/END-MAP replace the general DO/LOOP construct
within function definitions.  They can all be nested like do/loop, the
index variable is I for the inner, J for the next inner and K for the
third loop like on nested do/loops.

4 make-vct map i s>f end-map { v }
v .gen ==> #<vct[4] 0.000 1.000 2.000 3.000>

END-MAP eats one stack item on every loop (two items from the data
stack if object is of type Hash, one item from the float stack if
object is of type Vct, otherwise one item from the data stack).
MAP/END-MAP returns the changed object so we can create and initialize
an array or vct at once.

v each f. end-each ==> 0. 1. 2. 3.

EACH/END-EACH pushs the next value of its object on every loop on the
stack, END-EACH returns nothing.

[EACH]/[END-EACH] and [MAP]/[END-MAP] replace the [DO]/[LOOP]
construct outside definitions.  They must appear on one line and can't
be nested.

Loop-examples can be found e.g. in clm-ins.fs and in dlocsig.fs.

There exist convenient functions creating Strings, Arrays, and Vcts.
Theses functions parse only up to end of line!

*** String

Forth has the parsing word `s"' (s" foo" returning addr len).  But
creating an object of type String you can use `$"' ($" foo" returning
string object).  I think it's much easier to handle an object of type
String on the stack than address and length of a generic Forth string.
One can ask whether it's of type String or an other object, but an
address and length are simple integers without type specifiers.
Keyword arguments are simply handled with String objects.  That's why
GFM uses String objects ($" ...") and nearly no generic Forth strings
(s" ...").

Forth strings:                        GFM Strings:

s" foo" ( addr len ) 2constant bar    $" foo" ( str ) value bar

$" This line returns an object of type String.\n"
$" If we want to concatenate two Strings we use `$+'\n" $+
$" Now we have a long string which we can see by typing `.$'" $+ .$

Strings recognize C's printf escape sequences (see \"-parse in
gforth.info), i.e. \a, \b, \e, \f, \n, \r, \t, \v \", \000, and \xxx.

*** Array

Arrays can be created by N MAKE-ARRAY, M1 ... MN N >ARRAY or by the
parsing word ARRAY[:

array[ 0 1 2 3 4 ] .gen #<Array[5] 0 1 2 3 4>
array[ make-oscil make-triangle-wave ]
       .gen #<Array[2]
               #<oscil freq: 440.000Hz, phase: 0.000>
               #<triangle-wave freq: 440.000Hz, phase: 0.000, scaler: 1.000>
             >
array[ 0 1 2e float>float 3e float>float 4e float>float ]
       .gen #<Array[5] 0 1 2.0000 3.0000 4.0000>

FLOAT>FLOAT (or MAKE-FLOAT) requires a number on the float stack and
converts this number to an object of type Float.  Now the address of
this object can be handled on the normal data stack and therefore we
can place it in a normal Array object.  This conversion is not
intended for time consuming computing (see FULLMIX in clm-ins.fs).

*** Vct

Vcts can be created by N MAKE-VCT, M1 ... MN N >VCT or by the parsing
word VCT[:

vct[ 0e 1e 2e 3e 4e ]       .gen #<vct[5] 0.000 1.000 2.000 3.000 4.000> 

It is possible to nest creations of Arrays and Vcts but not to nest
those parsing words array[, and vct[, only one of them is possible.

array[  0e 1e 2e  3 >vct  ] .gen #<Array[1] vct[ 0.e 1.e 2.e ]>
vct[ 0e 1e 2e ] 1 >array    .gen #<Array[1] vct[ 0.e 1.e 2.e ]>
array[ $" foo, bar" ]       .gen #<Array[1] "foo, bar"> 

** Generators   

All generators have the following definitions:

make-generator ( ... -- gen )
generator ( ... gen -- r )
?generator ( gen -- f )
gen .gen
gen1 gen2 gen=

e.g.
make-oscil ( keyword-args -- gen )
oscil ( f: fm f: pm gen -- r )
?oscil ( gen -- f )
os .gen

instrument: simp { f: start f: dur f: freq f: amp -- }
    :frequency freq make-oscil { os }
    :envelope vct[ 0e 0e 25e 1e 75e 1e 100e 0e ] :scaler amp :duration dur make-env { en }
    start dur run-instrument  os oscil-0  en env  f*   end-run
    os mus-free
    en mus-free
;instrument

lambda: 0e 2e 330e 0.3e simp ; with-sound

The inspect function is .GEN; all generators show their description as
string.

make-oscil .gen
make-oscil value os
os .gen

The comparison function is GEN=.

make-oscil value os1
make-oscil value os2
os1 os2 gen= .

** Accessors, Conventions

Following the Forth-conventions GFM provides different forms of names
as used in CLM's sndlib.  Functions for storing and fetching values
end in `!' and `@', functions printing results have the prefix `.'
and functions leaving a flag on the stack have the prefix `?'.
Functions converting values have a `>' in between the word-name.

Scheme                               Forth
(set! gen (make-oscil :frequency freq :initial-phase phase))
                                     :frequency freq :initial-phase phase make-oscil value gen
(oscil gen fm pm)                    fm pm gen oscil
(oscil? gen)                         gen ?oscil
gen                                  gen .gen
(set! (frequency gen) val)           val gen frequency!
(frequency gen)                      gen frequency@
(file->sample gen samp chan)         samp chan gen file>sample

All generators have the same accessors like CLM.  The example below
shows the usage of FREQUENCY!, PHASE!, SCALER@, and SCALER!  on a
square-wave generator.

instrument: simp { f: start f: dur }
    make-square-wave { sw }
    \ sw .gen ( this would show the description of the square-wave generator )
    0 { stp }
    start seconds>samples { beg }
    dur mus-srate@ f* 10e f/ f>s { lim }
    start dur run-instrument
	i beg = if 0e sw phase! then
	i beg - 5000 = if 660e sw frequency! then
	stp lim = if
	    sw scaler@ 0.1e f- sw scaler!
	    0 to stp
	then
	stp 1+ to stp
	0.1e  0e sw square-wave  f*
    end-run
    sw mus-free
;instrument

lambda: 0e 1e simp ; with-sound

** Arrays, Envelops, Partials

Envelops and partials are only arrays of floats, not integers mixed
with floats.  To make an envelop generator we can write:

vct[ 0e 0e 25e 1e 75e 1e 100e 0e ] value env-array
:envelope env-array :duration 1.5e make-env value en

instrument: simp { f: start f: dur f: freq f: amp }
    :frequency freq make-oscil { os }
    :envelope vct[ 0e 0e 100e 1e ] :scaler amp  :duration dur make-env { en }
    start dur run-instrument   en env  os oscil-0  f*   end-run
    en mus-free
    os mus-free
;instrument

lambda: 0e 1e 440e 0.5e simp ; with-sound

* Emacs

;;;;
;;;; GForth additions
;;;;
(autoload 'forth-mode "gforth" "Major mode for GForth." t)
(autoload 'forth-block-mode "gforth" "Major mode for GForth." t)
(autoload 'inferior-forth-mode "gforth" "Major mode for GForth." t)

(require 'forth-mode "gforth")

(setq forth-use-objects t)

(setq forth-custom-words
      '((("instrument:" "event:" "callback") definition-starter (font-lock-keyword-face . 1)
	 "[ \t\n]" t name (font-lock-function-name-face . 3))
	(("lambda:") definition-starter (font-lock-keyword-face . 1))
	(("interpret/compile:") non-immediate (font-lock-keyword-face . 1)
	 "[ \t\n]" t name (font-lock-function-name-face . 3))
	((";instrument" ";event" "callback;") definition-ender (font-lock-keyword-face . 1))
	(("const-does>") compile-only (font-lock-keyword-face . 1))
	(("unless" "run" "run-instrument" "end-run" "each" "end-each" "map" "end-map")
	 compile-only (font-lock-keyword-face . 2))
	(("float" "double") non-immediate (font-lock-constant-face . 2))
	(("fvalue") non-immediate (font-lock-type-face . 2)
	 "[ \t\n]" t name (font-lock-variable-name-face . 3))
	(("make-?obj" "make-inspect") non-immediate (font-lock-keyword-face . 2)
	 "[ \t\n]" t name (font-lock-function-name-face . 3))
	(("[unless]" "[i]" "[each]" "[map]" "[end-each]" "[end-map]")
	immediate (font-lock-keyword-face . 2))
	(("fto") immediate (font-lock-keyword-face . 2)
	 "[ \t\n]" t name (font-lock-variable-name-face . 3))
	(("nil" "two-pi" "half-pi") non-immediate (font-lock-constant-face . 2))
	(("defines") non-immediate (font-lock-type-face . 2)
	 "[ \t\n]" t name (font-lock-function-name-face . 3))
	(("s\\\"" "c\\\"" "$\"") immediate (font-lock-string-face . 1)
	 "[^\\][\"\n]" nil string (font-lock-string-face . 1))
	(("vct[" "array[") immediate (font-lock-variable-name-face . 2)
	 "]" nil string (font-lock-variable-name-face . 2))
	((".\\\"") compile-only (font-lock-string-face . 1)
	 "[^\\][\"\n]" nil string (font-lock-string-face . 1))
	(("lambda-create" "noname-create") non-immediate (font-lock-type-face . 2) nil)))

(setq forth-custom-indent-words
      '((("instrument:" "event:" "lambda:" "callback") (0 . 2) (0 . 2) non-immediate)
	(("unless" "[unless]" "run" "run-instrument" "each" "map" "[each]" "[map]") (0 . 2) (0 . 2))
	(("end-run" "end-map" "end-each" "[end-each]" "[end-map]") (-2 . 0) (-2 . 0))
	(("const-does>") (-1 . 1) (0 . 0))
	((";instrument" ";event" "callback;") (-2 . 0) (0 . -2))))

(defun forth-quit ()
  "Kill inferior Forth mode."
  (interactive)
  (unless (one-window-p)
    (delete-window (get-buffer-window forth-process-buffer)))
  (delete-process (get-buffer-process forth-process-buffer))
  (kill-buffer forth-process-buffer))

(defun forth-proc-p ()
  "Return non-nil if no process buffer available."
  (save-current-buffer
    (comint-check-proc forth-process-buffer)))

(defun forth-load-file (file-name)
  "Load a Forth file FILE-NAME into the inferior Forth process."
  (interactive (comint-get-source "Load Forth file: " forth-prev-l/c-dir/file
				  forth-source-modes t))
  (comint-check-source file-name)
  (setq forth-prev-l/c-dir/file (cons (file-name-directory    file-name)
				      (file-name-nondirectory file-name)))
  (comint-send-string (forth-proc) (concat "include " file-name "\n")))

(defun make-forth-mode-menu ()
  "Make `forth-mode' menu."
  (define-key (current-local-map) [menu-bar forth-mode]
    (cons "Forth" (make-sparse-keymap "Forth")))
  (define-key (current-local-map) [menu-bar forth-mode quit]
    '(menu-item "Kill Forth Process" forth-quit
		:enable (forth-proc-p)))
  (define-key (current-local-map) [menu-bar forth-mode switch]
    '(menu-item "Switch to Forth Process" forth-switch-to-interactive
		:enable (forth-proc-p)))
  (define-key (current-local-map) [menu-bar forth-mode start]
    '(menu-item "Start Forth Process" run-forth
		:enable (not (forth-proc-p))))
  (define-key (current-local-map) [menu-bar forth-mode sep-doc] '(menu-item "--"))
  (define-key (current-local-map) [menu-bar forth-mode describe]
    '(menu-item "Describe mode" describe-mode))
  (define-key (current-local-map) [menu-bar forth-mode symdoc]
    '(menu-item "Info lookup ..." info-lookup-symbol
		:enable (forth-proc-p)))
  (define-key (current-local-map) [menu-bar forth-mode sep-eval] '(menu-item "--"))
  (define-key (current-local-map) [menu-bar forth-mode region]
    '(menu-item "Eval region" forth-send-region
		:enable (and (forth-proc-p) mark-active)))
  (define-key (current-local-map) [menu-bar forth-mode defun]
    '(menu-item "Eval paragraph" forth-send-paragraph
		:enable (forth-proc-p)))
  (define-key (current-local-map) [menu-bar forth-mode defun]
    '(menu-item "Eval buffer" forth-send-buffer
		:enable (forth-proc-p)))
  (define-key (current-local-map) [menu-bar forth-mode sep-compile] '(menu-item "--"))
  (define-key (current-local-map) [menu-bar forth-mode load]
    '(menu-item "Load file ..." forth-load-file
		:enable (forth-proc-p))))

(add-hook 'forth-mode-hook
	  '(lambda ()
	     (define-key (current-local-map) "\C-c\C-k" 'forth-quit)
	     (define-key (current-local-map) "\C-c\C-q" 'forth-quit)
	     (define-key (current-local-map) "\C-c\C-o" 'forth-send-buffer)
	     (define-key (current-local-map) "\C-c\C-r" 'forth-send-region)
	     (define-key (current-local-map) "\C-c\C-e" 'forth-send-paragraph)
	     (define-key (current-local-map) "\C-c\C-s" 'run-forth)
	     (make-forth-mode-menu)))

* Files

** package
*** COPYING
*** README
*** Makefile

** note list examples
*** sndtest.gfm
*** bird.gfm
*** cm-examp.gfm
*** fmviolin.gfm

** library files
*** gfm.fs
*** utils.fs
*** gfm-defs.fs
*** csndlib.fs
*** fsndlib.fs
*** clm-ins.fs
*** spectr.fs
*** dlocsig.fs

* Author: Michael Scholz <scholz-micha@gmx.de>
