sfztools / sfizz

SFZ parser and synth c++ library, providing a JACK standalone client
https://sfz.tools/sfizz/
BSD 2-Clause "Simplified" License
417 stars 57 forks source link

Preload list of .sfz files recursively within a directory #1040

Closed kmturley closed 1 year ago

kmturley commented 2 years ago

Thank-you for your great work so far on this open-source tool!

Background

When I loaded the sfizz VST3, it created a folder at: /Users/username/Documents/SFZ instruments

I downloaded/unzipped instruments into the SFZ instruments directory e.g.:

SalamanderGrandPiano/
SalamanderGrandPiano/Salamander Grand Piano V3.sfz
Virtuosity Drums/
Virtuosity Drums/Programs/01-basic-kit.sfz
VSUpright1_SFZ/
VSUpright1_SFZ/Upright No 1.sfz

Problem

I was expecting that when reloading sfizz, it would recursively scan the SFZ instruments directory and find all .sfz files. However I had to manually load each .sfz file into sfizz.

Potential solution

It appears there is some code for file/folder scanning already, so unsure if this is a bug or not implemented yet: https://github.com/sfztools/sfizz/blob/develop/plugins/common/plugin/SfizzFileScan.cpp

However my expectation would be that sfizz will detect .sfz files in the default directory: /Users/username/Documents/SFZ instruments

Or in a custom directory if I select in settings e.g.: /Users/username/Library/Audio/Plug-Ins/SFZ

Impact

With auto-scanning of directories you will save time for every user, allowing them to focus on selecting a preset rather than selecting files/folders. Nearly all audio plugins auto-scan and load presets on the users behalf to save them time. This will improve adoption of sfizz and .sfz files as they will require less manual steps to install.

I'm working on a plugin management system, which installs plugins automatically: https://github.com/studiorack I would like users to be able to install sfizz + a .sfz sample pack with easy commands: e.g.

studiorack plugin install sfizz
studiorack plugin install salamander-grand-piano

For this functionality, I need to install the .sfz sample pack into a default directory, where it will be automatically scanned by sfizz.

Debugging

Here is a screenshot from my sfizz VST3 containing the debug information:

Screen Shot 2021-11-21 at 8 50 25 AM
paulfd commented 2 years ago

Hi! Thanks for the thorough proposal. It's a good idea. Wouldn't using "presets" functionality in DAWs be a good approach for this kind of thing (rather than in the plugin itself I mean)?

Edit: I'm not sure how sfizz reacts to presets to be honest, so we may need to work a little to make it seamless :smile:

kmturley commented 2 years ago

I have seen some VST plugins which allow you select the preset within the DAW, OR the VST itself and they stay in sync e.g. GuitarRig SoloMaker preset is selected in both places:

Screen Shot 2021-11-21 at 10 55 07 AM

However have not seen this functionality in any open-source plugin yet... I don't think it's necessary, although it's cool. I believe it allows the preset to be changed via a midi controller?

You already have the functionality to pick up .sfz files in the same folder (you can use left/right arrows to select the next file)

Screen Shot 2021-11-21 at 10 49 27 AM

A short-term feature which would be helpful, to extend the existing functionality to be recursive?

I don't know C++ very well, but it appears you have some recursive code already here: https://github.com/sfztools/sfizz/blob/develop/plugins/common/plugin/SfizzFileScan.cpp#L74

The method seems to be able to loop through subdirectories: https://en.cppreference.com/w/cpp/filesystem/recursive_directory_iterator

paulfd commented 2 years ago

One issue with this is that it's pretty common practice for sfz libraries to have "included files" in subdirectories, so you run the risk of having alot of irrelevant files. Some libraries even have pretty complex include structure (e.g. the Naked Drums one).

This file scanning you reference is made to find a file if it were saved in another computer/session. We can use this with preset abilities to have it automatically find the file in the user SFZ folders maybe ?

paulfd commented 2 years ago

So the VST SDK just reads presets from a set number of locations depending on the OS: https://developer.steinberg.help/display/VST/Preset+Locations

These are just "saved states" from the plugin, so you can use supposedly any host to create the .vstpreset file and put these in the user preset folder. Using this in combination with our VST3 plugin "User SFZ directory" means that the SFZ file will be search in the set user directory by default if not found in the actual path contained. To be more robust, your script may also edit the SFZ path in the state---it's just binary data---so that they match the actual path it was installed too.

