Skip to content

Commit

Permalink
Add 'Trainer' to play through C Major
Browse files Browse the repository at this point in the history
  • Loading branch information
ZaneH committed Jul 4, 2022
1 parent 233b80f commit 13eef9c
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 10 deletions.
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import Keyboard from './components/Keyboard'
import { TrainerPiano } from './components/Trainer'
import TrainerProvider from './components/TrainerProvider'

function App() {
return (
<div className='App'>
<Keyboard />
<TrainerProvider>
<TrainerPiano />
<Keyboard />
</TrainerProvider>
</div>
)
}
Expand Down
20 changes: 11 additions & 9 deletions src/components/Keyboard/Keyboard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useContext, useEffect, useState } from 'react'
import { KeyboardShortcuts, MidiNumbers, Piano } from 'react-piano'
import 'react-piano/dist/styles.css'
import SoundfontProvider from '../SoundfontProvider'
import { TrainerContext } from '../TrainerProvider'

const Keyboard = () => {
const { nextTargetNote, setNoteCounter } = useContext(TrainerContext)
const [activeNotes, setActiveNotes] = useState<{ [note: string]: boolean }>(
{}
)
const firstNote = MidiNumbers.fromNote('c3')
const lastNote = MidiNumbers.fromNote('b4')
const lastNote = MidiNumbers.fromNote('c5')
const keyboardShortcuts = KeyboardShortcuts.create({
firstNote,
lastNote,
Expand All @@ -25,7 +27,7 @@ const Keyboard = () => {

listen('midi_message', (event) => {
const payload = event.payload as { message: number[] }
const [command, note, velocity] = payload.message
const [command, note] = payload.message

if (command === 144) {
setActiveNotes((an) => ({
Expand All @@ -50,10 +52,6 @@ const Keyboard = () => {
onLoadCallback()
}, [onLoadCallback, isListening])

useEffect(() => {
console.log(Object.keys(activeNotes).filter((v: string) => activeNotes[v]))
}, [activeNotes])

return (
<SoundfontProvider
instrumentName={'acoustic_grand_piano'}
Expand All @@ -65,10 +63,14 @@ const Keyboard = () => {
return (
<Piano
noteRange={{ first: firstNote, last: lastNote }}
playNote={(midiNumber: any) => {
playNote={(midiNumber: number) => {
if (midiNumber === nextTargetNote) {
setNoteCounter?.((nc) => nc + 1)
}

playNote(midiNumber)
}}
stopNote={(midiNumber: any) => {
stopNote={(midiNumber: number) => {
stopNote(midiNumber)
}}
keyboardShortcuts={keyboardShortcuts}
Expand Down
53 changes: 53 additions & 0 deletions src/components/Trainer/TrainerPiano.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useContext } from 'react'
import { Keyboard, MidiNumbers } from 'react-piano'
import styled from 'styled-components'
import { ignoreOctave } from '../../utils'
import { TrainerContext } from '../TrainerProvider'

const PianoContainer = styled.div`
height: 50vh;
width: 60%;
`

const InKeyMarker = styled.div`
border-radius: 50%;
width: 2em;
height: 2em;
background-color: #acddec;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Arial', 'Helvetica', sans-serif;
font-weight: 600;
padding: 4px;
margin: 2vh auto;
`

const TrainerPiano = () => {
const { nextTargetNote, scale } = useContext(TrainerContext)

return (
<PianoContainer>
<Keyboard
noteRange={{
first: MidiNumbers.fromNote('c3'),
last: MidiNumbers.fromNote('g4'),
}}
activeNotes={[nextTargetNote]}
onPlayNoteInput={() => {}}
onStopNoteInput={() => {}}
keyWidthToHeight={0.33}
renderNoteLabel={({ midiNumber }: { midiNumber: number }) => {
const modScale = ignoreOctave(scale || [])
return (
modScale[midiNumber % 12] && (
<InKeyMarker>{modScale[midiNumber % 12]}</InKeyMarker>
)
)
}}
/>
</PianoContainer>
)
}

export default TrainerPiano
1 change: 1 addition & 0 deletions src/components/Trainer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as TrainerPiano } from './TrainerPiano'
52 changes: 52 additions & 0 deletions src/components/TrainerProvider/TrainerProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
createContext,
Dispatch,
FC,
SetStateAction,
useEffect,
useState,
} from 'react'
import { CMajor, ScaleType } from '../../utils'

type TrainerContextType = {
children?: React.ReactNode
nextTargetNote?: number
setNextTargetNote?: Dispatch<SetStateAction<number>>
scale?: ScaleType
setScale?: Dispatch<SetStateAction<ScaleType>>
noteCounter?: number // responsible for resetting the target note when we reach the end of a scale
setNoteCounter?: Dispatch<SetStateAction<number>>
}

export const TrainerContext = createContext({} as TrainerContextType)

const TrainerProvider: FC<TrainerContextType> = ({ children }) => {
const [scale, setScale] = useState<ScaleType>(CMajor)
const [nextTargetNote, setNextTargetNote] = useState<number>(
Number(Object.keys(scale)[0])
)
const [noteCounter, setNoteCounter] = useState(0)

const context: TrainerContextType = {
nextTargetNote,
setNextTargetNote,
scale,
setScale,
noteCounter,
setNoteCounter,
}

useEffect(() => {
const newTargetNote = Number(Object.keys(scale)[noteCounter % 8])
console.log('NEW:', newTargetNote)
setNextTargetNote(newTargetNote)
}, [noteCounter, setNextTargetNote, scale])

return (
<TrainerContext.Provider value={context}>
{children}
</TrainerContext.Provider>
)
}

export default TrainerProvider
1 change: 1 addition & 0 deletions src/components/TrainerProvider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, TrainerContext } from './TrainerProvider'
5 changes: 5 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type ScaleType = {
[midi: number]: string
}

export * from './scales/Majors'
25 changes: 25 additions & 0 deletions src/utils/scales/Majors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ScaleType } from '..'

const CMajor: ScaleType = {
48: 'I',
50: 'ii',
52: 'iii',
53: 'IV',
55: 'V',
57: 'vi',
59: 'viiº',
60: 'I',
}

const ignoreOctave = (scale: ScaleType): ScaleType => {
const scaleKeys: string[] = Object.keys(scale)
const modKeys: ScaleType = {}

for (const k of scaleKeys) {
modKeys[Number(k) % 12] = scale[Number(k)]
}

return modKeys
}

export { CMajor, ignoreOctave }

0 comments on commit 13eef9c

Please sign in to comment.