Skip to content

Add triads API#49

Open
moonwave99 wants to merge 4 commits intomasterfrom
triads
Open

Add triads API#49
moonwave99 wants to merge 4 commits intomasterfrom
triads

Conversation

@moonwave99
Copy link
Owner

While brushing up my fretboard triad knowledge (read: relearning it from scratch), I couldn't help writing the feature for fretboard.js (mainly for the inversions).

One can now do:

import {
    Fretboard,
    TriadLayouts,
    TriadInversion,
} from "@moonwave99/fretboard.js";

const fretboard = new Fretboard();

// renders a C major triad:
// - in root position
// - starting on the fifth string
// - with one note on the fifth string and two on the fourth
fretboard.renderTriad("C", {
    string: 5,
    layout: TriadLayouts.OnePlusTwo,
});

// renders a G minor triad:
// - in first inversion
// - starting on the fifth string
// - with the default layout (over three consecutive strings)
fretboard.renderTriad("Gm", {
    string: 5,
    inversion: TriadInversions.First,
});

// renders a C major triad:
// - starting on the second string
// - with the OnePlusTwo layout
// - starting at the 13th fret
fretboard.renderTriad("C", {
    string: 2,
    inversion: TriadInversions.OnePlusTwo,
    nextOctave: true,
});

@jaredjj3
Copy link
Contributor

jaredjj3 commented Jun 6, 2022

I'm excited to see that you're developing this! I have some comments. Feel free to ignore or address any of them:

  1. nextOctave is a little ambiguous because it makes me ask myself "next relative to what?". It may be better for the caller to specify an octave number instead.

  2. Is this the first instance of using qualified chord names (e.g. "C", "Gm", "AbM")? Knowing your style, I imagine that you want to keep the signature simple, where the first parameter is a string. However, there are multiple ways to express certain chords. For example, a G major 7th chord can be expressed as GMaj7, GM7, GΔ, G⑦, etc. As a caller, I may not be sure how to express a certain chord as a string. I recommend adding a utility that takes a note name and an array of chord modifiers, and outputs a string that represents the chord. This string can be safely used in the triads API.

  3. Long term, I think the fretboard's main concern should just be rendering dots and other glyphs to the SVG. The current design couples the concern of rendering with computing what to render. What do you think about abstracting the triad API into a separate layer? I'm thinking of something between tonaljs and fretboard - similar to your approach with tetrachords. The tradeoff is that now fretboard.js becomes a more complex library, but at the benefit of being able to "complete" the Fretboard class. A composable MusicTheory API might sound ambitious, but this might be the path to offer adopters (and yourself) maximum flexibility in the long term.

For discussion sake, a very rough draft of what I'm thinking of is:

type Note = string;
type Constraint = (notes: Note[]) => Error[];
type Transform = (notes: Note[]) => Note[];

const requireExactNoteLength = (expectedLength: number): Constraint => (notes) => {
  if (notes.length !== expectedLength) {
    return [new Error(`expected note length of ${expectedLength}, got: ${notes.length}];
  }
  return [];
};

const majorTriad = musicTheory()
  .setConstraint(requireExactNoteLength(1))
  .addTransform((notes) => {
    const root = notes[0];
    const third = tonaljs.Note.transpose(root, '3M'); 
    const fifth = tonaljs.Note.transpose(root, '5P');
    return [root, third, fifth];
  });

const firstInversion = majorTriad
  .setConstraint(requireExactNoteLength(3));
  .addTransform((notes) => {
    const [root, third, fifth] = notes;
    return [third, fifth, tonaljs.Note.transpose(root, '8P')];
  });

Then, some utility could transform these fully qualified note names into guitar positions and those could be subsequently fed to a Fretboard instance to render.

@moonwave99
Copy link
Owner Author

moonwave99 commented Jul 22, 2022

Hi @jaredjj3

sorry for the extremely late response!

I basically stopped working on this (and on a parallel, similar piano diagram generator that I drafted) because...it took away precious practice time : D

After 2/3h working on a virtual fretboard, I don't want to touch a real one! Or at least I don't have time for work + project + drilling :/

This does not mean I won't work on this anymore of course, I will actively check the inbox of the project. At least I would like to properly merge this.


Addressing your points:

nextOctave is a little ambiguous because it makes me ask myself "next relative to what?". It may be better for the caller to specify an octave number instead.

You are right ^^ the idea is "after the middle fret", but I don't think that afterTheMiddleFret: true is much better.
I don't even remember the parsing mechanism from memory, but I may be easy to just decide based on the octave of the note.

Another possibility may be afterFret: 12, then the code decide the next available note on such string after the given fret, computing the octave automatically.

For example, a G major 7th chord can be expressed as GMaj7, GM7, GΔ, G⑦,

The library is using @tonaljs/chord for this purpose, that supports:

aliases: ["maj7", "Δ", "ma7", "M7", "Maj7"],

So we should be covered here! That should be documented of course.

I think the fretboard's main concern should just be rendering dots and other glyphs to the SVG.

You are right ^^ at the same time, very often you'll need to render specific things like chords / scales / triads...I just included convenience methods that allow you to do:

const fretboard = new Fretboard(...);

fretboard.renderChord('G7b9');
fretboard.renderScale('G lydian dominant');

instead of:

import Fretboard, { getChord, getScale } from '...';

const fretboard = new Fretboard(...);

fretboard.setDots(getChord({
  root: 'C',
  type: '7b9',
  string: 5
));

fretboard.setDots(getScale({
  root: 'G',
  type: 'Lydian dominant',
  string: 6
));

There is already the FretboardSystem component that acts as an headless Position[] generator, many of its features are exposed. One could just expose convenience wrappers that parse single strings arguments with reasonable defaults / possibilities of specifying more, and that could be a nice compromise.


Your musicTheory library idea is VERY interesting, because it is very coherent with the transformation / piping idea of concepts like transposing, mode rotation, etc...another thing I would love to implement and test, but that would steal time from my non js guitar :D

@jaredjj3
Copy link
Contributor

@moonwave99, thanks for taking the time out to respond! I moved recently, and I've been very occupied with that. I'll try to prototype some of the ideas we discussed here and come back for more feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants