diff --git a/.changeset/stale-roses-unite.md b/.changeset/stale-roses-unite.md new file mode 100644 index 00000000..62e2083d --- /dev/null +++ b/.changeset/stale-roses-unite.md @@ -0,0 +1,27 @@ +--- +"@tonaljs/abc-notation": minor +"@tonaljs/chord": minor +"@tonaljs/chord-detect": minor +"@tonaljs/chord-type": minor +"@tonaljs/collection": minor +"@tonaljs/duration-value": minor +"@tonaljs/interval": minor +"@tonaljs/key": minor +"@tonaljs/midi": minor +"@tonaljs/mode": minor +"@tonaljs/modules": minor +"@tonaljs/note": minor +"@tonaljs/pcset": minor +"@tonaljs/progression": minor +"@tonaljs/range": minor +"@tonaljs/roman-numeral": minor +"@tonaljs/scale": minor +"@tonaljs/scale-type": minor +"@tonaljs/time-signature": minor +"tonal": minor +"@tonaljs/voice-leading": minor +"@tonaljs/voicing": minor +"@tonaljs/voicing-dictionary": minor +--- + +Publish tonal in `tonal` package. So use `npm install tonal` instead of `npm install @tonaljs/tonal` diff --git a/README.md b/README.md index 8013ae61..78547079 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # tonal -[![npm version](https://img.shields.io/npm/v/@tonaljs/tonal.svg?style=flat-square)](https://www.npmjs.com/package/@tonaljs/tonal) +[![npm version](https://img.shields.io/npm/v/tonal.svg?style=flat-square)](https://www.npmjs.com/package/tonal) [![build status](https://img.shields.io/github/workflow/status/tonaljs/tonal/tests?style=flat-square)](https://github.com/tonaljs/tonal/actions) ![minified size](https://img.shields.io/badge/minified-31.1kb-blue?style=flat-square) ![gzipped size](https://img.shields.io/badge/gzipped-11.01kb-blue?style=flat-square) @@ -18,7 +18,7 @@ mutation, and entities are represented by data structures instead of objects. ## Example ```js -import { Interval, Note, Scale } from "@tonaljs/tonal"; +import { Interval, Note, Scale } from "tonal"; Note.midi("A4"); // => 60 Note.freq("a4").freq; // => 440 @@ -34,7 +34,7 @@ Scale.get("C major").notes; // =>["C", "D", "E", "F", "G", "A", "B"]; Install all packages at once: ```bash -npm install --save @tonaljs/tonal +npm install --save tonal ``` ## Usage @@ -44,13 +44,13 @@ Tonal is compatible with both ES5 and ES6 modules, and browser. #### ES6 `import`: ```js -import { Note, Scale } from "@tonaljs/tonal"; +import { Note, Scale } from "tonal"; ``` #### ES5 `require`: ```js -const { Note, Scale } = require("@tonaljs/tonal"); +const { Note, Scale } = require("tonal"); ``` #### Browser @@ -58,7 +58,7 @@ const { Note, Scale } = require("@tonaljs/tonal"); You can use the browser version from jsdelivr CDN directly in your html: ```html - + @@ -70,11 +70,13 @@ from the repository. #### Bundle size -`@tonaljs/tonal` includes all published modules. +`tonal` includes all published modules. -Although the final bundle it is small (~10kb minified and gzipped), you can +Although the final bundle it is small, you can reduce bundle sizes even more by installing the modules individually, and -importing only the functions you need: +importing only the functions you need. + +Note that individual modules are prefixed with `@tonaljs`. For example: ```bash npm i @tonaljs/note @@ -87,11 +89,11 @@ transpose("A4", "P5"); ## Documentation -Generally, you just need to install: +Generally, you just need to install `tonal` package (before it was called `@tonaljs/tonal`). -- [@tonaljs/tonal](/packages/tonal): All modules bundled in one package +- [tonal](/packages/tonal): All modules bundled in one package -The API documentation lives inside README.md file of each module +The API documentation is inside README.md of each module 👇 #### Notes and intervals diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 83971179..bca6a1b0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,7 +6,7 @@ Adopt a fixed/locked mode with lerna. Before, each module has it's own version. Deprecated modules: -- @tonaljs/modules (use @tonaljs/tonal) +- @tonaljs/modules (use tonal) - @tonaljs/array (use @tonaljs/collection) #### Releases diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 418e3f7a..3731771e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -39,7 +39,21 @@ To create a new module: - Add a new folder inside packages: `packages/my-module` - Add a new package.json inside the folder (see any of them as an example) - Add required dependencies to "dependencies" inside package.json. Ensure correct dependency versions. For example, if your module needs to use `tonal/core` look at core's package.json to see what version to use -- After adding your dependencies, use lerna to wire them up: run `yarn lerna` at root folder - Add your functionality and tests - Ensure everything works: run `yarn test:ci` at root folder - Create a pull request + +## Release + +Releases are built using changesets: https://turbo.build/repo/docs/handbook/publishing-packages/versioning-and-publishing: + +``` +# Add a new changeset +yarn changeset + +# Create new versions of packages +yarn changeset version + +# Publish all changed packages to npm +yarn changeset publish +``` diff --git a/package.json b/package.json index 317bba41..2a8fb89b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "tonal-workspace", + "name": "workspace", "private": true, "workspaces": [ "packages/*" diff --git a/packages/abc-notation/README.md b/packages/abc-notation/README.md index bb46cfd2..dbd69974 100644 --- a/packages/abc-notation/README.md +++ b/packages/abc-notation/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { AbcNotation } from "@tonaljs/tonal"; +import { AbcNotation } from "tonal"; ``` nodejs: ```js -const { AbcNotation } = require("@tonaljs/tonal"); +const { AbcNotation } = require("tonal"); ``` ## API diff --git a/packages/chord-detect/README.md b/packages/chord-detect/README.md index 05d6ffa4..4a1af813 100644 --- a/packages/chord-detect/README.md +++ b/packages/chord-detect/README.md @@ -5,13 +5,13 @@ With ES6 `import`: ```js -import { Chord } from "@tonaljs/tonal"; +import { Chord } from "tonal"; ``` With ES5 `require`: ```js -const { Chord } = require("@tonaljs/tonal"); +const { Chord } = require("tonal"); ``` Standalone: diff --git a/packages/chord-detect/package.json b/packages/chord-detect/package.json index 15a7a176..a92a2366 100644 --- a/packages/chord-detect/package.json +++ b/packages/chord-detect/package.json @@ -6,7 +6,7 @@ "chord-detect", "music", "theory", - "@tonaljs/tonal" + "tonal" ], "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/packages/chord-type/README.md b/packages/chord-type/README.md index fb05428f..8e67e642 100644 --- a/packages/chord-type/README.md +++ b/packages/chord-type/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { ChordType } from "@tonaljs/tonal"; +import { ChordType } from "tonal"; ``` node: ```js -const { ChordType } = require("@tonaljs/tonal"); +const { ChordType } = require("tonal"); ``` ## API @@ -71,8 +71,8 @@ add(["1P", "3M", "5P"], ["M"], "mayor"); ```js ChordType.all() - .filter(get => get.length === 3) - .map(get => get.name); + .filter((get) => get.length === 3) + .map((get) => get.name); ``` #### How to add a chord type to the dictionary? diff --git a/packages/chord/README.md b/packages/chord/README.md index 374aa8c4..5673503d 100644 --- a/packages/chord/README.md +++ b/packages/chord/README.md @@ -5,13 +5,13 @@ ES6: ```js -import { Chord } from "@tonaljs/tonal"; +import { Chord } from "tonal"; ``` Nodejs: ```js -const { Chord } = require("@tonaljs/tonal"); +const { Chord } = require("tonal"); ``` ## API diff --git a/packages/collection/README.md b/packages/collection/README.md index f265a2fd..b70b99f9 100644 --- a/packages/collection/README.md +++ b/packages/collection/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Collection } from "@tonaljs/tonal"; +import { Collection } from "tonal"; ``` node: ```js -const { Collection } = require("@tonaljs/tonal"); +const { Collection } = require("tonal"); ``` ## API diff --git a/packages/duration-value/README.md b/packages/duration-value/README.md index 7d422e10..2644a039 100644 --- a/packages/duration-value/README.md +++ b/packages/duration-value/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { DurationValue } from "@tonaljs/tonal"; +import { DurationValue } from "tonal"; ``` node: ```js -const { DurationValue } = require("@tonaljs/tonal"); +const { DurationValue } = require("tonal"); ``` single module: diff --git a/packages/interval/README.md b/packages/interval/README.md index 07be22c3..ecc449c2 100644 --- a/packages/interval/README.md +++ b/packages/interval/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Interval } from "@tonaljs/tonal"; +import { Interval } from "tonal"; ``` nodejs: ```js -const { Interval } = require("@tonaljs/tonal"); +const { Interval } = require("tonal"); ``` ## API diff --git a/packages/key/README.md b/packages/key/README.md index db6982a3..f39acc13 100644 --- a/packages/key/README.md +++ b/packages/key/README.md @@ -9,13 +9,13 @@ Get scale and chords of major and minor keys. ES6: ```js -import { Key } from "@tonaljs/tonal"; +import { Key } from "tonal"; ``` nodejs: ```js -const { Key } = require("@tonaljs/tonal"); +const { Key } = require("tonal"); ``` ## API diff --git a/packages/midi/README.md b/packages/midi/README.md index 96e191be..38ed6100 100644 --- a/packages/midi/README.md +++ b/packages/midi/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Midi } from "@tonaljs/tonal"; +import { Midi } from "tonal"; ``` nodejs: ```js -const { Midi } = require("@tonaljs/tonal"); +const { Midi } = require("tonal"); ``` ## API diff --git a/packages/mode/README.md b/packages/mode/README.md index 056dda97..d64521de 100644 --- a/packages/mode/README.md +++ b/packages/mode/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Mode } from "@tonaljs/tonal"; +import { Mode } from "tonal"; ``` node: ```js -const { Mode } = require("@tonaljs/tonal"); +const { Mode } = require("tonal"); ``` ## API @@ -109,7 +109,7 @@ Mode.relativeTonic("minor", "major", "C"); // => "A" For example, "A major" mode: ```js -import { Mode, Note } from "@tonaljs/tonal"; +import { Mode, Note } from "tonal"; Mode.get("major").intervals.map(Note.transposeFrom("A")); ["A", "B", "C#", "D", "E", "F#", "G#"]; diff --git a/packages/modules/CHANGELOG.md b/packages/modules/CHANGELOG.md index bbd60a96..da979317 100644 --- a/packages/modules/CHANGELOG.md +++ b/packages/modules/CHANGELOG.md @@ -6,4 +6,4 @@ - Bug fixing - Updated dependencies - - @tonaljs/tonal@4.6.10 + - tonal@4.6.10 diff --git a/packages/modules/README.md b/packages/modules/README.md index 02ec9985..7d8f7640 100644 --- a/packages/modules/README.md +++ b/packages/modules/README.md @@ -1,3 +1,3 @@ # Deprecated -## Use: [@tonaljs/tonal](/packages/tonal) +## Use: [tonal](/packages/tonal) diff --git a/packages/modules/index.ts b/packages/modules/index.ts index 800a2249..b3735713 100644 --- a/packages/modules/index.ts +++ b/packages/modules/index.ts @@ -1 +1 @@ -export * from "@tonaljs/tonal"; +export * from "tonal"; diff --git a/packages/modules/package.json b/packages/modules/package.json index d55a6efd..b8e272e8 100644 --- a/packages/modules/package.json +++ b/packages/modules/package.json @@ -10,7 +10,7 @@ ], "types": "dist/index.d.ts", "dependencies": { - "@tonaljs/tonal": "^4.6.10" + "tonal": "^4.6.10" }, "author": "danigb@gmail.com", "license": "MIT", diff --git a/packages/note/README.md b/packages/note/README.md index b31b035f..fd2510de 100644 --- a/packages/note/README.md +++ b/packages/note/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Note } from "@tonaljs/tonal"; +import { Note } from "tonal"; ``` nodejs: ```js -const { Note } = require("@tonaljs/tonal"); +const { Note } = require("tonal"); ``` ## API diff --git a/packages/pcset/README.md b/packages/pcset/README.md index 47d7c5ec..681c5726 100644 --- a/packages/pcset/README.md +++ b/packages/pcset/README.md @@ -9,13 +9,13 @@ A pitch class set is a set (no repeated) of pitch classes (notes without octaves ES6: ```js -import { Pcset } from "@tonaljs/tonal"; +import { Pcset } from "tonal"; ``` nodejs: ```js -const { Pcset } = require("@tonaljs/tonal"); +const { Pcset } = require("tonal"); ``` ## API @@ -104,5 +104,5 @@ Take a look to [@tonal/scale-type]() or [@tonal/chord-type]() that are, in fact, import { chromas, pcset } from "@tonaljs/pcset"; import { transposeFrom } from "@tonaljs/note"; -chromas().map(chroma => pcset(chroma).intervals.map(transposeFrom("C"))); +chromas().map((chroma) => pcset(chroma).intervals.map(transposeFrom("C"))); ``` diff --git a/packages/progression/README.md b/packages/progression/README.md index 2aae281c..e2ef4bad 100644 --- a/packages/progression/README.md +++ b/packages/progression/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Progression } from "@tonaljs/tonal"; +import { Progression } from "tonal"; ``` node: ```js -const { Progression } = require("@tonaljs/tonal"); +const { Progression } = require("tonal"); ``` ## API diff --git a/packages/range/README.md b/packages/range/README.md index 4d626557..dba3da23 100644 --- a/packages/range/README.md +++ b/packages/range/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { Range } from "@tonaljs/tonal"; +import { Range } from "tonal"; ``` nodejs: ```js -const { Range } = require("@tonaljs/tonal"); +const { Range } = require("tonal"); ``` Single module: diff --git a/packages/roman-numeral/README.md b/packages/roman-numeral/README.md index cfc3179e..99173b27 100644 --- a/packages/roman-numeral/README.md +++ b/packages/roman-numeral/README.md @@ -9,13 +9,13 @@ A roman numeral symbol is a string like `"bVIImaj7"` that can be used to represe ES6: ```js -import { RomanNumeral } from "@tonaljs/tonal"; +import { RomanNumeral } from "tonal"; ``` node: ```js -const { RomanNumeral } = require("@tonaljs/tonal"); +const { RomanNumeral } = require("tonal"); ``` ## API @@ -49,7 +49,7 @@ RomanNumeral.get("bVIIMaj7"); `romanNumeral` function accepts a `Pitch` as argument: ```js -import { Interval, RomanNumeral } from "@tonaljs/tonal"; +import { Interval, RomanNumeral } from "tonal"; RomanNumeral.get(Interval.get("3m")).name; // => "bIII" ``` diff --git a/packages/scale-type/README.md b/packages/scale-type/README.md index 8f2283e5..8094cf14 100644 --- a/packages/scale-type/README.md +++ b/packages/scale-type/README.md @@ -9,13 +9,13 @@ ES6: ```js -import { ScaleType } from "@tonaljs/tonal"; +import { ScaleType } from "tonal"; ``` nodejs: ```js -const { ScaleType } = require("@tonaljs/tonal"); +const { ScaleType } = require("tonal"); ``` ## API @@ -68,8 +68,8 @@ ScaleType.add(["1P", "5P"], null, ["5"]); ```js ScaleType.all() - .filter(scaleType => scaleType.intervals.length === 5) - .map(scaleType => scaleType.name); + .filter((scaleType) => scaleType.intervals.length === 5) + .map((scaleType) => scaleType.name); ``` #### How do to add a scale to the dictionary? @@ -84,7 +84,7 @@ ScaleType.scale("quinta justa"); // => { name: "quinta", intervals: ... } Some sources explaining various scale systems: -- [Modes](https://en.wikipedia.org/wiki/Mode_(music)) +- [Modes]() - [Blues Scales](https://en.wikipedia.org/wiki/Blues_scale) - [Jazz Scales](https://en.wikipedia.org/wiki/Jazz_scale) - [Messiaen's "Modes of Limited Transposition" (wikipedia, en)](https://en.wikipedia.org/wiki/Mode_of_limited_transposition) diff --git a/packages/scale/README.md b/packages/scale/README.md index 86c811b2..db6bbc93 100644 --- a/packages/scale/README.md +++ b/packages/scale/README.md @@ -9,13 +9,13 @@ ES6: ```js -import { Scale } from "@tonaljs/tonal"; +import { Scale } from "tonal"; ``` nodejs: ```js -const { Scale } = require("@tonaljs/tonal"); +const { Scale } = require("tonal"); ``` Single module: diff --git a/packages/time-signature/README.md b/packages/time-signature/README.md index c878544e..886eea17 100644 --- a/packages/time-signature/README.md +++ b/packages/time-signature/README.md @@ -7,13 +7,13 @@ ES6: ```js -import { TimeSignature } from "@tonaljs/tonal"; +import { TimeSignature } from "tonal"; ``` node: ```js -const { TimeSignature } = require("@tonaljs/tonal"); +const { TimeSignature } = require("tonal"); ``` single module: diff --git a/packages/tonal-tonal/CHANGELOG.md b/packages/tonal-tonal/CHANGELOG.md new file mode 100644 index 00000000..c238fac4 --- /dev/null +++ b/packages/tonal-tonal/CHANGELOG.md @@ -0,0 +1,27 @@ +# tonal + +## 4.6.10 + +### Patch Changes + +- Bug fixing +- Updated dependencies + - @tonaljs/abc-notation@4.6.10 + - @tonaljs/array@4.6.10 + - @tonaljs/chord@4.6.10 + - @tonaljs/chord-type@4.6.10 + - @tonaljs/collection@4.6.10 + - @tonaljs/core@4.6.10 + - @tonaljs/duration-value@4.6.10 + - @tonaljs/interval@4.6.10 + - @tonaljs/key@4.6.10 + - @tonaljs/midi@4.6.10 + - @tonaljs/mode@4.6.10 + - @tonaljs/note@4.6.10 + - @tonaljs/pcset@4.6.10 + - @tonaljs/progression@4.6.10 + - @tonaljs/range@4.6.10 + - @tonaljs/roman-numeral@4.6.10 + - @tonaljs/scale@4.6.10 + - @tonaljs/scale-type@4.6.10 + - @tonaljs/time-signature@4.6.10 diff --git a/packages/tonal-tonal/LICENSE b/packages/tonal-tonal/LICENSE new file mode 100644 index 00000000..77ac35ab --- /dev/null +++ b/packages/tonal-tonal/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 danigb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/packages/tonal-tonal/README.md b/packages/tonal-tonal/README.md new file mode 100644 index 00000000..de839c41 --- /dev/null +++ b/packages/tonal-tonal/README.md @@ -0,0 +1,3 @@ +# tonal ![tonal](https://img.shields.io/badge/@tonaljs-modules-yellow.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/tonal.svg?style=flat-square)](https://www.npmjs.com/package/tonal) + +[Use `tonal` package directly.](https://www.npmjs.com/package/tonal) diff --git a/packages/tonal-tonal/index.ts b/packages/tonal-tonal/index.ts new file mode 100644 index 00000000..e3258744 --- /dev/null +++ b/packages/tonal-tonal/index.ts @@ -0,0 +1,54 @@ +import AbcNotation from "@tonaljs/abc-notation"; +import * as Array from "@tonaljs/array"; +import Chord from "@tonaljs/chord"; +import ChordType from "@tonaljs/chord-type"; +import Collection from "@tonaljs/collection"; +import * as Core from "@tonaljs/core"; +import DurationValue from "@tonaljs/duration-value"; +import Interval from "@tonaljs/interval"; +import Key from "@tonaljs/key"; +import Midi from "@tonaljs/midi"; +import Mode from "@tonaljs/mode"; +import Note from "@tonaljs/note"; +import Pcset from "@tonaljs/pcset"; +import Progression from "@tonaljs/progression"; +import Range from "@tonaljs/range"; +import RomanNumeral from "@tonaljs/roman-numeral"; +import Scale from "@tonaljs/scale"; +import ScaleType from "@tonaljs/scale-type"; +import TimeSignature from "@tonaljs/time-signature"; + +export * from "@tonaljs/core"; + +// deprecated (backwards compatibility) +const Tonal = Core; +const PcSet = Pcset; +const ChordDictionary = ChordType; +const ScaleDictionary = ScaleType; + +export { + AbcNotation, + Array, + Chord, + ChordType, + Collection, + Core, + DurationValue, + Note, + Interval, + Key, + Midi, + Mode, + Pcset, + Progression, + Range, + RomanNumeral, + Scale, + ScaleType, + TimeSignature, + // backwards API compatibility (3.0) + Tonal, + PcSet, + ChordDictionary, + ScaleDictionary, +}; diff --git a/packages/tonal-tonal/package.json b/packages/tonal-tonal/package.json new file mode 100644 index 00000000..fb023a78 --- /dev/null +++ b/packages/tonal-tonal/package.json @@ -0,0 +1,52 @@ +{ + "name": "@tonaljs/tonal", + "version": "4.6.10", + "description": "tonal music theory full library", + "keywords": [ + "music", + "theory", + "music-theory" + ], + "main": "dist/index.js", + "module": "dist/index.mjs", + "files": [ + "dist", + "browser" + ], + "types": "dist/index.d.ts", + "dependencies": { + "@tonaljs/abc-notation": "^4.6.10", + "@tonaljs/array": "^4.6.10", + "@tonaljs/chord": "^4.6.10", + "@tonaljs/chord-type": "^4.6.10", + "@tonaljs/collection": "^4.6.10", + "@tonaljs/core": "^4.6.10", + "@tonaljs/duration-value": "^4.6.10", + "@tonaljs/interval": "^4.6.10", + "@tonaljs/key": "^4.6.10", + "@tonaljs/midi": "^4.6.10", + "@tonaljs/mode": "^4.6.10", + "@tonaljs/note": "^4.6.10", + "@tonaljs/pcset": "^4.6.10", + "@tonaljs/progression": "^4.6.10", + "@tonaljs/range": "^4.6.10", + "@tonaljs/roman-numeral": "^4.6.10", + "@tonaljs/scale": "^4.6.10", + "@tonaljs/scale-type": "^4.6.10", + "@tonaljs/time-signature": "^4.6.10" + }, + "author": "danigb@gmail.com", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup index.ts --sourcemap --dts --format esm,cjs", + "test": "jest" + }, + "jest": { + "transform": { + ".(ts|tsx)": "ts-jest" + } + } +} diff --git a/packages/tonal-tonal/test.ts b/packages/tonal-tonal/test.ts new file mode 100644 index 00000000..f4178a24 --- /dev/null +++ b/packages/tonal-tonal/test.ts @@ -0,0 +1,275 @@ +import * as Tonal from "./index"; + +describe("tonal", () => { + test("exported modules and functions", () => { + const exportedNames = Object.keys(Tonal).sort(); + expect(exportedNames).toEqual([ + "AbcNotation", + "Array", + "Chord", + "ChordDictionary", + "ChordType", + "Collection", + "Core", + "DurationValue", + "Interval", + "Key", + "Midi", + "Mode", + "Note", + "PcSet", // <- deprecated + "Pcset", + "Progression", + "Range", + "RomanNumeral", + "Scale", + "ScaleDictionary", + "ScaleType", + "TimeSignature", + "Tonal", + "accToAlt", + "altToAcc", + "coordToInterval", + "coordToNote", + "decode", + "deprecate", + "distance", + "encode", + "fillStr", + "interval", + "isNamed", + "isPitch", + "note", + "stepToLetter", + "tokenizeInterval", + "tokenizeNote", + "transpose", + ]); + }); + test("Modules exports functions", () => { + const modNames = Object.keys(Tonal) + .sort() + .filter((name) => name[0] === name.toUpperCase()[0]); + + const exportedFunctions = modNames.reduce((exported, modName) => { + const mod = (Tonal as any)[modName]; + exported[modName] = Object.keys(mod).sort(); + return exported; + }, {} as Record); + + expect(exportedFunctions).toEqual({ + AbcNotation: [ + "abcToScientificNotation", + "distance", + "scientificToAbcNotation", + "tokenize", + "transpose", + ], + Array: [ + "compact", + "permutations", + "range", + "rotate", + "shuffle", + "sortedNoteNames", + "sortedUniqNoteNames", + ], + Chord: [ + "chord", + "chordScales", + "detect", + "extended", + "get", + "getChord", + "reduced", + "tokenize", + "transpose", + ], + ChordDictionary: [ + "add", + "all", + "chordType", + "entries", + "get", + "keys", + "names", + "removeAll", + "symbols", + ], + ChordType: [ + "add", + "all", + "chordType", + "entries", + "get", + "keys", + "names", + "removeAll", + "symbols", + ], + Collection: ["compact", "permutations", "range", "rotate", "shuffle"], + Core: [ + "accToAlt", + "altToAcc", + "coordToInterval", + "coordToNote", + "decode", + "deprecate", + "distance", + "encode", + "fillStr", + "interval", + "isNamed", + "isPitch", + "note", + "stepToLetter", + "tokenizeInterval", + "tokenizeNote", + "transpose", + ], + DurationValue: ["fraction", "get", "names", "shorthands", "value"], + Interval: [ + "add", + "addTo", + "distance", + "fromSemitones", + "get", + "invert", + "name", + "names", + "num", + "quality", + "semitones", + "simplify", + "substract", + "transposeFifths", + ], + Key: ["majorKey", "majorTonicFromKeySignature", "minorKey"], + Midi: ["freqToMidi", "isMidi", "midiToFreq", "midiToNoteName", "toMidi"], + Mode: [ + "all", + "distance", + "entries", + "get", + "mode", + "names", + "notes", + "relativeTonic", + "seventhChords", + "triads", + ], + Note: [ + "accidentals", + "ascending", + "chroma", + "descending", + "enharmonic", + "freq", + "fromFreq", + "fromFreqSharps", + "fromMidi", + "fromMidiSharps", + "get", + "midi", + "name", + "names", + "octave", + "pitchClass", + "simplify", + "sortedNames", + "sortedUniqNames", + "tr", + "trBy", + "trFifths", + "trFrom", + "transpose", + "transposeBy", + "transposeFifths", + "transposeFrom", + ], + PcSet: [ + "chroma", + "chromas", + "filter", + "get", + "intervals", + "isEqual", + "isNoteIncludedIn", + "isSubsetOf", + "isSupersetOf", + "modes", + "num", + "pcset", + ], + Pcset: [ + "chroma", + "chromas", + "filter", + "get", + "intervals", + "isEqual", + "isNoteIncludedIn", + "isSubsetOf", + "isSupersetOf", + "modes", + "num", + "pcset", + ], + Progression: ["fromRomanNumerals", "toRomanNumerals"], + Range: ["chromatic", "numeric"], + RomanNumeral: ["get", "names", "romanNumeral"], + Scale: [ + "extended", + "get", + "modeNames", + "names", + "rangeOf", + "reduced", + "scale", + "scaleChords", + "scaleNotes", + "tokenize", + ], + ScaleDictionary: [ + "add", + "all", + "entries", + "get", + "keys", + "names", + "removeAll", + "scaleType", + ], + ScaleType: [ + "add", + "all", + "entries", + "get", + "keys", + "names", + "removeAll", + "scaleType", + ], + TimeSignature: ["get", "names", "parse"], + Tonal: [ + "accToAlt", + "altToAcc", + "coordToInterval", + "coordToNote", + "decode", + "deprecate", + "distance", + "encode", + "fillStr", + "interval", + "isNamed", + "isPitch", + "note", + "stepToLetter", + "tokenizeInterval", + "tokenizeNote", + "transpose", + ], + }); + }); +}); diff --git a/packages/tonal/CHANGELOG.md b/packages/tonal/CHANGELOG.md index 8eabc09d..c238fac4 100644 --- a/packages/tonal/CHANGELOG.md +++ b/packages/tonal/CHANGELOG.md @@ -1,4 +1,4 @@ -# @tonaljs/tonal +# tonal ## 4.6.10 diff --git a/packages/tonal/README.md b/packages/tonal/README.md index c1305c50..ab21c0ba 100644 --- a/packages/tonal/README.md +++ b/packages/tonal/README.md @@ -1,35 +1,180 @@ -# @tonaljs/tonal ![tonal](https://img.shields.io/badge/@tonaljs-modules-yellow.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/@tonaljs/tonal.svg?style=flat-square)](https://www.npmjs.com/package/@tonaljs/tonal) +# tonal -`@tonaljs/tonal` +[![npm version](https://img.shields.io/npm/v/tonal.svg?style=flat-square)](https://www.npmjs.com/package/tonal) +[![build status](https://img.shields.io/github/workflow/status/tonaljs/tonal/tests?style=flat-square)](https://github.com/tonaljs/tonal/actions) +![minified size](https://img.shields.io/badge/minified-31.1kb-blue?style=flat-square) +![gzipped size](https://img.shields.io/badge/gzipped-11.01kb-blue?style=flat-square) -Tonal library +`tonal` is a music theory library. Contains functions to manipulate tonal +elements of music (note, intervals, chords, scales, modes, keys). It deals with +abstractions (not actual music or sound). + +`tonal` is implemented in Typescript and published as a collection of Javascript +npm packages. + +It uses a functional programing style: all functions are pure, there is no data +mutation, and entities are represented by data structures instead of objects. + +## Example + +```js +import { Interval, Note, Scale } from "tonal"; + +Note.midi("A4"); // => 60 +Note.freq("a4").freq; // => 440 +Note.accidentals("c#2"); // => '#' +Note.transpose("C4", "5P"); // => "G4" +Interval.semitones("5P"); // => 7 +Interval.distance("C4", "G4"); // => "5P" +Scale.get("C major").notes; // =>["C", "D", "E", "F", "G", "A", "B"]; +``` ## Install +Install all packages at once: + ```bash -npm i --save @tonaljs/tonal -# or -yarn add @tonaljs/tonal +npm install --save tonal ``` ## Usage -Import: +Tonal is compatible with both ES5 and ES6 modules, and browser. + +#### ES6 `import`: ```js -// ES6 -import { Note, Key } from "@tonaljs/tonal"; -// node -const { Note, Key } = require("@tonaljs/tonal"); -// browser -const { Note, Key } = window.Tonal; +import { Note, Scale } from "tonal"; ``` -Use: +#### ES5 `require`: ```js -Note.transpose("A4", "5P"); -Key.majorKey("Gb"); +const { Note, Scale } = require("tonal"); +``` + +#### Browser + +You can use the browser version from jsdelivr CDN directly in your html: + +```html + + ``` -See [README.md](/#documentation) for documentation. +Or if you prefer, grab the +[minified browser ready version](https://raw.githubusercontent.com/tonaljs/tonal/master/packages/tonal/browser/tonal.min.js) +from the repository. + +#### Bundle size + +`tonal` includes all published modules. + +Although the final bundle it is small, you can +reduce bundle sizes even more by installing the modules individually, and +importing only the functions you need. + +Note that individual modules are prefixed with `@tonaljs` . For example: + +```bash +npm i @tonaljs/note +``` + +```js +import { transpose } from "@tonaljs/note"; +transpose("A4", "P5"); +``` + +## Documentation + +Generally, you just need to install `tonal` package (before it was called `@tonaljs/tonal`). + +- [tonal](/packages/tonal): All modules bundled in one package + +The API documentation is inside README.md of each module 👇 + +#### Notes and intervals + +- [@tonaljs/note](/packages/note): Note operations (simplify, transposeBy ) +- [@tonaljs/midi](/packages/midi): Midi number conversions +- [@tonaljs/interval](/packages/interval): Interval operations (add, simplify, + invert) +- [@tonaljs/pitch-notation-abc](/packages/pitch-notation-abc): Parse ABC + notation notes + +#### Scales and chords + +- [@tonaljs/scale](/packages/scale): Scales +- [@tonaljs/scale-type](/packages/scale-type): A dictionary of scales +- [@tonaljs/chord](/packages/chord): Chords +- [@tonaljs/chord-type](/packages/chord-type): A dictionary of chords +- [@tonaljs/chord-detect](/packages/chord-detect): Detect chords from notes +- [@tonaljs/pcset](/packages/pcset): Pitch class sets. Compare note groups. + +#### Keys, chord progressions + +- [@tonaljs/key](/packages/key): Major and minor keys, it's scales and chords +- [@tonaljs/mode](/packages/mode): A dictionary of Greek modes (ionian, + dorian...) +- [@tonaljs/progression](/packages/progression): Chord progressions +- [@tonaljs/roman-numeral](/packages/roman-numeral): Parse roman numeral symbols + +#### Time, rhythm + +- [@tonaljs/time-signature](/packages/time-signature): Parse time signatures +- [@tonaljs/duration-value](/packages/duration-value): Note duration values + +#### Utilities + +- [@tonaljs/core](/packages/core): Core functions (note, interval, transpose and + distance) +- [@tonaljs/collection](/packages/collection): Utility functions to work with + collections (range, shuffle, permutations) +- [@tonaljs/range](/packages/range): Create note ranges + +## Contributing + +Read [contributing document](/docs/CONTRIBUTING.md) for instructions + +## Inspiration + +This library takes inspiration from other music theory libraries: + +- Teoria: https://github.com/saebekassebil/teoria +- Impro-Visor: https://www.cs.hmc.edu/~keller/jazz/improvisor/ +- MusicKit: https://github.com/benzguo/MusicKit +- Music21: http://web.mit.edu/music21/doc/index.html +- Sharp11: https://github.com/jsrmath/sharp11 +- python-mingus: https://github.com/bspaans/python-mingus + +## Projects using tonal + +Showcase of projects that are using Tonal: + +- [Solfej](https://www.solfej.io/) by + [Shayan Javadi](https://github.com/ShayanJavadi) +- [EarBeater](https://www.earbeater.com/online-ear-training/) by + [Morten Vestergaard](https://github.com/vellebelle) +- [Sonid](https://sonid.app/) + ([play store](https://play.google.com/store/apps/details?id=org.stroopwafel.music.app), + [apple store](https://apps.apple.com/us/app/sonid/id1490221762?ls=1)) by + [martijnmichel](https://github.com/martijnmichel) +- [Songcraft](https://songcraft.io/) by + [Gabe G'Sell](https://github.com/gabergg) +- [React Guitar](https://react-guitar.com/) by + [4lejandrito](https://github.com/4lejandrito) +- [Fretty.app](https://fretty.app/) by [tfeldmann](https://github.com/tfeldmann) +- [Chordify](https://ashleymays.github.io/Chordify) by [ashleymays](https://github.com/ashleymays) +- [Chordal](https://chordal.vercel.app) by [kad1kad](https://github.com/kad1kad) +- [muted.io](https://muted.io/) by [thisisseb](https://github.com/thisisseb) + +Thank you all! + +Add your project here by +[editing this file](https://github.com/tonaljs/tonal/edit/main/README.md) + +## License + +[MIT License](docs/LICENSE) diff --git a/packages/tonal/package.json b/packages/tonal/package.json index 87ad6e4d..228ab718 100644 --- a/packages/tonal/package.json +++ b/packages/tonal/package.json @@ -1,7 +1,7 @@ { - "name": "@tonaljs/tonal", + "name": "tonal", "version": "4.6.10", - "description": "@tonaljs music theory full library", + "description": "tonaljs music theory library", "keywords": [ "music", "theory", diff --git a/packages/tonal/test.ts b/packages/tonal/test.ts index 612e7a0b..f4178a24 100644 --- a/packages/tonal/test.ts +++ b/packages/tonal/test.ts @@ -1,7 +1,6 @@ -import { modeNames } from "@tonaljs/scale"; import * as Tonal from "./index"; -describe("@tonaljs/tonal", () => { +describe("tonal", () => { test("exported modules and functions", () => { const exportedNames = Object.keys(Tonal).sort(); expect(exportedNames).toEqual([ diff --git a/packages/voice-leading/README.md b/packages/voice-leading/README.md index 55a2065d..414589e8 100644 --- a/packages/voice-leading/README.md +++ b/packages/voice-leading/README.md @@ -7,13 +7,13 @@ Contains a collection functions to find optimal transitions between chord voicin ES6: ```js -import { VoiceLeading } from '@tonaljs/tonal'; +import { VoiceLeading } from "tonal"; ``` Nodejs: ```js -const { VoiceLeading } = require('@tonaljs/tonal'); +const { VoiceLeading } = require("tonal"); ``` ## API @@ -21,7 +21,10 @@ const { VoiceLeading } = require('@tonaljs/tonal'); ### VoiceLeading ```ts -declare type VoiceLeadingFunction = (voicings: string[][], lastVoicing: string[]) => string[]; +declare type VoiceLeadingFunction = ( + voicings: string[][], + lastVoicing: string[] +) => string[]; ``` A function that decides which of a set of voicings is picked as a follow up to lastVoicing. @@ -34,8 +37,10 @@ const topNoteDiff: VoiceLeadingFunction = (voicings, lastVoicing) => { // if no lastVoicing is given return voicings[0]; } - const topNoteMidi = (voicing: string[]) => Note.midi(voicing[voicing.length - 1]) || 0; - const diff = (voicing: string[]) => Math.abs(topNoteMidi(lastVoicing) - topNoteMidi(voicing)); + const topNoteMidi = (voicing: string[]) => + Note.midi(voicing[voicing.length - 1]) || 0; + const diff = (voicing: string[]) => + Math.abs(topNoteMidi(lastVoicing) - topNoteMidi(voicing)); return voicings.sort((a, b) => diff(a) - diff(b))[0]; // return voicing with least diff }; ``` @@ -45,10 +50,10 @@ Usage ```ts topNoteDiff( [ - ['F3', 'A3', 'C4', 'E4'], // top note = E4 - ['C4', 'E4', 'F4', 'A4'], // top note = A4 + ["F3", "A3", "C4", "E4"], // top note = E4 + ["C4", "E4", "F4", "A4"], // top note = A4 ], - ['C4', 'E4', 'G4', 'B4'] // top note = B4 + ["C4", "E4", "G4", "B4"] // top note = B4 ); // ['C4', 'E4', 'F4', 'A4'] // => A4 is closer to B4 than E4 ``` diff --git a/packages/voicing-dictionary/README.md b/packages/voicing-dictionary/README.md index 4cb7b804..5c158fb1 100644 --- a/packages/voicing-dictionary/README.md +++ b/packages/voicing-dictionary/README.md @@ -7,13 +7,13 @@ Contains dictionaries for many chord voicings. Used by [@tonaljs/voicings](../vo ES6: ```js -import { VoicingDictionary } from '@tonaljs/tonal'; +import { VoicingDictionary } from "tonal"; ``` Nodejs: ```js -const { VoicingDictionary } = require('@tonaljs/tonal'); +const { VoicingDictionary } = require("tonal"); ``` ## API @@ -24,18 +24,18 @@ Maps a chord symbol to a set of voicings: ```ts const lefthand = { - m7: ['3m 5P 7m 9M', '7m 9M 10m 12P'], - '7': ['3M 6M 7m 9M', '7m 9M 10M 13M'], - '^7': ['3M 5P 7M 9M', '7M 9M 10M 12P'], - '69': ['3M 5P 6A 9M'], - m7b5: ['3m 5d 7m 8P', '7m 8P 10m 12d'], - '7b9': ['3M 6m 7m 9m', '7m 9m 10M 13m'], - '7b13': ['3M 6m 7m 9m', '7m 9m 10M 13m'], - o7: ['1P 3m 5d 6M', '5d 6M 8P 10m'], - '7#11': ['7m 9M 11A 13A'], - '7#9': ['3M 7m 9A'], - mM7: ['3m 5P 7M 9M', '7M 9M 10m 12P'], - m6: ['3m 5P 6M 9M', '6M 9M 10m 12P'], + m7: ["3m 5P 7m 9M", "7m 9M 10m 12P"], + "7": ["3M 6M 7m 9M", "7m 9M 10M 13M"], + "^7": ["3M 5P 7M 9M", "7M 9M 10M 12P"], + "69": ["3M 5P 6A 9M"], + m7b5: ["3m 5d 7m 8P", "7m 8P 10m 12d"], + "7b9": ["3M 6m 7m 9m", "7m 9m 10M 13m"], + "7b13": ["3M 6m 7m 9m", "7m 9m 10M 13m"], + o7: ["1P 3m 5d 6M", "5d 6M 8P 10m"], + "7#11": ["7m 9M 11A 13A"], + "7#9": ["3M 7m 9A"], + mM7: ["3m 5P 7M 9M", "7M 9M 10m 12P"], + m6: ["3m 5P 6M 9M", "6M 9M 10m 12P"], }; ``` diff --git a/packages/voicing/README.md b/packages/voicing/README.md index bd6f09fa..859e8669 100644 --- a/packages/voicing/README.md +++ b/packages/voicing/README.md @@ -7,13 +7,13 @@ Contains functions to generate voicings. If you're not sure what voicings are, [ ES6: ```js -import { Voicing } from '@tonaljs/tonal'; +import { Voicing } from "tonal"; ``` Nodejs: ```js -const { Voicing } = require('@tonaljs/tonal'); +const { Voicing } = require("tonal"); ``` ## API @@ -27,7 +27,7 @@ Voicing.search(chord: string, range?: [string, string], dictionary?: VoicingDict This method returns all possible voicings of the given chord, inside the given range, as defined in the dictionary: ```ts -Voicing.search('C^7', ['E3', 'D5'], { '^7': ['3M 5P 7M 9M', '7M 9M 10M 12P'] }); +Voicing.search("C^7", ["E3", "D5"], { "^7": ["3M 5P 7M 9M", "7M 9M 10M 12P"] }); /* => [ ['E3', 'G3', 'B3', 'D4'], ['E4', 'G4', 'B4', 'D5'], @@ -38,8 +38,8 @@ Voicing.search('C^7', ['E3', 'D5'], { '^7': ['3M 5P 7M 9M', '7M 9M 10M 12P'] }); The VoicingDictionary param uses the format of [@tonaljs/voicing-dictionary](../voicing-dictionary). Instead of defining your own, you could also use an existing dictionary from there: ```ts -import { VoicingDictionary } from '@tonaljs/voicing-dictionary'; -Voicing.search('C^7', ['E3', 'D5'], VoicingDictionary.lefthand); +import { VoicingDictionary } from "@tonaljs/voicing-dictionary"; +Voicing.search("C^7", ["E3", "D5"], VoicingDictionary.lefthand); /* => [ ['E3', 'G3', 'B3', 'D4'], ['E4', 'G4', 'B4', 'D5'], @@ -50,7 +50,7 @@ Voicing.search('C^7', ['E3', 'D5'], VoicingDictionary.lefthand); If no range and/or dictionary is given, there is a fallback to [default values](./index.ts) (look for defaultRange / defaultDictionary): ```ts -Voicing.search('C^7'); +Voicing.search("C^7"); /* => [ ['E3', 'G3', 'B3', 'D4'], ['E4', 'G4', 'B4', 'D5'], @@ -73,21 +73,19 @@ Voicing.get( Returns the best voicing for **chord** inside the given **range**, as contained in the **dictionary**, using **voiceLeading** to decide which voicing to pick after **lastVoicing**. Internally calls Voicing.search to generate the available voicings: ```ts -Voicing.get('Dm7'); +Voicing.get("Dm7"); /* ['F3', 'A3', 'C4', 'E4']); */ -Voicing.get('Dm7', ['F3', 'A4'], lefthand, topNoteDiff); +Voicing.get("Dm7", ["F3", "A4"], lefthand, topNoteDiff); /* ['F3', 'A3', 'C4', 'E4']; */ -const last = ['C4', 'E4', 'G4', 'B4']; -Voicing.get('Dm7', ['F3', 'A4'], lefthand, topNoteDiff, last); +const last = ["C4", "E4", "G4", "B4"]; +Voicing.get("Dm7", ["F3", "A4"], lefthand, topNoteDiff, last); /* ['C4', 'E4', 'F4', 'A4']; */ // => A4 is closest to B4 ``` ## Optional: Voicing.analyze ```ts -export declare function analyze( - voicing: string[] -): { +export declare function analyze(voicing: string[]): { topNote: string; bottomNote: string; midiAverage: number; @@ -97,9 +95,9 @@ export declare function analyze( Returns some useful info on the given voicing: ```ts -expect(Voicing.analyze(['C4', 'E4', 'G4', 'B4'])).toEqual({ - topNote: 'B4', - bottomNote: 'C4', +expect(Voicing.analyze(["C4", "E4", "G4", "B4"])).toEqual({ + topNote: "B4", + bottomNote: "C4", midiAverage: 85.4, // did not check :) // many more values possible }); @@ -121,7 +119,9 @@ export declare function analyzeTransition( Returns some useful info on the given voice transition ```ts -expect(Voicing.analyzeTransition(['C4', 'E4', 'G4', 'B4'], ['D4', 'F4', 'A4', 'C5'])).toEqual({ +expect( + Voicing.analyzeTransition(["C4", "E4", "G4", "B4"], ["D4", "F4", "A4", "C5"]) +).toEqual({ topNoteDiff: 1, bottomNoteDiff: 2, movement: 5, @@ -133,15 +133,22 @@ Could also use intervals instead of semitones (but semitones are easier to compa ## Optional: Voicing.intervalSets ```ts -export declare function intervalSets(chordSymbol: string, dictionary: VoicingDictionary); +export declare function intervalSets( + chordSymbol: string, + dictionary: VoicingDictionary +); ``` Get possible interval sets for given chord in given dictionary: ```ts -expect(Voicing.intervalSets('M7', lefthand)).toEqual([['3M 5P 7M 9M', '7M 9M 10M 12P']]); +expect(Voicing.intervalSets("M7", lefthand)).toEqual([ + ["3M 5P 7M 9M", "7M 9M 10M 12P"], +]); // could also be used with chord symbol (ignore root) -expect(Voicing.intervalSets('CM7', lefthand)).toEqual([['3M 5P 7M 9M', '7M 9M 10M 12P']]); +expect(Voicing.intervalSets("CM7", lefthand)).toEqual([ + ["3M 5P 7M 9M", "7M 9M 10M 12P"], +]); ``` Note that it works, even if the chord symbol "M7" is just an alias of the "^7" symbol used in the dictionary. @@ -149,7 +156,11 @@ Note that it works, even if the chord symbol "M7" is just an alias of the "^7" s ## Optional: Voicing.searchSets ```ts -export declare function searchSets(intervalSets: string[][], range: string[], root: string); +export declare function searchSets( + intervalSets: string[][], + range: string[], + root: string +); ``` Renders all sets of notes that represent any of the interval sets inside the given range, relative to the root: @@ -158,16 +169,16 @@ Renders all sets of notes that represent any of the interval sets inside the giv expect( Voicing.searchSets( [ - ['1P', '3M', '5P'], - ['3M', '5P', '8P'], + ["1P", "3M", "5P"], + ["3M", "5P", "8P"], ], - ['C3', 'G4'], - 'C' + ["C3", "G4"], + "C" ) ).toEqual([ - ['C3', 'E3', 'G3'], - ['E3', 'G3', 'C4'], - ['C4', 'E4', 'G4'], + ["C3", "E3", "G3"], + ["E3", "G3", "C4"], + ["C4", "E4", "G4"], ]); ```