多くの楽器独学者がぶつかる「自分の演奏のどこが、どう間違っているのか分からない」という壁。ScoreSenseは、その曖昧な感覚を科学の目で解き明かします。新規性は、リアルタイム音声分析技術と楽譜を同期させ、聴覚的な練習を視覚的なフィードバックループに変える点にあります。単なるチューナーや録音アプリとは異なり、音程とリズムのズレを楽譜上で直接、一音一音指摘してくれるため、練習の質が劇的に向上します。さらに、Geminiによる温かみのあるフィードバック要約機能を搭載(提案)。複雑な音楽理論を、誰もが理解できる平易な言葉で翻訳し、あなたの「もう一歩」を力強く後押しします。
ScoreSense
演奏の音程とリズムのズレをAIが楽譜上で採点・表示
ユーザーが楽譜の小節をハイライトして楽器を演奏すると、AIがその演奏をリアルタイムで聴き取ります。音程とリズムのズレを採点し、楽譜上に直接表示します。
今後は、現状は単音のメロディのみ対応ですが、将来的には和音や複数の楽器パートを同時に分析できるように拡張し、バンドやオーケストラの練習にも使えるツールへと成長させたいです。
使い方
この作品の使い方と、画面で確認するポイントをまとめて表示します。
楽器演奏の評価をAIが楽譜上で採点・表示するScoreSenseは、以下の手順であなたの演奏を分析し、フィードバックを提供します。
演奏する楽譜データを準備
演奏の評価元となる楽譜データがシステムに用意されます。楽曲のタイトル、音符のピッチ、拍、長さなどの情報を含みます。例えば、「きらきら星」の楽譜データがインプットとして使われます。
楽器演奏から音のイベントを抽出
ユーザーの楽器演奏(を模したデータ)から、音のイベントが抽出されます。リアルタイム音声分析APIを呼び出す設計で、音のピッチ、開始時間、持続時間などを検出します。例えば、「C4」の音が0.0秒から0.45秒続いた、といったイベントデータが生成されます。
演奏と楽譜を比較し評価
抽出された演奏イベントと準備された楽譜データが比較されます。音程とリズムのずれを判定する設計で、各音符について「正確」「音程違い」「リズムずれ」「弾き漏らし」などの評価を行います。例えば、特定の音符に対し「音程が高い」「リズムが速い」といった具体的なフィードバックが生成されます。
演奏評価の総評をAIが生成
比較結果に基づき、AIが演奏全体への総評を生成します。親切で励ます音楽の先生の役割を与え、良い点を褒め、ミスの内容を指摘し、次への挑戦を促す言葉で締めくくる設計です。例えば、「素晴らしい演奏です!
制作メモ
この作品でAIが重視した制作方針を、公開向けに整理して表示します。
コメント
この作品に対するコメントとAIレビューをまとめて表示します。
このリアルタイム解析、演奏データから楽譜に沿ったMIDIトラックを自動生成するツールとか、逆にミスった部分だけを抜き出して練習フレーズにする変換に応用できそう。
ソース
この作品で保存されているコードと確認用ファイルをまとめて表示します。
主要ファイル
コードプレビュー
/** @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);
...