What we may then do is try to query presets from within the plugin to display a list, as you showed. Not really sure how to do this but there's probably a way; at the very least we can scan the folders manually.

paulfd commented 2 years ago

Note: in LV2 it's even easier, since the states/presets are in textual form. However the user SFZ directory is not active with the LV2 plugin, which has its own portability mechanisms for paths.

kmturley commented 2 years ago

I was reading through the Steinberg documentation and found this page which gives more context on presets: https://developer.steinberg.help/pages/viewpage.action?pageId=9798267

Presets

"the data of a preset is nothing more than its state... allow the user to change these presets by the means of parameter editing. the user can be allowed to store the modifications as preset file"

An ideal use-case would be to tweak the SalamanderGrandPiano filters/volume/panning/effects and save a preset like Muffled Piano. Here is screenshot of the parameters which could be saved in a preset:

Screen Shot 2021-11-22 at 3 28 36 PM

Program lists

"If a plug-in uses a large pool of programs that require some kind of caching or that need to be preloaded, using preset files may not be a sufficient choice. In this case, the plug-in can define a program list... This way of using program lists should only be chosen if programs do require a lot of resources that need to be cached in order to achieve fast program changes (good examples for this are sample-based plug-ins)."

An ideal use-case would be a sample-based plug-in, which use more memory than presets and their could be more items.

This is also interesting:

"All programs are always transmitted as a flat list to the host. But the plug-in can assign a number of attributes to each program of the list. This enables the host to organize and filter them in a very flexible way. The value for an instrument category of a program is 'Piano' for example. But it is possible to specify a subcategory like 'Acoustic Piano' as well. In this case, the strings need to be chained like this: 'Piano|Acoustic Piano'. This allows the host to organize presets in a category tree view, for example."

Potentially you could do something like:

kinwie commented 2 years ago

Hey, just read this! This is a really interesting request. I have the same thought for a long time for Aria/sforzando sfz presets organization, with its unpleasant behavior but have to adapt to it since it will never changed, and I made a workaround for myself. So as DropZone. It has a cool preset system and can be use as example case.

I'll post some details later to give some thoughts for Paul to consider for this issue.

IsaakCode commented 2 years ago

I have seen some VST plugins which allow you select the preset within the DAW, OR the VST itself and they stay in sync e.g. GuitarRig SoloMaker preset is selected in both places: Screen Shot 2021-11-21 at 10 55 07 AM

Just a sidenote: Ableton can read/display the presets of a plugin, if the plugin is in VST2 format, but fails to sync the presets if you use the VST3 version of the same plugin. At least that what I experience in Ableton Live 11.0

I believe Ableton only started supporting VST3 recently, so it's best to stick with VST2 in the meantime, until it's wholly supported.

paulfd commented 2 years ago

@IsaakCode sorry but this cannot happen. We don't have an historical VST2 distribution license, and we cannot legally distribute it. If you have the VST2 SDK you can compile one though. Whatever we do on this front can only be VST3 and LV2.

kinwie commented 2 years ago

So. I'm going to post some long presentation...

First, I need to explain that I'm not native VST user but came from Pro Tools realm in RTAS and AAX plugin format, which has some different preset management than VST. Though, I use a lot of VST2 so I know a bit how its preset works. For VST3 I have no clue, so please correct me if I'm wrong. Let's start with the preset structure.

If I understand correctly, what previously @kmturley wrote, Presets and Program lists (for VST3?), is similar to FXP and FXB in VST2. The description matched afaik. In VST2, single preset iis saved as .fxp file by the host and the chunk that contain all presets saved as .fxb. Then loading a fxb file from host, user can switch and change preset within the list. And all the plugin state also saved in the host session file. For most synths and instruments plugin, they are responded to MIDI Program change message to change and select preset within the bank (fxb). IIRC with VST2, different host offer different way to organize and load fxp/fxb file. So then devs made their own preset system inside their VST2 plugin to manage their presets more convenient, faster and easier for their users experience. P.S. : As far as I can see, sfizz as VST3 doesn't use chunk and only has 1 preset slot and this is better imho.

In other format as example, RTAS/AAX only has 1 host preset file, with .tfx extension, which stores only the current state/setting of that plugin. The tfx files are stored at a fixed plugin settings folder with its own root folder and can be flexibly organized by users. Then the list of all tfx presets will appeared at that plugin's preset loader. This format doesn't recognize MIDI program change message like VST2 but for browsing and selecting those presets can be done by using shortcut keys (up/down arrow) when the preset window opened. But for plugins that are ported from other format e.g. VST, the chunk state also well-saved with .tfx file.

