Carla icon indicating copy to clipboard operation
Carla copied to clipboard

JSFX plugin format

Open jpcima opened this issue 2 years ago • 33 comments

This is a source-based plugin format, which is made by Cockos and part of Reaper. (Note that JSFX does not stand for JavaScript.)

There are dozens of these plugins shipped in Reaper (see InstallData/Effects folder). Most are rather short source code files, tagged with a free software license on them.

The implementation of JSFX is partially free software. EEL2 is the underlying programming language, which is published as part of WDL. There exists a free software implementation in jsusfx.

Some notes

  • the language would be useful for prototyping all sorts of dsp
  • the files are arranged in a multi-level folder hierarchy. one might need to compile them in order to scan them
  • the source is expected to compile at runtime to native assembly ; the build system is a little convoluted, which has architecture-dependent code and involves assembly in the nasm syntax.
  • the jsusfx implementation is last updated 3 years ago, and the EEL2 is a bit behind; in particular, the later one has support of the arm 64bit processor.

jpcima avatar Oct 10 '21 17:10 jpcima

lets keep the discussion here then. my main doubt against all this is a sorta "vendor lockin" if we place this in carla. It kinda makes sense for carla to load them since it is really a plugin format in the end, plus the drag&drop would make this handy... but it would be something limited to carla instead of being usable everywhere.

the dynamic nature of these files likely makes it very tricky to have as general plugins though, that part I do not know how to solve.

If carla really ends up supporting jsfx, then I would see it as a motivation to do a carla mini-host, a 1-to-1 wrapper for the things carla is able to load.

how fast is the scanning for these files?

falkTX avatar Oct 10 '21 17:10 falkTX

how fast is the scanning for these files?

This is not yet at a stage where I'm able to tell about the scanning speed.

limited to carla instead of being usable everywhere.

The idea is to go with the jsusfx library after patching it with some desirable updates. It doesn't look much maintained nowadays, so a fork seems in order.

These might be problems

  • plugin unique identification
  • requirement of nasm as build tool, or alternatively have some .o prebuilts in the tree

With that being said, in Reaper the use of these as plugins is pretty transparent, same as other plugin types.

jpcima avatar Oct 10 '21 19:10 jpcima

There is the current status at this branch. Currently it builds the support libraries. https://github.com/jpcima/Carla/tree/jsfx

jpcima avatar Oct 11 '21 07:10 jpcima

Looks good so far. How are we going to deal with plugin folder, is there a spec to where these would be stored? If not, we probably need to store filenames as the carla project state.

Also, I asked about the speed of discovery of such plugins because we can either discovery them through python alike ladspa, dssi, vst2 and vst3 or through libcarla_utils.so that handles this directly (for those formats where scanning is known to be fast) Would be nice if the scanning was as fast as LV2 discovery, but not sure if really possible.

falkTX avatar Oct 11 '21 08:10 falkTX

How are we going to deal with plugin folder, is there a spec to where these would be stored?

It's from some knowledge gathered so far, correct if I'm wrong.

There is not a path defined by spec, neither can Reaper set custom folders; instead, all goes in a fixed "Effects" folder. If there exists something like a unique ID, it might be such as analysis/gain_reduction_scope, a path relative to the effects folder, where the file can optionally be suffixed .jsfx. Files .jsfx-inc should be ignored.

Another thing is the import statements: "this filename will be searched within the JS effect directory" So the plugin should be made aware of what root folder it's loaded from, to set the imports right I guess.

Would be nice if the scanning was as fast as LV2 discovery, but not sure if really possible.

A quick look at jsusfx API hinted this to be possible, but will have to be verified.

jpcima avatar Oct 11 '21 08:10 jpcima

The current progress of JSFX

  • converted the ASM code to GAS syntax, and checked the objdump outputs to be 1:1, removing nasm as dependency
  • implemented the discovery; currently it can't tell if the plugin requires MIDI in/out, or has custom graphics.
  • implemented the frontend mostly, some uncertainty about what plugin label should be
  • set the default paths for each OS to these of Reaper; not added the installation directory, because these JSFX get automatically copied by Reaper into the Home folder, creating duplicates in the scan.

notably missing

  • [x] MIDI in/out: jsusfx API uses byte-based buffers instead of clearly separated events: it was intended for puredata. Also these events don't carry their offset, so they can't be frame-accurate. It needs modification of jsusfx to address these problems.
  • [x] save/load: it should handle the optional serialize jsfx section. According to jsusfx's documentation, this is a to-do item.

There are TODO(jsfx) mentions at relevant places.

jpcima avatar Oct 13 '21 05:10 jpcima

