Change Clip BPM from max device
Under the Clip section in the LOM (Live Object Model: https://docs.cycling74.com/max8/vignettes/live_object_model#live_obj_anchor_Clip), there seems to be no property to get or set the Clip BPM (on audio clips). Does anyone know any other way to change this from a max device?
AFAIK Clips don't have BPM, the "BPM" number box is actually for Segment BPM, the BPM of a segment (between 2 warp markers or 2 transient markers, I believe).
So for a Clip that only has 2 warp markers(start and end, there is an extra hidden last one you can ignore ) like most, you have to calculate the BPM from the reported "sample_time" and "beat_time" of the 2nd marker.
I think dividing the sample_time by the beat_time gets you how long a beat lasts (beat length), then you calculate how many times that length fits in a minute, by dividing 60 by the beat length.
Then you get the BPM for that segment, which in a clip with only 2 warp markers is the only relevant segment.
I think it is safe to round up/down the number to just one decimal (or even just the integer).
Ah, went on a tangent there.
Property warp_markers can't be set, but since function "move_warp_marker" moves warp markers, that basically changes segment BPM for the adjacent segments.
Probably can use remove_warp_marker and add_warp_marker in some way too.
Ah, this was the exact information I needed, thank you! I haven't had time to come to a full solution today, but I will try to test things out tomorrow. One thing I noticed is that the last "hidden" marker always seems to. be just shortly after the second last marker (or in most cases, the first marker). So I guess I need to move this second marker, even though it is basically right beside the first at the start of the clip. But it seems like that small distance is still what is being used to calculate the Segment BPM. I will have to experiment tomorrow and see what's what.
Hmm, running into a couple of challenges here.
1. When I try to call remove_warp_marker, I get the error message: [The shadow marker can't be deleted.: 'remove_warp_marker 0.03125']
So I guess you can't remove the last shadow marker. And there's only two markers; the first one is just 0,0 and the second one is the shadow marker.
2. When I try to call move_warp_marker, it just gives me this error: [Invalid arguments: 'move_warp_marker 0.03125 0.01828']
I'm guessing this is because it is not letting me move the shadow marker. If that's the case, it's a shame because I looked at the beat_time and sample_time of the shadow marker, and it is indeed encoding the segment BPM information.
Maybe I'm missing something?
I've now confirmed that you can't move the shadow marker, because now it is giving me the error "The shadow marker can't be moved." That is quite strange because changing the Segment BPM in the UI is effectively just moving the shadow marker, so I don't see why they don't let you do it through the LiveAPI.
To allow this, I think they could add a couple of things to the LOM:
- A clip property called ‘clip_segments_count’, which you could get. This would effectively be the same as the amount of visible warp markers on the clip. But it would be useful for indexing into 'clip segments' because it would give you an upper bound.
- A function called ‘set_segment_bpm’ which takes parameters ‘bpm’ and ‘segment_index’. So for my use case, I would just use a segment_index of 0 (since there is only one segment, between the first marker and shadow marker), and then set the BPM.
For now I've come up with a hacky solution, where I add a new warp marker to the end of the sample region in the clip, and move that warp marker to the newly placed 'loop_end' after the song bpm has been changed. This keeps everything in the proper time, but it is actually warping the audio in a way that doesn't happen when you simply change the segment BPM in the UI (to match the new bpm of the entire set). I've overcome this by automatically setting Complex Pro warp mode, but it's still not an ideal solution.
But even with this roundabout solution, now I can't even use it because I just found out that you can't temporarily disable the count-in (since you can't set the 'count_in_duration' of the Song). This makes a bit of sense because the count-in duration is a global setting in Live, but it would be nice if I could temporarily disable it at least. I think at this point, I need to create an entirely new device that will handle the recording, and then after the recording is finished, it will change the tempo and then start dumping the recording into a clip in real time. That way I can prevent the warping issue and also avoid the count-in for this specific feature. It would be nice if the LOM included the ability to create audio clips, and also to dump a buffer from max into the audio clip instantly. This would actually also solve a lot of problems for me in general.
But if anyone knows any ways around the above issues, that would be a big help. Otherwise, I hope Ableton will consider adding the additions to the LOM I mentioned in this post. Thanks.
Hello Zack
I am facing this exact same problem.
I just want to stretch an audio clip with M4L
I am a bit lost with Dictionnaries.
I see that moving the shadow marker is impossible.
Can you please post your solution to add a marker at the end of an audio clip and to move it to the end of the loop bracket of the clip ?
I am really lost with the add_marker and move_marker function..
Any help will be much appreciated !
thank you
Hey Schlam, here's some of the code related to the solution I came up with. It might be a bit overcomplicated for your purpose because I was using it for a slightly strange scenario, but hopefully it's possible to understand how the dictionaries work from this. You might have to include 'loop_end' somewhere for your situation.
I also included some helper functions that my code used (to make sure I am always using the same LiveAPI object for efficiency, and for better organization). Let me know if you have more questions about this.
///// These are some helper functions
function livePath(path) {
if (!api) {
api = new LiveAPI(path);
}
else {
api.goto(path);
}
}
// WP stands for 'With Path', since it sets the path of the LiveAPI object first.
function getPropWP(prop, path) {
livePath(path);
return apiGet(prop);
}
function getIntPropWP(prop, path) {
livePath(path);
return parseInt(api.get(prop));
}
function getFloatPropWP(prop, path) {
livePath(path);
return parseFloat(api.get(prop));
}
function getDictPropWP(prop, path) {
livePath(path);
return JSON.parse(api.get(prop));
}
function getSongTempo() {
var songPath = getSongPath();
return getFloatPropWP("tempo", songPath);
}
///// This code deals with the warp markers
var sample_length = getIntPropWP("sample_length", clipPath);
var sample_rate = getFloatPropWP("sample_rate", clipPath);
var sampleLengthSeconds = sample_length / sample_rate;
var bpm = getSongTempo();
var beatsPerSecond = bpm / 60.0;
var secondsPerBeat = 1.0 / beatsPerSecond;
var sampleEndBeatTime = sampleLengthSeconds * beatsPerSecond;
var warpMarkersDict0 = getDictPropWP("warp_markers", clipPath);
var endMarker_BeatTime = getFloatPropWP("end_marker", clipPath); //might want loop_end here instead
//var endMarker_SampleTime = endMarker_BeatTime * secondsPerBeat;
var warpMarkersArr = warpMarkersDict0["warp_markers"];
var sampleEnd_WarpMarker_BeatTime = -1;
// If there were extra warp markers added already (might be able to leave this out)
if (warpMarkersArr.length >= 3) {
for(var i = 1; i < warpMarkersArr.length - 1; i++) {
log("Removing marker at index #" + i);
livePath(clipPath);
api.call("remove_warp_marker", warpMarkersArr[i].beat_time);
}
}
var warpMarkersDict0_2 = getDictPropWP("warp_markers", clipPath);
var warpMarkersArr_2 = warpMarkersDict0_2["warp_markers"];
//log("warpMarkersArr_2.length:", warpMarkersArr_2.length);
//log("warpMarkersDict0_2:", warpMarkersDict0_2);
// We need to add the extra warp marker ourselves
var wmDict = JSON.stringify({ "beat_time": sampleEndBeatTime, "sample_time": sampleLengthSeconds });
livePath(clipPath);
api.call("add_warp_marker", [wmDict]); //If this doesn't work, try removing the array brackets
sampleEnd_WarpMarker_BeatTime = sampleEndBeatTime;
var beatTimeDiff = endMarker_BeatTime - sampleEnd_WarpMarker_BeatTime;
livePath(clipPath);
api.call("move_warp_marker", sampleEnd_WarpMarker_BeatTime, beatTimeDiff);
// Set the clip's warping to Complex Pro to remove warping artifacts.
// This wouldn't have been necessary if the LiveAPI allowed the modification of the segment BPM directly (moving the shadow marker)
var complexPro_modeNum = 6;
setPropWP("warp_mode", complexPro_modeNum, clipPath);
var complexPro_modeNum = 6;
setPropWP("warp_mode", complexPro_modeNum, clipPath);
Thank you a lot Zack,
The thing is that I never use codebox or js etc,
The device I am making is managing in the same time several midi and audio clips.
It contrains my 1st recording (of several tracks) to fit a regular numbers of bar.
If, for example, the initial tempo is set to 120 and I record a 3.7 bars, it stretchs the clips to the closest "regular" bar number " here : 4, and change the tempo accordingly for the clips to sound exactly how I recorded them. It's working quite good for midi clips. My problem is for audio clips since , as you mentionned, the shadow marker at the end of audio clip is not movable with API.
I really don"t know how to use js script.
I tried to put in in a JS object but I don't know how to tell the object it has to process all the audio clips currently recording.
I am not on my music computer, so I cannot post the patch I have for now...
Thank you again !
Oh ok, yeah I don't use direct max patching to do work with the LiveAPI so I might not be able to help too much there. Maybe I can give you a few hints though. The warp_markers dictionary contains an entry, where the key is the string "warp_markers", and the value is an array (list) of all the warp marker objects. Each individual warp marker object is itself a dictionary. And each of warp marker dictionary contains the keys "beat_time" and "sample_time". The beat_time is the time in beats (for example, if there are 4 beats per bar, and we are at the end of the second bar, the beat time would be 8). The sample_time is the time in seconds.
What I had to do was the following: first I had to place a warp marker at the end of the sample itself. So I had to calculate the beat_time and sample_time of the end of the sample to create the warp marker. To get the sample_time I had to divide the "sample_length" of the clip by the "sample_rate" of the song, which gives the time in seconds. Then I could calculate the beat_time based on that sample_time and the bpm. The equation is: beat_time = sample_time * (bpm / 60). Using the beat_time and sample_time, I could place the warp marker at the end of the sample using the "add_warp_marker" function.
After that, I had to move that warp marker to the "end_marker" of the clip (again, you might want to use "loop_end" instead, experiment). To do this, I had to find the difference between the "end_marker" and the "beat_time" of the end of the sample itself (found above). The equation is: beat_time_difference = end_marker - beat_time;
Then I could call the "move_warp_marker" function with the two arguments: beat_time and beat_time_difference.
That should stretch the sample to the full length of the clip. I would recommend changing the warp_mode of the clip to "complex_pro" because it is better at removing warping artifacts. You can do this by setting the "warp_mode" property of the clip to the number 6.
Hope that helps. Also, here is a quick video on dictionaries in max: