spessasynth_core documentation
    Preparing search index...

    Contributing to spessasynth_core

    I welcome new contributors that want to help develop spessasynth_core!

    Below are the guidelines for contributing.

    I recommend developing the libraries using the web app as the test environment, as it allows testing the changes extensively. It also enforces the requirement of spessasynth_core being able to run in the AudioWorklet scope.

    1. If you are on Windows, consider installing WSL.
    2. Create a directory where you want to develop.
    3. Clone your fork of spessasynth_core into the directory.
    4. Install the dependencies with npm install.
    5. Clone spessasynth_lib (or your fork of it) into the directory and install dependencies.
    6. Clone SpessaSynth into the directory and install dependencies.
    7. You now should have the following structure:
      • your directory
        • spessasynth_core/ (your fork of it)
        • spessasynth_lib/
        • SpessaSynth/

    To test your changes, run the npm run dev script in SpessaSynth. This does three things:

    1. It builds your copy of spessasynth_core
    2. It builds your copy of spessasynth_lib, using your copy of core.
    3. It builds SpessaSynth using your copies of both the libraries.

    You can keep the npm start script (the local web server) running in another terminal during this. Once the build is done, perform a hard reload in your browser (Ctrl + Shift + R).

    Before opening a pull request, run npm run build in spessasynth_core and make sure that it passes, as it lints and type-checks the code.

    After everything works as planned, open the pull request. Thank you for your contribution!

    npm run build must fully pass. No errors or warnings.

    npm run docs must fully pass. No errors or warnings.

    Both are included as pull request checks, so they are checked automatically every time you update your pull request.

    Below is my preferred style of writing code for spessasynth_core.

    It is highly recommended to follow it. If you are using a coding agent, it must follow it.

    Code must be formatted according to ESLint and Prettier before commits. These two dictate most of the formatting. Do not override them.

    Rules listed here not covered by ESLint/TypeScript or Prettier. There are more rules, but they are automatically enforced.

    1. Classes - PascalCase
    2. Variables - camelCase
    3. Constants - SCREAMING_SNAKE_CASE
    4. Enums (defined as as const objects with keyof typeof types) - PascalCase

    Important note: acronyms such as MIDI have to be written in all caps, unless they are first in camelCase. So:

    • const midiFile
    • class MIDIBuilder
    • const gsEffect
    • class MidiBuilder

    erasableSyntaxOnly is enabled so regular TypeScript enums are disabled.

    Use a const object + singular union type of its values:

    const SampleTypes = {
    a: 1,
    b: 2,
    c: 3
    } as const;
    type SampleType = (typeof SampleTypes)[keyof typeof SampleTypes];

    Do not use single-line JSDoc comments.

    • Avoid duplicate code when a shared base class or utility already provides the behavior. Use super.
    • Avoid duplicated code across classes in an inheritance hierarchy. Move to the base class if inheriting classes use it.

    Inline small functions that are only used once instead of extracting them. If the function is very large, then it may be extracted.

    If every child class overrides a base method, getter, or setter, declare it abstract in the base class rather than providing a meaningless fallback or error implementation.

    Do not explicitly specify return types for functions/methods unless the build fails without it.

    Do not specify class's member types if they can be inferred in the constructor, except defined types. For example when an interface is defined and a raw object is assigned in the constructor, the type may be explicitly set to the interface.

    function add(a: number, b: number) {
    return a + b;
    }
    class Foo {
    public a;

    public constructor(a: number) {
    this.a = a;
    }
    }
    function add(a: number, b: number): number {
    return a + b;
    }
    class Foo {
    public a: number;

    public constructor(a: number) {
    this.a = a;
    }
    }

    Prefer const array = new Array<T>() over const array: T[] = [].

    Regular declaration uses T[], not Array<T>. The above only applies to construction.

    Use Array.from with length to inline construction in the property declaration instead of splitting up into a for loop.

    const arr = new Array<number>();
    
    class Foo {
    public readonly elements = Array.from({ length: 16 }, (_, i) =>
    createElement(i)
    );
    }
    const arr: number[] = [];
    
    class Foo {
    public readonly channels = new Array<Element>();

    public constructor() {
    for (let i = 0; i < 16; i++) {
    this.channels.push(createElement(i));
    }
    }
    }

    Unfortunately most classes are very large, so a splitting code pattern has emerged:

    • export function name(this: Class, ...)
    • protected readonly name = name.bind(this);

    Avoid this if possible, but follow it when extending the existing large classes. A PR cleaning this up would be welcome too.

    Use @internal when a function should not be publicly available and not included in the documentation.