Skip to content

Commit

Permalink
Feat/extract pitch modules (#399)
Browse files Browse the repository at this point in the history
* feat: move named to pitch

* feat: add notation-interval

* feat: extract interval

* chore: rename to pitch-interval

* feat: extract pitch-note

* chore: fix pitch-interval documentation

* feat: add pitch-distance

* chore: changeset

* chore: add typedoc

* feat: create notation-scientific

* fix: readme badges

* chore: notation-scientific not yet ready to publish

* chore: bump version

* chore: deps

* chore: code cleanup
  • Loading branch information
danigb authored Nov 29, 2023
1 parent 96e638a commit c25c05e
Show file tree
Hide file tree
Showing 46 changed files with 996 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ compiled
.awcache
.rpt2_cache
.turbo
tmp/
tmp/
typedoc/
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"lint": "eslint 'packages/**/*.ts'",
"lint:fix": "eslint 'packages/**/*.ts' --fix",
"test:ci": "yarn format && yarn lint && yarn build && yarn test -- --no-cache",
"doc": "yarn typedoc",
"prepare": "husky install"
},
"prettier": {},
Expand Down Expand Up @@ -49,20 +50,21 @@
"devDependencies": {
"@changesets/cli": "^2.26.1",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/node": "^20.10.1",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"esbuild": "^0.19.6",
"eslint": "^8.54.0",
"husky": "^8.0.0",
"jest": "^29.5.0",
"jest-config": "^29.5.0",
"jest": "^29.5.0",
"lint-staged": "^15.1.0",
"lodash": "^4.17.15",
"prettier": "^3.1.0",
"ts-jest": "^29.1.0",
"tsup": "^8.0.0",
"turbo": "^1.10.3",
"typedoc": "^0.25.4",
"typescript": "^5.2.2"
}
}
10 changes: 10 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @tonaljs/core

## 4.10.2

### Patch Changes

- Move core into pitch modules
- Updated dependencies [4689b77]
- @tonaljs/pitch-distance@5.0.0
- @tonaljs/pitch-interval@5.0.0
- @tonaljs/pitch-note@5.0.0

## 4.10.1

### Patch Changes
Expand Down
25 changes: 20 additions & 5 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { isNamedPitch } from "@tonaljs/pitch";

export * from "@tonaljs/pitch";
export * from "./src/distance";
export * from "./src/interval";
export * from "./src/named";
export * from "./src/note";
export * from "./src/utils";
export * from "@tonaljs/pitch-distance";
export * from "@tonaljs/pitch-interval";
export * from "@tonaljs/pitch-note";

export const fillStr = (s: string, n: number) => Array(Math.abs(n) + 1).join(s);

export function deprecate<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ResultFn extends (this: any, ...newArgs: any[]) => ReturnType<ResultFn>,
>(original: string, alternative: string, fn: ResultFn) {
return function (this: unknown, ...args: unknown[]): ReturnType<ResultFn> {
// tslint:disable-next-line
console.warn(`${original} is deprecated. Use ${alternative}.`);
return fn.apply(this, args);
};
}

