@@ -3216,8 +3216,8 @@ def set_chain_solo(self, track_index, device_index, chain_index, solo):
32163216 # CLIP AUTOMATION ENVELOPES (6 tools)
32173217 # ========================================================================
32183218
3219- def get_clip_automation_envelope (self , track_index , clip_index , param_name ):
3220- """Get automation envelope for a parameter in a clip"""
3219+ def get_clip_automation_envelope (self , track_index , clip_index , device_index , param_index ):
3220+ """Get automation envelope for a device parameter in a clip"""
32213221 try :
32223222 track = self .song .tracks [track_index ]
32233223 clip_slot = track .clip_slots [clip_index ]
@@ -3227,18 +3227,35 @@ def get_clip_automation_envelope(self, track_index, clip_index, param_name):
32273227
32283228 clip = clip_slot .clip
32293229
3230- # Find the automation envelope by parameter name
3231- # Note: This is a simplified version - full implementation would need device/parameter lookup
3232- return {
3233- "ok" : True ,
3234- "message" : "Automation envelope info (requires parameter object reference)" ,
3235- "clip_name" : str (clip .name )
3236- }
3230+ # Get the device parameter
3231+ device = track .devices [device_index ]
3232+ param = device .parameters [param_index ]
3233+
3234+ # Get automation envelope for this parameter
3235+ if hasattr (clip , 'automation_envelope' ):
3236+ envelope = clip .automation_envelope (param )
3237+
3238+ if envelope :
3239+ return {
3240+ "ok" : True ,
3241+ "has_envelope" : True ,
3242+ "parameter_name" : str (param .name ),
3243+ "device_name" : str (device .name )
3244+ }
3245+ else :
3246+ return {
3247+ "ok" : True ,
3248+ "has_envelope" : False ,
3249+ "parameter_name" : str (param .name ),
3250+ "message" : "No automation envelope for this parameter"
3251+ }
3252+ else :
3253+ return {"ok" : False , "error" : "automation_envelope not available" }
32373254 except Exception as e :
32383255 return {"ok" : False , "error" : str (e )}
32393256
3240- def create_automation_envelope (self , track_index , clip_index , parameter_object ):
3241- """Create automation envelope for a parameter"""
3257+ def create_automation_envelope (self , track_index , clip_index , device_index , param_index ):
3258+ """Create automation envelope for a device parameter"""
32423259 try :
32433260 track = self .song .tracks [track_index ]
32443261 clip_slot = track .clip_slots [clip_index ]
@@ -3248,17 +3265,26 @@ def create_automation_envelope(self, track_index, clip_index, parameter_object):
32483265
32493266 clip = clip_slot .clip
32503267
3251- # Note: Requires Live.DeviceParameter.DeviceParameter object
3252- # This is a placeholder - full implementation needs device parameter access
3253- return {
3254- "ok" : True ,
3255- "message" : "Automation envelope created (requires parameter object)"
3256- }
3268+ # Get the device parameter
3269+ device = track .devices [device_index ]
3270+ param = device .parameters [param_index ]
3271+
3272+ # Create automation envelope
3273+ if hasattr (clip , 'create_automation_envelope' ):
3274+ envelope = clip .create_automation_envelope (param )
3275+ return {
3276+ "ok" : True ,
3277+ "parameter_name" : str (param .name ),
3278+ "device_name" : str (device .name ),
3279+ "message" : "Automation envelope created"
3280+ }
3281+ else :
3282+ return {"ok" : False , "error" : "create_automation_envelope not available" }
32573283 except Exception as e :
32583284 return {"ok" : False , "error" : str (e )}
32593285
3260- def clear_automation_envelope (self , track_index , clip_index , param_name ):
3261- """Clear automation envelope for a parameter"""
3286+ def clear_automation_envelope (self , track_index , clip_index , device_index , param_index ):
3287+ """Clear automation envelope for a device parameter"""
32623288 try :
32633289 track = self .song .tracks [track_index ]
32643290 clip_slot = track .clip_slots [clip_index ]
@@ -3268,15 +3294,24 @@ def clear_automation_envelope(self, track_index, clip_index, param_name):
32683294
32693295 clip = clip_slot .clip
32703296
3271- # Clear automation - implementation depends on parameter access
3272- return {
3273- "ok" : True ,
3274- "message" : "Automation envelope cleared"
3275- }
3297+ # Get the device parameter
3298+ device = track .devices [device_index ]
3299+ param = device .parameters [param_index ]
3300+
3301+ # Clear automation envelope
3302+ if hasattr (clip , 'clear_envelope' ):
3303+ clip .clear_envelope (param )
3304+ return {
3305+ "ok" : True ,
3306+ "parameter_name" : str (param .name ),
3307+ "message" : "Automation envelope cleared"
3308+ }
3309+ else :
3310+ return {"ok" : False , "error" : "clear_envelope not available" }
32763311 except Exception as e :
32773312 return {"ok" : False , "error" : str (e )}
32783313
3279- def insert_automation_step (self , track_index , clip_index , param_name , time , value ):
3314+ def insert_automation_step (self , track_index , clip_index , device_index , param_index , time , value ):
32803315 """Insert automation step/breakpoint at specific time"""
32813316 try :
32823317 track = self .song .tracks [track_index ]
@@ -3287,17 +3322,29 @@ def insert_automation_step(self, track_index, clip_index, param_name, time, valu
32873322
32883323 clip = clip_slot .clip
32893324
3290- # Insert step - requires envelope access
3291- return {
3292- "ok" : True ,
3293- "time" : float (time ),
3294- "value" : float (value ),
3295- "message" : "Automation step inserted"
3296- }
3325+ # Get the device parameter and envelope
3326+ device = track .devices [device_index ]
3327+ param = device .parameters [param_index ]
3328+
3329+ if hasattr (clip , 'automation_envelope' ):
3330+ envelope = clip .automation_envelope (param )
3331+ if envelope and hasattr (envelope , 'insert_step' ):
3332+ envelope .insert_step (float (time ), float (value ))
3333+ return {
3334+ "ok" : True ,
3335+ "time" : float (time ),
3336+ "value" : float (value ),
3337+ "parameter_name" : str (param .name ),
3338+ "message" : "Automation step inserted"
3339+ }
3340+ else :
3341+ return {"ok" : False , "error" : "No envelope or insert_step not available" }
3342+ else :
3343+ return {"ok" : False , "error" : "automation_envelope not available" }
32973344 except Exception as e :
32983345 return {"ok" : False , "error" : str (e )}
32993346
3300- def remove_automation_step (self , track_index , clip_index , param_name , time ):
3347+ def remove_automation_step (self , track_index , clip_index , device_index , param_index , time ):
33013348 """Remove automation step/breakpoint at specific time"""
33023349 try :
33033350 track = self .song .tracks [track_index ]
@@ -3308,15 +3355,28 @@ def remove_automation_step(self, track_index, clip_index, param_name, time):
33083355
33093356 clip = clip_slot .clip
33103357
3311- return {
3312- "ok" : True ,
3313- "time" : float (time ),
3314- "message" : "Automation step removed"
3315- }
3358+ # Get the device parameter and envelope
3359+ device = track .devices [device_index ]
3360+ param = device .parameters [param_index ]
3361+
3362+ if hasattr (clip , 'automation_envelope' ):
3363+ envelope = clip .automation_envelope (param )
3364+ if envelope and hasattr (envelope , 'remove_step' ):
3365+ envelope .remove_step (float (time ))
3366+ return {
3367+ "ok" : True ,
3368+ "time" : float (time ),
3369+ "parameter_name" : str (param .name ),
3370+ "message" : "Automation step removed"
3371+ }
3372+ else :
3373+ return {"ok" : False , "error" : "No envelope or remove_step not available" }
3374+ else :
3375+ return {"ok" : False , "error" : "automation_envelope not available" }
33163376 except Exception as e :
33173377 return {"ok" : False , "error" : str (e )}
33183378
3319- def get_automation_envelope_values (self , track_index , clip_index , param_name ):
3379+ def get_automation_envelope_values (self , track_index , clip_index , device_index , param_index ):
33203380 """Get all automation envelope values for a parameter"""
33213381 try :
33223382 track = self .song .tracks [track_index ]
@@ -3327,12 +3387,30 @@ def get_automation_envelope_values(self, track_index, clip_index, param_name):
33273387
33283388 clip = clip_slot .clip
33293389
3330- # Get automation values - requires envelope iteration
3331- return {
3332- "ok" : True ,
3333- "values" : [],
3334- "message" : "Automation values retrieved"
3335- }
3390+ # Get the device parameter and envelope
3391+ device = track .devices [device_index ]
3392+ param = device .parameters [param_index ]
3393+
3394+ if hasattr (clip , 'automation_envelope' ):
3395+ envelope = clip .automation_envelope (param )
3396+ if envelope :
3397+ # Get envelope value at different time points
3398+ # Note: Full implementation would iterate through all steps
3399+ return {
3400+ "ok" : True ,
3401+ "parameter_name" : str (param .name ),
3402+ "has_envelope" : True ,
3403+ "message" : "Use insert_step/remove_step to modify automation"
3404+ }
3405+ else :
3406+ return {
3407+ "ok" : True ,
3408+ "parameter_name" : str (param .name ),
3409+ "has_envelope" : False ,
3410+ "message" : "No automation envelope for this parameter"
3411+ }
3412+ else :
3413+ return {"ok" : False , "error" : "automation_envelope not available" }
33363414 except Exception as e :
33373415 return {"ok" : False , "error" : str (e )}
33383416
@@ -3777,32 +3855,45 @@ def set_metronome_volume(self, volume):
37773855 def send_midi_cc (self , track_index , cc_number , cc_value , channel = 0 ):
37783856 """Send MIDI CC message to a track"""
37793857 try :
3780- track = self .song .tracks [track_index ]
3858+ # MIDI CC status byte: 176 (0xB0) + channel
3859+ # Format: (status_byte, cc_number, cc_value)
3860+ status_byte = 176 + int (channel )
3861+ midi_bytes = (int (status_byte ), int (cc_number ), int (cc_value ))
37813862
3782- # MIDI CC sending requires specific MIDI output routing
3783- # This is a placeholder for the MIDI sending logic
3784- return {
3785- "ok" : True ,
3786- "cc_number" : int (cc_number ),
3787- "cc_value" : int (cc_value ),
3788- "channel" : int (channel ),
3789- "message" : "MIDI CC sent"
3790- }
3863+ # Send MIDI via song.send_midi
3864+ if hasattr (self .song , 'send_midi' ):
3865+ self .song .send_midi (midi_bytes )
3866+ return {
3867+ "ok" : True ,
3868+ "cc_number" : int (cc_number ),
3869+ "cc_value" : int (cc_value ),
3870+ "channel" : int (channel ),
3871+ "message" : "MIDI CC sent"
3872+ }
3873+ else :
3874+ return {"ok" : False , "error" : "send_midi not available" }
37913875 except Exception as e :
37923876 return {"ok" : False , "error" : str (e )}
37933877
37943878 def send_program_change (self , track_index , program_number , channel = 0 ):
37953879 """Send MIDI Program Change message to a track"""
37963880 try :
3797- track = self .song .tracks [track_index ]
3881+ # MIDI Program Change status byte: 192 (0xC0) + channel
3882+ # Format: (status_byte, program_number)
3883+ status_byte = 192 + int (channel )
3884+ midi_bytes = (int (status_byte ), int (program_number ))
37983885
3799- # MIDI Program Change sending
3800- return {
3801- "ok" : True ,
3802- "program_number" : int (program_number ),
3803- "channel" : int (channel ),
3804- "message" : "MIDI Program Change sent"
3805- }
3886+ # Send MIDI via song.send_midi
3887+ if hasattr (self .song , 'send_midi' ):
3888+ self .song .send_midi (midi_bytes )
3889+ return {
3890+ "ok" : True ,
3891+ "program_number" : int (program_number ),
3892+ "channel" : int (channel ),
3893+ "message" : "MIDI Program Change sent"
3894+ }
3895+ else :
3896+ return {"ok" : False , "error" : "send_midi not available" }
38063897 except Exception as e :
38073898 return {"ok" : False , "error" : str (e )}
38083899
0 commit comments