add_notes_to_clip
Add MIDI notes to a specific clip in Ableton Live using clip ID and note details like pitch, duration, and velocity for precise music production.
Instructions
Add notes to clip by clip id
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| clip_id | Yes | ||
| notes | Yes | [array] the notes to add. |
Implementation Reference
- src/tools/clip-tools.ts:85-99 (handler)The main execution handler for the 'add_notes_to_clip' tool. It retrieves the clip by ID, creates a note snapshot for undo support, sets the new notes on the clip, and returns success.
@tool({ name: 'add_notes_to_clip', description: 'Add notes to clip by clip id', enableSnapshot: true, paramsSchema: { notes: z.array(NOTE).describe('[array] the notes to add.'), clip_id: z.string() } }) async addClipNotes({ notes, clip_id, historyId }: { notes: Note[], clip_id: string, historyId: number }) { const clip = getClipById(clip_id) await createNoteSnapshot(clip, historyId) await clip.setNotes(notes) return Result.ok() } - src/types/zod-types.ts:25-32 (schema)Zod schema for individual NOTE objects, used in the tool's input schema as z.array(NOTE).
export const NOTE = createZodSchema<Note>({ pitch: z.number().min(0).max(127).describe('[int] the MIDI note number, 0...127, 60 is C3.'), time: z.number().describe('[float] the note start time in beats of absolute clip time.'), duration: z.number().describe('[float] the note length in beats.'), velocity: z.number().min(0).max(127).default(100) .describe('[float] the note velocity, 0 ... 127 (100 by default).'), muted: z.boolean().default(false).describe('[bool] true = the note is deactivated (false by default).') }) - src/main.ts:39-42 (registration)The ClipTools class containing the add_notes_to_clip handler is registered here in the tools array for the MCP server.
await startMcp({ // Register tool classes, make decorators available tools: [BrowserTools, ClipTools, DeviceTools, HistoryTools, SongTools, TrackTools, ExtraTools, ApplicationTools] }) - src/main.ts:8-8 (registration)Import of the ClipTools class that defines the tool.
import ClipTools from './tools/clip-tools.js' - src/tools/clip-tools.ts:5-99 (helper)Imports of helper functions used in the handler: getClipById to retrieve clip, createNoteSnapshot for undo history.
import { batchModifyClipProp, getClipProps, NoteToNoteExtended } from '../utils/obj-utils.js' import { Result } from '../utils/common.js' import { getClipById } from '../utils/obj-utils.js' import { createNoteSnapshot, getNotes, removeNotesExtended, replaceClipNotesExtended } from '../utils/clip-utils.js' import { ableton } from '../ableton.js' class ClipTools { @tool({ name: 'get_clip_properties', description: 'Get clip properties by clip id. To get specific properties, set the corresponding property name to true in the properties parameter.', paramsSchema: { clip_id: z.string(), properties: ClipGettableProp, } }) async getClipInfoById({ clip_id, properties }: { clip_id: string, properties: z.infer<typeof ClipGettableProp> }) { const clip = getClipById(clip_id) return await getClipProps(clip, properties) } @tool({ name: 'get_clip_notes', description: 'Get clip notes by clip id. Returns NoteExtended array for Live 11+ and Note array for Live 10 and below', paramsSchema: { clip_id: z.string(), from_pitch: z.number().min(0).max(127), from_time: z.number(), time_span: z.number(), pitch_span: z.number(), } }) async getClipNotes({ clip_id, from_pitch, from_time, time_span, pitch_span }: { clip_id: string, from_pitch: number, from_time: number, time_span: number, pitch_span: number }) { const clip = getClipById(clip_id) const notes = await getNotes(clip, from_pitch, pitch_span, from_time, time_span) return Result.data(notes) } @tool({ name: 'remove_clip_notes', description: 'Remove clip notes by clip id', enableSnapshot: true, paramsSchema: { clip_id: z.string(), from_pitch: z.number().min(0).max(127), pitch_span: z.number().describe('The number of semitones to remove. Must be a value greater than 0.'), from_time: z.number(), time_span: z.number().describe('The number of beats to remove. Must be a value greater than 0.'), } }) async removeClipNotes({ clip_id, from_pitch, pitch_span, from_time, time_span, historyId }: { clip_id: string from_pitch: number pitch_span: number from_time: number time_span: number historyId: number }) { const clip = getClipById(clip_id) await createNoteSnapshot(clip, historyId) await removeNotesExtended(clip, from_pitch, pitch_span, from_time, time_span) return Result.ok() } @tool({ name: 'remove_notes_by_ids', description: 'Remove notes by clip id and note ids', enableSnapshot: true, paramsSchema: { clip_id: z.string(), note_ids: z.array(z.number()).describe('note ids, get from get_clip_notes'), } }) async removeClipNotesById({ clip_id, note_ids, historyId }: { clip_id: string, note_ids: number[], historyId: number }) { const clip = getClipById(clip_id) await createNoteSnapshot(clip, historyId) await clip.removeNotesById(note_ids) return Result.ok() } @tool({ name: 'add_notes_to_clip', description: 'Add notes to clip by clip id', enableSnapshot: true, paramsSchema: { notes: z.array(NOTE).describe('[array] the notes to add.'), clip_id: z.string() } }) async addClipNotes({ notes, clip_id, historyId }: { notes: Note[], clip_id: string, historyId: number }) { const clip = getClipById(clip_id) await createNoteSnapshot(clip, historyId) await clip.setNotes(notes) return Result.ok() }