Filesystem Calls

November 11, 2025 ยท View on GitHub

The MetaModule uses newlib for libc. This provides standard library filesystem functions like fopen, fread, fclose.

You can use these exactly as they are used on a desktop computer. However, there are some things to keep in mind:

  • Paths are different. Paths start with the volume name (3 characters), then a colon, then a slash. The volumes available on the MetaModule are:

    • sdc:/ -- The SD Card drive.
    • usb:/ -- The USB drive.
    • ram:/ -- The internal RAM disk. This is where plugins live, as well as unsaved patches. Everything in here is temporary. You could create temp files here, but it's usually a better idea to just store temp data in memory as normal C/C++ variables. If you need shared global state, a small file could work (like a lock file used in Linux systems).
    • nor:/ -- NOR Flash (internal storage). There is a ~2MB volume on this flash chip that can hold patch files and other data. The filesystem is LFS, but you don't need to consider that when using the drive. It's highly recommended to avoid using this drive except for very tiny files (and not a lot of files). Users will want to save their patches here, so do not fill this up with module or patch data.
  • Users might eject a disk at any time. Always check for errors when doing any file system access and handle them appropriately.

  • Filesystem access should never be done from the audio context.

  • Disk access is slow. The MetaModule was not designed to read/write to disks quickly, like a desktop computer would be designed. Also, SD Cards are by nature slow to read and write. Users might be using a slow or heavily fragmented disk. USB drives vary widely in performance. Multiple modules may be requesting disk access at the same time as your module. All these factors mean it's not surprising if a read request occasionally takes up to 4 or 5 seconds even for just a few kB of data. You must plan for stalls and delays. If you have large files, buffering data to/from RAM is a good idea.

  • Disk access is not allowed from the audio context. That is, you cannot read or write files in the module's process() or update() function. Accessing the filesystem is allowed only in the module's constructor and destructor, load_state or save_state function (dataToJson and dataFromJson for VCV ports), in any GUI code (*_graphic_display()), VCV context menus, and in AsyncThreads. See CoreProcessor for a detailed discussion.

    Note: do not access the filesystem in the audio context. This is also true for desktop computer usage, but due to the threading model and speed of modern disks, it might only cause problems on some user's computers some of the time, and appear to work OK other times. However, it violates DSP best practices and should be avoided regardless of the intended platform.

Helper functions

See filesystem/helpers.hh

Namespace: MetaModule::Filesystem

Filesystem::translate_path_to_local()

std::string translate_path_to_local(std::string_view path, std::string_view local_path, unsigned num_subdirs = 0);

Translate a path for a file on a computer hard drive, to a path for a file on the MetaModule.

The common use case is when a module stores a path to a sample or wavetable file. The module can call this function to translate the stored path into one that's relative to the currently playing patch file.

The function works by appending the file name from the computer path to the provided local path. Typically the local path would be the path to the currently playing patch file.

Stated another way: you give it a path to a file on a hard drive, and it creates a path to a file with the same name, but in the patch directory on the MetaModule. From the user's perspective, they would need to copy the file into the same directory as the patch .yml file on their USB drive or SD Card, and then everything will work as expected.

The returned path may or may not refer to an actual file, so the module must handle the case where the file does not exist.

As an optional parameter, one or more parent directories from the computer path can be appended. This lets the module access files in a sub-directory that's in the same directory as the patch file (for example samples/).

The function checks if the path appears to be local already by seeing if it begins with a MetaModule volume name. If so, it returns the path unaltered. This allows the module to always process all file paths with translate_path_to_local.

Parameters:

  • path: the path from a computer that you want to translate. If the path is already local, it will be returned unaltered. If num_subdirs is 0, the filename will be extracted from the path and the rest will be ignored. If num_subdirs > 0, the filename plus num_subdirs parent directories will be kept.
  • local_path: the MetaModule path you want to prepend. Typically this is Patch::get_dir().
  • num_subdirs: sets how many subdirectories to capture from path (default 0, max 2)

This function allocates strings, and is not safe to call from the audio context.

Example usage:

The simplest way is to have the user copy any files that a patch uses into the same directory as the patch .yml file. Then the module calls this:

std::string mm_path = Filesystem::translate_path_to_local(vcv_computer_path, Patch::get_dir());

mm_path will point to a file with the same name as the file in vcv_computer_path, but in the same dir as the currently playing patch file.

#include "filesystem/helpers.hh"
#include "patch/patch_file.hh"

using namespace MetaModule;

class MyModule : rack::engine::Module {
    
    //...

	void dataFromJson(json_t* rootJ) override {
		json_t* samplePathJ = json_object_get(rootJ, "sampleFilePath");
		if (samplePathJ) {
            // Get the path to a file on a computer hard drive, e.g. /Users/Me/rack/samples/loop.wav
			std::string samplePath = json_string_value(samplePathJ);

            // Translate the path to "usb:/loop.wav"
            std::string localSamplePath = Filesystem::translate_path_to_local(samplePath, Patch::get_dir());

            load_sample_file(localSamplePath);
        }
	}


Examples with different num_subdirs

translate_path_to_local("/Users/4ms/music/rack/samples/loop.wav", "sdc:/live-set/", num_subdirs)

  • num_subdirs = 0: => sdc:/live-set/loop.wav
  • num_subdirs = 1: => sdc:/live-set/samples/loop.wav
  • num_subdirs = 2: => sdc:/live-set/rack/samples/loop.wav

More Examples:

- translate_path_to_local("/Users/user/rack/samples/a.wav", "sdc:/", 0)				==> sdc:/a.wav
- translate_path_to_local("/Users/user/rack/samples/a.wav", "sdc:/", 1)				==> sdc:/samples/a.wav
- translate_path_to_local("/Users/user/rack/samples/a.wav", "sdc:/patches/", 0)		==> sdc:/patches/a.wav
- translate_path_to_local("/Users/user/rack/samples/a.wav", "sdc:/patches/", 1)		==> sdc:/patches/samples/a.wav
- translate_path_to_local("C:\User\rack\samples\a.wav", "usb:/patches/", 1)			==> usb:/patches/samples/a.wav
- translate_path_to_local("usb:/samples/a.wav", "nor:/ignored/", 1)					==> usb:/samples/a.wav (path is already local)
- translate_path_to_local("/root/a.wav", "usb:/patches/", 0)						==> usb:/patches/a.wav
- translate_path_to_local("/root/a.wav", "usb:/patches/", 1)						==> usb:/patches/root/a.wav
- translate_path_to_local("/root/a.wav", "usb:/patches/", 2)						==> usb:/patches/root/a.wav (only one parent dir)

Filesystem::is_local_path()

bool is_local_path(std::string_view path);

Given a path, returns true if it appears to be a valid MetaModule path.

This only checks if the path starts with a valid volume name and colon, it does not check if the file or directories actually exist or if the volume is mounted.

Valid volume names (with colon appended) are:

  • sdc:
  • usb:
  • nor:
  • ram:

This function is safe to call in the audio context.