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).
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.
This is a good place to find jack: http://jackaudio.org/download or go for compiled packages for your system.
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:
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
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.
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:
See the 'What can be configured in fJACK?' chapter to find out more about changing such config options.
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.
History
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.
All iForth versions are supported. The fJACK sources should be in - or go to - the include directory. IN fjack will do.
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.
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.
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.
\ 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.
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
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.
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.
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.
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
: *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.
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.
: 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
: 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.
: 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?
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.
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
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.
: 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.
: 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.
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.
: 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.
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
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.
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
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:
In effect all plugins can assume, that data in a lower slot number have been completely processed already.
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.
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.
: 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.
: 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
: 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.
: 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.
: 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.
: 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.
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.
: 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.
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
: 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.
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 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.
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
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
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.
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.
: 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
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 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
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