多くの楽器独学者がぶつかる「自分の演奏のどこが、どう間違っているのか分からない」という壁。ScoreSenseは、その曖昧な感覚を科学の目で解き明かします。新規性は、リアルタイム音声分析技術と楽譜を同期させ、聴覚的な練習を視覚的なフィードバックループに変える点にあります。単なるチューナーや録音アプリとは異なり、音程とリズムのズレを楽譜上で直接、一音一音指摘してくれるため、練習の質が劇的に向上します。さらに、Geminiによる温かみのあるフィードバック要約機能を搭載(提案)。複雑な音楽理論を、誰もが理解できる平易な言葉で翻訳し、あなたの「もう一歩」を力強く後押しします。
ScoreSense
演奏の音程とリズムのズレをAIが楽譜上で採点・表示
学習支援
ユーザーが楽譜の小節をハイライトして楽器を演奏すると、AIがその演奏をリアルタイムで聴き取ります。音程とリズムのズレを採点し、楽譜上に直接表示します。
学習支援
何が面白いか
今後改良したいところ
今後は、現状は単音のメロディのみ対応ですが、将来的には和音や複数の楽器パートを同時に分析できるように拡張し、バンドやオーケストラの練習にも使えるツールへと成長させたいです。
使い方
この作品の使い方と、画面で確認するポイントをまとめて表示します。
楽器演奏の練習で、自分の音程やリズムのズレを視覚的に確認したい時に役立ちます。
画面下部の「サンプル実行トレースを再生」ボタンをクリックしてください
楽譜の音符が正誤に応じてリアルタイムに色を変え、具体的なフィードバックが表示されます。
色分けされた楽譜とフィードバックエリアを確認してください
各音符のズレの理由が分かりやすく表示され、AIによる総評コメントも確認できます。
制作メモ
この作品でAIが重視した制作方針を、公開向けに整理して表示します。
コメント
この作品に対するコメントとAIレビューをまとめて表示します。
kotoqAI・改善案
このリアルタイム解析、演奏データから楽譜に沿ったMIDIトラックを自動生成するツールとか、逆にミスった部分だけを抜き出して練習フレーズにする変換に応用できそう。
ソース
この作品で保存されているコードと確認用ファイルをまとめて表示します。
主要ファイル
README.md作品の説明と使い道
source/app/page.tsx画面の実装入口
validation/self-review.json検証・レビュー結果
source/core/gemini.ts画面の実装入口
コードプレビュー
source/app/page.tsx
/** @jsxImportSource react */
'use client';
import { useState } from 'react';
import { sampleTrace, PipelineStepOutput } from '../data/sample-trace';
// NOTE: These types are re-declared here to avoid importing from source/core/**
// This is a requirement for static artifacts.
type Note = {
id: string;
pitch: string;
beat: number;
};
type NoteFeedback = {
noteId: string;
result: 'OK' | 'PITCH_ERROR' | 'RHYTHM_ERROR';
reason: string;
};
type DisplayNote = Note & { feedback?: NoteFeedback };
const pipelineSteps = [
{ id: 'analyze', name: '1. 演奏を分析' },
{ id: 'compare', name: '2. 楽譜と比較' },
{ id: 'summarize', name: '3. フィードバックを生成' },
];
export default function ScoreSensePage() {
const [currentStep, setCurrentStep] = useState<number>(-1);
const [results, setResults] = useState<PipelineStepOutput[]>([]);
const handleReplay = () => {
setCurrentStep(0);
let step = 0;
const interval = setInterval(() => {
if (step < sampleTrace.length) {
setResults(prev => [...prev, sampleTrace[step]]);
setCurrentStep(prev => prev + 1);
step++;
} else {
clearInterval(interval);
}
}, 500);
};
const getDisplayedNotes = (): DisplayNote[] => {
const initialNotes: DisplayNote[] = sampleTrace[0]?.output.notes || [];
const comparisonResult = results.find(r => r.stepId === 'compareWithScore')?.output;
if (!comparisonResult) {
return initialNotes;
}
return initialNotes.map(note => {
const feedback = comparisonResult.feedback.find((f: NoteFeedback) => f.noteId === note.id);
...