wondering if this is better in a branch until 100% finalized, or already good enough to be placed in the main branch... what are your thoughts on it?

This depends of your own criteria of what makes it acceptable to merge or not. I'd call it beta for the time being, because of a few remaining items.

  • state: i wouldn't make a compatibility guarantee on the state format yet, until this gets tested better. That is, I should extract what state REAPER saves vs ours in a variety of scenarios. For instance, I distrust how jsusfx did file_mem serialization, which is intended for binary blobs I believe, but it wants to store the bytes identical to vars (that is, as floats)

  • midi in/out: needs testing also. The midi_arp, while it outputs, doesn't seem that it works correctly. Also the short events output by jsfx are always 3 bytes, but MIDI allows some to be size 2 or even 1; so there's likely a correction of the event size to do after the output of jsfx.

  • some interaction with frontend and carla in general, please review these relevant TODO(jsfx) comments another thing, it's that perhaps the identification is best handled by Unique ID where that ID would be the path's basename. Again, needs to test again Reaper for what happens there.

Misc: most JSFX sources shipped in reaper have more metadata as comments (plugin category and author) example: //tags: lo-fi distortion filter mangler Perhaps we should parse the info comments. It might be that tags: and author: are planned as keywords for a future JSFX, who knows.

jpcima avatar Oct 14 '21 15:10 jpcima

Verified the state of Jsusfx against Reaper, that's how it is:

  • [x] the state is in 2 parts, parameters ("sliders") and serialization
  • [x] maximum of 64 sliders which are saved
  • [x] Reaper saves the parameters which are equal to default, not Jsusfx
  • [x] serialized values get saved as float sequence, both file_var and file_mem
  • [x] Reaper encodes the float sequence in little-endian followed by base64. Jsusfx lets the client chose the storage format. I went with a form of XML, but it outputs one element per var, I should modify it for base64.

jpcima avatar Oct 14 '21 17:10 jpcima

It can be confirmed from Reaper saves that the relative path serves as unique ID. As an example, the unique ID would be analysis/loudness_meter for the file /opt/REAPER/InstallData/Effects/analysis/loudness_meter.

Does Carla provide a way to handle this?

jpcima avatar Oct 22 '21 12:10 jpcima

The File class provides some ways to make relative paths.

During plugin load the carla settings struct provides access to the paths to look for in plugins. So this seems doable to adapt to in carla.

falkTX avatar Oct 22 '21 12:10 falkTX

During plugin load the carla settings struct provides access to the paths to look for in plugins.

And can the discovery have access to the search paths too? It starts only with the absolute pathname.

jpcima avatar Oct 24 '21 09:10 jpcima

For carla utils library yes, for discovery via CLI, no. Which makes sense, if we run carla-discovery-native jsfx /path/to/something.jsfx how could that possibly know what would JSFX_PATH be?

If we keep carla-discovery are purely informational (like done with lv2) and the real searching&parsing through the utils/CachedPlugins.cpp code, then this will be doable. The first call to carla_get_cached_plugin_count usually includes the paths to look for, so we can cache and use that.

falkTX avatar Oct 24 '21 11:10 falkTX

Understood. Then should carla-discovery of jsfx get rid of doInit perhaps? I make this run the compilation, but success can't be guaranteed without the import path deduced properly.

jpcima avatar Oct 24 '21 11:10 jpcima

I would say we should avoid the compilation if possible, or is that required to fetch plugin info?

falkTX avatar Oct 24 '21 13:10 falkTX

I would say we should avoid the compilation if possible, or is that required to fetch plugin info?

