fJACK - a JACK Audio Connection Kit client








Introduction

When doing audio processing in Forth systems we were dependent on a working audio handler made available by the Forth vendor or had a lot of work to do.

Modern sound systems regularly make use of a callback driven model, ALSA for linux is just one example. But ALSA is not portable and doesn't allow connecting applications together in a sound-flow environment. One popular system allowing this is the 'jack-audio-connection-kit' JACK. It is available for all major desktop platforms, Linux, OSX and Windows. Jack makes use of the available hardware driver in a real time process. There are many audio applications around using the JACK interface and Forth can now connect to all of them.

fJACK is written in Forth and uses some of the advanced techniques like threading, binding of external libraries and callbacks. So it is not ANS Forth. The good news: it's available for all iForth versions (Linux, Windows and OSX - 32/64bit) and VFX Forth (Linux, Windows and OSX).




Installation

Installing the jack audio connection kit

First you must install a JACK system and get it running. Read the HOWTO in the net and specialized audio groups to find out how you can get the most out of it. Getting JACK running with low latency and no drops/xruns is sometimes a bit difficult. Look out for hardware that is well supported for your environment. It used to be necessary to use a real-time-kernel, with modern low-latency linux kernels and optimized settings in security.conf The situations has become much better over the last years.

Downloading jack

This is a good place to find jack: http://jackaudio.org/download or go for compiled packages for your system.

What sort of jack?

Currently there is a change in the jack system, either the 'old' jackd or the newer jack2 system available for all linux distributions, windows and OSX. Also the new systems supports multicore cpus much better, things like netjack are reimplemented and a session management is integrated. So i strongly suggest to upgrade to jack2, at the moment it's v1.9.7. Some advanced features of fjack are only available with the latest releases.

All newer versions (since 0.118 or 1.9.4) have the ability to auto-start a jack server when a client wants to register and no running server is found. Even so - this is not a good way to start a jack server at all. fJACK can still do so but it's much better to use the session controlling applications like QjackCtl. On linux systems, this also means that other sound systems like pulseaudio are easy to integrate.