So my first comment is, each host has their own preset file format that not compatible each other unless there is a converter for it. For plugin that will have tons of presets like synths and samplers, relying on host native format is not a pretty solution for preset exchange between users. In this case, sfizz already exist in VST3, LV2 and AU, and maybe next AAX, so I suggest to not depend on host-format preset system but rather, have sfizz's internal preset management system.

kinwie commented 2 years ago

Second is, to talk how the preset works related to SFZ files.

Some examples already exist afaik and for different approaches. What we do first in a sampler-type plugin is of course load a sample set, in this case we load a sfz file. So I can say that, sfz file is actually our 'raw' preset file. Two approaches for the usage of sfz that I know are :

  1. The sfz file is fully configured or no much CC parameters involved or just a little tweaks needed that not so often, so the sfz can be just loaded and played. In this case, we don't need to make another plugin preset for that sfz. This is common for acoustic instruments.

  2. The sfz only contain essential opcodes e.g. only key-mapping and velocity-mapping, which is intended so it will use the plugin's synthesis capability to shape the sound into desired one. This usually synths type sounds. Some sfz-based plugins that mainly use this method e.g. Cakewalk Rapture/DimPro, Zampler, Alchemy, etc. Then we have the sfz file and the plugin preset file for the sound to load as desired.

For the 1st usage, sforzando and DropZone has an internal browser for the "ready-to-play" sfz to be loaded and organized. Both use pop-up style browser. I attached pics for these two :

So, users can freely organizing their sfz library, open the broswer, load the desired sfz and play, as in the pics I organized them by instruments category (but I also have vendor folders for some big-size libs, just for example).

sforzando_user

dropzone_sfz

Setting wise, Aria/sforzando use a root folder that need to be specified by users to be able to show it as User's pop-up browser. DropZone also use a root folder and it recognize sfz file, as well as wav and aiff to be appeared at its browser.

Usually sfz samples folders are placed along side the sfz preset file and put in one folder. When placed like this, all the audio samples and sfz files will be scanned by both sforzando and DropZone. One disadvantage with sforzando is that when the library folder go bigger and bigger, the scanning wil take more longer time and sforzando always scan this User's sfz folder on every first load because it doesn't use a cache file. Well, cause of this issue I create my own workaround for my growing libs :))

OTOH, DropZone use cache file. So it will only scan once and next load is just fast as it is. Adding new stuff just need to refresh the browser. Selecting next and previous preset can use keyboard shortcut when the browser is opened, using Up/Down arrow key and Enter. This design is much more convenient imho.

For the 2nd usage, as example in DropZone or Rapture, the sfz just a plain key-mapping then tweaks all the needed parameters from plugin's UI controls then saved as new preset file. Cakewalk stuff (Rapture, DropZone, DimPro, etc) use .prog file as the chunk/main preset file and .elem for each Element slots preset. Both file will be placed at the root folder for browsing. Aria/sforzando also use .ariax preset file for this kind of usage to save its multi-slots setting in ARIA Player or as 'snapshot' in sforzando.

aria_multi

In this case I think sfizz also need some kind of own file extension for this kind of usage that can be cross-platform transferable.

paulfd commented 2 years ago

Hi,

So thanks for the input and writeup. Not entirely sure what's the best solution for this. Having something handled through the plugin itself does make sense, however I'm not entirely fond of duplicating functionality if it's not needed. Also, I mentioned the preset functionality because of @kmturley scripts

I'm working on a plugin management system, which installs plugins automatically: https://github.com/studiorack I would like users to be able to install sfizz + a .sfz sample pack with easy commands: e.g.

studiorack plugin install sfizz studiorack plugin install salamander-grand-piano

For this functionality, I need to install the .sfz sample pack into a default directory, where it will be automatically scanned by sfizz.

I still believe recursively iterating in folders is going to be messy, since you always have weird include patterns in sfz files, with subsubsubfiles and the like. The best one could do is build an include tree somehow, and find all "root" files recursively, but I'm sure there are some libraries where this wouldn't work (or output garbage files that people forgot are there).