It's not required at all, just a note for later work: it needs to check the presence of a gfx section, that tells if plugin would have UI or not. There's a deficiency of the jsusfx library, which can only identify this after compiling (it's fixable).

Currently, discovery follows a logic such as: if doInit, compile, otherwise just read header

jpcima avatar Oct 24 '21 13:10 jpcima

Personally I would be ok with not knowing if jsfx have custom UIs or not. At least initially.

The "doInit" makes sense if process checks are enabled during discovery, but that only applies for carla-discovery not the utils lib

falkTX avatar Oct 24 '21 13:10 falkTX

  • [x] Note: there appears to be a mistake in file jsusfx.cpp The following should be modified from ifdef to ifndef, to let scripts print their messages. #ifdef EEL_STRING_STDOUT_WRITE In addition, it should preferably use the carla-specific IO functions.

jpcima avatar Oct 31 '21 18:10 jpcima

  • [x] Other: values don't parse correctly when LC_NUMERIC != C Test case is Reaper guitar/phaser, which fails to modulate anything, because of multiplier 0.5 parsed as 0.

jpcima avatar Oct 31 '21 19:10 jpcima

There is a class in carla utils for the locate issues, CarlaScopeLocale or something like that. either put it around the entire file loading or specific sections of it.

falkTX avatar Oct 31 '21 20:10 falkTX

Yes, this source code is C though. I'll fix it. Culprit is here https://github.com/falkTX/Carla/blob/555caea8a0e3aa28c1c1d9df081403b3efeadbe9/source/modules/eel2/source/WDL/eel2/nseel-compiler.c#L5869

jpcima avatar Oct 31 '21 20:10 jpcima

Re: carla-specific IO functions The Jsfx output connects to a function which has the fwrite signature (it writes in chunks). Hence it's not possible to connect with carla_stdout, as is?

jpcima avatar Oct 31 '21 20:10 jpcima

if it expects no newline, then no. all the carla_std* functions append a newline at the end

falkTX avatar Oct 31 '21 21:10 falkTX

it's okay to do a light refactor of carla's io functions, to get access to that FILE handle for direct writing?

also carla_stdout and its friends have FILE* as a static-local into a static-inline function, it could be that these static handles end up multiple. (if multiple source units duplicate the function)

jpcima avatar Oct 31 '21 23:10 jpcima

  • [x] the files should be compiled as part of init, rather than reload a recompilation may modify the number of i/o ports, which is not acceptable.

jpcima avatar Nov 01 '21 00:11 jpcima

Regarding author and category metadata

Many files contain comments similar to these ones.

//tags: guitar modulation filter gain
//author: Cockos

These look similar to the desc: tag except for being commented. Most of the stock jsfx have these, yet it's not a standardized syntax afaik. (it might be for a future extension of the language?) For time being, there is absolutely a possibility of parsing these pseudo-tags.

jpcima avatar Nov 02 '21 19:11 jpcima

Makes sense to try to carefully parse them I think, they provide useful info.

btw, how do we differentiate between FX and synths?

falkTX avatar Nov 02 '21 20:11 falkTX

btw, how do we differentiate between FX and synths?

  • by category pseudo tags ("synthesis")
  • absence of input pins
  • possibly some light code analysis, for presence of midi calls

jpcima avatar Nov 02 '21 20:11 jpcima

  • [ ] jsusfx does not support convolution plugins

It does not support sliders written in that form, and the compilation/readHeader fails: slider1:/amp_models:none:Model

The idea is that this slider (menu rather) controls an index of a file into a directory of wav IR files. These wav files reside in the Data/amp_models folder, whereas the effect is located inside Effects/guitar.

The specification has something to say about this, more precisely: it scans wav/txt/ogg/raw files It does not mention that files should be listed in lexicographical order, but in Reaper they are.

There is library support to implement, but also the pesky path search. (since we have to go one above from Effects and into Data, such that Data is a hardcoded element of the path)

jpcima avatar Nov 02 '21 23:11 jpcima

  • [ ] hidden sliders

The sliders whose desc begins with character '-' should be hidden parameters. Specification says

You can also hide sliders by prefixing their names with "-". Such parameters will not be visible in the plug-in UI but still be active, automatable, etc.

Edit: perhaps leave that one for now until UI support

jpcima avatar Nov 03 '21 01:11 jpcima

  • [ ] output-only sliders

There is this unspecified syntax, which creates a slider with empty range (min=max=0), and its purpose is to serve as output-only parameter. (but we don't normally know that unless examining the code) slider10:0,Reduction (dB)

  • [ ] slider automation from DSP

The observation related to the item above is that DSP can assign to a slider variable and have the slider move as a result. Carla should ~~detect the slider change~~ respond to jsfx sliderchange() API, and automate the parameter. Edit: check also the slider_automate API which jsusfx has marked as to-do

jpcima avatar Nov 03 '21 01:11 jpcima

Just wanted to come and drop a huge note of appreciation and support for this!

Many JSFX are as good or better than paid plugins, and there's such a large community collection of them.

Would be incredible to have a cross-platform implementation of JSFX host.

Thank you for working on this =)

GavinRay97 avatar Nov 15 '21 17:11 GavinRay97

Would be incredible to have a cross-platform implementation of JSFX host.

There exists one as part the host support library. If you can build this, you get a vst plugin which can host jsfx. https://github.com/jpcima/ysfx

I'll write a document later on how to build this.

jpcima avatar Nov 15 '21 19:11 jpcima

Oh this is awesome, thanks for the tip @jpcima !

GavinRay97 avatar Nov 16 '21 17:11 GavinRay97