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.
spessasynth_core into the directory.npm install.spessasynth_lib (or your fork of it) into the directory and install dependencies.SpessaSynth into the directory and install dependencies.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:
spessasynth_corespessasynth_lib, using your copy of core.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.
as const objects with keyof typeof types) - PascalCaseImportant note: acronyms such as MIDI have to be written in all caps, unless they are first in camelCase. So:
const midiFileclass MIDIBuilderconst gsEffectclass MidiBuildererasableSyntaxOnly 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.
super.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.