From 7bdf2994f1d3fe4a645f020b234e4124cfc671a1 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 13 Sep 2024 14:27:31 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20jotai=20=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/stores/settings.ts | 13 +++++++++++++ yarn.lock | 5 +++++ 3 files changed, 19 insertions(+) create mode 100644 src/stores/settings.ts diff --git a/package.json b/package.json index 6962466..6532819 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@reacticons/ionicons": "^7.1.0", "@tauri-apps/api": ">=2.0.0-rc.0", "@tauri-apps/plugin-shell": ">=2.0.0-rc.0", + "jotai": "^2.9.3", "next": "^14.2.8", "paper": "^0.12.18", "react": "^18.2.0", diff --git a/src/stores/settings.ts b/src/stores/settings.ts new file mode 100644 index 0000000..b56ed9c --- /dev/null +++ b/src/stores/settings.ts @@ -0,0 +1,13 @@ +import { Settings } from '@/types'; +import { atom } from 'jotai'; + +// bpm +export const bpnAtom = atom(120); +// 表示する音階の範囲 +export const octaveRangeAtom = atom([3, 5]); +// 小節数 +export const barCountAtom = atom(8); +// 1小節あたりの拍数 +export const beatCountAtom = atom(4); +// 最小の音符の長さ (1/N 拍) +export const minNoteDurationAtom = atom(4); diff --git a/yarn.lock b/yarn.lock index 8fdc328..7461022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3216,6 +3216,11 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" +jotai@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.9.3.tgz#abcae49a737cd50e3144a6c9eb39840db077c727" + integrity sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw== + joycon@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" From 05901e6f3b67a509cbc396fb8bfe2397f735f775 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 13 Sep 2024 14:37:35 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20jotai=20=E3=81=AB=E7=BD=AE?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/tap/features/RecordRhythm/const/index.ts | 5 ----- .../features/RecordRhythm/functions/highlightMap2Beat.ts | 4 +--- src/app/tap/features/RecordRhythm/hooks/useRhythmBar.ts | 9 ++++++++- 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 src/app/tap/features/RecordRhythm/const/index.ts diff --git a/src/app/tap/features/RecordRhythm/const/index.ts b/src/app/tap/features/RecordRhythm/const/index.ts deleted file mode 100644 index 8b3ad7c..0000000 --- a/src/app/tap/features/RecordRhythm/const/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * 1小節あたりに含まれる1/4拍の数を表す定数。 - * 1小節には4拍としているので4*4=16 - */ -export const ONE_BAR = 16; diff --git a/src/app/tap/features/RecordRhythm/functions/highlightMap2Beat.ts b/src/app/tap/features/RecordRhythm/functions/highlightMap2Beat.ts index 443d3ac..bb3b411 100644 --- a/src/app/tap/features/RecordRhythm/functions/highlightMap2Beat.ts +++ b/src/app/tap/features/RecordRhythm/functions/highlightMap2Beat.ts @@ -1,7 +1,6 @@ import { HighlightedSection } from '../types'; import { Beat } from '@/models'; -import { ONE_BAR } from '../const'; /** * highlightMap2Beat @@ -15,8 +14,7 @@ import { ONE_BAR } from '../const'; * @returns {Beat[]} - Beat の配列 */ -export function highlightMap2Beat(highlightMap: HighlightedSection[], numberOfBar: number): Beat[] { - const length = numberOfBar * ONE_BAR; +export function highlightMap2Beat(highlightMap: HighlightedSection[], length: number): Beat[] { const minBeat = 100 / length; return highlightMap diff --git a/src/app/tap/features/RecordRhythm/hooks/useRhythmBar.ts b/src/app/tap/features/RecordRhythm/hooks/useRhythmBar.ts index eef9e45..c09d990 100644 --- a/src/app/tap/features/RecordRhythm/hooks/useRhythmBar.ts +++ b/src/app/tap/features/RecordRhythm/hooks/useRhythmBar.ts @@ -1,7 +1,13 @@ import { useEffect, useState } from 'react'; import { highlightMap2Beat } from '../functions/highlightMap2Beat'; +import { useAtomValue } from 'jotai'; +import { barCountAtom, beatCountAtom, minNoteDurationAtom } from '@/stores/settings'; export function useRhythmBar(duration: number) { + const barCount = useAtomValue(barCountAtom); // 小節数 + const beatCount = useAtomValue(beatCountAtom); // 拍子 + const minNoteDuration = useAtomValue(minNoteDurationAtom); // 最小音符の長さ + const [pushSpaceKey, setPushSpaceKey] = useState(false); const [progress, setProgress] = useState(0); const [highlightedSections, setHighlightedSections] = useState<{ start: number; end: number }[]>([]); @@ -75,7 +81,8 @@ export function useRhythmBar(duration: number) { }, [progress, pushSpaceKey]); useEffect(() => { - const Beat = highlightMap2Beat(highlightedSections, 8); + const length = barCount * beatCount * minNoteDuration; + const Beat = highlightMap2Beat(highlightedSections, length); }, [isFinished]); return { pushSpaceKey, progress, highlightedSections, currentHighlightStart, isStarted }; From 9e6f316af2fac839d17e857a198e7791626a1edf Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 13 Sep 2024 14:44:08 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20jotai=20=E3=81=AB=E7=BD=AE?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/draw/features/FreeDrawing/const/index.ts | 2 -- src/app/draw/features/FreeDrawing/functions/curveSprit.ts | 7 +++---- src/app/draw/features/FreeDrawing/hooks/useDrawing.ts | 8 +++++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/app/draw/features/FreeDrawing/const/index.ts b/src/app/draw/features/FreeDrawing/const/index.ts index 5a53717..85afe53 100644 --- a/src/app/draw/features/FreeDrawing/const/index.ts +++ b/src/app/draw/features/FreeDrawing/const/index.ts @@ -1,5 +1,3 @@ -export const BAR_NUM = 8; -export const NOTE_NUM = 4; export const SCALE_NUM = 7; export const WHOLE_NOTE = [1, 2, 4, 5, 6]; diff --git a/src/app/draw/features/FreeDrawing/functions/curveSprit.ts b/src/app/draw/features/FreeDrawing/functions/curveSprit.ts index 46fae3e..5c61a01 100644 --- a/src/app/draw/features/FreeDrawing/functions/curveSprit.ts +++ b/src/app/draw/features/FreeDrawing/functions/curveSprit.ts @@ -1,10 +1,9 @@ -import { BAR_NUM, NOTE_NUM } from '../const'; import { Coordinate } from '../dto/curve'; -export function splitCurve(coordinates: Coordinate[]): Coordinate[] { +export function splitCurve(coordinates: Coordinate[], totalBeatCount: number): Coordinate[] { const resultCurve = Array.from( - { length: BAR_NUM * NOTE_NUM }, - (_, i) => coordinates[Math.floor((i * coordinates.length) / (BAR_NUM * NOTE_NUM))], + { length: totalBeatCount }, + (_, i) => coordinates[Math.floor((i * coordinates.length) / totalBeatCount)], ); return resultCurve; diff --git a/src/app/draw/features/FreeDrawing/hooks/useDrawing.ts b/src/app/draw/features/FreeDrawing/hooks/useDrawing.ts index 0d58482..123a4b1 100644 --- a/src/app/draw/features/FreeDrawing/hooks/useDrawing.ts +++ b/src/app/draw/features/FreeDrawing/hooks/useDrawing.ts @@ -3,8 +3,14 @@ import paper from 'paper'; import { curveDraw } from '../functions/curveDraw'; import { splitCurve } from '../functions/curveSprit'; import { createInterval } from '../functions/intervalCreate'; +import { useAtomValue } from 'jotai'; +import { barCountAtom, beatCountAtom } from '@/stores/settings'; export function useDrawing() { + const barCount = useAtomValue(barCountAtom); + const beatCount = useAtomValue(beatCountAtom); + const totalBeatCount = barCount * beatCount; + const canvasRef = useRef(null); useEffect(() => { @@ -13,7 +19,7 @@ export function useDrawing() { if (canvas) { paper.setup(canvas); const curveInformation = await curveDraw(); - const curveCoordinates = splitCurve(curveInformation.coordinates); + const curveCoordinates = splitCurve(curveInformation.coordinates, totalBeatCount); const interval = createInterval(curveCoordinates); } }; From 9f782d67aa9a8d41744517744489de56c690d189 Mon Sep 17 00:00:00 2001 From: satooru65536 Date: Fri, 13 Sep 2024 15:45:24 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E3=83=A2=E3=83=87=E3=83=AB=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/const/index.ts | 6 +++++ src/models/index.ts | 58 +++++++++++++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/const/index.ts b/src/const/index.ts index 32d2cdc..37af146 100644 --- a/src/const/index.ts +++ b/src/const/index.ts @@ -53,3 +53,9 @@ export const BASE_SCALES = [ ] as const satisfies BaseScale[]; export const SCALES = [...BASE_SCALES.map((s) => s.scale)] as const; + +export enum Range { + MIN, + MAX, +} +export const { MIN, MAX } = Range; diff --git a/src/models/index.ts b/src/models/index.ts index e0a9669..fe23180 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,15 +1,29 @@ -import { SCALES } from '@/const'; +import { MAX, MIN, SCALES } from '@/const'; import { DiatonicChordType, Scale, Settings } from '@/types'; import { CSSProperties } from 'react'; +// IDを持つ基底クラス +class Identifiable { + private _id = `${Date.now()}-${Math.random().toString(36)}`; + + /** + * IDを取得 + */ + get id(): string { + return this._id; + } +} + // 1拍 -export class Beat { +export class Beat extends Identifiable { // 開始位置 ex: 1/4拍=1 - start: number; + declare start: number; // 音の長さ ex: 1/4拍=1 - duration: number; + declare duration: number; constructor(start: number, duration: number) { + super(); + this.start = start; this.duration = duration; } @@ -17,8 +31,8 @@ export class Beat { // 1音 export class Note extends Beat { - octave: number; - scale: Scale; + declare octave: number; + declare scale: Scale; constructor(props: { scale: Scale; octave: number; start: number; duration: number }) { const { scale, octave, start, duration } = props; @@ -32,14 +46,22 @@ export class Note extends Beat { /** * 描画位置をcss変数として取得 */ - public getStyleVars(octaveRange: Settings['octaveRange']): CSSProperties { + public getPositionStyleVars(barIndex: number, octaveRange: Settings['octaveRange']): CSSProperties { + // 1小節の const colStart = this.start + 1; const colEnd = this.start + this.duration + 1; - const i = SCALES.findIndex((s) => s === this.scale); - const rowStart = i + (this.octave - octaveRange[0]) * 12 + 1; + // 小節内の音階の位置 + const scalePosition = SCALES.findIndex((s) => s === this.scale) + 1; + // 相対的な小節の位置 + const relativeBarPosition = this.octave - octaveRange[MIN]; + // 小節の位置 + const barPosition = (octaveRange[MAX] - octaveRange[MIN] - relativeBarPosition + 1) * 12; + + const rowStart = barPosition - scalePosition + 1; return { + ['--bar-index' as string]: barIndex, ['--col-start' as string]: colStart, ['--col-end' as string]: colEnd, ['--row-start' as string]: rowStart, @@ -77,23 +99,29 @@ export class DiatonicChord extends Note { } // 1小節 -export class Bar { - notes: Note[] = []; - chord: DiatonicChord; +export class Bar extends Identifiable { + declare notes: Note[]; + declare chord: DiatonicChord; constructor(props: { notes: Note[]; chord: DiatonicChord }) { const { notes, chord } = props; + super(); + this.notes = notes; this.chord = chord; } } // 楽譜 -export class Music { - bars: Bar[] = []; +export class Music extends Identifiable { + declare bars: Bar[]; + + constructor(props: { bars: Bar[] }) { + const { bars } = props; + + super(); - constructor(bars: Bar[]) { this.bars = bars; } }