export const isNamed = deprecate("isNamed", "isNamedPitch", isNamedPitch);
7 changes: 5 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tonaljs/core",
"version": "4.10.1",
"version": "4.10.2",
"description": "Music theory library",
"keywords": [
"music",
Expand All @@ -18,7 +18,10 @@
"access": "public"
},
"dependencies": {
"@tonaljs/pitch": "^5.0.0"
"@tonaljs/pitch": "^5.0.0",
"@tonaljs/pitch-interval": "^5.0.0",
"@tonaljs/pitch-distance": "^5.0.0",
"@tonaljs/pitch-note": "^5.0.0"
},
"scripts": {
"build": "tsup index.ts --sourcemap --dts --format esm,cjs"
Expand Down
21 changes: 0 additions & 21 deletions packages/core/src/named.ts

This file was deleted.

18 changes: 0 additions & 18 deletions packages/core/src/utils.ts

This file was deleted.

16 changes: 8 additions & 8 deletions packages/core/test/tonal.named.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { interval, isNamed, note } from "../index";
import { interval, isNamedPitch, note } from "../index";

describe("@tonaljs/core", () => {
test("isNamed", () => {
expect(isNamed(note("C4"))).toBe(true);
expect(isNamed(interval("P4"))).toBe(true);
expect(isNamed(note("X"))).toBe(true);
test("isNamedPitch", () => {
expect(isNamedPitch(note("C4"))).toBe(true);
expect(isNamedPitch(interval("P4"))).toBe(true);
expect(isNamedPitch(note("X"))).toBe(true);

expect(isNamed(undefined)).toBe(false);
expect(isNamed(null)).toBe(false);
expect(isNamed("")).toBe(false);
expect(isNamedPitch(undefined)).toBe(false);
expect(isNamedPitch(null)).toBe(false);
expect(isNamedPitch("")).toBe(false);
});
});
4 changes: 2 additions & 2 deletions packages/mode/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { rotate } from "@tonaljs/collection";
import { deprecate, Named, NoteName, transpose } from "@tonaljs/core";
import { deprecate, NamedPitch, NoteName, transpose } from "@tonaljs/core";
import { simplify, transposeFifths } from "@tonaljs/interval";
import { EmptyPcset, Pcset } from "@tonaljs/pcset";
import { get as getType } from "@tonaljs/scale-type";
Expand Down Expand Up @@ -44,7 +44,7 @@ modes.forEach((mode) => {
});
});

type ModeLiteral = string | Named;
type ModeLiteral = string | NamedPitch;

/**
* Get a Mode by it's name
Expand Down
2 changes: 1 addition & 1 deletion packages/mode/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("Mode", () => {
});
expect(Mode.get("major")).toEqual(Mode.get("ionian"));
});
test("accept Named as parameter", () => {
test("accept NamedPitch as parameter", () => {
expect(Mode.get(Mode.get("major"))).toEqual(Mode.get("major"));
expect(Mode.get({ name: "Major" })).toEqual(Mode.get("major"));
});
Expand Down
1 change: 1 addition & 0 deletions packages/notation-scientific/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @tonaljs/notation-scientific
22 changes: 22 additions & 0 deletions packages/notation-scientific/LICENSE
Original file line number Diff line number Diff line change
@@ -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.

39 changes: 39 additions & 0 deletions packages/notation-scientific/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# @tonaljs/notation-scientific ![tonal](https://img.shields.io/badge/@tonaljs-notation_scientific-yellow.svg?style=flat-square) [![npm version](https://img.shields.io/npm/v/@tonaljs/notation-scientific.svg?style=flat-square)](https://www.npmjs.com/package/@tonaljs/notation-scientific)

> Parse notes in scientific notation
## Usage

⚠️ It's probably you don't need to use this package directly. Use [tonal-note](/packages/note) instead.

```js
import { parse, name, tokenize } from "@tonaljs/notation-scientific";

parse("cb4"); // => { step: 0, oct: 4, alt: -1 }
```

## API

### `tokenize(name: string) => [string, string, string, string]`

Given a note name in [scientific notation] it returns an array with the different parts:

```js
tokenize("Abb4 major"); // => ["", "A", "bb", "4", "major"]
```

### `parse(name: string) => { step, alt, oct? } | undefined`

Given a note name, it returns an object with the following properties:

- step (number): the letter number (0..6)
- alt (number): the accidental number (..., -1 = 'b', 0 = '', 1 = '#', ...)
- oct (number?): the optional octave

### `name(parsed) => string`

Given the result of `parse` it returns the name.

```js
name({ step: 1, oct: 2, alt: 1 }); // => "D#1"
```
60 changes: 60 additions & 0 deletions packages/notation-scientific/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
type ScientificPitch = {
step: number;
alt: number;
oct?: number;
};

const fillStr = (s: string, n: number) => Array(Math.abs(n) + 1).join(s);

const altToAcc = (alt: number): string =>
alt < 0 ? fillStr("b", -alt) : fillStr("#", alt);

/**
* Given a note literal (a note name or a note object), returns the Note object
* @example
* note('Bb4') // => { name: "Bb4", midi: 70, chroma: 10, ... }
*/

type NoteTokens = [string, string, string, string];

const REGEX = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)$/;

