Skip to content

Commit

Permalink
Introduce Tonal to replace Teoria
Browse files Browse the repository at this point in the history
  • Loading branch information
BHSPitMonkey committed Mar 4, 2024
1 parent 524e529 commit 83d3af2
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 20 deletions.
68 changes: 68 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@tonaljs/note": "^4.10.3",
"@tonaljs/pitch-note": "^6.0.0",
"@types/react": "^15.7.30",
"@types/vexflow": "^1.2.42",
"babel-loader": "^9.1.3",
Expand Down
6 changes: 3 additions & 3 deletions src/modes/free-play.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class FreePlay extends React.Component {
state: {
clef: 'bass' | 'alto' | 'treble' | 'grand';
flatKeyboardLabels?: boolean;
keysDown?: Set<string>;
keysDown?: Set<number>;
};
notesOn: {
[x: number]: true;
Expand Down Expand Up @@ -49,9 +49,9 @@ export default class FreePlay extends React.Component {
* - The on-screen musical keyboard (KeyboardButtons component)
* - Keyboard input
*
* @param {Set} entries The names of the key(s) being pressed.
* @param string entry Name of a pitch class, probably without octave (e.g. 'F#')
*/
handleKeyPress(entry: any): void;
handleKeyPress(entry: string): void;
render(): React.JSX.Element;
}
export {};
25 changes: 15 additions & 10 deletions src/modes/free-play.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { PreferencesState, PreferenceGroup } from '../common/practice-intro';
import SheetMusicView from '../sheet-music-view';
import KeyboardButtons from '../keyboard-buttons';
import * as Midi from '../midi'
import TonalNoteUtil from '@tonaljs/note';
import { Note as TonalNote } from '@tonaljs/pitch-note';

type FreePlayProps = {
prefs: PreferencesState,
Expand All @@ -26,7 +28,7 @@ export default class FreePlay extends React.Component {
state: {
clef: 'bass' | 'alto' | 'treble' | 'grand',
flatKeyboardLabels?: boolean,
keysDown?: Set<string>,
keysDown?: Set<number>,
};
notesOn: { [x: number]: true };

Expand Down Expand Up @@ -145,13 +147,14 @@ export default class FreePlay extends React.Component {
* - The on-screen musical keyboard (KeyboardButtons component)
* - Keyboard input
*
* @param {Set} entries The names of the key(s) being pressed.
* @param string entry Name of a pitch class, probably without octave (e.g. 'F#')
*/
handleKeyPress(entry) {
handleKeyPress(entry: string) {
const keysDown = this.state.keysDown;
const autoHold = this.props.prefs['keyboardAutoHold'];
const note = Teoria.note(entry);
const midiNote = note.midi();
//const note = Teoria.note(entry);
const tonalNote = TonalNoteUtil.get(entry + '4'); // Force an octave since there is none here
const midiNote = tonalNote.midi;

// If there are multiple notes in this question, toggle this key in keysDown
if (autoHold) {
Expand All @@ -169,14 +172,16 @@ export default class FreePlay extends React.Component {
}

// Save changed keysDown to state
this.setState({ keysDown: keysDown });
this.setState({ tonalNotesDown: keysDown });
}

render() {
// Map this.state.keysDown into Teoria notes
const notesDown = [];
//const notesDown = [];
const tonalNotes = [];
this.state.keysDown.forEach(midiNote => {
notesDown.push(Teoria.note.fromMIDI(midiNote));
//notesDown.push(Teoria.note.fromMIDI(midiNote));
tonalNotes.push(TonalNoteUtil.get(TonalNoteUtil.fromMidi(midiNote)));
});

return (
Expand All @@ -186,15 +191,15 @@ export default class FreePlay extends React.Component {
<SheetMusicView
clef={this.state.clef}
keySignature={'C'}
keys={notesDown}
tonalNotes={tonalNotes}
/>
<KeyboardButtons
onEntry={this.handleKeyPress}
style={{margin: "10px auto"}}
showLabels={this.props.prefs["keyboardLabels"]}
useFlats={this.state.flatKeyboardLabels}
enableSound={this.props.prefs["keyboardSound"]}
keysDown={this.state.keysDown}
//keysDown={this.state.keysDown}
/>
</CardText>
</Card>
Expand Down
2 changes: 1 addition & 1 deletion src/modes/sight-reading-practice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ export default class SightReadingPractice extends React.Component {
<SheetMusicView
clef={this.state.clef}
keySignature={this.state.keySignature}
keys={this.state.keys}
keys={this.state.keys} // TODO: Migrate to TonalNotes
/>
<KeyboardButtons
onEntry={this.handleGuess}
Expand Down
3 changes: 3 additions & 0 deletions src/sheet-music-view.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import { Note as TonalNote } from '@tonaljs/pitch-note';
type SheetMusicViewProps = {
clef: 'bass' | 'alto' | 'treble' | 'grand';
height?: number;
width?: number;
keySignature?: string;
keys?: TeoriaNote[];
tonalNotes?: TonalNote[];
};
/**
* Visual display of a snippet of sheet music (wraps an engraving library)
Expand All @@ -16,6 +18,7 @@ declare class SheetMusicView extends React.Component {
componentDidMount(): void;
componentDidUpdate(prevProps: any, prevState: any): void;
teoriaKeysToVexflowKeys(keys: TeoriaNote[]): string[];
tonalNoteNamesToVexflowKeys(notes: TonalNote[]): string[];
/**
* Redraw the contents of the canvas
*/
Expand Down
41 changes: 35 additions & 6 deletions src/sheet-music-view.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react';
import * as ReactDOM from 'react-dom';
import { Note as TonalNote } from '@tonaljs/pitch-note';
import Vex from 'vexflow';

type SheetMusicViewProps = {
clef: 'bass' | 'alto' | 'treble' | 'grand',
height?: number,
width?: number,
keySignature?: string,
keys?: TeoriaNote[],
keys?: TeoriaNote[], // Deprecated, migrate to tonalNotes instead
tonalNotes?: TonalNote[], // Replaces 'keys'
};

/**
Expand Down Expand Up @@ -43,6 +45,20 @@ class SheetMusicView extends React.Component {
});
}

tonalNoteNamesToVexflowKeys(notes: TonalNote[]) {
// Enforce octaves
notes.forEach(note => {
if (note.oct === null) {
throw Error("Note given to SheetMusicView missing octave information");
}
});

const sorted = notes.toSorted((a, b) => a.midi - b.midi);
return sorted.map(function (note) {
return note.pc + "/" + note.oct;
});
}

/**
* Redraw the contents of the canvas
*/
Expand Down Expand Up @@ -84,13 +100,20 @@ class SheetMusicView extends React.Component {
}

// The StaveNote can have one or more keys (i.e. mono- or polyphonic)
if (this.props.keys.length > 0) {
if (this.props.tonalNotes.length > 0 || this.props.keys.length > 0) {
const staveNotes = [];

// Come up with a StaveNote for each staff
if (isGrand) {
var upperKeys = this.teoriaKeysToVexflowKeys(this.props.keys.filter(teoriaKey => teoriaKey.octave() >= 4));
var lowerKeys = this.teoriaKeysToVexflowKeys(this.props.keys.filter(teoriaKey => teoriaKey.octave() < 4));
if (this.props.tonalNotes.length > 0) {
// Use new Tonal note set
var upperKeys = this.tonalNoteNamesToVexflowKeys(this.props.tonalNotes.filter(note => note.oct >= 4));
var lowerKeys = this.tonalNoteNamesToVexflowKeys(this.props.tonalNotes.filter(note => note.oct < 4));
} else {
// Use old Teoria note set
var upperKeys = this.teoriaKeysToVexflowKeys(this.props.keys.filter(teoriaKey => teoriaKey.octave() >= 4));
var lowerKeys = this.teoriaKeysToVexflowKeys(this.props.keys.filter(teoriaKey => teoriaKey.octave() < 4));
}

if (upperKeys.length > 0) {
staveNotes.push((new Vex.Flow.StaveNote({
Expand All @@ -109,7 +132,11 @@ class SheetMusicView extends React.Component {
})).setStave(stave2));
}
} else {
var keys = this.teoriaKeysToVexflowKeys(this.props.keys);
if (this.props.tonalNotes.length > 0) {
var keys = this.tonalNoteNamesToVexflowKeys(this.props.tonalNotes);
} else {
var keys = this.teoriaKeysToVexflowKeys(this.props.keys);
}
staveNotes.push((new Vex.Flow.StaveNote({
clef: this.props.clef,
keys: keys,
Expand Down Expand Up @@ -150,6 +177,8 @@ class SheetMusicView extends React.Component {
SheetMusicView.defaultProps = {
width: 150,
height: 140,
clef: "treble"
clef: "treble",
keys: [],
tonalNotes: [],
};
export default SheetMusicView;

0 comments on commit 83d3af2

Please sign in to comment.