If you're building a way to automate plugin installation, I think adding a preset list that is installed alongside the plugin is probably much easier and can be done right now without waiting for a more complete preset functionality builtin. I also has the bonus that you can save the current "CC and keyswitch state" naturally in the VST3 state, which I think is what these .ariax and .prog files do as per @kinwie writeup.

Then, for the builtin preset functionality, I have to think more and this will not be on my immediate checklist but I'll keep it in mind :)

kmturley commented 2 years ago

Thanks for the response. I understand the "loop through folder" solution might not be optimal. I mentioned it because that is how VST plugins are currently handled by DAWs. It loops through the folder on startup only, and then caches the list of discovered plugins.

For my studiorack solution, I also needed to detect the VST/SFZ plugins installed on a system:

studiorack plugin listLocal

This outputs:

┌──────────────────────────────────────────────────────────┬──────────────────────────────┬─────────┬────────────┬──────────────┬───────────────────────────────────┐
│ Id                                                       │ Name                         │ Version │ Date       │ License      │ Tags                              │
├──────────────────────────────────────────────────────────┼──────────────────────────────┼─────────┼────────────┼──────────────┼───────────────────────────────────┤
│ studiorack/salamander-grand-piano/salamander-grand-piano │ Salamander Grand Piano       │ 3.0.0   │ 2021-09-20 │ other        │ Samples, Piano, Yamaha, sfz       │
├──────────────────────────────────────────────────────────┼──────────────────────────────┼─────────┼────────────┼──────────────┼───────────────────────────────────┤
│ studiorack/smartguitaramp/smartguitaramp                 │ SmartGuitarAmp               │ 1.2.0   │ 2020-12-09 │ apache-2.0   │ Amp, Guitar, Effect               │
└──────────────────────────────────────────────────────────┴──────────────────────────────┴─────────┴────────────┴──────────────┴───────────────────────────────────┘

Behind the scenes, this has run a local node file system command:

glob.sync('/Users/username/Library/Audio/Plug-ins/**/*.{component,aax,rta,lv2,sfz,tdm,vst,vst3'})

The length of the scan depends on how many files/folders match the glob command. One of the optimizations I make is to look for the file extension:

/Users/username/Library/Audio/Plug-ins/SFZ/studiorack/salamander-grand-piano/salamander-grand-piano/3.0.0/Salamander Grand Piano V3.sfz

Then look for a .json file with the same path:

/Users/username/Library/Audio/Plug-ins/SFZ/studiorack/salamander-grand-piano/salamander-grand-piano/3.0.0/Salamander Grand Piano V3.json

If the .json file exists, load the metadata (which is rich compared what the .sfz file contains):

{
  "author": "Alexander Holm",
  "homepage": "https://github.com/sfzinstruments/SalamanderGrandPiano/",
  "name": "Salamander Grand Piano",
  "description": "Yamaha C5, recorded with two AKG c414 disposed in an AB position ~12cm above the strings.",
  "tags": [
    "Samples",
    "Piano",
    "Yamaha",
    "sfz"
  ],
  "version": "3.0.0",
  "id": "salamander-grand-piano",
  "date": "2021-09-20T07:00:00.000Z",
  "files": {
    "audio": {
      "name": "salamander-grand-piano.flac",
      "size": 365662
    },
    "image": {
      "name": "salamander-grand-piano.jpg",
      "size": 88748
    },
    "linux": {
      "name": "salamander-grand-piano.zip",
      "size": 748416023
    },
    "mac": {
      "name": "salamander-grand-piano.zip",
      "size": 748416023
    },
    "win": {
      "name": "salamander-grand-piano.zip",
      "size": 748416023
    }
  },
  "release": "v3.0.0",
  "license": {
    "key": "other",
    "name": "Other",
    "url": "https://choosealicense.com"
  },
  "repo": "studiorack/salamander-grand-piano",
  "paths": []
}

Could you use a similar approach?

kinwie commented 2 years ago

Hi Paul,

Yeah, this could be a long-term implementation I guess. I just wrote how those two sfz players' browser behavior that I understand how they works. My example only for a generic type of categorization. Different users have different choice of course. How it will implemented in sfizz fully depend on your flavor of course and how wide sfizz will be used by worldwide users in the future.

I still believe recursively iterating in folders is going to be messy, since you always have weird include patterns in sfz files, with subsubsubfiles and the like. The best one could do is build an include tree somehow, and find all "root" files recursively, but I'm sure there are some libraries where this wouldn't work (or output garbage files that people forgot are there).

