Skip to content

Instantly share code, notes, and snippets.

@dfl
Last active January 27, 2026 17:19
Show Gist options
  • Select an option

  • Save dfl/45e894d28e8df238adc6b6109a92d827 to your computer and use it in GitHub Desktop.

Select an option

Save dfl/45e894d28e8df238adc6b6109a92d827 to your computer and use it in GitHub Desktop.

Revisions

  1. dfl revised this gist Jan 24, 2026. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions default-parameters.md
    Original file line number Diff line number Diff line change
    @@ -91,3 +91,8 @@ process = filter(1000, 1.5);
    process = filter(1000, gain: 2.0); // skip q, override gain
    process = filter(freq: 1000, gain: 2.0); // named + default
    ```

    ### Branch

    - **Branch**: [`feature/default-parameters`](https://github.com/dfl/faust/tree/feature/default-parameters)
    - **Base**: `master-dev`
  2. dfl revised this gist Jan 24, 2026. 1 changed file with 9 additions and 0 deletions.
    9 changes: 9 additions & 0 deletions python-style-imports.md
    Original file line number Diff line number Diff line change
    @@ -75,3 +75,12 @@ The large diff in `faustlexer.cpp`, `faustparser.cpp`, and `faustparser.hpp` is
    | `global.cpp` | +1 |
    | `global.hh` | +1 |
    | `sourcereader.cpp` | +40 |

    ### Backward Compatibility

    Fully backward compatible - existing import syntax continues to work unchanged.

    ### Branch

    - **Branch**: [`feature/python-style-imports`](https://github.com/dfl/faust/tree/feature/python-style-imports)
    - **Base**: `master-dev`
  3. dfl revised this gist Jan 24, 2026. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions keyword-arguments.md
    Original file line number Diff line number Diff line change
    @@ -121,3 +121,8 @@ After integration:
    filter(freq, q=0.707) = ...;
    process = filter(freq: 1000); // q defaults to 0.707
    ```

    ### Branch

    - **Branch**: [`feature/keyword-arguments`](https://github.com/dfl/faust/tree/feature/keyword-arguments)
    - **Base**: `master-dev`
  4. dfl revised this gist Jan 24, 2026. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions inline-libraries.md
    Original file line number Diff line number Diff line change
    @@ -129,6 +129,5 @@ Fully backward compatible - new option, no changes to existing behavior.

    ### Branch

    - **Worktree**: `../faust-inline-libs`
    - **Branch**: `feature/inline-libraries`
    - **Branch**: [`feature/inline-libraries`](https://github.com/dfl/faust/tree/feature/inline-libraries)
    - **Base**: `master-dev`
  5. dfl revised this gist Jan 24, 2026. 1 changed file with 9 additions and 1 deletion.
    10 changes: 9 additions & 1 deletion keyword-arguments.md
    Original file line number Diff line number Diff line change
    @@ -107,7 +107,15 @@ Fully backward compatible - existing positional syntax continues to work unchang

    ### Future Work

    This feature is designed to integrate with default parameters (separate PR):
    #### Integration with default parameters

    This feature is designed to integrate with default parameters (separate PR). When rebasing onto default-params:

    1. Extend `DefParamMap` to store parameter names (currently stores only default values by position)
    2. Update `buildBoxApplWithDefaults()` to handle mixed positional + kwargs
    3. Resolve kwargs to positions using stored parameter names

    After integration:

    ```faust
    filter(freq, q=0.707) = ...;
  6. dfl revised this gist Jan 24, 2026. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions default-parameters.md
    Original file line number Diff line number Diff line change
    @@ -54,7 +54,7 @@ scale(x, factor, offset=factor*0.1) = x * factor + offset;
    ### Implementation

    - **Parser**: Extended function parameter syntax to accept `= expression`
    - **Boxes**: Parameters stored with optional default values
    - **Boxes**: Default values stored by position (nil for required params)
    - **Eval**: Modified function application to fill missing arguments from defaults

    ### Testing
    @@ -71,7 +71,8 @@ The large diff in generated files (`faustparser.cpp`, etc.) is from flex/bison r
    | File | Lines changed |
    |------|---------------|
    | `faustparser.y` | +97 |
    | `global.hh` | +5 |
    | `global.hh` | +6 |
    | `sourcereader.cpp` | +16/-9 |

    ### Backward Compatibility

  7. dfl revised this gist Jan 24, 2026. 1 changed file with 28 additions and 12 deletions.
    40 changes: 28 additions & 12 deletions inline-libraries.md
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@ This PR adds a new `-il` (`--inline-libraries`) option that produces a self-cont
    faust -il myfile.dsp -o myfile_standalone.dsp
    ```

    The output preserves readable Faust source code, unlike `-e` which exports an unreadable expanded box tree.
    The output preserves readable Faust source code, unlike `-e` which exports an unreadable expanded box tree. **Tree-shaking** automatically removes unused definitions to keep output minimal.

    ### Motivation

    @@ -25,27 +25,30 @@ fx = library("myeffects.lib");
    process = _ : half : fx.gain;
    ```

    **Output with `-il`:**
    **mymath.lib:**
    ```faust
    half(x) = x * 0.5;
    double(x) = x * 2; // unused by this program
    ```

    **Output with `-il`:** (note: `double` is removed by tree-shaking)
    ```faust
    // Self-contained DSP file generated with -il option
    // Inlined libraries: 2
    // Tree-shaking: enabled (5 definitions used)
    // ===== Imported: mymath.lib =====
    half(x) = x * 0.5;
    double(x) = x * 2;
    // ===== End of: mymath.lib =====
    // Library: mymath.lib
    __lib_mymath = environment {
    half(x) = x * 0.5;
    double(x) = x * 2;
    };
    // Library: myeffects.lib
    __lib_myeffects = environment {
    mm = __lib_mymath;
    gain = *(0.8);
    attenuate = mm.half;
    };
    // ===== Main DSP =====
    @@ -55,14 +58,25 @@ process = _ : half : fx.gain;

    ### How It Works

    1. **Collection phase**: Recursively discovers all `import()` and `library()` dependencies
    2. **Output phase**:
    1. **Evaluation phase**: Runs normal Faust evaluation, tracking which definitions are used
    2. **Collection phase**: Recursively discovers all `import()` and `library()` dependencies
    3. **Output phase**:
    - `import()` content inlined directly at top level (into global scope)
    - `library()` content wrapped in `environment { }` as top-level definitions
    - **Tree-shaking**: Only used definitions are included in output
    - Files used as both `import()` and `library()` are handled correctly
    - Circular dependencies detected and reported
    - Duplicate variable definitions skipped

    ### Tree-Shaking

    The `-il` option automatically removes unused definitions by:
    1. Evaluating the program first to determine which symbols are actually used
    2. Tracking all symbol lookups during evaluation in `gUsedDefinitions`
    3. Filtering the output to only include definitions whose names appear in the tracked set

    This significantly reduces output size when importing large libraries but only using a few functions.

    ### Implementation

    As suggested in the original discussion, this reuses infrastructure from the `-i` option:
    @@ -81,14 +95,16 @@ The `-i` option handles C++ `#include` in architecture files with simple text su
    | | `library()` → wrap in `environment { }` |
    | Single use pattern | Files can be used as both import AND library |
    | | Must track variable definitions to avoid duplicates |
    | | Tree-shaking removes unused definitions |

    | File | Changes |
    |------|---------|
    | `global.hh` | +1 line (flag) |
    | `global.hh` | +2 lines (flags) |
    | `global.cpp` | +8 lines (option parsing, init, help) |
    | `libcode.cpp` | +15 lines (integration) |
    | `sourcereader.hh` | +3 lines (declaration) |
    | `sourcereader.cpp` | +233 lines (inlining logic) |
    | `sourcereader.cpp` | +280 lines (inlining + tree-shaking logic) |
    | `eval.cpp` | +8 lines (symbol tracking) |

    ### Testing

    @@ -97,8 +113,8 @@ The `-i` option handles C++ `#include` in architecture files with simple text su
    faust -il test.dsp -o test_inlined.dsp
    faust -e test_inlined.dsp # Verify it compiles

    # With standard libraries
    faust -il complex.dsp -o complex_inlined.dsp # ~43K lines for stdfaust.lib
    # Tree-shaking in action
    faust -il uses_one_function.dsp -o small_output.dsp
    ```

    ### Option Naming
  8. dfl created this gist Jan 24, 2026.
    92 changes: 92 additions & 0 deletions default-parameters.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    ## Add default parameter values for function definitions

    ### Summary

    This PR adds support for default parameter values in function definitions, reducing boilerplate and improving API ergonomics:

    ```faust
    // Define with defaults
    lowpass(freq, q=0.707) = fi.resonlp(freq, q, 1);
    // Call - omit defaulted parameters
    process = lowpass(1000); // q = 0.707
    process = lowpass(1000, 1.5); // q = 1.5
    ```

    ### Motivation

    Currently, Faust functions require all parameters at every call site, even when sensible defaults exist:

    ```faust
    // Without defaults - must specify q every time
    lowpass(freq, q) = fi.resonlp(freq, q, 1);
    process = lowpass(1000, 0.707), lowpass(2000, 0.707), lowpass(500, 0.707);
    // With defaults - cleaner code
    lowpass(freq, q=0.707) = fi.resonlp(freq, q, 1);
    process = lowpass(1000), lowpass(2000), lowpass(500);
    ```

    This could be especially valuable for library functions where most users want standard values but experts need tunability.

    ### Syntax

    ```faust
    // Single default
    gain(x, amount=1.0) = x * amount;
    // Multiple defaults (must be rightmost parameters)
    mix(a, b, ratio=0.5) = a * (1-ratio) + b * ratio;
    env(attack=0.01, decay=0.1, sustain=0.7, release=0.3) = ...;
    // Defaults can be expressions
    SR = 48000;
    nyquist(sr=SR/2) = sr;
    // Defaults can reference earlier parameters
    scale(x, factor, offset=factor*0.1) = x * factor + offset;
    ```

    **Constraints:**
    - Parameters with defaults must come after parameters without defaults
    - Default expressions are evaluated at definition time

    ### Implementation

    - **Parser**: Extended function parameter syntax to accept `= expression`
    - **Boxes**: Parameters stored with optional default values
    - **Eval**: Modified function application to fill missing arguments from defaults

    ### Testing

    Pass tests:
    - `default-params-basic.dsp` - single default parameter
    - `default-params-multiple.dsp` - multiple defaults in one function
    - `default-params-expression.dsp` - defaults using expressions/constants

    ### Note on diff size

    The large diff in generated files (`faustparser.cpp`, etc.) is from flex/bison regeneration. Actual source changes:

    | File | Lines changed |
    |------|---------------|
    | `faustparser.y` | +97 |
    | `global.hh` | +5 |

    ### Backward Compatibility

    Fully backward compatible - existing function definitions without defaults work unchanged.

    ### Future Work

    Combines naturally with keyword arguments (separate PR):

    ```faust
    filter(freq, q=0.707, gain=1.0) = ...;
    // All these work:
    process = filter(1000);
    process = filter(1000, 1.5);
    process = filter(1000, gain: 2.0); // skip q, override gain
    process = filter(freq: 1000, gain: 2.0); // named + default
    ```
    118 changes: 118 additions & 0 deletions inline-libraries.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    ## Add -il option to inline all libraries

    **Discussion**: https://github.com/grame-cncm/faust/discussions/832

    ### Summary

    This PR adds a new `-il` (`--inline-libraries`) option that produces a self-contained DSP file with all library code inlined:

    ```bash
    faust -il myfile.dsp -o myfile_standalone.dsp
    ```

    The output preserves readable Faust source code, unlike `-e` which exports an unreadable expanded box tree.

    ### Motivation

    From issue #832: Users need self-contained DSP files for sharing, archiving, or using in environments without library access. The existing `-e` option exports expanded DSP but produces unreadable output.

    ### Example

    **Input (`mytest.dsp`):**
    ```faust
    import("mymath.lib");
    fx = library("myeffects.lib");
    process = _ : half : fx.gain;
    ```

    **Output with `-il`:**
    ```faust
    // Self-contained DSP file generated with -il option
    // Inlined libraries: 2
    // ===== Imported: mymath.lib =====
    half(x) = x * 0.5;
    double(x) = x * 2;
    // ===== End of: mymath.lib =====
    // Library: mymath.lib
    __lib_mymath = environment {
    half(x) = x * 0.5;
    double(x) = x * 2;
    };
    // Library: myeffects.lib
    __lib_myeffects = environment {
    mm = __lib_mymath;
    gain = *(0.8);
    attenuate = mm.half;
    };
    // ===== Main DSP =====
    fx = __lib_myeffects;
    process = _ : half : fx.gain;
    ```

    ### How It Works

    1. **Collection phase**: Recursively discovers all `import()` and `library()` dependencies
    2. **Output phase**:
    - `import()` content inlined directly at top level (into global scope)
    - `library()` content wrapped in `environment { }` as top-level definitions
    - Files used as both `import()` and `library()` are handled correctly
    - Circular dependencies detected and reported
    - Duplicate variable definitions skipped

    ### Implementation

    As suggested in the original discussion, this reuses infrastructure from the `-i` option:
    - Uses `gGlobal->gAlreadyIncluded` set for tracking visited files
    - Uses `fopenSearch()` for resolving library paths
    - Similar recursive inject pattern

    **Why new code was needed on top of `-i`:**

    The `-i` option handles C++ `#include` in architecture files with simple text substitution. The `-il` option needs Faust-specific semantics:

    | `-i` (architecture) | `-il` (libraries) |
    |---------------------|-------------------|
    | Detects `#include <faust/...>` | Must parse `import("...")` and `library("...")` |
    | Just copies file content | `import()` → inline directly |
    | | `library()` → wrap in `environment { }` |
    | Single use pattern | Files can be used as both import AND library |
    | | Must track variable definitions to avoid duplicates |

    | File | Changes |
    |------|---------|
    | `global.hh` | +1 line (flag) |
    | `global.cpp` | +8 lines (option parsing, init, help) |
    | `libcode.cpp` | +15 lines (integration) |
    | `sourcereader.hh` | +3 lines (declaration) |
    | `sourcereader.cpp` | +233 lines (inlining logic) |

    ### Testing

    ```bash
    # Simple test
    faust -il test.dsp -o test_inlined.dsp
    faust -e test_inlined.dsp # Verify it compiles

    # With standard libraries
    faust -il complex.dsp -o complex_inlined.dsp # ~43K lines for stdfaust.lib
    ```

    ### Option Naming

    Follows the pattern of `-it` (`--inline-table`):
    - `-il` = inline libraries
    - `-it` = inline table

    ### Backward Compatibility

    Fully backward compatible - new option, no changes to existing behavior.

    ### Branch

    - **Worktree**: `../faust-inline-libs`
    - **Branch**: `feature/inline-libraries`
    - **Base**: `master-dev`
    115 changes: 115 additions & 0 deletions keyword-arguments.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    ## Add keyword arguments for UI primitives and user-defined functions

    ### Summary

    This PR introduces Ruby/Python-style keyword arguments to Faust, enabling more readable and self-documenting code for both UI primitives and user-defined functions:

    ```faust
    // UI primitives
    hslider("volume", init: 0.5, min: 0.0, max: 1.0, step: 0.01)
    // User-defined functions
    mix(a, b, ratio) = a * (1-ratio) + b * ratio;
    process = mix(dry, wet, ratio: 0.7);
    ```

    ### Motivation

    Faust's UI primitives require multiple positional arguments that are easy to confuse:

    ```faust
    // Which is init? min? max? step?
    hslider("volume", 0.5, 0.0, 1.0, 0.01)
    ```

    Similarly, user-defined functions with multiple parameters benefit from named arguments for clarity at call sites.

    ### Syntax

    #### UI Primitives

    ```faust
    // Before: positional only
    hslider("volume", 0.5, 0.0, 1.0, 0.01)
    // After: keyword arguments (any order)
    hslider("volume", init: 0.5, min: 0.0, max: 1.0, step: 0.01)
    hslider("volume", min: 0.0, max: 1.0, init: 0.5, step: 0.01)
    ```

    Supported: `hslider`, `vslider`, `nentry`, `hbargraph`, `vbargraph`

    #### User-Defined Functions

    ```faust
    filter(freq, q, gain) = ...;
    // Mixed positional + kwargs
    process = filter(1000, q: 0.7, gain: 1.0);
    // All kwargs (any order)
    process = filter(gain: 1.0, freq: 1000, q: 0.7);
    ```

    **Constraints (Ruby-style):**
    - Keyword arguments must come after positional arguments
    - All required parameters must be provided
    - Unknown keywords produce clear error messages

    ### Error Messages

    ```
    error: hslider: unknown keyword 'foo'
    Valid keywords are: init, min, max, step
    error: hslider: missing required keyword 'step'
    Required keywords are: init, min, max, step
    error: unknown keyword argument 'c'
    ```

    ### Implementation

    - **Lexer**: Added `KWARG` token (pattern: `{ID}":"`)
    - **Parser**: Added `kwarg`, `kwarglist` grammar rules
    - **Boxes**: Added `BOXKWARG` box type for keyword argument representation
    - **Eval**: Modified `applyList()` to resolve kwargs to parameter positions

    ### Testing

    Pass tests:
    - `kwargs-ui-primitives.dsp` - all UI primitive variants
    - `kwargs-functions.dsp` - user-defined function calls
    - `kwargs-mixed.dsp` - combining both in one program

    Error tests:
    - `kwargs-unknown-ui.dsp` - unknown keyword for UI primitive
    - `kwargs-missing-ui.dsp` - missing required keyword
    - `kwargs-unknown-func.dsp` - unknown keyword for function

    ### Note on diff size

    The large diff in `faustlexer.cpp`, `faustparser.cpp`, and `faustparser.hpp` is due to these being **generated files**. The actual source changes are:

    | File | Lines changed |
    |------|---------------|
    | `faustlexer.l` | +1 |
    | `faustparser.y` | +130 |
    | `boxes.cpp` | +14 |
    | `boxes.hh` | +7 |
    | `eval.cpp` | +114 |
    | `global.cpp` | +1 |
    | `global.hh` | +1 |

    ### Backward Compatibility

    Fully backward compatible - existing positional syntax continues to work unchanged.

    ### Future Work

    This feature is designed to integrate with default parameters (separate PR):

    ```faust
    filter(freq, q=0.707) = ...;
    process = filter(freq: 1000); // q defaults to 0.707
    ```
    77 changes: 77 additions & 0 deletions python-style-imports.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    ## Add Python-style selective imports

    ### Summary

    This PR adds a new import syntax that allows importing specific definitions from a library file, optionally with aliasing:

    ```faust
    from "maths.lib" import PI, SR;
    from "filters.lib" import lowpass, highpass as hp;
    ```

    ### Motivation

    Currently, importing a library dumps all its definitions into the namespace, requiring the use of short prefixes like `ma.`, `fi.`, etc:

    ```faust
    import("stdfaust.lib");
    process = fi.lowpass(2, ma.PI * 100);
    ```

    This has two drawbacks:
    1. **Cryptic prefixes** - `ma`, `fi`, `os` etc. are not self-documenting
    2. **Namespace pollution** - entire libraries are imported even when only one or two definitions are needed

    The new syntax allows explicit, selective imports:

    ```faust
    from "maths.lib" import PI;
    from "filters.lib" import lowpass;
    process = lowpass(2, PI * 100);
    ```

    ### Syntax

    ```faust
    // Import specific names
    from "library.lib" import name1, name2, name3;
    // Import with aliasing
    from "library.lib" import originalName as aliasName;
    // Mixed
    from "maths.lib" import PI, SR as sampleRate;
    ```

    The syntax mirrors Python's `from X import Y` and uses the existing `,` (PAR) separator that Faust already uses for argument lists.

    ### Implementation

    - **Lexer**: Added `FROM` and `AS` keywords
    - **Parser**: Added grammar rules for `from uqstring import importnames`
    - **AST**: New `selectiveImportFile(filename, names)` tree type
    - **Source reader**: Modified `expandRec()` to filter definitions when expanding selective imports, with optional renaming for aliases

    The implementation reuses existing infrastructure - selective imports are expanded during the same phase as regular imports, just with filtering applied.

    ### Testing

    Tested with:
    - Basic selective import from a simple library
    - Multiple imports in one statement
    - Aliasing (`import X as Y`)
    - Importing from standard libraries (`maths.lib`)

    ### Note on diff size

    The large diff in `faustlexer.cpp`, `faustparser.cpp`, and `faustparser.hpp` is due to these being **generated files** that are regenerated by flex/bison. The actual source changes are minimal:

    | File | Lines changed |
    |------|---------------|
    | `faustlexer.l` | +2 |
    | `faustparser.y` | +14 |
    | `boxes.cpp` | +9 |
    | `boxes.hh` | +3 |
    | `global.cpp` | +1 |
    | `global.hh` | +1 |
    | `sourcereader.cpp` | +40 |