/**
* @private
*/
export function tokenize(str: string): NoteTokens {
const m = REGEX.exec(str) as string[];
return [m[1].toUpperCase(), m[2].replace(/x/g, "##"), m[3], m[4]];
}

export function parse(noteName: string): ScientificPitch | null {
const tokens = tokenize(noteName);
if (tokens[0] === "" || tokens[3] !== "") {
return null;
}

const letter = tokens[0];
const acc = tokens[1];
const octStr = tokens[2];

const step = (letter.charCodeAt(0) + 3) % 7;
const alt = acc[0] === "b" ? -acc.length : acc.length;
const oct = octStr.length ? +octStr : undefined;

return {
alt,
oct,
step,
};
}

export function name(props: ScientificPitch): string {
const { step, alt, oct } = props;
const letter = "CDEFGAB".charAt(step);
if (!letter) {
return "";
}

const pc = letter + altToAcc(alt);
return oct || oct === 0 ? pc + oct : pc;
}
28 changes: 28 additions & 0 deletions packages/notation-scientific/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@tonaljs/notation-scientific",
"version": "4.8.0",
"private": true,
"description": "Parse intervals in shorthand notation",
"keywords": [
"note",
"music",
"theory",
"interval",
"shorthand notation"
],
"dependencies": {},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"author": "[email protected]",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsup index.ts --sourcemap --dts --format esm,cjs"
}
}
37 changes: 37 additions & 0 deletions packages/notation-scientific/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { name, parse, tokenize } from "./index";

describe("Scientific Notation", () => {
test("tokenize", () => {
expect(tokenize("Cbb5 major")).toEqual(["C", "bb", "5", "major"]);
expect(tokenize("Ax")).toEqual(["A", "##", "", ""]);
expect(tokenize("CM")).toEqual(["C", "", "", "M"]);
expect(tokenize("maj7")).toEqual(["", "", "", "maj7"]);
expect(tokenize("")).toEqual(["", "", "", ""]);
expect(tokenize("bb")).toEqual(["B", "b", "", ""]);
expect(tokenize("##")).toEqual(["", "##", "", ""]);
});

test("parse", () => {
expect(parse("C3")).toEqual({ alt: 0, oct: 3, step: 0 });
expect(parse("A4")).toEqual({ alt: 0, oct: 4, step: 5 });
expect(parse("D#")).toEqual({ alt: 1, oct: undefined, step: 1 });
expect(parse("D##")).toEqual({ alt: 2, oct: undefined, step: 1 });
expect(parse("D###")).toEqual({ alt: 3, oct: undefined, step: 1 });
expect(parse("eb")).toEqual({ alt: -1, oct: undefined, step: 2 });
expect(parse("ebbb")).toEqual({ alt: -3, oct: undefined, step: 2 });
expect(parse("ebbb3")).toEqual({ alt: -3, oct: 3, step: 2 });
expect(parse("ebbb-3")).toEqual({ alt: -3, oct: -3, step: 2 });
expect(parse("ebbbbbb-3")).toEqual({ alt: -6, oct: -3, step: 2 });
expect(parse("bb")).toEqual(parse("Bb"));
});

test("name", () => {
expect(name({ step: 1, alt: -1 })).toBe("Db");
expect(name({ step: 2, alt: 1 })).toBe("E#");
expect(name({ step: 2, alt: 1, oct: 4 })).toBe("E#4");
expect(name({ step: 5, alt: 0 })).toBe("A");
expect(name({ step: 5, alt: 0, oct: 2 })).toBe("A2");
expect(name({ step: -1, alt: 0 })).toBe("");
expect(name({ step: 8, alt: 0 })).toBe("");
});
});
Loading

0 comments on commit c25c05e

Please sign in to comment.