I know my sfz files are ugly at some point, hahaa. Anyway, how I scripted an sfz is depend on how complex the instrument and samples are. Some simple few layers of synths or percussion sampled instruments will result in a very simple sfz in my way. I'm not pushing a crazy nerd-like sfz scripts everytime, lol.

So taking Naked Drums as example, It seems less tidier than my other sfz. It because the sample naming is embeded with some auto-generated unique code. So everything needs to be break in detail and looks so messy. I have other good example of drums sfz that very tidy, which because the samples are edited and named very tidy, so the sfz goes tidy too.

I'll explain why I did this. As we know, sfz sturcture is pyramid-like, one <global> pointing to more and more lower header until <region>. My sfz is an upside-down pyramid. Where the highest headers have lot of its own scripts with its own opcodes values, pointing to less and less downward to lower headers, until the <region> headers. This way, making changes to the region scripts will affect all other higher headers simultaneously (e.g. the group or master). For me this works well for instruments that have multiple layers of velocity or round-robin etc, with the same mapping range, e.g Piano. And instruments that have multiple mics, which are drums mostly.

This approach existed already for long, in SoundFont (.sf2) format, where one "Preset" can have (include) multiple same "Instruments" with different parameter values. And I choose to use .txt file for those child scripts because it won't be shown in sforzando's browser. Sforzando only register .sfz extension for its known file in its browser, while DropZone's browser recognize all the files it can open, .prog .elem .sfz .wav .aiff. While I know sfizz can open a txt file as sfz preset. Well, if I'm going to put out sfz instruments again, it will still the same scripted that way :))

Just a writeup, not a request :p

kinwie commented 2 years ago

I also has the bonus that you can save the current "CC and keyswitch state" naturally in the VST3 state, which I think is what these .ariax and .prog files do as per @kinwie writeup.

Yes in .ariax, CC and keyswitch state are saved, along with all other player-level settings, e.g. Tune, Trans, Poly, RAM allocation, Disk Pre-cache, etc. So for example, one new sfz instrument loaded via .ariax.can have its own new RAM allocation setting, overiding previous RAM setting.

In .prog actually not the CC state are saved, but it's the plugin parameter state. As example with Rapture, most of the shown knobs in GUI has its parameter lane, but not per CC number. The CC number (by MIDI-Learn or manual assignment) are saved globally in another file. Keyswitch not saved within, it's called by the DAW track automation. The onboard Flex-EG also saved with internal format, not published as VST parameters. It just how it works, not important though. I just sharing some information how Cake's players preset behave.

I believe you can find the best solution for how sfizz preset state implemented.

paulfd commented 2 years ago

I know my sfz files are ugly at some point, hahaa. Anyway, how I scripted an sfz is depend on how complex the instrument and samples are. Some simple few layers of synths or percussion sampled instruments will result in a very simple sfz in my way. I'm not pushing a crazy nerd-like sfz scripts everytime, lol.

No problem at all, I'm just trying to think about supporting all these use cases. Using a different file extension is smart too :smile:

kinwie commented 2 years ago

Wait, I remember in some past talks, this sfz file listing and browser feature would be suitable for the player version of sfizz? When someone adopt sfizz as sfz engine to make a player plugin of it, then an integrated browser is part in the player UI, no?

dardoor commented 2 years ago

I also think it would be nice to have a better sfz browsing functionality, because the current loading through the file manager is slow and cumbersome. I imagine some button that the user L-clicks and then navigates to the desired sfz file, just like in Sforzando, as kinwie mentioned a few posts earlier. Here's a rough sketch, just so we know what I'm talking about: sfizz-edit (Edited with a slightly better looking button. "Q.Load" stands for "quick load".)

There could also be a R-click context menu on the button to select the SFZ folder and rescan the library, if it isn't done automatically (if automatic scanning slows the startup, you can also delay it by a couple of seconds).

I think this feature would reduce the friction and encourage more users with existing SFZ libraries to try Sfizz. And maybe it's not too hard to implement (?). Later on (long term), a fancier library management could be added. I imagine it would have features such as favorites, tags/categories and search.

redtide commented 1 year ago

Stupid question (I'm a bit confused about this): is this about to NOT preload all the instruments but just build a menu like in sforzando, to choose instruments to load from there when needed instead having to deal manually with several filedialogs? If yes than this should be an UI feature, not in library.