refactor: added UI-only tool result summary to all the tools; move summary generation logic to tool's own ts file
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AddNotesTool } from './AddNotesTool';
|
||||
import { KGCore } from '../../core/KGCore';
|
||||
import { KGProject } from '../../core/KGProject';
|
||||
import { KGMidiTrack } from '../../core/track/KGMidiTrack';
|
||||
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||
|
||||
const storeState = {
|
||||
activeRegionId: null as string | null,
|
||||
};
|
||||
|
||||
vi.mock('../../stores/projectStore', () => ({
|
||||
useProjectStore: {
|
||||
getState: () => storeState,
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AddNotesTool', () => {
|
||||
beforeEach(() => {
|
||||
storeState.activeRegionId = null;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('builds a compact summary for successful note creation', () => {
|
||||
const track = new KGMidiTrack('Lead', 1);
|
||||
const region = new KGMidiRegion('region-1', track.getId().toString(), track.getTrackIndex(), 'Verse Melody', 0, 32);
|
||||
track.setRegions([region]);
|
||||
const project = new KGProject('summary-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
project.setTracks([track]);
|
||||
storeState.activeRegionId = region.getId();
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
getSelectedItems: () => [],
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new AddNotesTool();
|
||||
const summary = tool.buildToolResultDisplayContent(
|
||||
{
|
||||
notes: [
|
||||
{ pitch: 'C4', start: 16, length: 4 },
|
||||
{ pitch: 'E4', start: 20, length: 8 },
|
||||
],
|
||||
},
|
||||
{ success: true, result: 'raw result' },
|
||||
);
|
||||
|
||||
expect(summary).toBe(
|
||||
'Successfully created 2 notes in region **Verse Melody** on track **Lead**, spanning bars 5 to 7.'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns no compact summary when the target region cannot be resolved', () => {
|
||||
const project = new KGProject('summary-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
getSelectedItems: () => [],
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new AddNotesTool();
|
||||
const summary = tool.buildToolResultDisplayContent(
|
||||
{
|
||||
notes: [{ pitch: 'C4', start: 0, length: 4 }],
|
||||
},
|
||||
{ success: true, result: 'raw result' },
|
||||
);
|
||||
|
||||
expect(summary).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,14 @@ import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||
import { useProjectStore } from '../../stores/projectStore';
|
||||
import { KGCore } from '../../core/KGCore';
|
||||
|
||||
interface AddNotesSummaryData {
|
||||
noteCount: number;
|
||||
regionName: string;
|
||||
trackName: string;
|
||||
earliestNoteStartBar: number;
|
||||
latestNoteEndBar: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool for adding notes to MIDI regions
|
||||
* Integrates with the existing command system for undo/redo support
|
||||
@@ -53,6 +61,19 @@ export class AddNotesTool extends BaseTool {
|
||||
}
|
||||
};
|
||||
|
||||
buildToolResultDisplayContent(args: Record<string, unknown> | null, toolResult: ToolResult): string | undefined {
|
||||
if (!toolResult.success || !args) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const summary = this.buildSummaryData(args);
|
||||
if (!summary) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `Successfully created ${summary.noteCount} ${summary.noteCount === 1 ? 'note' : 'notes'} in region **${summary.regionName}** on track **${summary.trackName}**, spanning bars ${summary.earliestNoteStartBar} to ${summary.latestNoteEndBar}.`;
|
||||
}
|
||||
|
||||
async execute(params: Record<string, unknown>): Promise<ToolResult> {
|
||||
try {
|
||||
// Validate parameters
|
||||
@@ -149,48 +170,84 @@ export class AddNotesTool extends BaseTool {
|
||||
* Priority: 1) Specified regionId, 2) Active piano roll region, 3) Selected regions, 4) Error if none found
|
||||
*/
|
||||
private findTargetRegion(regionId?: string): KGMidiRegion | null {
|
||||
return this.findTargetRegionContext(regionId)?.region ?? null;
|
||||
}
|
||||
|
||||
private findTargetRegionContext(regionId?: string): { region: KGMidiRegion; trackName: string } | null {
|
||||
const project = this.getCurrentProject();
|
||||
const tracks = project.getTracks();
|
||||
|
||||
|
||||
if (regionId) {
|
||||
// Find specific region by ID
|
||||
for (const track of tracks) {
|
||||
const regions = track.getRegions();
|
||||
const region = regions.find(r => r.getId() === regionId);
|
||||
if (region && region instanceof KGMidiRegion) {
|
||||
return region;
|
||||
return {
|
||||
region,
|
||||
trackName: track.getName() || `Track ${track.getTrackIndex() + 1}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// Smart region finding: try different sources in priority order
|
||||
|
||||
// 1. Try active piano roll region
|
||||
const storeState = useProjectStore.getState();
|
||||
if (storeState.activeRegionId) {
|
||||
for (const track of tracks) {
|
||||
const regions = track.getRegions();
|
||||
const region = regions.find(r => r.getId() === storeState.activeRegionId);
|
||||
if (region && region instanceof KGMidiRegion) {
|
||||
return region;
|
||||
return {
|
||||
region,
|
||||
trackName: track.getName() || `Track ${track.getTrackIndex() + 1}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try selected regions
|
||||
|
||||
const core = this.getKGCore();
|
||||
const selectedItems = core.getSelectedItems();
|
||||
for (const item of selectedItems) {
|
||||
if (item instanceof KGMidiRegion) {
|
||||
return item;
|
||||
const track = tracks.find(candidate => candidate.getId().toString() === item.getTrackId());
|
||||
return {
|
||||
region: item,
|
||||
trackName: track?.getName() || `Track ${item.getTrackIndex() + 1}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 3. No fallback - return null to trigger error
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private buildSummaryData(args: Record<string, unknown>): AddNotesSummaryData | null {
|
||||
const typedArgs = args as {
|
||||
notes?: Array<{ start: number; length: number }>;
|
||||
region_id?: string;
|
||||
};
|
||||
|
||||
if (!Array.isArray(typedArgs.notes) || typedArgs.notes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetRegion = this.findTargetRegionContext(typedArgs.region_id);
|
||||
if (!targetRegion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const beatsPerBar = this.getCurrentProject().getTimeSignature().numerator;
|
||||
const earliestNoteStartBeat = Math.min(...typedArgs.notes.map(note => note.start));
|
||||
const latestNoteEndBeat = Math.max(...typedArgs.notes.map(note => note.start + note.length));
|
||||
|
||||
return {
|
||||
noteCount: typedArgs.notes.length,
|
||||
regionName: targetRegion.region.getName(),
|
||||
trackName: targetRegion.trackName,
|
||||
earliestNoteStartBar: Math.floor(earliestNoteStartBeat / beatsPerBar) + 1,
|
||||
latestNoteEndBar: Math.max(1, Math.ceil(latestNoteEndBeat / beatsPerBar)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get KGCore instance for selection access
|
||||
*/
|
||||
@@ -232,4 +289,4 @@ export class AddNotesTool extends BaseTool {
|
||||
|
||||
return midiNote;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,17 @@ export abstract class BaseTool {
|
||||
*/
|
||||
abstract execute(params: Record<string, unknown>): Promise<ToolResult>;
|
||||
|
||||
/**
|
||||
* Optionally build a compact UI summary for a successful tool result.
|
||||
* The raw tool result remains the canonical output stored in agent history.
|
||||
*/
|
||||
buildToolResultDisplayContent(
|
||||
_args: Record<string, unknown> | null,
|
||||
_toolResult: ToolResult,
|
||||
): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool definition in OpenAI function calling format
|
||||
*/
|
||||
|
||||
@@ -108,4 +108,19 @@ describe('ReadChordProgressionTool', () => {
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.result).toContain('No active or selected MIDI region found');
|
||||
});
|
||||
|
||||
it('builds a compact summary for the resolved region span', () => {
|
||||
const { project, midiRegion } = buildProjectWithRegionAndOptionalChords(['Am']);
|
||||
storeState.activeRegionId = midiRegion.getId();
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
getSelectedItems: () => [],
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadChordProgressionTool();
|
||||
const summary = tool.buildToolResultDisplayContent({}, { success: true, result: 'raw result' });
|
||||
|
||||
expect(summary).toBe('Read the chord progression from bars 1 to 8.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,25 @@ export class ReadChordProgressionTool extends BaseTool {
|
||||
|
||||
readonly parameters: Record<string, ToolParameter> = {};
|
||||
|
||||
buildToolResultDisplayContent(args: Record<string, unknown> | null, toolResult: ToolResult): string | undefined {
|
||||
if (!toolResult.success) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const targetRegion = this.findTargetRegion();
|
||||
if (!targetRegion) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const beatsPerBar = this.getCurrentProject().getTimeSignature().numerator;
|
||||
const startBar = Math.floor(targetRegion.getStartFromBeat() / beatsPerBar) + 1;
|
||||
const endBar = Math.max(1, Math.ceil((targetRegion.getStartFromBeat() + targetRegion.getLength()) / beatsPerBar));
|
||||
const barRange = startBar === endBar ? `bar ${startBar}` : `bars ${startBar} to ${endBar}`;
|
||||
void args;
|
||||
|
||||
return `Read the chord progression from ${barRange}.`;
|
||||
}
|
||||
|
||||
async execute(_params: Record<string, unknown>): Promise<ToolResult> {
|
||||
try {
|
||||
const targetRegion = this.findTargetRegion();
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ReadMusicTool } from './ReadMusicTool';
|
||||
import { KGCore } from '../../core/KGCore';
|
||||
import { KGProject } from '../../core/KGProject';
|
||||
import { KGMidiTrack } from '../../core/track/KGMidiTrack';
|
||||
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||
import { KGMidiNote } from '../../core/midi/KGMidiNote';
|
||||
|
||||
function buildTrack(name: string, id: number, regionStartBeat: number, regionLength: number): KGMidiTrack {
|
||||
const track = new KGMidiTrack(name, id);
|
||||
const region = new KGMidiRegion(`region-${id}`, track.getId().toString(), track.getTrackIndex(), `${name} Region`, regionStartBeat, regionLength);
|
||||
region.addNote(new KGMidiNote(`note-${id}`, 0, 4, 60));
|
||||
track.setRegions([region]);
|
||||
return track;
|
||||
}
|
||||
|
||||
describe('ReadMusicTool', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('builds a compact summary for a single track read', () => {
|
||||
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
const leadTrack = buildTrack('Lead', 1, 0, 16);
|
||||
project.setTracks([leadTrack]);
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadMusicTool();
|
||||
const summary = tool.buildToolResultDisplayContent(
|
||||
{
|
||||
track_id: leadTrack.getId().toString(),
|
||||
start: 5,
|
||||
length: 6,
|
||||
},
|
||||
{ success: true, result: 'raw result' },
|
||||
);
|
||||
|
||||
expect(summary).toBe('Read track Lead from bars 2 to 3.');
|
||||
});
|
||||
|
||||
it('builds a compact summary for reading multiple tracks', () => {
|
||||
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
const leadTrack = buildTrack('Lead', 1, 0, 16);
|
||||
const bassTrack = buildTrack('Bass', 2, 0, 12);
|
||||
project.setTracks([leadTrack, bassTrack]);
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadMusicTool();
|
||||
const summary = tool.buildToolResultDisplayContent(
|
||||
{
|
||||
track_id: 'all',
|
||||
start: 0,
|
||||
length: 16,
|
||||
},
|
||||
{ success: true, result: 'raw result' },
|
||||
);
|
||||
|
||||
expect(summary).toBe('Read tracks Lead and Bass from bars 1 to 4.');
|
||||
});
|
||||
|
||||
it('returns no compact summary when the requested track cannot be resolved', () => {
|
||||
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
project.setTracks([buildTrack('Lead', 1, 0, 16)]);
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadMusicTool();
|
||||
const summary = tool.buildToolResultDisplayContent(
|
||||
{
|
||||
track_id: 'missing-track',
|
||||
start: 0,
|
||||
length: 4,
|
||||
},
|
||||
{ success: true, result: 'raw result' },
|
||||
);
|
||||
|
||||
expect(summary).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns a professional empty-project message when all MIDI tracks are empty', async () => {
|
||||
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
const emptyTrack = new KGMidiTrack('Lead', 1);
|
||||
emptyTrack.setRegions([
|
||||
new KGMidiRegion('region-1', emptyTrack.getId().toString(), emptyTrack.getTrackIndex(), 'Lead Region', 0, 36),
|
||||
]);
|
||||
project.setTracks([emptyTrack]);
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadMusicTool();
|
||||
const result = await tool.execute({ track_id: 'all', start: 0, length: 36 });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.result).toBe('No musical content is present in the project yet.');
|
||||
});
|
||||
|
||||
it('returns a professional empty-range message when the selected range has no MIDI notes', async () => {
|
||||
const project = new KGProject('read-music-project', 8, 0, 120, { numerator: 4, denominator: 4 }, 'C major');
|
||||
const leadTrack = buildTrack('Lead', 1, 0, 8);
|
||||
project.setTracks([leadTrack]);
|
||||
|
||||
vi.spyOn(KGCore, 'instance').mockReturnValue({
|
||||
getCurrentProject: () => project,
|
||||
} as unknown as KGCore);
|
||||
|
||||
const tool = new ReadMusicTool();
|
||||
const result = await tool.execute({ track_id: 'all', start: 16, length: 8 });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.result).toBe('No musical content was found in the selected range.');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BaseTool } from './BaseTool';
|
||||
import type { ToolResult, ToolParameter } from './BaseTool';
|
||||
import type { KGTrack } from '../../core/track/KGTrack';
|
||||
import { KGMidiTrack } from '../../core/track/KGMidiTrack';
|
||||
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||
import { convertRegionToABCNotation } from '../../util/abcNotationUtil';
|
||||
@@ -32,6 +33,20 @@ export class ReadMusicTool extends BaseTool {
|
||||
}
|
||||
};
|
||||
|
||||
buildToolResultDisplayContent(args: Record<string, unknown> | null, toolResult: ToolResult): string | undefined {
|
||||
if (!toolResult.success || !args) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const summary = this.buildSummaryData(args);
|
||||
if (!summary) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const trackLabel = summary.trackNames.length === 1 ? 'track' : 'tracks';
|
||||
return `Read ${trackLabel} ${this.formatTrackNameList(summary.trackNames)} from ${this.formatBarRange(summary.startBar, summary.endBar)}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name for percussion instruments, or null if not percussion
|
||||
*/
|
||||
@@ -120,6 +135,135 @@ export class ReadMusicTool extends BaseTool {
|
||||
}
|
||||
}
|
||||
|
||||
private buildSummaryData(args: Record<string, unknown>): {
|
||||
trackNames: string[];
|
||||
startBar: number;
|
||||
endBar: number;
|
||||
} | null {
|
||||
const project = this.getCurrentProject();
|
||||
const tracks = project.getTracks();
|
||||
if (tracks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const beatsPerBar = project.getTimeSignature().numerator;
|
||||
const startBeat = (args.start as number) || 0;
|
||||
const length = args.length as number | undefined;
|
||||
if (startBeat < 0 || (length !== undefined && length <= 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const roundedStartBeat = Math.floor(startBeat / beatsPerBar) * beatsPerBar;
|
||||
const rawEndBeat = length !== undefined ? startBeat + length : undefined;
|
||||
const roundedEndBeat = rawEndBeat !== undefined
|
||||
? Math.ceil(rawEndBeat / beatsPerBar) * beatsPerBar
|
||||
: this.getTrackReadEndBeat(args, tracks, roundedStartBeat);
|
||||
|
||||
const trackNames = this.resolveSummaryTrackNames(args, tracks);
|
||||
if (trackNames.length === 0 || roundedEndBeat === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
trackNames,
|
||||
startBar: Math.floor(roundedStartBeat / beatsPerBar) + 1,
|
||||
endBar: Math.max(1, Math.ceil(roundedEndBeat / beatsPerBar)),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveSummaryTrackNames(
|
||||
args: Record<string, unknown>,
|
||||
tracks: KGTrack[],
|
||||
): string[] {
|
||||
const trackId = args.track_id as string | undefined;
|
||||
|
||||
if (!trackId || trackId === '' || trackId === 'all') {
|
||||
const midiTracks = tracks.filter(track => track instanceof KGMidiTrack) as KGMidiTrack[];
|
||||
const tracksToSkip = this.findTracksToSkip(midiTracks);
|
||||
return midiTracks
|
||||
.filter(track => !tracksToSkip.includes(track))
|
||||
.map((track, index) => track.getName() || `Track ${index + 1}`);
|
||||
}
|
||||
|
||||
const targetTrack = tracks.find(track => track.getId().toString() === trackId);
|
||||
if (!(targetTrack instanceof KGMidiTrack)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [targetTrack.getName() || 'Unnamed Track'];
|
||||
}
|
||||
|
||||
private getTrackReadEndBeat(
|
||||
args: Record<string, unknown>,
|
||||
tracks: KGTrack[],
|
||||
roundedStartBeat: number,
|
||||
): number | undefined {
|
||||
const trackId = args.track_id as string | undefined;
|
||||
|
||||
if (!trackId || trackId === '' || trackId === 'all') {
|
||||
const midiTracks = tracks.filter(track => track instanceof KGMidiTrack) as KGMidiTrack[];
|
||||
const tracksToSkip = this.findTracksToSkip(midiTracks);
|
||||
const visibleTracks = midiTracks.filter(track => !tracksToSkip.includes(track));
|
||||
const endBeats = visibleTracks.flatMap(track =>
|
||||
track.getRegions()
|
||||
.filter(region => region instanceof KGMidiRegion)
|
||||
.map(region => region.getStartFromBeat() + region.getLength())
|
||||
);
|
||||
return endBeats.length > 0 ? Math.max(roundedStartBeat, ...endBeats) : roundedStartBeat;
|
||||
}
|
||||
|
||||
const targetTrack = tracks.find(track => track.getId().toString() === trackId);
|
||||
if (!(targetTrack instanceof KGMidiTrack)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const endBeats = targetTrack.getRegions()
|
||||
.filter(region => region instanceof KGMidiRegion)
|
||||
.map(region => region.getStartFromBeat() + region.getLength());
|
||||
return endBeats.length > 0 ? Math.max(roundedStartBeat, ...endBeats) : roundedStartBeat;
|
||||
}
|
||||
|
||||
private formatTrackNameList(trackNames: string[]): string {
|
||||
if (trackNames.length === 1) {
|
||||
return trackNames[0];
|
||||
}
|
||||
if (trackNames.length === 2) {
|
||||
return `${trackNames[0]} and ${trackNames[1]}`;
|
||||
}
|
||||
|
||||
return `${trackNames.slice(0, -1).join(', ')}, and ${trackNames.at(-1)}`;
|
||||
}
|
||||
|
||||
private formatBarRange(startBar: number, endBar: number): string {
|
||||
return startBar === endBar
|
||||
? `bar ${startBar}`
|
||||
: `bars ${startBar} to ${endBar}`;
|
||||
}
|
||||
|
||||
private hasMidiContentInRange(track: KGMidiTrack, startBeat: number, endBeat?: number): boolean {
|
||||
const rangeEndBeat = endBeat ?? Infinity;
|
||||
|
||||
return track.getRegions().some(region => {
|
||||
if (!(region instanceof KGMidiRegion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regionStart = region.getStartFromBeat();
|
||||
const regionEnd = regionStart + region.getLength();
|
||||
const overlapsRange = regionStart < rangeEndBeat && regionEnd > startBeat;
|
||||
|
||||
return overlapsRange && region.getNotes().length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
private getEmptyProjectMessage(): string {
|
||||
return 'No musical content is present in the project yet.';
|
||||
}
|
||||
|
||||
private getEmptyRangeMessage(): string {
|
||||
return 'No musical content was found in the selected range.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find tracks that should be skipped because they have no musical content
|
||||
* (no regions or regions with no notes)
|
||||
@@ -166,11 +310,20 @@ export class ReadMusicTool extends BaseTool {
|
||||
const midiTracks = tracks.filter(track => track instanceof KGMidiTrack);
|
||||
|
||||
if (midiTracks.length === 0) {
|
||||
return 'No MIDI tracks found in the project.';
|
||||
return this.getEmptyProjectMessage();
|
||||
}
|
||||
|
||||
// Find tracks to skip (tracks with no content)
|
||||
const tracksToSkip = this.findTracksToSkip(midiTracks);
|
||||
const visibleTracks = midiTracks.filter(track => !tracksToSkip.includes(track));
|
||||
if (visibleTracks.length === 0) {
|
||||
return this.getEmptyProjectMessage();
|
||||
}
|
||||
|
||||
const hasContentInRange = visibleTracks.some(track => this.hasMidiContentInRange(track, startBeat, endBeat));
|
||||
if (!hasContentInRange) {
|
||||
return this.getEmptyRangeMessage();
|
||||
}
|
||||
|
||||
// Get project settings for proper notation
|
||||
const project = this.getCurrentProject();
|
||||
@@ -252,6 +405,16 @@ export class ReadMusicTool extends BaseTool {
|
||||
return `Track is not a MIDI track.`;
|
||||
}
|
||||
|
||||
if (track.getRegions().length === 0 || !track.getRegions().some(region => (
|
||||
region instanceof KGMidiRegion && region.getNotes().length > 0
|
||||
))) {
|
||||
return this.getEmptyProjectMessage();
|
||||
}
|
||||
|
||||
if (!this.hasMidiContentInRange(track, startBeat, endBeat)) {
|
||||
return this.getEmptyRangeMessage();
|
||||
}
|
||||
|
||||
// Get project settings for proper notation
|
||||
const project = this.getCurrentProject();
|
||||
const timeSignature = project.getTimeSignature();
|
||||
@@ -304,4 +467,4 @@ export class ReadMusicTool extends BaseTool {
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user