So the user can choose between three options:

  • Start the jack server before using fJACK with a tool like qjackctl. This allows precise setups of jack according to your system and is normally done at startup. I think this is the best way. Also this allows session management, starting fjack turnkey applications and other audio application with one click and connecting them all.
  • Let fJACK start the server automatically. This needs a correct setup of jack and you will have to set JACK-AUTOSTARTING? to true.
  • Write your special KICKSTART word before loading fjack. This method is the least preferable.
  • What about the fftw3 library?

    All recent fJACK versions use the fftw3 library to define the filter response of FIR filters so you really should install it even when you don't need the signal analysis fft plugin. BAND-PASS BAND-STOP and USER-FIR filters with a user-specified frequency response and the EQUALIZER will not work without it. Calculating the filter parameters is not a real performance problem. But doing signal analysis on large amount of data or with sizes <> 2^n depends on a 'calibrated' fftw3 system. Currently creating fftw3 plans uses #FFTW_ESTIMATE because in fjack only 2^n sizes are used and then there not much of a difference. See fftw3lib.frt and fftw3.frt

    What about performance?

    fJACK 0.122 or later uses optimized sse2 code filter kernels on all platforms. This is done by including the code generated on iForth systems via very simple binary code snippets. These are included at compile time depending on the used architecture and are also used in turnkeys. So for vfx this means the same performance as iforth systems.

    For iForth this also means the assembler is only loaded when you want to change the optimized code snippets, so probably this is only used by me. If you really want to go deep into sse2 assembler programming you will find the compile-time flag save-sse2-overlay? at the fjack.frt preamble. But -- be warned!

    The fjack client tests the running cpu, if your machine doesn't offer sse2 code there is a fallback to forth code which gives roughly half performance in all FIR filters. This test just uses the cpuid instruction, all supported operating systems make sure, the xmm registers are safe with threading.

    What about memory?

    fJACK needs quite a lot of ram allocated from the system. As this is a realtime application, all memory must be permanent and MUST NOT be swapped in/out. When using the default config parameters fJACK needs ~10MB of allocated memory. A rather precise calculation of allocated memory can be done by:

  • JACK-CHANNELS #PLUGINS + #HISTORY-SIZE * SFLOATS
  • See the 'What can be configured in fJACK?' chapter to find out more about changing such config options.

    Contact, help & bug reports

    Feel free to ask for help at hanno@schwalm-bremen.de

    Whenever you plan to use fJACK in commercial applications read the Licence.txt and send me an email. Using fJACK for private or educational use is free of charge on all platforms.

    Release History


      History

    Where to put the fjack sources?

    The fJACK system sources are in the fjack directory containing the client and extension module sources plus the 'loader' fjack.frt. The fjack/sysfiles directory holds the connections and some binary files. This subdirectory should be made writable.

    iForth

    All iForth versions are supported. The fJACK sources should be in - or go to - the include directory. IN fjack will do.

    VFX Forth

    The fJACK sources coming with your vfx system are included in the examples directory. You should cd to this directory, then INCLUDE fjack.frt will do. When you get an fjack update you could replace that version or have it somewhere else.

    Testing the client

    Loading as above compiles the basic Forth interface to jack, defines a JACK client with some ports and connects these ports. There are little demos availabe, see fjack/examples/tests.frt or fjack/examples/board.frt.

    iforth comes with some advanced tools using graphical frontends to control filters. Try gui/jackdemo.frt to get an idea. Sorry - this is and won't be available for vfx.

    Making fJACK Turnkey Applications

    fJACK can also be used in turnkey applications, all plugin object structs and the client data are in permanent memory space to support this.

    fJACK inside a turnkey applications will start automatically, you will just have to put your application 'xt' in the boot chain.

    ' application AtCold

    Stopping the client and system housekeeping is done in the AtExit chain.

    If you want to use fJACK inside sessions as supported by qjackctl you'll have to make a turnkey binary. vfx produces 'complete' binaries, iforth SAVE-SESSION writes an image file that must be loaded by the iforth server application. When you have used the linux iforth installer there will already by a script 'fJACK' in your dfwforth directory. This script must be copied to fjack/sysfiles/fJACK - this is a place every fJACK installation knows about and the session callbacks use.





    The fJACK Client User Manual





    \ MARKER -fjack
    The ANS MARKER -fjack can be safely used to remove fJACK from your forth system. It's supported on all platforms.

    VOCABULARY JACKAUDIO
    The JACKAUDIO vocabulary holds internals of the fJACK system, it's words could be changed in next releases and should be used in end-user applications with great care.

    A short processing overview

    fJACK does real-time processing on a user defined number of audio channels (ports) JACK-CHANNELS, it controls and is controlled by a jack server. You can connect any other jack-ready application via these ports with fJACK. To allow audio data processing you can define audio plugins and put them in any of the slots. So the basic overview is like this


    The Signal Processing Graph

    Please recognize that the processing callback has changed in version 0.115, now every processing plugin has it's history buffer. Later you will find out, that every plugin can indeed read data from any history buffer. This new sheme allows much improved sound effects and mixing.




    What can be configured in fJACK?

    Following are some system wide parameters defined in fjack/config.frt that could be changed by you! All these configure options are defined by a cCONSTANT definer; it's used like a CONSTANT but it looks for a definition having the same name. If such a definition exits, it won't define a new constant but will use your predefined constant.

    So - let's say you want to use fJACK on stage with a powerful computer, you You will have to define JACK-CHANNELS and #HISTORY-SIZE before INCLUDE fjack.frt

    16 CONSTANT JACK-CHANNELS
    $20000 CONSTANT #HISTORY-SIZE

    This way you may configure your fJACK application without changing the fJACK source itself.

    #4 cCONSTANT JACK-CHANNELS
    It can be set to any number >= 1 you like. The default is 4, when you have a powerful computer and wish to use more audio channels, just set this to 8 or whatever you need. Each channel has one input and one output port defined. These ports are used for the audio processing plugins described later. There is also a programmers interface to be used by you to define your own sound processing tools. Also think about using other jack plugins available in the net to be used by your forth application. Just one hint - more channels than needed will use a lot of memory and slow down the system a bit.

    #32 cCONSTANT JACK-IO-SLOTS
    Defines the maximum number of processing plugins that can be placed in the Input-Port->Output-Port processing chain for a channel. Also see #PLUGINS

    #32 cCONSTANT #PLUGINS
    The maximum number of plugins that can be used at the same time in the input->output processing chain.

    FALSE cCONSTANT JACK-AUTOSTARTING?
    Should the jack server be autostarted with the client? This requires a moder jack server installed correctly.

    [DEFINED] KICKSTART 0= [IF] : KICKSTART ; [THEN]
    Sometimes you might want to start the jack server in a user specified way not using any of the control applications or the autostart feature.

    In this case you can define a word KICKSTART before loading fjack that does so.

    : KICKSTART S" start /d C:\jack\jackdmp_0.71\bin\windows /i jackdmp -S -d portaudio -p1024" SYSTEM ;

    [UNDEFINED] JACK-LOGIN-WAVFILE [IF] : JACK-LOGIN-WAVFILE S" fjack/snippets/start.wav" ; [THEN]
    This is the wavfile played when starting the fjack client. You may choose any other wav filename and define JACK-LOGIN-WAVFILE before loading fjack.frt

    [UNDEFINED] JACK-CLIENT-NAME [IF] : JACK-CLIENT-NAME S" fJACK" ; [THEN]
    Normally the fjack client is called fJACK. To support session management you may define another client name for your target application. To further support jack session management it is assumed here that you will have a shell script available in your iforth directory with exactly the name of the client name, this will be started when reading your session. To use another client name you'll have to define \*forth{JACK-CLIENT-NAME} before loading fjack.

    TRUE cCONSTANT JACK-MODULES?
    Load the plugin modules? Without these modules no filtering is possible!

    FALSE cCONSTANT JACK-VERBOSE?
    Some extra information in the log

    FALSE cCONSTANT JACK-MIDI?
    Might switch on midi services.

    #2048 cCONSTANT #FIR_BUFF
    This defines the maximum number of taps in adaptive and fir filters; every defined FIR filter object uses 5*#FIR_BUFF DFLOATS dynamic memory. Must be a power of 2 and at least 4. This is quite a good default as normal fir filters won't need larger buffers. When using special effect plugins using the generic fir filter routines you might need larger buffers.

    #16 cCONSTANT #jack-fifos       \ MUST be a power of 2
    Gapless wav playing and writing is controlled via a FIFO with #jack-fifos elements. If an applications writes small chunks of wav data at very high speed you could increase this. More information is available in the wav handling chapter.

    $800 cCONSTANT #max-frame-size  \ MUST be a power of 2
    The maximum frame size suported. Only with very slow hardware you will want larger buffers as the latency gets higher. #1024 is the default for the current jack versions so it must be at least $400.

    $10000 cCONSTANT #HISTORY-SIZE  \ must be a power of 2 and at least 4 * #max-frame-size
    fJACK may want access to data played some time before for delaying or data analysis so data are saved in this circular buffer. This history is available in every plugin. The mixer and delay plugins both need the history size to be large enough.

    FALSE cCONSTANT AUTO-HISTORY-CLEAR?
    Whenever a plugin object is put into the processing chain the history might be cleared automatically. I don't think it's worth to set this to TRUE but it's your decision.

    1 cCONSTANT DEFAULT-FIR-WINDOW
    The coefficients of FIR filters are calculated by a windowed inverse FFT, this windowing function can 0,1 or 2. See SET-FIR-WINDOWING for more information.

    #64 cCONSTANT JACK-BOARDJOBS
    The number of wavboard jobs to be used at the same time. Must be at least one

    TRUE cCONSTANT OPTIMIZED-FILTER-CODE?
    Developers might wish to disable the sse2 optimized filter primitives.




    Datatypes used in the client

    Datatype implementation details might change in future releases. Currently the object-makers all return object-pointers or are defined as CONSTANTs. Please read the fjack/types.frt to understand how it works.




    Using the Client

    1E0 FVALUE PLAY-WAV-LEVEL
    The level of the WAV data played common for all channels. The default of 1.0 is ok in most situations, when you have wav files that are a) 8bit-coded b) on almost full level and c) need resampling you might set this to a smaller value like 0.9 to avoid distortions.

    1E0 FVALUE READ-WAV-LEVEL
    The level of the recorded=read WAV data common for all channels.

    1E0 FVALUE JACK-IO-LEVEL
    This level is applied to data read at the InputPort before the Input->Output processing is done, it's common for all channels. The leveler plugin uses it as an example.

    #10000 VALUE JACK-SAMPLE-RATE   ( -- n )
    The sample rate the JACK server is running. The fJACK client is told about it via a callback, you can - and should - only read it.

    #1024 VALUE JACK-CHKSIZE        ( -- n )
    Number of samples in a process frames callback as returned by the JACK server.

    0 VALUE JACK-XRUNS      ( -- n )
    Counter for the xrun callbacks

    0 VALUE fJACK-ID        ( -- n )
    The client id returned by the JACK library call when registering a client is kept here.

    0 VALUE JACK-SESSION-BYE?
    This is a global value that can be tested by user applications. It is set to *\bolf{TRUE} when a jack session manager like QjackCtl tells save&quit. As there is no global safe way to leave all apllications this is done via this flag.

    DEFER MIDI-PROCESS-HOOK (  -- ) ' NOOP IS MIDI-PROCESS-HOOK
    Done by the midi handler once per PROCESS-FRAMES callback.

    DEFER GRAPH-REORDER-HOOK ( -- ) ' NOOP IS GRAPH-REORDER-HOOK
    Called by the reorder callback; you can define a function watching the jack environment, for example you could make sure about connections.

    DEFER PORT-CONNECT-HOOK ( a b flag -- ) ' 3DROP IS      PORT-CONNECT-HOOK
    Called be the port-connect callback; you can define a function that looks for connections. This function must take the flag and both port id's from the stack

    DEFER SAVE-SESSION-DATA ( event -- )    ' DROP IS SAVE-SESSION-DATA
    The session management of the jack server allows saving of data, this is a hook for your user applications, event is the file descriptor of an already r/w opended file. See DEFAULT-SESSION-SAVER as an example.

    0 VALUE #FJACK-SAMPLE
    In the input->output processing chain you can use this value as the sample number

    : SET-CHANNEL-VOLUME    \ ( channel -- ) (F level -- )
    Besides setting the global volume you may set a volume to a specified output channel by this function.

    : GET-CHANNEL-VOLUME    \ ( channel -- ) (F -- level )
    reads the channels output volume.

    : OBJECT-HISTORY        ( plugin-object|channel -- addr )
    To use @HISTORY-DATA, MOVE-HISTORY-DATA, @IDX-HISTORY-DATA, RECONNECT-PLUGIN or @INTERPOLATED-HISTORY-DATA you need the history buffers base address, this calculates it for you. The parameter can be either the channel getting the inport history buffer or the plugin object, then the plugins private history buffers is returned. As the history holds sfloat data there is a very small precision loss compared to pre-0.115 versions.

    There are always JACK-CHANNELS history buffers for the input ports and #PLUGINS buffers for the active plugin objects allocated. The size of each history buffers is #HISTORY-SIZE SFLOATS. See for the config.frt file to change the default values. Each history holds several groups of JACK-CHKSIZE SFLOATS segments


    History buffers

    : *N-HISTORY            ( history idx -- addr )                 #HISTORY-SIZE 1- AND SFLOATS + ;
    Calculates the address inthe history buffer

    : @IDX-HISTORY-DATA     ( history idx -- ) ( F -- data )        *N-HISTORY SF@ ;
    Reads the audio data in the history buffer at position idx.

    : @HISTORY-DATA         ( history offset -- ) ( F -- data )     #FJACK-SAMPLE SWAP - *N-HISTORY SF@ ;
    Reads the audio data in the history buffer relative to the current position in the audio stream.

    : MOVE-HISTORY-DATA     ( historybuffer startoffset size target-addr -- )
    Copies size samples of audio data from the history buffer to the target buffer. The offset marks the end of data.

    : @INTERPOLATED-HISTORY-DATA    \ ( historybuffer -- ) (F time-offset -- data )
    You may read the interpolated audio data from the history buffer by this, the time-offset is measured in seconds. a zero time offset always references to the currently processed sample in the input->output processing chain

    so it is only valid there.

    : JACK-TIME?            ( -- d )
    The time of the JACK server is always 64bit, so for portability it's a double in Forth. The time is derived from the high resolution timer.

    : JACK-UNIQUE           ( -- d )
    This is a unique double telling about the position in JACKs processing queue. See for more details in the libjack sources or API documentation, not much use for this in most situations.

    : JACK-POSITION         ( -- u32 )
    The position in the audio data stream, it's an unsigned 32bit integer.

    : JACK-LOAD             ( -- n )
    The JACK server measures the time spent in the clients callback by using the high-resolution-timer and calculates the 'load' as it also knows the time between callbacks. The value n is the load in percent.

    : JACK-RUNNING?         ( -- flag )
    To be used by the user application testing both the client id and the state given by the processing callback. TRUE when the client is really active.

    : JACK-ZOMBIE?          ( -- flag )
    This flag is TRUE when the fjack client was zombiefied by the server.




    Managing blocks of 'WAV' data

    0 VALUE WAV-PLAY-MODE
    When playing wav data the audio samples are inserted in the input->output procesing stream. This can be before or after the signal processing is done. WAV-PLAY-MODE is used to switch between these modes , n=0 means before processing.

    1 VALUE WAV-READ-MODE
    When recording wav data from the input->output processing stream the samples can be fetched from before or after are signal processing. WAV-READ-MODE is used to switch between these modes , n=0 means before processing.

    So wav-playing and recording uses history buffers selected by the values of WAV-PLAY-MODE and WAV-READ-MODE. In this graphics using a value of 0 uses the green buffers, red buffers otherwise.


    Wav playing and reading

    : CALC-WAV-RATE         ( srate -- true-sample-rate )
    The sample rate conversion when playing or recording wav data uses a rather simple linear approximation algorithm, the used 'real' sample rates can be any truerate = n * samplerate / JACK-CHKSIZE. This ensures a small distortion and no glitches are in recorded or played data. Another point is: there are better buffersizes than others! When you want to use resampling, CALC-WAV-RATE tells the true sample rate calculated from the desired one.

    This is only relevant for PLAY-WAV RECORD-WAV.

    : CALC-WAV-BLOCK        ( srate channels mode -- n )
    As told at CALC-WAV-RATE there are 'better buffersizes than others'. CALC-WAV-BLOCK gives a good suggestion what the block size should be, or better multiples of this.

    : WAV-MODE?     ( mode -- flag )
    Flag is true when the wav mode is supported by this system.

    : PLAY-WAV      ( addr size rate channels mode -- id ?error )
    Plays WAV data available at an addr and a size. These data are not played immediately but are inserted into a FIFO 'slot' and will be played after all before playing commands have finished. When size is negative the FIFO is cleared before inserting this block into the FIFO so playing will start at once. More precise - it will start when the next PROCESS-FRAMES callback is taken. A negative channel number results in reversed channels when listening, playing wav files with only one channel will be played as stereo. Thew rate can be freely choosen, the mode can be any of the 8bit/16bit/32bit/32bit-float modes supported here. Named constants for these are defined in JACKAUDIO these are defined STEREO MONO 8BIT-AUDIO 16BIT-AUDIO 32BIT-AUDIO SFLOAT-AUDIO.

    The 'id' can be used to test a block of wav data in the FIFO. See: WAV-PLAYING? STOP-PLAY-WAV, ?error tells about the status

  • 0 -- everything was ok and id is 'legal'
  • 1 -- not supported mode
  • 2 -- too many channels
  • 3 -- FIFO overflow
  • 4 -- JACK is not running or has stopped
  • : READ-WAV      ( addr size rate channels mode -- id ?error )
    READ-WAV works as PLAY-WAV except it reads the data from the audio stream and puts them into the buffer at addr. More info at PLAY-WAV

    : STOP-PLAY-WAV ( ms id -- )
    The data that had earlier been put into the play FIFO and had returned 'id' will stop playing after the specified time ms (milliseconds). When the original block had a shorter duration all data will be played.

    : STOP-READ-WAV ( ms id -- )
    The data that had earlier been put into the read FIFO and had returned 'id' will stop reading into this buffer after the specified time ms (milliseconds). When the original block had a shorter duration all data read later won't be put into the buffer.

    Checking for blocks of wav data

    : WAV-PLAYING?          ( id -- val )
    Checks the status of the played wav block 'id'. 'val' is the remaining time until this block is played completely or zero when the fifo block is unused. A typical piece of code code be

    ( id ) BEGIN DUP WAV-PLAYING? DUP MS 0= UNTIL DROP

    : WAV-READING?          ( id -- val )
    Checks the status of the recorded wav block 'id'. 'val' is the remaining time until this block is recorded completely or zero when the fifo block is unused. See WAV-PLAYING?

    Using the Wavboard

    The Wavboard is a new feature in fJACK 1.10. You can now play wav data directly from memory without the other wav playing sheme.

    You can play up to JACK-BOARDJOBS sounds at the same time - this is a config option -, you may also auto-repeat them and define a starting time delay.

    : START-WAVBOARD-JOB    ( delay firstchannel repeats address size channels wavmode -- id )      ( F: speedup -- )
    This starts playing the wavdata from memory after delay milliseconds, it will be repeated repeats times. When delay is negative the absolute value is the number of samples to be delayed. All negative values between -15 and -1 should not be used as these are reserved for extensions. Also when delay is negative the returned value of WAVBOARD-PLAYING? will be the number of samples. firstchannel is the lowest fjack channel to be used. address points to the data, size is the size of the wavdata block in bytes. wavmode is the same as for other wavdata shown in PLAY-WAV.

    Playing such wav data can be done at any sample rate; conversion is done automatically from wav-rate to JACK-SAMPLE-RATE, an extra tuning with speedup. Please note that wavdata at a) the original samplerate and b) 1-4 channels have a much lower jack load so it might be worth to do conversion before. As the whole block of data is already available there is no restriction for wavrates

    : GET-WAVBOARD-POSITION ( id -- done total )
    You can read the number of samples done already and total. Both values are corrected using the used JACK-SAMPLE-RATE

    : SET-WAWBOARD-POSITION ( newpos id -- )
    Sets the playing position for this wavboard job. newpos is the new position, see also GET-WAVBOARD-POSITION

    : SHOW-WAVBOARD ( -- )
    Lists active wavboard jobs

    : STOP-WAVBOARD-JOB     ( delay id -- )
    A wavboard job started earlier can be stopped by this function, it requires the job id and a delay time. delay can also be negative as described in START-WAVBOARD-JOB

    : SPEEDUP-WAVBOARD-JOB  ( id -- ) ( F: speedup -- )
    A wavboard job can be given a new samplerate, the given speedup is multiplied with the speedup used when the wavboard was started.

    : WAVBOARD-PLAYING?             ( id -- time )
    Checks the status of the played wavboard 'id'. time is the remaining time until this wavboard sound is played completely or zero if it couldn't be found. You could use this to start another wavblock right after this one. At least 1 is returned as val, the maximum is $7FFFFFFF. time can be either the time in msec or in samples, this depends the delay parameter when the wavboard job had started.

    : STOP-ALL-WAVBOARD     ( -- )
    You may also stop all jobs right now.




    The Callbacks

    These functions are not to be used in user applications. They are functions to be called by the callbacks defined for this client. They could be used by testing tools to measure the exact performance of the io system but so far no such tools exist.

    : XT-PROCESS-FRAMES     ( frames parameter -- 0 )
    The main processing callback; must return 0. When the client is initialised the jack server starts a realtime thread, this thread later calls this callback and MUST NEVER use any forth word that might add some sort of delay longer than a few ms. Otherwise the client might be terminated or lots of xruns are signalled.

    So --- No semaphores! No disk-io! No terminal-io!

    The XT-PROCESS-FRAMES callback does all the signal processing defined by you in the input->output processing slots. This practically limits things you can do in the processing chain, when writing your own plugins you should keep this in mind. Data sampling to/from disk or network, signal analysis, graphics ... all must be done in a strictly asynchronous way. As you can not safely use semaphores you should use shared memory for the data and thread-local data for your 'housekeeping'.

    : XT-GRAPH-REORDER      ( 0 -- 0 )
    Whenever the jack servers processing graph is changed this callback is executed. This happens when ports are disconnected or a new jack application is starting. The user-defined GRAPH-REORDER-HOOK ( -- ) is executed in this callback to allow you to watch the surrounding.

    : XT-PORT-CONNECT       ( a b parameter flag 0 -- 0 )
    Whenever a port is connected or reconnected this callback is executed. You can find the fJACK ports with >inport ( idx -- id ) and >outport ( idx -- id), both words are in JACKAUDIO. idx is the channel, the returned id is unique and given by the jack server. I won't give any details here how to use these id's or how to use the PORT-CONNECT-HOOK - go and read the jack manual, you will need deeper informations to make use of it. PORT-CONNECT-HOOK is deferred,

    \ : YOUR-DEFINITION ( a b flag -- ) ....... ;
    The prototype for the client supplied function that is called whenever a connection is changed.

     a: one of two ports connected or disconnected, b: one of two ports connected or disconnected
     flag non-zero if ports were connected, zero if ports were disconnected

    : XT-NEW-FRAMES ( fr parameter -- 0 )
    When some other client changes the chunksize this is told here. So far i don't know ANY client that does so and JACK versions up to 0.109 didn't allow this to happen at all. But the jacklib API defines it and the latest JACK development goes in this direction - jackdmp derived sources. See JACK-CHKSIZE

    : XT-NEW-SAMPLERATE ( sr parameter -- FALSE )
    The callback when other clients change the sampling rate for the jackd. See JACK-SAMPLE-RATE

    : XT-CLIENT-STOPPED     ( parameter -- 0 )
    A client that is forced by some other client to shut down tells all it's registered clients about the new situation.

    : XT-COUNT-XRUNS        ( parameter -- 0 )
    When the JACK server gets an interrupt by the audio master clock to do new processing and the queue of callbacks of the last interrupt has not been fully processed, this condition is noted to all clients - it's called a 'XRUN'. See JACK-XRUNS

    : XT-NEW-TRANSPORT      ( s p dummy -- f )
    A Repositioning callback, so far the position is not used.

    : XT-SERVER-ERROR ( str -- str )
    The callback function to receive a jack server error message.

    : XT-SERVER-INFO ( str -- str )
    The callback to receive a jack server information message.

    : XT-SESSION-EVENT      ( event *args -- )
    The callback serving session events

    : XT-LATENCIES          ( mode *args -- )
    The latency callback




    Connecting the ports

    There are some graphical tools around to connect ports between the different JACK clients or the playback or capture ports. You may use these nice tools to connect any ports but how can you save and restore your configuration?

    : SAVE-CONNECTIONS      ( addr cnt -- )
    writes a file specified by the name 'addr cnt' holding information about all ports connected to your fJACK client. This file can later be used with LOAD-CONNECTIONS and CLEAR-CONNECTIONS. Actually the file is a forth source.

    Please make sure the directory fjack/sysfiles has writing enabled for you.

    : CONNECT-JACKPORTS     ( -- )  \ source destination
    Connects two ports, the portnames are read from the input stream.

    : UNCONNECT-JACKPORTS   ( -- )  \ source destination
    Disconnects two ports, the portnames are read from the input stream.

    : CLEAR-CONNECTIONS     ( -- )
    Unconnects all ports of the fJACK client.

    : LOAD-CONNECTIONS      ( addr len -- )
    Connects all connections specified in the file.

    : UNLOAD-CONNECTIONS    ( addr len -- )
    Disconnects all connections specified in the file.




    Getting detailed information.

    : SETUP-LATENCIES       ( -- )
    The current fJACK client (>1.12) takes care about setting latencies to be checked by other clients and the jack server. SETUP-LATENCIES makes sure these data are available, you normally don't have to call it as the plugin handler does so already.

    : JACK-LATENCIES        ( -- max min )
    Returns the latencies found for the fJACK client and it's ports.

    : JACKMESSAGES  ( many -- )
    Shows buffered error messages

    : WRITE-JACKLOG ( -- )
    Writes all messages to a logfile

    : JACKLOG       ( -- )
    Shows the new buffered error messages

    : JACK-INFO     ( -- )
    Prints information about the fJACK client.

    : .JACK-VERSION ( -- )
    prints the current fJACK description

    : ANY-PORT-CONNECTED? ( -- flag )
    Checks for output port connections done.

    : JACK-PORTS    ( -- )
    Prints information about all connections to the fJACK client.




    Running the fJACK client

    : START-JACK-TRANSPORT  ( -- )
    As the name says the JACK server transport is started.

    : STOP-JACK-TRANSPORT   ( -- )
    As the name says the JACK server transport is stopped.

    : JACK-SERVER-RUNNING?  ( -- flag )
    flag is TRUE when a running jack server is available

    : STOP-JACK-CLIENT              ( -- )
    Stops the fJACK client running.

    : START-JACK-CLIENT     ( -- )
    Starts the fJACK client - this is done automatically when fjack is compiled and also via the AtCold hook so you will only need it after you have stopped the client or it has been stopped by the control application. If the clients name is not unique - there could be another fJACK client running already - the jack server will choose another name like client-01. This is totally OK as the client struct in fJACK takes care about this, and knows about the connections. There is one good reason to do so:

    Different applications can all use the default client fJACK and don't have to care about other names. So you may start several Forth terminals and start a fJACK client, all these clients can share the connection files - the true client name is just hidden. You might also choose another client name, see the config option JACK-CLIENT-NAME

    Earlier fJACK versions tried to start the jack server somehow; this has always been somewhat tricky and needed OS dependent tricks. All jack versions 0.121/1.97 or later can start the jack server automatically but autostarting the jack server is not the default anymore, you can change this via the JACK-AUTOSTART config option.

    BTW, you should really switch to modern jack control applications like QjackCtl as they support application sessions, and many more features for a better desktop integration.

    Sessions

    fJACK 1.10 brings support for session management, this feature allows control applications like 'qjackctl' to start a bunch of jack applications and connect them all in a way you defined. fJACK should be saved in a turnkey application and have a starting bash script. This script can be called by the session manager. This feature is very new and not well tested by other users and probably only works on iforth linux systems ... please let me know what you found out.




    The Extern Stream Toolbox

    : STREAM-EXTERN-FRAMES  ( rate channels #frames in-xt out-xt -- left-#frames )
    Whe the fJACK client is not running you may stream audio data through the input->output processing chain. You have to tell the sample-rate, the number of channels and number of samples to be processed.

    Also there is are no defined input and output mechanisms -- instead of using the data delivered and used by the jack server you must define functions to deliver input data and do with the output data whatever you like.

    The in-xt ( addr channel #frames -- ) must fill the buffer at addr with your audio data for the specified channel, it must deliver #frames SFLOATS.

    The out-xt ( addr channel #frames -- ) must take the buffer at addr with the filtered audio data for the specified channel, it must use #frames SFLOATS.

    STREAM-EXTERN-FRAMES returns the number of non-processed frames.

    What can this be used for?

    Let me give two examples - not implemented - for this. You could write an application that reads data from a wav file, does the desired processing and writes a new wav file. You could also use this mechanism to use the existing fjack framework for other hardware drivers.

    Please note that this framework is in alpha state, i did only a few tests and it works, if you use it and and run into trouble, let me know and i'll fix it.





    Using .wav Files





    Some constants - in JACKAUDIO - for better readability

        2 CONSTANT STEREO
        1 CONSTANT MONO
        8 CONSTANT 8BIT-AUDIO
      #16 CONSTANT 16BIT-AUDIO
      #32 CONSTANT 32BIT-AUDIO
     #-32 CONSTANT SFLOAT-AUDIO
    

    As playing and recording of complete .wav files is done in a background thread is it sometimes conveniant to have a hook when the recording/playing is actually started or stopped in the thread.

    DEFER BEGIN-OF-PLAYING          ' NOOP IS BEGIN-OF-PLAYING
    DEFER END-OF-PLAYING            ' NOOP IS END-OF-PLAYING
    DEFER BEGIN-OF-RECORDING        ' NOOP IS BEGIN-OF-RECORDING
    DEFER END-OF-RECORDING          ' NOOP IS END-OF-RECORDING
    

    : .WAV  ( c-addr u -- ) 1 (PLAY-WAVFILE) ;
    Plays the complete file defined by the parameters in a background thread, show some information about the file.

    : .SILENTWAV ( c-addr u -- ) 0 (PLAY-WAVFILE) ;
    Plays the complete file defined by the parameters in a background thread, no information or error messages are printed.

    : .SOUND ( samplerate channels mode c-addr u -- ) 2 (PLAY-WAVFILE) ;
    Play any wav file with specified parameters.

    : STOP-PLAYING  ( -- )
    Any file already playing can be stopped by this command.

    : REPOSITION-PLAY-FILE  ( position relative-flag -- )
    The played wav file may be repositioned during playing. If relative-flag is TRUE the position is relative to the current position, otherwise it's the absolute position. The 'position' always means a sample position, so you don't have to worry about channels and mode.

    : PLAY-FILE-POSITION    ( -- position|-1 )
    This tells you the current position of the played wav file, it's also a sample postion so don't care about channels and mode. If the position is negative, there is no file played at the moment.

    : START-RECORDING       ( time samplerate channels mode name namelen -- )
    Starts recording a .wav file with a given file name. Also the audio parameters must be specified. time is in seconds. This word is the basis of the other file recording commands.

    : STOP-RECORDING        ( -- )
    Stops .wav file recording.

    : RECORD-WAV    ( time name len -- )
    Records a .wav file with the duration of time seconds and the given file name. audio parameters are JACK-SAMPLE-RATE STEREO 16BIT-AUDIO

    : RECORD-SIMPLE ( time name len -- )
    Records a .wav file with the duration of time seconds and the given file name. audio parameters are 16000 MONO 8BIT-AUDIO

    : RECORD-FLOATS ( time name len -- )
    Records a .wav file with the duration of time seconds and the given file name. audio parameters are JACK-SAMPLE-RATE STEREO SFLOAT-AUDIO

    : RECORD-STUDIO ( time name len -- )
    Records a 4 channel .wav file with SFLOAT-AUDIO. Best for later processing in applications like audacity ...

    : WAV-PLAYING-ON?       ( -- flag )
    flag is true when fJACK is already playing a .wav file.

    : WAV-RECORDING-ON?     ( -- flag )
    flag is true when fJACK is already recording a .wav file.

    : SNIPPET ( c-addr u -- ) \ name
    Expects a filename on stack and creates a word 'name'. The file must be a wavfile, it's read into allocated memory. The runtime behaviour of the defined word is: play the audio data immediatly via the fJACK client.

    Snippets are defined in a linked list; audio data are also available in turnkey applications as they are loaded in allocated memory automatically.

    : WAVSOUND ( c-addr u -- ) \ name
    Expects a filename on stack and creates a word 'name'. The file must be a wavfile, it's read into allocated memory. The runtime behaviour of the defined word is:

    name ( delay-ms lowest-channel repeats -- id ) ( F: speedup/-1 -- )

    ... playing the audio data via the wavboard. See START-WAVBOARD-JOB for more details. If speedup is -1 the samplerate conversion is turned off.

    wavsounds are defined in the snippets linked list; audio data are also available in turnkey applications as they are loaded in allocated memory automatically.

    : .SNIPPETS     ( -- )
    Prints a lift of all defined SNIPPETs and WAVSOUNDs.

    : .WAVBOARD     ( name len -- 0|id )
    Plays the file via the wavboard and returns the wavboard job id or 0 when playing could not be started. This is done with a back thread making sure all memory is deallocated when playing has finished




    The MIDI Future ...

    So far the fJACK MIDI support is mediocre but there are many users out there that might profit from a MIDI integration. Also JACKs future plans seem to follow that direction. As i have no experience with MIDI at all i am dependent on contributions from everyone out there. A first step has been done already, the libjack library calls are integrated and the processing callback reads the incoming events and presents them in an event queue.

    Some hope is here - a fJACK (Bill Codington) user has started some sort of midi support. See also examples/

    : SHOW-MIDI-EVENTS
    Shows processed midi events until a key is pressed.




    The FFTW3 library interface

    This library can be tuned for optimized performance, this implementation currently supports the import of system wisdom files - see the fftw3 docs how to generate such a wisdom file.

    Without such wisdom the best algorithm is just estimated, you can change this to one of the other methods in fftw3lib.frt.

    : CHECK-FFTW-SIZE       ( n -- ok-mask )                        \ sizes in fftw may be multiples of primes 2-13
    Checks the desired size of a fftw3 plan. Principally all sizes are suported but many ready-compiled libraries only support a limited number range. For all

    n = 2*a * 3^b * 5^c * 7^d * 11^e * 13^f

    the fftw3 library has algorithms available. The 'ok' is a bit-mask about a-f being >= 1, for probably impossible sizes %000000 is returned

    %000001-a, %000010-b, %000100-c, %001000-d, %010000-e, %100000-f
    %000001 has best algorithms
    %001110 still is good
    %110000 has existing algorithms in probably all compiled libraries but will be slower

    : WINDOWING-OFF ( struct )
    The input parameters for the signal analysis may be 'windowed', this switches windowing off.

    : HAMMING-WINDOW ( struct )
    The input parameters for the signal analysis may be 'windowed', this switches to 'hamming'

    : HANN-WINDOW   ( struct )
    The input parameters for the signal analysis may be 'windowed', this switches to 'hann'

    : FFT-STRUCT    ( n -- struct )
    When you want to to signal analysis using the fftw3 library you must define a forth struct that defines the interface to the fftw3 library. 'n' is the number of samples to be analysed, FFT-STRUCT defines the struct and returns a pointer to it. This struct pointer will be used by the next words.

    Internally there are arrays for real parameter data and complex results at place, a fftw-plan is calculated. See CHECK-FFTW-SIZE for a discussion about possible sizes.

    : INVERSE-FFT-STRUCT    ( n -- struct )
    When you want to to signal analysis using the fftw3 library you must define a forth struct that defines the interface to the fftw3 library. 'n' is the number of samples to be analysed, INVERSE-FFT-STRUCT defines the struct and returns a pointer to it. This struct pointer will be used by the next words.

    Internally there are arrays for real parameter data and complex results at place, a fftw-plan is calculated. See CHECK-FFTW-SIZE for a discussion about possible sizes.

    : #FFT-SIZE     ( struct -- #elements )
    tells the number of samples for this struct.

    : >FFT-COMPLEXDATA      ( struct idx -- addr )
    Calculates the address in a FFT-STRUCT result complex datastructure or INVERSE-FFT-STRUCT input parameter complex datastructure.

    All FFT data are of DFLOAT type and so must be accessed with DF@ DF! and friends. In complex number arrays the real-part is always at the lower address.

    : >FFT-DATA     ( struct idx -- addr )
    Calculates the address in a FFT-STRUCT input parameter datastructure or INVERSE-FFT-STRUCT result datastructure.

    : CALCULATE-FFT ( struct )
    After storing the input data into the FFT-STRUCT parameter array you can do the signal analysis with CALCULATE-FFT.

    : CALCULATE-INVERSE-FFT ( struct )
    After storing the input data into the INVERSE-FFT-STRUCT complex parameter array you can do the signal





    The fJACK Input->Output Processing





    You know that you can put audio processing plugin objects in any one of the slots, this module takes care of it. The order of processing in the jack-callback is like this:


    Processing order

    In effect all plugins can assume, that data in a lower slot number have been completely processed already.




    Defining the plugin objects

    A plugin object is a data struct, some parts of this struct are the same in all plugin objects - see the client.frt source for details. These public parts are used by the plugin manager to take care about safety, setting parameters like frequency, quality and more.

    Also the structs are 'connected' to other plugin objects or the input/output jack ports via plug_input and plug_output. basically a plugin object reads samples from the plug_input history buffer, does it's specific processing and writes data to the plug_input history buffer - in the graphics it's the red arrows. Also you may read data from other history buffers by words like @HISTORY-DATA and friends, see the green arrow and docs in the client manual.


    Plugin objects

    The fjack system knows some predefined plugin object types, when you write other plugins use numbers above #256 for your private applications or let me know to include them in here.

    $01 CONSTANT LOW-PASS
    $02 CONSTANT HIGH-PASS
    $03 CONSTANT BAND-PASS
    $04 CONSTANT BAND-STOP
    $05 CONSTANT ADAPTIVE
    $06 CONSTANT DECODER
    $07 CONSTANT SIN-TONE
    $08 CONSTANT DELAYER
    $09 CONSTANT AUTOLEVEL
    $0A CONSTANT USER-FIR
    $0B CONSTANT MIXER-PLUGIN
    $0C CONSTANT LESLIE-PLUGIN
    $0D CONSTANT PHASER-PLUGIN
    $0E CONSTANT ECHO&HALL-PLUGIN
    $0F CONSTANT VIBRATO-PLUGIN
    $10 CONSTANT EQUALIZER
    $11 CONSTANT EFFECTS-FIR
    

    \ CONSTANT #PRIVATE-DSP ( -- n )
    All plugin objects have some data in common, the public part of the objects struct is used by the plugin handler.

    The common part of a plugin object holds predefined DSP-VALUEs, so when you are making a plugin object definer you must leave this public part in peace. So when defining the struct you should 'start with #PRIVATE-DSP.

    : REORDER-PLUGINS               ( -- )
    It compresses the execution token list to a zero-terminated list for faster processing in the input->output processing chain.




    Using the plugin objects in the signal processing chain

    : LIST-PLUGINS          ( -- )
    prints all objects plugged into the process chain.

    : LIST-ALL-PLUGINS
    prints information about all defined plugins.

    : REMOVE-JACK-PLUGIN    ( filterobject -- )
    revoves the object from the process chain.

    : MAKE&ALLOCATE-PLUGIN-OBJECT   ( initialize-xt dynmemsize objectsize -- object )
    Whenever you define a plugin object you MUST use this function to initialize it. 'size' is the objects size in bytes, the XT is the function to initialize the object.

    Dynamic memory size is allocated and plug_dynmem is set.

    This is done at compile time - so the object can be used at once - also this allows turnkey applications, as when booting all defined objects are initialized.

    When developing your own plugins you can and definitly should use the automatic memory allocation, this is done before the initialize-xt is called.

    : SET-PLUGIN-BYPASS     ( flag filterobject -- )
    You may set the objects bypass status. When the bypass flag is set - flag is TRUE, the plugin just passed the audio data to it's history buffer but does no processing on the data. See comments in SET-JACK-PLUGIN.

    : SET-JACK-PLUGIN       ( filterobject slot channel -- )
    Puts the object in the slot/channel. channel must be between 0 and JACK-CHANNELS-1, slot must be between 0 and JACK-IO-SLOTS. If there are wrong parameters an abort message is displayed. If an object is already in use at that slot it is removed.

    Please remember two things, at the moment you may only use #PLUGINS at the same time, the history buffers are allocated when starting fjack. SET-JACK-PLUGIN and REMOVE-JACK-PLUGIN change the processing order and clear the history buffers used. On slow machines the clearing might take some time resulting in audio drops when changing the plugin objects in use. To overcome this problems you could keep a plugin object in the processing chain and just toggle it's bypass status.

    : RECONNECT-PLUGIN      ( history-buffer object/output-port -- )
    Due to the new input->output sheduler every plugin object or output port can get it's data from another history buffer. This function can be used to connect at runtime. See the example where the two dotted red lines show reconnected plugins, the green line shows a reconnected output port. You can get the address of the history buffer by OBJECT-HISTORY.


    Reconnecting

    : LOCATE-JACK-PLUGIN    ( object -- slot channel true | false )
    Gives information about the slot/channel for a specified object and a TRUE flag when the object was installed or FALSE when not found.

    : GET-PLUGIN-OBJECT             ( slot channel -- 0|filterobject )
    Gives the object found in slot/channel, when no object has been placed there with SET-JACK-PLUGIN a FALSE flag is returned.

    : SET-OBJ-FREQUENCY     ( frequency object -- )
    Many plugins have a specified tune-to-frequency function available, you can use this to do so. This sets the frequency of the specified object, the object itself known what do do, f.e. filters will be tuned to the specified frequency. A few object types have to frequencies, BAND-PASS BAND-STOP filters expect two integers as a special case.

    : SET-OBJ-FLOAT-FREQUENCY       ( object -- ) ( F: frequency -- )
    In many situations the precision of SET-OBJ-FREQUENCY is not good enough, then you can set the frequency by this. BEWARE: ONLY ONE parameter possible as float!

    : SET-OBJ-QUALITY       ( quality object -- )
    Many plugins have a quality - in notch filters this would be the sharpness in FIR and adaptive filters this will be the number of taps. This functions handles the objects quality in a general way so look for the special plugin docs. The effect of setting the quality can be viewed in the fft display plugin, here is a band filter with a lowpass set to ~1.5kHz with 41 and 125 poles


    lowquality


    high quality

    : SET-OBJ-LEVEL \ ( object -- ) (F level -- )
    All plugin objects have a specific level value this word sets it.

    : SLOT/CHANNEL-HISTORY  ( slot channel -- history )
    Finds the history address of a slot/channel pair, if the slot is less than 0 it always means the input port. If a slots object is undefined the result is 0.

    : CALCULATE-FILTER-DELAY        ( slot1 slot2 channel -- n )
    Calculates the phase delay in the processing chain between 2 slots for a given channel, the result is the phase shift measured in samples.

    : OBJ-BENCHMARK ( object -- )
    You can measure the performance of any filter plugin with this, define any sort of filter object and pass the object to this tool.




    The Group of Finite Impulse Response Filters

    : MAKE-FIR-FILTER       ( filtype size -- object )
    Constructs a linear-phase FIR filter object and leave it's address. 'type' can be LOW-PASS HIGH-PASS BAND-PASS BAND-STOP USER-FIR, the 'size' is the number of taps corrected to odd. FIR filters add a signal delay by half the number of taps.

    You may select a different number of taps later via SET-OBJ-QUALITY ( taps object -- ), also you might change the frequency response of this filter by SET-FIR-TUNING.

    BAND-PASS BAND-STOP USER-FIR filters and the SET-FIR-TUNING SET-FIR-WINDOWING SET-FIR-COEFF are only possible when the fftw3 library is installed.

    : FIR-FILTER:   ( type size -- ) \ name
    Defines a named MAKE-FIR-FILTER plugin object, calling 'name' returns the object address.

    : MAKE-EQUALIZER        ( size -- object )
    Define an linear phase equalizer object with size taps corrected to odd, it's a special type of the generic FIR filter. It offers 256 frequency bands in the 0-JACK-SAMPLE-RATE/2 range, each band has it's own defined level. You can set these levels by SET-EQUALIZER-BAND and SET-EQUALIZER-RANGE. You may also select a different number of taps later via SET-OBJ-QUALITY ( taps object -- ).

    For iForth users only: the showfft module now can directly control a bound equalizer filter, see SET-FFT-EQUALIZER in the gui/showfft module.

    : EQUALIZER:    ( size -- )
    Defines a named MAKE-EQUALIZER plugin object, calling 'name' returns the object address.

    : SET-EQUALIZER-BAND    \ ( object -- ) (F dBlevel frequency -- )
    Sets the level for the specified frequency in the equalizer object and calculates the filter coefficients.

    : SET-EQUALIZER-RANGE   \ ( object -- ) (F dBlevel1 frequency1 dBlevel2 frequency2 -- )
    Sets a linear level curve between the level/frequency pairs in for the equalizer objects levels and calculates the filter coefficients. At least one point is set.

    : SET-EQUALIZER-MODE    ( mode object -- )
    Sets the approximation mode for SET-EQUALIZER-RANGE, it can be either 0 for linear or 1 for sinoid mode.

    : EXPORT-EQUALIZER      ( name len object -- )
    Exports the equalizer setings to a file

    : IMPORT-EQUALIZER      ( name len object -- )
    Imports the equalizer setings from a file

    : SET-FIR-WINDOWING     ( windowmode object -- )
    Calculating the filter coefficents is done with a IFFT algorithm using a windowing function. This windowing function can be set for each filter with SET-FIR-WINDOWING. See the literature for descriptions of these windowing functions. The default is set by DEFAULT-FIR-WINDOW.

    The blackmann window 0 gives the deepest out-of-band suppression with a modest steepness.

    The hamming window 1 has a steeper flank with less out of band suppression

    The rectangle window 2 has the steepest flank, highest ripple and lowest out-of-band suppression.

    : SET-FIR-TUNING        ( 0|tuning-xt object -- )
    The MAKE-FIR-FILTERs frequency response curve of each filter can be tuned to your needs by adding a tuning-function-xt describing the desired freuency response. The function must have this stack effect:

    (F level frequency -- your-level )

    It must be able to return values for frequencies from 0 to sample-rate/2.

    : SET-FIR-COEFF         ( 0|set-coeff-xt object -- )
    The USER-FIR MAKE-FIR-FILTER is a very special type of FIR filter, the dsp algorithm is the same but there are many situations where you don't want to specify a frequency response of the filter but want to add effects like hall, echo, delay, averagers and more.

    To use this sort of filter you will need to think about the way the filter coefficients are calculated. Whenever the filter is tuned, resized or defined there is the default function that sets x[0] to 1.0e0, all others are zero. To define a filter according to your needs you will have to tell the coefficient-calculating-function execution token xt. The calculating function must take two integer parameters ( number-of-filtertaps index) and must return the float (F -- coefficient) for that tap. This automatically set the filter size, so don't use SET-OBJ-QUALITY after calculating. The maximum number of taps is #FIR_BUFF so when you need a larger range think of the generic effects plugin.

    The graphics shows the signal flow, the number of taps is the number of multipliers.


    The generic FIR filter signal flow




    The Adaptive Filter object

    : MAKE-ADAPTIVE-FILTER  ( size -- object )
    Constructs an adaptive filter object and leave it's address, the 'size' is the number of taps corrected to even. Adaptive filters add a signal delay by half the number of taps. This filter uses a simple 'least mean square' algorithm.

    You may select a different number of taps later via SET-OBJ-QUALITY ( taps object -- ).

    : ADAPTIVE-FILTER:      ( n -- ) \ name
    Defines a named adaptive plugin object, calling 'name' returns the object address.

    : SET-FREEZE-ADAPTIVE   ( flag object -- )
    You can freeze the adaptive filter coefficients - this is usefull when you have 'tuned' to a noise signal and want to stay with this filter. FALSE will continue adapting otherwise it will be frozen. As changing the filter qaulity means other coefficients SET-OBJ-QUALITY for an adaptive filter always unfreezes it.

    : SET-ADAPTIVE-SPEED    ( n object -- )
    Sets the speed of the adapting algorithm, 0-100 are accepted.




    The Infinite Impulse Response Filter

    : MAKE-IIR-FILTER       ( type -- filter)
    Constructs a 10pole IIR-FILTER (Butterworth) plugin object and leaves the address of the object. The freqency of this filter can be set as usual with SET-OBJ-FREQUENCY the quality (or sharpness) of the filter with SET-OBJ-QUALITY

    : IIR-FILTER:           ( type -- )     \ name
    Defines a named iir-filter plugin object, calling 'name' returns the object address.




    The FFT Analyser Plugin

    Quite often you will have to do a signal analysis of your audio signals somewhere in the input->output processing chain. You can define such an analyser plugin with MAKE-FFT-PORT and can always do an ad-hoc real time analysis of the signal.

    This plugin does an fft analysis, avaraging of the spectrum, finds maxima and the average level in a defined frequency range. In the background this uses the fftw3 library.

    : FFT-PORT-SIZE         ( fftport -- size )
    Tells the size of the port.

    : FFT-HAMMING           ( mode fftport )
    Changes the 'windowing' function of the analyser port; 0 means no windowing, 1 is hamming and 2 hann.

    : FFT-ANARANGE          ( low-frequency high-frequency fftport -- )
    Sets the frequency range in which the average signal level is calculated.

    : FFT-GET-ANARANGE      ( fftport -- low-frequency-index high-frequency-index )
    Gets the frequency range for average analysis.

    : MAKE-FFT-PORT ( size -- fftport )
    Define a fft-port, this can be used as a plugin in the processing chain. For the iForth system there is a fft viewing module available FFT-VIEWPORT: that works perfectly with fft-ports.


    The FFT viewer

    : FFT-PORT:     \ ( size -- ) name
    Defines a named fft-port.

    : FFT-ANALYSIS  ( fftport -- )
    Does an fft analysis of the last audio data in the stream plus avaraging and maxima finding.

    : @FFT-dB-LAST          \ ( fftport idx -- ) (F -- level-dB )
    gets the average level for the last 8 FFT-ANALYSIS for this port.

    : @FFT-dB-AV            \ ( fftport idx -- ) (F -- average-dB )
    gets the average level for the last 8 FFT-ANALYSIS for this port.

    : FFT-MAXIMUM?          \ ( fftport idx -- flag )
    returns TRUE when a maximum is detected at this point.

    : FFT-GREATEST-MAXIMUM  \ ( fftport -- index ) (F -- level )
    Finds the greatest maximum for the port and returns the index.

    : FIND-AVERAGE-LEVEL    \ ( fftport -- ) (F -- avlevel )
    returns the average signal level in the specified frequency range measured in dB

    : FFT-IDX>FREQU         \ ( index fftport -- frequency )
    Converts index for this port to frequency.

    : FFT-FREQU>IDX         \ ( frequency fftport -- idx fftport )
    Converts frequency for this port to index, the port kept on the stack as this is most often used later.

    : SET-FFT-DELAY         ( delay fftport -- )
    The fft analyser allows a delay in the history to compensate in fft displays.




    The Channel-Mixer (and processing) plugin object.

    Allows mixing and processing of audio data.

    : MAKE-MIXER    ( -- object )
    Construct a mixer plugin object and leave it's address. The value in the processing stream is calculated by the presented value plus all other slots/channels that have been added to the mixing by SET-MIXER-LEVEL. SET-OBJ-LEVEL sets the level of the original source of this plugin.

    The standard way to mix the audio data is just adding all values. There is a special feature here to do some more tricks on the data, you may replace the 'adding' function by some other word with SET-MIXER-ACTION.

    : MIXER:
    Creates a named mixer plugin.

    : SET-MIXER-INPUT       \ ( slot channel mixer -- ) (F level delay -- )
    Sets the level and delaytime (seconds) in the mixer. A slot-number <0 means the input port. You can only mix data from smaller slots than the mixer objects slot. The graph shows the mixer inputs


    Mixer sources

    : SET-MIXER-ACTION      ( action-xt object -- )
    This replaces the default 'adding' function of a mixer plugin object by another one.

    The stack effect of the new function must be

    : my-function ( array cnt -- ) (F -- value )

    The array holds cnt sfloats of input data read from all the activated mixer channels, you can do whatever you want to do with these data but you MUST take the parameters and MUST leave the floating result. All data have already been changed to the desired level, the first position in the array holds the original input stream to this slot.

    : .MIXER-INFO   ( object -- )
    Show the status of the mixer object; level is absolute; delay measured in seconds.




    The generic Effect Filter

    Allows the user defined definitions of effects. The generic effect plugin is just a special type of FIR filter, the dsp algorithm is the same but the coefficients are not calculated from a defined frequency response. So the SET-OBJECT-FREQUENCY tool is undefined, instead it uses a callback function defining the coefficient for each tap number.

    : DEFINE-EFFECT-FILTER  ( calc-xt|0 object -- )
    calculates the effect filter coefficients for a given plugin object with the calculating function xt. The calculating function must take two integer parameters ( number-of-filtertaps index -- ) and must return the float (F -- coefficient) value for the index tap. When DEFINE-EFFECT-FILTER is executed, it calls the function for every tap from 0 to {#HISTORY-SIZE}, so the default setting will allow processing in the ~3 seconds range for CD sampling rate. When the xt is FALSE the effects filter is 'just-do-nothing'.

    : MAKE-EFFECT-FILTER    ( -- object )
    Constructs an effects plugin object leaves it's address. Calculation of the filter coefficients is done with DEFINE-EFFECT-FILTER.

    This word is meant for user extensions like hall and echo effects, combs ... let's see what we will make out of it.





    Some tests





    Some working plugins found in fjack/examples are here for you to start. All the plugins defined here and the test suite are NOT included in the default fjack module.




    The Sinus Tone Oszillator Object, a detailed example for writing plugin objects

    When writing more plugins read this and the types.frt carefully, also you can use the prototype.frt as a starting point.

    Words that are used inside the object should be defined in the JACKAUDIO vocabulary.

    ALSO JACKAUDIO ALSO DEFINITIONS
    

    Now a pointer to a counted text should be defined, later this is set to the plug_type object-element; it's there for a) making the plug_type defined and b) the LIST-PLUGINS and LIST-ALL-PLUGINS tools gives more detailed information.

    HERE ," SINUS oscillator" CONSTANT #sin-id
    #PRIVATE-DSP
            DSP-FVALUE sin_phase
            DSP-FVALUE sin_step
            CONSTANT #sinstruct
    

    Then the object elements are defined and the size of the whole object struct is made a CONSTANT

    : CHK-SIN       #sin-id plug_type <> ABORT" Invalid SINUS-TONE object" ;
    Check for a correct plug_type and aborts when this not ok

    Each object type MUST have a specified signal processing funtion; the execution token of this function is stored in the plug_xt element. The funcion MUST take one FLOAT as a parameter and MUST leave one FLOAT as a result. So the depth of the floating stack must never be changed.

    : calc_sin_val  \ ( -- ) (F val -- val' )
            sin_step sin_phase F+ REDUCE.2PI FDUP TO sin_phase FSIN plug_level F* F+ ;
    

    Before you start writing your own audio-data-processing code remember that

  • the code is executed in a JACK demon callback
  • the code MUST exit with a correct stack and floating stack
  • the code MUST NEVER do anything that might give larger delays than a few usecs
  • the code MUST NEVER do text/graphics output, file writing, use semaphores
  • the JACK demon will halt the client as 'zombified' when delay is too large
  • Whenever you want to do things like that: FORGET ABOUT THEM in the process function. What you could and should do: Write thread-safe buffering functions!

    You may watch the jack performance with two included tools, JACK-LOAD and JACK-XRUNS.

    Many - as this example plugin - can be tuned to a frequency; the io_procs module implements a generic tuning word SET-PLUGIN-FREQUENCY which calls a function defined in the object making phase.

    : (tune-sine-object)    ( frequency -- )
            FDUP TO plug_frequency PI F* F2* JACK-SAMPLE-RATE S>F F/ TO sin_step ;
    

    Every object definer needs a function that initialises the plugin object when it is defined and also in the turnkey bootstrap word INIT-ALL-JACK-OBJECTS. If - in some cases - there is no such function needed MAKE&ALLOCATE-PLUGIN-OBJECT must be given 0.

    : (init-sine-object)
             ; \ nothing to do; no allocation
    

    Now you can make an object-definer, as this should be public you should switch to FORTH before.

    FORTH DEFINITIONS
    : MAKE-SINE-OSCILLATOR  ( -- addr )     \ Create a sinus oszillator struct
            \ Make an object and make it the 'current' object
            ['] (init-sine-object) 0 #sinstruct MAKE&ALLOCATE-PLUGIN-OBJECT
            \ Set the correct plug_type and set a plug_filtype mode
            #sin-id TO plug_type  SIN-TONE TO plug_filtype
            \ set the execution token of the signal processing function
            ['] calc_sin_val TO plug_xt
            \ set the tuning function
            ['] (tune-sine-object) TO plug_xt_tune
            \ these data can be permanent
            0e0 TO sin_step  0e0 TO sin_phase
            \ leave the 'current' object on the stack
            @DSP ;
    
    \ A more simple definer makes the object and defines it as a constant
    : SINE-OSCILLATOR:      ( -- ) \ name
            MAKE-SINE-OSCILLATOR CONSTANT ;
    
    
    PREVIOUS PREVIOUS DEFINITIONS
    



    A Bank of notch filters

    Implements a bank of notch IIR butterworth filters, the slots 15-22 in a channel are used

    8 CONSTANT #NOTCHES
    The number of notch filters available

    : GET-FREE-NOTCH        ( -- object flag )
    Try to get a notch filter object from a bunch of 8, if all notches are already in use flag is FALSE.

    : NOTCH-THERE?          ( frequ channel -- object flag )
    checks for an already used notch filter with frequency - or very close to. If such a notch is plugged in, the object and a TRUE flag are returned.

    : SET-NOTCH-FILTER      \ ( frequ channel -- )
    Sets a notch filter from a bunch of 8. If there is a notch already plugged in it is switched off.

    : CLEAR-ALL-NOTCHES     ( -- )
    removes all notches from the processing queue.




    The Delayer Object

    This can add an echo to the current signal, the level of the echo and the original signal can be set.

    : MAKE-DELAYER  ( -- object )
    Construct a delayer object and leave it's address.

    : DELAYER:      ( -- ) \ name
    Define a named delayer object

    : SET-DELAY-TIME        ( useconds object -- )
    Sets the dapay time for the delayer object. Time in usec.

    : SET-DELAY-LEVELS      \ ( object -- ) (F delayed-level now-level -- )
    Sets the signal levels for a delayer object; both level must be floats.




    A Vibrato effect - you can select the vibrato frequency and the strength.

    : MAKE-VIBRATO  ( -- addr )
    Make a vibrato effect plugin object and leave a pointer to it. When setting the frequency by SET-OBJ-FREQUENCY the argument is the frequency*1000 to allow more precise settings.

    The vibrato strength is set as usual by the SET-OBJ-LEVEL method.

    : VIBRATO:      ( -- ) \ name
    make a named vibrato effect plugin




    The Autolevel Object

    This set the JACK-IO-LEVEL to a moderate level.

    : MAKE-AUTOLEVELER      ( -- object )
    Construct a autolevel object and leave it's address.

    : AUTOLEVELER:  \ ( -- ) name
    Define a named autolevel object




    The Phaser Object

    The phaser plugin emulates a phaser/flanger. You can control the timing of the shifted signal, the timing is also modulated by an oszillator.

    : MAKE-PHASER   ( -- object )
    Construct a phaser object and leave it's address. The default settings are a timeshift of 10msec, a strength of 0.2 and an oszillating speed of 5Hz.

    : PHASER:       ( -- ) \ name
    Define a named delayer object

    : SET-PHASER-PARAMETERS \ ( object -- ) (F frequ strength timeshift -- )
    Sets parameters for the phaser object.

    The strength may be choosen from 0 to 1, this controls the oszillating amount, the default of 0.2 is already pretty strong.

    Timeshift in milliseconds, the maximum depends on the history size, probably up to 100 msecs are usefull.

    frequency controls the shift oszillator in Hz.

    : JACK-BENCHMARK         ( -- )
    A little fjack processing benchmark





    Have Fun!





    fJACK is a pretty complex system -- don't be frustrated!

    Read the examples sources and feel free to send me an email, hanno@schwalm-bremen.de