Lume
    Preparing search index...

    Style Guide

    At a high level, Lume's code is structured in the style of a C# project, but within the syntax of TypeScript. Classes are used extensively throughout the codebase, with each class having it's own designated file.

    Syntax is handled predominantly through the command npm run linter, which runs the prettier plugin that Lume uses.

    Tabs are used instead of spaces.

    This section covers documentation rules applied to all classes.

    Documentation is generated using typedoc. When writing, ensure that the documentation can compile by manually building the documentation (via npm run docs). Note that TypeDoc is configured to fail with warnings.

    Public or protected methods, properties, and classes should have documentation.

    Private methods can optionally have documentation, however this will not show up in the generated HTML pages.

    All exported classes are default.

    Classes are written in PascalCase.

    export default class DiamondChord extends DynamicGraphic {}
    

    Documentation must include a @group tag that defines the section that the class is stored in.

    A section is defined as the one of the top-level folders in src, or src itself. Note that non-top folders should be ignored.

    Examples:

    • Graphic is stored in /src/drawing/Graphic.ts, giving it the Drawing group.
    • Symmez is stored in /src/layouts/simple/abstract/Symmez.ts, but it is given the Layouts group, and not Simple, Abstract or any combination of those options.
    /**
    * A Graphic represents an individual object or drawing rendered onto the
    * canvas.
    *
    * Each Graphic is given an instance of p5 and contains a draw() method invoked
    * by the rendering engine to draw a frame. Graphics are stored in a list, and
    * drawn sequentlly, one after the other.
    *
    * @group Drawing
    */
    export default abstract class Graphic {}

    Private and protected fields are prefixed with a single underscore, and written in camelCase.

    Fields need to have their access modifier explicitly declared.

    export default abstract class Graphic {
    protected readonly _p5: P5;
    protected readonly _rConfig: RenderConfig;
    private _transforms?: GraphicTransforms;

    constructor(p5: P5, rConfig: RenderConfig) {
    this._p5 = p5;
    this._rConfig = rConfig;

    this._transforms = undefined;
    }
    }

    Public fields should not be used.

    Fields do not need to be documented.

    Properties directly representing a backing field should be named the same except without the underscore prefix.

    export default class ADSREnvelope {
    private _attack: number;

    public constructor() {
    this._attack = 50;
    }

    public get attack(): number {
    return this._attack;
    }

    public set attack(value: number) {
    this._attack = value;
    }
    }

    A get and set property group that would have the same value only needs to have the documentation defined in the get property.

    export default class NoteAnalyzer extends DynamicGraphic {
    /**
    * NoteAnalyzer cannot predict a chord if there are less than two unique
    * note values currently playing. When one note is playing, this setting
    * determines if the previously predicted chord should be drawn (when this
    * value is falsy e.g., empy string), or to display default text.
    */
    public get noPredictionText(): string {
    return this._noPredictionText;
    }

    public set noPredictionText(value: string) {
    this._noPredictionText = value;
    }
    }

    Otherwise, they should have separate documentation.

    export default class Knob extends Graphic {
    /** Returns the width of the arc. */
    public get strokeWidth(): number {
    return this._strokeWidth;
    }

    /** Sets the width of the arc. */
    public set strokeWidth(value: number) {
    this._strokeWidth = value;
    }
    }

    set properties that can throw errors must have those errors documented.

    export default class DrumPadGrid extends DynamicGraphic {
    /**
    * Sets the width (in pixels) for the border that is inbetween the core of a
    * button and the ring around it.
    *
    * If this value is zero, there will be no gap whatsoever and the button
    * will appear to be a flat square.
    *
    * @throws {RangeError} Value must be zero or greater.
    */
    public set voidThickness(value: number) {
    if (value < 0) {
    throw new RangeError("voidThickness must be zero or greater.");
    }
    this._voidThickness = value;
    }
    }

    Methods must be type and parameter types explicitly defined.

    export default class ADSREnvelope {
    public calculateLevel(
    isNoteOn: boolean,
    timeSinceStateChange: number,
    ): number {}

    private clerp(a: number, b: number, alpha: number) {}

    private clamp(n: number, min: number, max: number) {}
    }

    The @param tag should be used for method parameters.

    /**
    * Class for calculating values based off of an ADSR Envelope.
    *
    * ADSR Envelopes are treated as percentages, with 1.0 (100%) as the value of
    * the peak, and 0.0 for the value of nil. This is intended to allow for easy
    * usage of lerp functions.
    *
    * @group MIDI
    * @category amogus
    */
    export default class ADSREnvelope {
    private _attack: number;
    private _decay: number;
    private _sustain: number;
    private _release: number;

    public constructor() {
    this._attack = 50;
    this._decay = 100;
    this._sustain = 0.5;
    this._release = 300;
    }

    /**
    * Returns the time (in milliseconds) that it takes the envelope to go from
    * nil to peak.
    *
    * @defaultValue 50
    */
    public get attack(): number {
    return this._attack;
    }

    /**
    * Sets the time (in milliseconds) for the envelope's attack value.
    *
    * @throws {RangeError} Value must be equal or greater than zero.
    */
    public set attack(value: number) {
    if (value < 0) {
    throw new RangeError("attack must be equal or greater than zero.");
    }
    this._attack = value;
    }

    /**
    * Returns the time (in milliseconds) that it takes the envelope to go from
    * peak to it's sustain level.
    *
    * @defaultValue 100
    */
    public get decay(): number {
    return this._decay;
    }

    /**
    * Sets the time (in milliseconds) for the envelope's decay value.
    *
    * @throws {RangeError} Value must be equal or greater than zero.
    */
    public set decay(value: number) {
    if (value < 0) {
    throw new RangeError("decay must be equal or greater than zero.");
    }
    this._decay = value;
    }

    /**
    * Returns the sustain level of the envelope.
    *
    * Sustain level is stored as a percentage, specifically as a number between
    * 0.0 and 1.0 (both inclusive).
    *
    * @defaultValue 0.5
    */
    public get sustain(): number {
    return this._sustain;
    }

    /**
    * Sets the sustain level of the envelope.
    *
    * @throws {RangeError} Value must be between 0.0 and 1.0 (both inclusive).
    */
    public set sustain(value: number) {
    if (value > 1.0 || value < 0.0) {
    throw new RangeError(
    "sustain must be between 0.0 (inclusive) and 1.0 (inclusive).",
    );
    }
    this._sustain = value;
    }

    /**
    * Returns the time (in milleseconds) that it takes the envelope to go from
    * the sustain level to nil.
    *
    * @defaultValue 300
    */
    public get release(): number {
    return this._release;
    }

    /**
    * Sets the time (in milleseconds) for the envelope's release value.
    *
    * @throws {RangeError} Value must be equal or greater than zero.
    */
    public set release(value: number) {
    if (value < 0) {
    throw new RangeError("release must be equal or greater than zero.");
    }
    this._release = value;
    }

    /**
    * Calculates a level given the current parameters of a note.
    *
    * @param {boolean} isNoteOn If the note being checked is currently active.
    * @param {number} timeSinceStateChange The time since the note was last
    * turned on/off.
    * @throws {RangeError} TimeSinceStateChange must be equal or greater than
    * zero.
    */
    public calculateLevel(
    isNoteOn: boolean,
    timeSinceStateChange: number,
    ): number {
    if (timeSinceStateChange < 0) {
    throw new RangeError(
    "timeSinceStateChange must be equal or greater than zero.",
    );
    }
    let power = -1;
    if (isNoteOn) {
    // sustain
    if (timeSinceStateChange > this._attack + this._decay) {
    power = this._sustain;
    }
    // decay
    else if (timeSinceStateChange > this._attack) {
    power = this.clerp(
    this._sustain,
    1,
    1 - (timeSinceStateChange - this._attack) / this._decay,
    );
    }
    // attack
    else {
    power = this.clerp(0, 1, timeSinceStateChange / this._attack);
    }
    } else {
    // release
    power = this.clerp(
    0,
    this._sustain,
    1 - timeSinceStateChange / this._release,
    );
    }
    return power;
    }

    // this method was a terrible move
    // thank god for unit testing otherwise this thing would've screwed me
    // over and i wouldn't have known
    private clerp(a: number, b: number, alpha: number) {
    return this.clamp(a + alpha * (b - a), a, b);
    }

    private clamp(n: number, min: number, max: number) {
    return Math.max(min, Math.min(n, max));
    }
    }

    This section provides specifications on how Lume's non-custom layouts should be styled, as well as documentation rules for Layout-derived classes.

    Custom layouts are not required to adhere to the style specifications, but must follow the documentation styling when applicable.

    All colors used in a layout must be one of the supported user-defined colors:

    • Note Active
    • Note Held
    • Note Inactive
    • Background
    • Transparent

    The exception to this is using p5's color lerp to generate partial transparent colors, for the purposes of legibility and balancing different graphics:

    // BirdBrain.ts
    // used to generate a semi-transparent keyboard body

    keyboard_body.fill =
    p5.lerpColor(
    rConfig.colorBackground,
    rConfig.colorTransparent
    0.5
    );
    Warning

    This section refers to a planned feature. This has not been implemented.

    Configuration Arguments (or config arguments/config args) are special instructions a user can give Lume. The implementation and name are derived from command line arguments in software development, which allow a program to change how it operates when started.

    NOTE: Arguments with values marked as flag do not process any values

    argument values description
    hideSongMetadata (flag) Hides metadata text about the song; song name, creator, tempo, time signature, etc.
    hideFrame (flag) Hides the frame on layouts that have them.