fix: rename start_beat to start and end_beat to end in 3 tools.
This commit is contained in:
@@ -25,9 +25,9 @@ Read existing musical content from the project. The output is in ABC notation. I
|
|||||||
Remove notes from a given beat range in the current region.
|
Remove notes from a given beat range in the current region.
|
||||||
|
|
||||||
## add_notes
|
## add_notes
|
||||||
Add notes to the current region. Pitches use scientific pitch notation with support for sharps and flats (e.g., `C4`, `F#3`, `Bb2`). **Important**: the `start_beat` parameter is always the **absolute** beat position in the project timeline — not relative to the current region's start. For example, to place a note at beat 6, set `start_beat` to 6 regardless of where the current region begins.
|
Add notes to the current region. Pitches use scientific pitch notation with support for sharps and flats (e.g., `C4`, `F#3`, `Bb2`). **Important**: the `start` parameter is always the **absolute** beat position in the project timeline — not relative to the current region's start. For example, to place a note at beat 6, set `start` to 6 regardless of where the current region begins.
|
||||||
|
|
||||||
To create a melodic line, use sequential `start_beat` values for each note. To create a chord, give multiple notes the same `start_beat`.
|
To create a melodic line, use sequential `start` values for each note. To create a chord, give multiple notes the same `start`.
|
||||||
|
|
||||||
# Tool Use Guidelines
|
# Tool Use Guidelines
|
||||||
|
|
||||||
@@ -80,12 +80,12 @@ You have access to two tools for working with the current music region: **remove
|
|||||||
- Check that chord progressions are appropriate for the key signature
|
- Check that chord progressions are appropriate for the key signature
|
||||||
- Confirm note lengths don't extend beyond reasonable musical phrases
|
- Confirm note lengths don't extend beyond reasonable musical phrases
|
||||||
- **Pitch Notation**: Use scientific pitch notation (e.g., C4, A#3, Bb2) and ensure octave numbers are appropriate for the instrument
|
- **Pitch Notation**: Use scientific pitch notation (e.g., C4, A#3, Bb2) and ensure octave numbers are appropriate for the instrument
|
||||||
- **Timing Constraints**: All start_beat and length values must align with the time signature grid
|
- **Timing Constraints**: All start and length values must align with the time signature grid
|
||||||
- When adding chord progressions, first break down the progression into individual notes based on the key signature, then add all the notes.
|
- When adding chord progressions, first break down the progression into individual notes based on the key signature, then add all the notes.
|
||||||
- For example, to create a I-V-vi-IV progression in C major with 4-beat chords:
|
- For example, to create a I-V-vi-IV progression in C major with 4-beat chords:
|
||||||
1. Check the key signature. In C major, the chords are C, G, Am, F.
|
1. Check the key signature. In C major, the chords are C, G, Am, F.
|
||||||
2. Convert each chord to individual notes: C = C4/E4/G4, G = G3/B3/D4, Am = A3/C4/E4, F = F3/A3/C4.
|
2. Convert each chord to individual notes: C = C4/E4/G4, G = G3/B3/D4, Am = A3/C4/E4, F = F3/A3/C4.
|
||||||
3. Call `add_notes` with all 12 notes: the C chord notes at `start_beat` 0, the G chord notes at `start_beat` 4, the Am chord notes at `start_beat` 8, and the F chord notes at `start_beat` 12 — each with `length` 4.
|
3. Call `add_notes` with all 12 notes: the C chord notes at `start` 0, the G chord notes at `start` 4, the Am chord notes at `start` 8, and the F chord notes at `start` 12 — each with `length` 4.
|
||||||
|
|
||||||
# Workflow Tips
|
# Workflow Tips
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
readonly parameters: Record<string, ToolParameter> = {
|
readonly parameters: Record<string, ToolParameter> = {
|
||||||
notes: {
|
notes: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'List of notes to add. To create a chord, give multiple notes the same start_beat. To create a melody, use sequential start_beat values.',
|
description: 'List of notes to add. To create a chord, give multiple notes the same start beat. To create a melody, use sequential start values.',
|
||||||
required: true,
|
required: true,
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -28,9 +28,9 @@ export class AddNotesTool extends BaseTool {
|
|||||||
description: 'Pitch in scientific notation: note name, optional accidental (# or b), and octave number. Examples: "C4" (middle C), "F#3" (F-sharp 3rd octave), "Bb2" (B-flat 2nd octave).',
|
description: 'Pitch in scientific notation: note name, optional accidental (# or b), and octave number. Examples: "C4" (middle C), "F#3" (F-sharp 3rd octave), "Bb2" (B-flat 2nd octave).',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
start_beat: {
|
start: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Absolute beat position on the project timeline where the note starts. This is NOT relative to the region — beat 6 means beat 6 in the project regardless of where the region begins. Fractional values are supported (e.g., 0.5 = half a beat after beat 0).',
|
description: 'Start beat — the absolute beat position on the project timeline where the note begins. This is NOT relative to the region — beat 6 means beat 6 in the project regardless of where the region begins. Fractional values are supported (e.g., 0.5 = half a beat after beat 0).',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
length: {
|
length: {
|
||||||
@@ -60,7 +60,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
|
|
||||||
const notes = params.notes as Array<{
|
const notes = params.notes as Array<{
|
||||||
pitch: string;
|
pitch: string;
|
||||||
start_beat: number;
|
start: number;
|
||||||
length: number;
|
length: number;
|
||||||
velocity?: number;
|
velocity?: number;
|
||||||
}>;
|
}>;
|
||||||
@@ -79,7 +79,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
|
|
||||||
// Validate and convert notes to creation data
|
// Validate and convert notes to creation data
|
||||||
const noteCreationData: NoteCreationData[] = [];
|
const noteCreationData: NoteCreationData[] = [];
|
||||||
const createdNotes: Array<{ pitch: string; start_beat: number; length: number }> = [];
|
const createdNotes: Array<{ pitch: string; start: number; length: number }> = [];
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
try {
|
try {
|
||||||
@@ -92,8 +92,8 @@ export class AddNotesTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate beat positions
|
// Validate beat positions
|
||||||
if (note.start_beat < 0) {
|
if (note.start < 0) {
|
||||||
return this.createErrorResult(`Invalid start_beat ${note.start_beat}. Must be >= 0.`);
|
return this.createErrorResult(`Invalid start ${note.start}. Must be >= 0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.length <= 0) {
|
if (note.length <= 0) {
|
||||||
@@ -102,7 +102,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
|
|
||||||
// Adjust note position relative to region's start beat
|
// Adjust note position relative to region's start beat
|
||||||
const regionStartBeat = targetRegion.getStartFromBeat();
|
const regionStartBeat = targetRegion.getStartFromBeat();
|
||||||
const adjustedStartBeat = note.start_beat - regionStartBeat;
|
const adjustedStartBeat = note.start - regionStartBeat;
|
||||||
const adjustedEndBeat = adjustedStartBeat + note.length;
|
const adjustedEndBeat = adjustedStartBeat + note.length;
|
||||||
|
|
||||||
// Create note creation data
|
// Create note creation data
|
||||||
@@ -116,7 +116,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
|
|
||||||
createdNotes.push({
|
createdNotes.push({
|
||||||
pitch: note.pitch,
|
pitch: note.pitch,
|
||||||
start_beat: note.start_beat,
|
start: note.start,
|
||||||
length: note.length
|
length: note.length
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ export class AddNotesTool extends BaseTool {
|
|||||||
// Create success message
|
// Create success message
|
||||||
const noteCount = createdNotes.length;
|
const noteCount = createdNotes.length;
|
||||||
const noteList = createdNotes
|
const noteList = createdNotes
|
||||||
.map(note => `${note.pitch} (beat ${note.start_beat}, length ${note.length})`)
|
.map(note => `${note.pitch} (beat ${note.start}, length ${note.length})`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
return this.createSuccessResult(
|
return this.createSuccessResult(
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
description: 'Which track to read. Pass a specific track ID, or "all" to read every track. If omitted, reads the first available track.',
|
description: 'Which track to read. Pass a specific track ID, or "all" to read every track. If omitted, reads the first available track.',
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
start_beat: {
|
start: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Absolute beat position to start reading from. The actual output will be rounded down to the nearest bar boundary. Defaults to 0.',
|
description: 'Start beat — the absolute beat position to start reading from. The actual output will be rounded down to the nearest bar boundary. Defaults to 0.',
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
length: {
|
length: {
|
||||||
@@ -39,11 +39,11 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
try {
|
try {
|
||||||
const instrument = track.getInstrument();
|
const instrument = track.getInstrument();
|
||||||
const instrumentInfo = FLUIDR3_INSTRUMENT_MAP[instrument];
|
const instrumentInfo = FLUIDR3_INSTRUMENT_MAP[instrument];
|
||||||
|
|
||||||
if (instrumentInfo && instrumentInfo.group === 'PERCUSSION_KIT') {
|
if (instrumentInfo && instrumentInfo.group === 'PERCUSSION_KIT') {
|
||||||
return instrumentInfo.displayName;
|
return instrumentInfo.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting percussion display name:', error);
|
console.error('Error getting percussion display name:', error);
|
||||||
@@ -55,23 +55,23 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
try {
|
try {
|
||||||
// Validate parameters
|
// Validate parameters
|
||||||
this.validateParameters(params);
|
this.validateParameters(params);
|
||||||
|
|
||||||
const trackId = params.track_id as string | undefined;
|
const trackId = params.track_id as string | undefined;
|
||||||
const startBeat = (params.start_beat as number) || 0;
|
const startBeat = (params.start as number) || 0;
|
||||||
const length = params.length as number | undefined;
|
const length = params.length as number | undefined;
|
||||||
|
|
||||||
const project = this.getCurrentProject();
|
const project = this.getCurrentProject();
|
||||||
const tracks = project.getTracks();
|
const tracks = project.getTracks();
|
||||||
|
|
||||||
if (tracks.length === 0) {
|
if (tracks.length === 0) {
|
||||||
return this.createErrorResult('No tracks found in the project');
|
return this.createErrorResult('No tracks found in the project');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate start_beat
|
// Validate start
|
||||||
if (startBeat < 0) {
|
if (startBeat < 0) {
|
||||||
return this.createErrorResult(`Invalid start_beat ${startBeat}. Must be >= 0.`);
|
return this.createErrorResult(`Invalid start ${startBeat}. Must be >= 0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate length
|
// Validate length
|
||||||
if (length !== undefined && length <= 0) {
|
if (length !== undefined && length <= 0) {
|
||||||
return this.createErrorResult(`Invalid length ${length}. Must be > 0.`);
|
return this.createErrorResult(`Invalid length ${length}. Must be > 0.`);
|
||||||
@@ -80,7 +80,7 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
// Get project settings for bar rounding
|
// Get project settings for bar rounding
|
||||||
const timeSignature = project.getTimeSignature();
|
const timeSignature = project.getTimeSignature();
|
||||||
const beatsPerBar = timeSignature.numerator;
|
const beatsPerBar = timeSignature.numerator;
|
||||||
|
|
||||||
// Round startBeat to floor bar beats and calculate endBeat
|
// Round startBeat to floor bar beats and calculate endBeat
|
||||||
const roundedStartBeat = Math.floor(startBeat / beatsPerBar) * beatsPerBar;
|
const roundedStartBeat = Math.floor(startBeat / beatsPerBar) * beatsPerBar;
|
||||||
const rawEndBeat = length !== undefined ? startBeat + length : undefined;
|
const rawEndBeat = length !== undefined ? startBeat + length : undefined;
|
||||||
@@ -94,27 +94,27 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
abcOutput = this.generateAllTracksABC(midiTracks, roundedStartBeat, roundedEndBeat);
|
abcOutput = this.generateAllTracksABC(midiTracks, roundedStartBeat, roundedEndBeat);
|
||||||
} else {
|
} else {
|
||||||
// Read specific track or first available track
|
// Read specific track or first available track
|
||||||
const targetTrack = trackId
|
const targetTrack = trackId
|
||||||
? tracks.find(t => t.getId().toString() === trackId)
|
? tracks.find(t => t.getId().toString() === trackId)
|
||||||
: tracks[0];
|
: tracks[0];
|
||||||
|
|
||||||
if (!targetTrack) {
|
if (!targetTrack) {
|
||||||
return this.createErrorResult(
|
return this.createErrorResult(
|
||||||
trackId
|
trackId
|
||||||
? `Track with ID "${trackId}" not found`
|
? `Track with ID "${trackId}" not found`
|
||||||
: 'No tracks available'
|
: 'No tracks available'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(targetTrack instanceof KGMidiTrack)) {
|
if (!(targetTrack instanceof KGMidiTrack)) {
|
||||||
return this.createErrorResult(`Track "${targetTrack.getName()}" is not a MIDI track`);
|
return this.createErrorResult(`Track "${targetTrack.getName()}" is not a MIDI track`);
|
||||||
}
|
}
|
||||||
|
|
||||||
abcOutput = this.generateSingleTrackABC(targetTrack, roundedStartBeat, roundedEndBeat);
|
abcOutput = this.generateSingleTrackABC(targetTrack, roundedStartBeat, roundedEndBeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.createSuccessResult(abcOutput);
|
return this.createSuccessResult(abcOutput);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return this.createErrorResult(`Failed to read music: ${error}`);
|
return this.createErrorResult(`Failed to read music: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -127,16 +127,16 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
private findTracksToSkip(tracks: KGMidiTrack[]): KGMidiTrack[] {
|
private findTracksToSkip(tracks: KGMidiTrack[]): KGMidiTrack[] {
|
||||||
try {
|
try {
|
||||||
const tracksToSkip: KGMidiTrack[] = [];
|
const tracksToSkip: KGMidiTrack[] = [];
|
||||||
|
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
const regions = track.getRegions();
|
const regions = track.getRegions();
|
||||||
|
|
||||||
// Skip tracks with no regions
|
// Skip tracks with no regions
|
||||||
if (regions.length === 0) {
|
if (regions.length === 0) {
|
||||||
tracksToSkip.push(track);
|
tracksToSkip.push(track);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all regions in this track are empty (have no notes)
|
// Check if all regions in this track are empty (have no notes)
|
||||||
const hasAnyNotes = regions.some(region => {
|
const hasAnyNotes = regions.some(region => {
|
||||||
if (region.getCurrentType() === 'KGMidiRegion') {
|
if (region.getCurrentType() === 'KGMidiRegion') {
|
||||||
@@ -144,42 +144,42 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip tracks where no regions have notes
|
// Skip tracks where no regions have notes
|
||||||
if (!hasAnyNotes) {
|
if (!hasAnyNotes) {
|
||||||
tracksToSkip.push(track);
|
tracksToSkip.push(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracksToSkip;
|
return tracksToSkip;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error finding tracks to skip:', error);
|
console.error('Error finding tracks to skip:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate ABC notation for all tracks
|
* Generate ABC notation for all tracks
|
||||||
*/
|
*/
|
||||||
private generateAllTracksABC(tracks: KGMidiTrack[], startBeat: number, endBeat?: number): string {
|
private generateAllTracksABC(tracks: KGMidiTrack[], startBeat: number, endBeat?: number): string {
|
||||||
const midiTracks = tracks.filter(track => track instanceof KGMidiTrack);
|
const midiTracks = tracks.filter(track => track instanceof KGMidiTrack);
|
||||||
|
|
||||||
if (midiTracks.length === 0) {
|
if (midiTracks.length === 0) {
|
||||||
return 'No MIDI tracks found in the project.';
|
return 'No MIDI tracks found in the project.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find tracks to skip (tracks with no content)
|
// Find tracks to skip (tracks with no content)
|
||||||
const tracksToSkip = this.findTracksToSkip(midiTracks);
|
const tracksToSkip = this.findTracksToSkip(midiTracks);
|
||||||
|
|
||||||
// Get project settings for proper notation
|
// Get project settings for proper notation
|
||||||
const project = this.getCurrentProject();
|
const project = this.getCurrentProject();
|
||||||
const timeSignature = project.getTimeSignature();
|
const timeSignature = project.getTimeSignature();
|
||||||
const keySignature = project.getKeySignature();
|
const keySignature = project.getKeySignature();
|
||||||
const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C';
|
const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C';
|
||||||
|
|
||||||
let output = `All Tracks (beats ${startBeat}-${endBeat || 'end'}):\n\n`;
|
let output = `All Tracks (beats ${startBeat}-${endBeat || 'end'}):\n\n`;
|
||||||
|
|
||||||
midiTracks.forEach((track, index) => {
|
midiTracks.forEach((track, index) => {
|
||||||
// Skip tracks that have no musical content
|
// Skip tracks that have no musical content
|
||||||
if (tracksToSkip.includes(track)) {
|
if (tracksToSkip.includes(track)) {
|
||||||
@@ -187,10 +187,10 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
const trackNumber = index + 1;
|
const trackNumber = index + 1;
|
||||||
const trackName = track.getName() || `Track ${trackNumber}`;
|
const trackName = track.getName() || `Track ${trackNumber}`;
|
||||||
|
|
||||||
// Check if this track uses a percussion instrument
|
// Check if this track uses a percussion instrument
|
||||||
const percussionDisplayName = this.getPercussionDisplayName(track);
|
const percussionDisplayName = this.getPercussionDisplayName(track);
|
||||||
|
|
||||||
let displayTrackName: string;
|
let displayTrackName: string;
|
||||||
if (percussionDisplayName) {
|
if (percussionDisplayName) {
|
||||||
// Use percussion instrument display name for all percussion tracks
|
// Use percussion instrument display name for all percussion tracks
|
||||||
@@ -202,12 +202,12 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
// Use original track name for other non-percussion tracks
|
// Use original track name for other non-percussion tracks
|
||||||
displayTrackName = trackName;
|
displayTrackName = trackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
output += `Track ${trackNumber} - ${displayTrackName}:\n`;
|
output += `Track ${trackNumber} - ${displayTrackName}:\n`;
|
||||||
|
|
||||||
// Get all regions from the track and convert each one
|
// Get all regions from the track and convert each one
|
||||||
const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[];
|
const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[];
|
||||||
|
|
||||||
if (regions.length === 0) {
|
if (regions.length === 0) {
|
||||||
output += 'X:' + trackNumber + '\n';
|
output += 'X:' + trackNumber + '\n';
|
||||||
output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`;
|
output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`;
|
||||||
@@ -219,11 +219,11 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
regions.forEach((region) => {
|
regions.forEach((region) => {
|
||||||
const regionStart = region.getStartFromBeat();
|
const regionStart = region.getStartFromBeat();
|
||||||
const regionEnd = regionStart + region.getLength();
|
const regionEnd = regionStart + region.getLength();
|
||||||
|
|
||||||
// Check if region overlaps with requested range
|
// Check if region overlaps with requested range
|
||||||
if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) {
|
if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) {
|
||||||
const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat);
|
const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat);
|
||||||
|
|
||||||
// Update the X: line to include track number
|
// Update the X: line to include track number
|
||||||
const lines = abcNotation.split('\n');
|
const lines = abcNotation.split('\n');
|
||||||
lines[0] = `X:${trackNumber}`;
|
lines[0] = `X:${trackNumber}`;
|
||||||
@@ -231,7 +231,7 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasContent) {
|
if (!hasContent) {
|
||||||
output += 'X:' + trackNumber + '\n';
|
output += 'X:' + trackNumber + '\n';
|
||||||
output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`;
|
output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`;
|
||||||
@@ -240,7 +240,7 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return output.trim();
|
return output.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,20 +251,20 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
if (!(track instanceof KGMidiTrack)) {
|
if (!(track instanceof KGMidiTrack)) {
|
||||||
return `Track is not a MIDI track.`;
|
return `Track is not a MIDI track.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get project settings for proper notation
|
// Get project settings for proper notation
|
||||||
const project = this.getCurrentProject();
|
const project = this.getCurrentProject();
|
||||||
const timeSignature = project.getTimeSignature();
|
const timeSignature = project.getTimeSignature();
|
||||||
const keySignature = project.getKeySignature();
|
const keySignature = project.getKeySignature();
|
||||||
const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C';
|
const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C';
|
||||||
|
|
||||||
const trackName = track.getName() || 'Unnamed Track';
|
const trackName = track.getName() || 'Unnamed Track';
|
||||||
|
|
||||||
let output = `Track "${trackName}" (beats ${startBeat}-${endBeat || 'end'}):\n`;
|
let output = `Track "${trackName}" (beats ${startBeat}-${endBeat || 'end'}):\n`;
|
||||||
|
|
||||||
// Get all regions from the track and convert each one
|
// Get all regions from the track and convert each one
|
||||||
const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[];
|
const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[];
|
||||||
|
|
||||||
if (regions.length === 0) {
|
if (regions.length === 0) {
|
||||||
output += 'X:1\n';
|
output += 'X:1\n';
|
||||||
output += `T:${trackName}\n`;
|
output += `T:${trackName}\n`;
|
||||||
@@ -278,11 +278,11 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
regions.forEach((region) => {
|
regions.forEach((region) => {
|
||||||
const regionStart = region.getStartFromBeat();
|
const regionStart = region.getStartFromBeat();
|
||||||
const regionEnd = regionStart + region.getLength();
|
const regionEnd = regionStart + region.getLength();
|
||||||
|
|
||||||
// Check if region overlaps with requested range
|
// Check if region overlaps with requested range
|
||||||
if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) {
|
if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) {
|
||||||
const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat);
|
const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat);
|
||||||
|
|
||||||
// Update the title to include track name
|
// Update the title to include track name
|
||||||
const lines = abcNotation.split('\n');
|
const lines = abcNotation.split('\n');
|
||||||
lines[1] = `T:${trackName}`;
|
lines[1] = `T:${trackName}`;
|
||||||
@@ -290,7 +290,7 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasContent) {
|
if (!hasContent) {
|
||||||
output += 'X:1\n';
|
output += 'X:1\n';
|
||||||
output += `T:${trackName}\n`;
|
output += `T:${trackName}\n`;
|
||||||
@@ -300,7 +300,7 @@ export class ReadMusicTool extends BaseTool {
|
|||||||
output += 'z4 | // No content in specified range';
|
output += 'z4 | // No content in specified range';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ export class RemoveNotesTool extends BaseTool {
|
|||||||
readonly description = 'Remove all MIDI notes whose start position falls within the specified beat range. Use this to clear a section before rewriting it, or to delete unwanted notes. Beat positions are absolute on the project timeline.';
|
readonly description = 'Remove all MIDI notes whose start position falls within the specified beat range. Use this to clear a section before rewriting it, or to delete unwanted notes. Beat positions are absolute on the project timeline.';
|
||||||
|
|
||||||
readonly parameters: Record<string, ToolParameter> = {
|
readonly parameters: Record<string, ToolParameter> = {
|
||||||
start_beat: {
|
start: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Absolute beat position where the removal range begins (inclusive). A note starting at exactly this beat will be removed.',
|
description: 'Start beat — the absolute beat position where the removal range begins (inclusive). A note starting at exactly this beat will be removed.',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
end_beat: {
|
end: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Absolute beat position where the removal range ends (exclusive). A note starting at exactly this beat will NOT be removed. Must be greater than start_beat.',
|
description: 'End beat — the absolute beat position where the removal range ends (exclusive). A note starting at exactly this beat will NOT be removed. Must be greater than start.',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
region_id: {
|
region_id: {
|
||||||
@@ -36,17 +36,17 @@ export class RemoveNotesTool extends BaseTool {
|
|||||||
// Validate parameters
|
// Validate parameters
|
||||||
this.validateParameters(params);
|
this.validateParameters(params);
|
||||||
|
|
||||||
const startBeat = params.start_beat as number;
|
const startBeat = params.start as number;
|
||||||
const endBeat = params.end_beat as number;
|
const endBeat = params.end as number;
|
||||||
const regionId = params.region_id as string | undefined;
|
const regionId = params.region_id as string | undefined;
|
||||||
|
|
||||||
// Validate beat range
|
// Validate beat range
|
||||||
if (startBeat < 0) {
|
if (startBeat < 0) {
|
||||||
return this.createErrorResult(`Invalid start_beat ${startBeat}. Must be >= 0.`);
|
return this.createErrorResult(`Invalid start ${startBeat}. Must be >= 0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endBeat <= startBeat) {
|
if (endBeat <= startBeat) {
|
||||||
return this.createErrorResult(`Invalid beat range: end_beat (${endBeat}) must be greater than start_beat (${startBeat}).`);
|
return this.createErrorResult(`Invalid beat range: end (${endBeat}) must be greater than start (${startBeat}).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the target region
|
// Find the target region
|
||||||
|
|||||||
+33
-33
@@ -57,7 +57,7 @@ export class KGDebugger {
|
|||||||
console.log(`📝 Converting selected region to ABC notation...`);
|
console.log(`📝 Converting selected region to ABC notation...`);
|
||||||
console.log(`📊 Selected items count: ${selectedItems.length}`);
|
console.log(`📊 Selected items count: ${selectedItems.length}`);
|
||||||
|
|
||||||
midiRegion = selectedItems.find(item =>
|
midiRegion = selectedItems.find(item =>
|
||||||
item.getCurrentType() === 'KGMidiRegion'
|
item.getCurrentType() === 'KGMidiRegion'
|
||||||
) as KGMidiRegion;
|
) as KGMidiRegion;
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ export class KGDebugger {
|
|||||||
// If no MIDI region selected, try to use active region from piano roll
|
// If no MIDI region selected, try to use active region from piano roll
|
||||||
if (!midiRegion) {
|
if (!midiRegion) {
|
||||||
console.log("📝 No MIDI region selected, checking for active region...");
|
console.log("📝 No MIDI region selected, checking for active region...");
|
||||||
|
|
||||||
const storeState = useProjectStore.getState();
|
const storeState = useProjectStore.getState();
|
||||||
const activeRegionId = storeState.activeRegionId;
|
const activeRegionId = storeState.activeRegionId;
|
||||||
const tracks = storeState.tracks;
|
const tracks = storeState.tracks;
|
||||||
@@ -75,7 +75,7 @@ export class KGDebugger {
|
|||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
const regions = track.getRegions();
|
const regions = track.getRegions();
|
||||||
const region = regions.find(r => r.getId() === activeRegionId);
|
const region = regions.find(r => r.getId() === activeRegionId);
|
||||||
|
|
||||||
if (region && region instanceof KGMidiRegion) {
|
if (region && region instanceof KGMidiRegion) {
|
||||||
midiRegion = region;
|
midiRegion = region;
|
||||||
console.log(`✅ Found active region: "${region.getName()}"`);
|
console.log(`✅ Found active region: "${region.getName()}"`);
|
||||||
@@ -95,7 +95,7 @@ export class KGDebugger {
|
|||||||
|
|
||||||
// Use provided startFromBeat or default to region start
|
// Use provided startFromBeat or default to region start
|
||||||
const effectiveStartBeat = startFromBeat ?? midiRegion.getStartFromBeat();
|
const effectiveStartBeat = startFromBeat ?? midiRegion.getStartFromBeat();
|
||||||
|
|
||||||
console.log(`🎵 Converting region: "${midiRegion.getName()}"`);
|
console.log(`🎵 Converting region: "${midiRegion.getName()}"`);
|
||||||
console.log(`📍 Region starts at beat: ${midiRegion.getStartFromBeat()}`);
|
console.log(`📍 Region starts at beat: ${midiRegion.getStartFromBeat()}`);
|
||||||
console.log(`📍 Conversion starts at beat: ${effectiveStartBeat}`);
|
console.log(`📍 Conversion starts at beat: ${effectiveStartBeat}`);
|
||||||
@@ -103,13 +103,13 @@ export class KGDebugger {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const abcNotation = convertRegionToABCNotation(midiRegion, effectiveStartBeat);
|
const abcNotation = convertRegionToABCNotation(midiRegion, effectiveStartBeat);
|
||||||
|
|
||||||
console.log("✅ ABC Notation conversion successful!");
|
console.log("✅ ABC Notation conversion successful!");
|
||||||
console.log("📄 Result:");
|
console.log("📄 Result:");
|
||||||
console.log("─".repeat(50));
|
console.log("─".repeat(50));
|
||||||
console.log(abcNotation);
|
console.log(abcNotation);
|
||||||
console.log("─".repeat(50));
|
console.log("─".repeat(50));
|
||||||
|
|
||||||
// Also copy to clipboard if possible
|
// Also copy to clipboard if possible
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(abcNotation).then(() => {
|
navigator.clipboard.writeText(abcNotation).then(() => {
|
||||||
@@ -118,7 +118,7 @@ export class KGDebugger {
|
|||||||
console.log("📋 Could not copy to clipboard (requires HTTPS)");
|
console.log("📋 Could not copy to clipboard (requires HTTPS)");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error converting to ABC notation:", error);
|
console.error("❌ Error converting to ABC notation:", error);
|
||||||
}
|
}
|
||||||
@@ -133,17 +133,17 @@ export class KGDebugger {
|
|||||||
const core = KGCore.instance();
|
const core = KGCore.instance();
|
||||||
const project = core.getCurrentProject();
|
const project = core.getCurrentProject();
|
||||||
const effectiveTimeSignature = timeSignature ?? project.getTimeSignature();
|
const effectiveTimeSignature = timeSignature ?? project.getTimeSignature();
|
||||||
|
|
||||||
console.log(`🧮 Testing quantization for ${durationBeats} beats...`);
|
console.log(`🧮 Testing quantization for ${durationBeats} beats...`);
|
||||||
console.log(`⏱️ Time signature: ${effectiveTimeSignature.numerator}/${effectiveTimeSignature.denominator}`);
|
console.log(`⏱️ Time signature: ${effectiveTimeSignature.numerator}/${effectiveTimeSignature.denominator}`);
|
||||||
|
|
||||||
// Import quantization testing (we'll need to expose some internal methods)
|
// Import quantization testing (we'll need to expose some internal methods)
|
||||||
// For now, let's create a simple test
|
// For now, let's create a simple test
|
||||||
const ticksPerBeat = 480 * (4 / effectiveTimeSignature.denominator);
|
const ticksPerBeat = 480 * (4 / effectiveTimeSignature.denominator);
|
||||||
const durationTicks = Math.round(durationBeats * ticksPerBeat);
|
const durationTicks = Math.round(durationBeats * ticksPerBeat);
|
||||||
|
|
||||||
console.log(`🎵 Input: ${durationBeats} beats = ${durationTicks} ticks`);
|
console.log(`🎵 Input: ${durationBeats} beats = ${durationTicks} ticks`);
|
||||||
|
|
||||||
// Test different quantization values manually for demonstration
|
// Test different quantization values manually for demonstration
|
||||||
const testValues = [
|
const testValues = [
|
||||||
{ name: '1/1', ticks: 1920 },
|
{ name: '1/1', ticks: 1920 },
|
||||||
@@ -155,25 +155,25 @@ export class KGDebugger {
|
|||||||
{ name: '1/12', ticks: 160 },
|
{ name: '1/12', ticks: 160 },
|
||||||
{ name: '1/16', ticks: 120 }
|
{ name: '1/16', ticks: 120 }
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log("📊 Quantization analysis:");
|
console.log("📊 Quantization analysis:");
|
||||||
let bestMatch = { name: '1/4', error: Infinity, ticks: 480 };
|
let bestMatch = { name: '1/4', error: Infinity, ticks: 480 };
|
||||||
|
|
||||||
testValues.forEach(val => {
|
testValues.forEach(val => {
|
||||||
const remainder = durationTicks % val.ticks;
|
const remainder = durationTicks % val.ticks;
|
||||||
const error = Math.min(remainder, val.ticks - remainder);
|
const error = Math.min(remainder, val.ticks - remainder);
|
||||||
const errorPercent = ((error / val.ticks) * 100).toFixed(1);
|
const errorPercent = ((error / val.ticks) * 100).toFixed(1);
|
||||||
|
|
||||||
if (error < bestMatch.error) {
|
if (error < bestMatch.error) {
|
||||||
bestMatch = { name: val.name, error, ticks: val.ticks };
|
bestMatch = { name: val.name, error, ticks: val.ticks };
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(` ${val.name}: ${error} ticks error (${errorPercent}%)`);
|
console.log(` ${val.name}: ${error} ticks error (${errorPercent}%)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const quantizedTicks = Math.round(durationTicks / bestMatch.ticks) * bestMatch.ticks;
|
const quantizedTicks = Math.round(durationTicks / bestMatch.ticks) * bestMatch.ticks;
|
||||||
const quantizedBeats = quantizedTicks / ticksPerBeat;
|
const quantizedBeats = quantizedTicks / ticksPerBeat;
|
||||||
|
|
||||||
console.log(`✅ Best match: ${bestMatch.name} grid`);
|
console.log(`✅ Best match: ${bestMatch.name} grid`);
|
||||||
console.log(`🎯 Quantized: ${quantizedBeats} beats = ${quantizedTicks} ticks`);
|
console.log(`🎯 Quantized: ${quantizedBeats} beats = ${quantizedTicks} ticks`);
|
||||||
console.log(`📏 Difference: ${Math.abs(durationBeats - quantizedBeats).toFixed(4)} beats`);
|
console.log(`📏 Difference: ${Math.abs(durationBeats - quantizedBeats).toFixed(4)} beats`);
|
||||||
@@ -185,20 +185,20 @@ export class KGDebugger {
|
|||||||
public debugSelectedItems(): void {
|
public debugSelectedItems(): void {
|
||||||
const core = KGCore.instance();
|
const core = KGCore.instance();
|
||||||
const selectedItems = core.getSelectedItems();
|
const selectedItems = core.getSelectedItems();
|
||||||
|
|
||||||
console.log(`🔍 Currently selected items: ${selectedItems.length}`);
|
console.log(`🔍 Currently selected items: ${selectedItems.length}`);
|
||||||
|
|
||||||
if (selectedItems.length === 0) {
|
if (selectedItems.length === 0) {
|
||||||
console.log("📝 No items selected. Try selecting regions or notes first.");
|
console.log("📝 No items selected. Try selecting regions or notes first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedItems.forEach((item, index) => {
|
selectedItems.forEach((item, index) => {
|
||||||
const type = item.getCurrentType();
|
const type = item.getCurrentType();
|
||||||
const id = item.getId();
|
const id = item.getId();
|
||||||
|
|
||||||
console.log(` ${index + 1}. ${type} (ID: ${id})`);
|
console.log(` ${index + 1}. ${type} (ID: ${id})`);
|
||||||
|
|
||||||
if (type === 'KGMidiRegion') {
|
if (type === 'KGMidiRegion') {
|
||||||
const region = item as KGMidiRegion;
|
const region = item as KGMidiRegion;
|
||||||
console.log(` 📍 Position: ${region.getStartFromBeat()} beats`);
|
console.log(` 📍 Position: ${region.getStartFromBeat()} beats`);
|
||||||
@@ -228,12 +228,12 @@ export class KGDebugger {
|
|||||||
console.log("─".repeat(50));
|
console.log("─".repeat(50));
|
||||||
console.log(input);
|
console.log(input);
|
||||||
console.log("─".repeat(50));
|
console.log("─".repeat(50));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const xmlBlocks = extractXMLFromString(input);
|
const xmlBlocks = extractXMLFromString(input);
|
||||||
|
|
||||||
console.log(`✅ Extraction successful! Found ${xmlBlocks.length} XML block(s):`);
|
console.log(`✅ Extraction successful! Found ${xmlBlocks.length} XML block(s):`);
|
||||||
|
|
||||||
if (xmlBlocks.length === 0) {
|
if (xmlBlocks.length === 0) {
|
||||||
console.log("📭 No XML blocks found in the input string.");
|
console.log("📭 No XML blocks found in the input string.");
|
||||||
console.log("💡 Try input with XML tags like: <add_notes>...</add_notes>");
|
console.log("💡 Try input with XML tags like: <add_notes>...</add_notes>");
|
||||||
@@ -244,7 +244,7 @@ export class KGDebugger {
|
|||||||
console.log(block);
|
console.log(block);
|
||||||
console.log("─".repeat(30));
|
console.log("─".repeat(30));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy all blocks to clipboard if possible
|
// Copy all blocks to clipboard if possible
|
||||||
if (navigator.clipboard && xmlBlocks.length > 0) {
|
if (navigator.clipboard && xmlBlocks.length > 0) {
|
||||||
const allBlocks = xmlBlocks.join('\n\n');
|
const allBlocks = xmlBlocks.join('\n\n');
|
||||||
@@ -255,7 +255,7 @@ export class KGDebugger {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error extracting XML:", error);
|
console.error("❌ Error extracting XML:", error);
|
||||||
}
|
}
|
||||||
@@ -268,13 +268,13 @@ export class KGDebugger {
|
|||||||
* Usage examples in browser console:
|
* Usage examples in browser console:
|
||||||
*
|
*
|
||||||
* // Single tool call:
|
* // Single tool call:
|
||||||
* await KGStudio.KGDebugger.testToolCall('{"name":"read_music","arguments":{"start_beat":0,"length":8}}')
|
* await KGStudio.KGDebugger.testToolCall('{"name":"read_music","arguments":{"start":0,"length":8}}')
|
||||||
*
|
*
|
||||||
* // Multiple tool calls:
|
* // Multiple tool calls:
|
||||||
* await KGStudio.KGDebugger.testToolCall('[{"name":"remove_notes","arguments":{"start_beat":0,"end_beat":4}},{"name":"add_notes","arguments":{"notes":[{"pitch":"C4","start_beat":0,"length":1}]}}]')
|
* await KGStudio.KGDebugger.testToolCall('[{"name":"remove_notes","arguments":{"start":0,"end_beat":4}},{"name":"add_notes","arguments":{"notes":[{"pitch":"C4","start":0,"length":1}]}}]')
|
||||||
*
|
*
|
||||||
* // Can also pass a JS object directly (no need to stringify):
|
* // Can also pass a JS object directly (no need to stringify):
|
||||||
* await KGStudio.KGDebugger.testToolCall({name:"read_music",arguments:{start_beat:0}})
|
* await KGStudio.KGDebugger.testToolCall({name:"read_music",arguments:{start:0}})
|
||||||
*
|
*
|
||||||
* @param input - JSON string, object, or array of tool call(s).
|
* @param input - JSON string, object, or array of tool call(s).
|
||||||
* Each tool call should have: { name: string, arguments: object }
|
* Each tool call should have: { name: string, arguments: object }
|
||||||
@@ -352,9 +352,9 @@ export class KGDebugger {
|
|||||||
console.log(" - Use browser developer tools for best experience");
|
console.log(" - Use browser developer tools for best experience");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("💡 testToolCall examples:");
|
console.log("💡 testToolCall examples:");
|
||||||
console.log(' await KGStudio.KGDebugger.testToolCall(\'{"name":"read_music","arguments":{"start_beat":0,"length":8}}\')');
|
console.log(' await KGStudio.KGDebugger.testToolCall(\'{"name":"read_music","arguments":{"start":0,"length":8}}\')');
|
||||||
console.log(' await KGStudio.KGDebugger.testToolCall({name:"add_notes",arguments:{notes:[{pitch:"C4",start_beat:0,length:1}]}})');
|
console.log(' await KGStudio.KGDebugger.testToolCall({name:"add_notes",arguments:{notes:[{pitch:"C4",start:0,length:1}]}})');
|
||||||
console.log(' await KGStudio.KGDebugger.testToolCall([{name:"remove_notes",arguments:{start_beat:0,end_beat:4}},{name:"read_music",arguments:{}}])');
|
console.log(' await KGStudio.KGDebugger.testToolCall([{name:"remove_notes",arguments:{start:0,end_beat:4}},{name:"read_music",arguments:{}}])');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user