Auto Advancing Song Parts

Hello all,

I am trying to find a way to auto advance song parts after a certain number of beats have passed. The beat “threshold” will be stored in a scaled widget value. I’ve been struggling for a day or two to get it right.

var knob1 : Widget

On WidgetValueChanged (newvalue: double) from knob1
var beat_thresh : integer = ScaleRange(newvalue, 0, 50)
SetWidgetLabel(knob1, beat_thresh)
end

// Called when beat changes
// Only beat number is valid for now
On BeatChanged(bar : integer, beat : integer)
    var beat_thresh : integer = ScaleRange(GetWidgetValue(knob1), 0, 50)
    var i : integer
    i = i + 1
    Print(i)
    if i > beat_thresh then SongNextPart()
end
End

This code will restart the beat “counter” every time I switch to a new song part. However, it will add an extra beat in before it begins the count on the next song part. For example, in the first song part, if I am asking it to switch after 8 beats, it will count the ninth beat when the part switches before starting back at beat 1 again. How can I get this to immediately start back at 1 when the song part changes?

Thanks for any help.

Auto Advancer.gig (25.1 KB)
gig

1 Like

Try this. I’m actually surprised your code was working as well as it did. The “I” variable was being declared at every beat (and not initialised to anything) and so I’m surprised it kept incrementing.

So I renamed “I” to “counter” and declared it as a global variable, resetting it when required. I also reset it each time the playhead started. It also made a difference where the “Print” function was placed.

Var knob1 : Widget
    counter : integer = 0

On WidgetValueChanged (newvalue: double) from knob1
    var beat_thresh : integer = ScaleRange(newvalue, 0, 50)
    SetWidgetLabel(knob1, beat_thresh)
end

// Called when beat changes
On BeatChanged(bar : integer, beat : integer)
    var beat_thresh : integer = ScaleRange(GetWidgetValue(knob1), 0, 50)
    counter = counter + 1
    if counter > beat_thresh then 
        SongNextPart() 
        counter = 1
    end
    Print(counter)
End

// Called on attempt to change playhead status - (Rackspace and GigScript only)
On SystemEvent(newValue : double) Matching PlayheadStateChanged
    if newValue == 1.0 then counter = 0 end
End

Sheer luck – depends totally what was on the stack at that point. If some other callback occurred in between the value could have easily been something else. Otherwise, whatever value was there last time would still be there, which is why it worked.

Ok so this is working well when the counter hits the target threshold. However, I need it to reset to one when I manually select a song part.

EDIT:
Adding another counter reset into the widget callback for the knob seems to do the trick.

On WidgetValueChanged (newvalue: double) from knob1
    var beat_thresh : integer = ScaleRange(newvalue, 0, 50)
    SetWidgetLabel(knob1, beat_thresh)
    counter = 0
end

Any foreseeable problems with this method?

1 Like

Okay that doesn’t work either. Throws it off by one beat again after the second song part. What I’m trying to achieve is a count that lasts exactly the number of beats indicated by the dial. It should reset at 1 each time the song part auto advances or is manually advanced via midi or mouse click.

I still can’t quite achieve the behavior I’d like, and maybe I need to accept that it’s just not possible right now.

@dhj, I love the custom script you blogged about for RJM Mastermind integration with GP. I was hoping to be able to use that script and the script we’ve been working on here to control scene changes in Ableton. Ideally, each song part in GP would have an auto advancing threshold which, if toggled on, would auto select the next song part and simultaneously advance the next Ableton Scene. If toggled off, the auto advance threshold would be bypassed and the Ableton scene would just continue to loop until the threshold was activated again, or a manual song part advance was triggered. I was thinking of it sort of like Ableton’s follow actions, but in reverse, with GP controlling the automation.

It would be awesome if one day we could see some sort of default function in GP Script that would allow us to treat song part changes like Ableton treats scene changes, i.e., tied to a global quantization setting. I guess it would require being able to poll GP’s bar and beat info at any point in time with a function, rather than just the OnBeatChanged callback. If, for example, I could determine that GP’s playhead were at, let’s say 12.2, and then script a SongNextPart() function to execute on a delayed schedule of my choosing; for example the top of the next bar (13.1), or the next beat, or the next 2 beats.

Great stuff. Having a lot of fun trying to solve these riddles.

What if you set the counter to 1 instead of 0 in the widget changed callback?

Did you see my take on quantised song part changes?

Okay this looks really awesome. Can’t wait to play with it tonight and see if I can customize it for my use case. Thanks for the good work!

So your quantize part changes solution is really cool. The only drawback, for my purposes, is not being able to dynamically change the automated timing switch to something other than the top of the next bar. It would be great to be able to store that beat or bar length in a widget and call it on a part by part basis. For example, song part 1 auto advances after 2 bars, but song part 2 auto advances after 4 bars. I guess it would require being able to reset the playhead on every song change, which seems to freak it out if you try to toggle the playhead status off and then on again in consecutive script steps.

Furthermore, I’m discovering the hard way that triggering an Ableton scene with GP as the master leads to some timing issues. If the GP song part changes at the top of the measure, Ableton doesn’t always get the scene change OSC message in time to stay synced. By changing the OnBeatChanged value to the 4th beat rather than the first, one can compensate for this. However, it means that GP will switch song parts one beat before Ableton launches the accompanying quantized scene. The goal would have been for them to launch at the same instant, but that may not be feasible.

Here’s another take on the auto advance song parts. This is a Gig script, and provides a method of auto advancing without widgets. It does this by:
(1) Define default bar lengths for song part names e.g. you can define the default length of “Intro” as 4 bars.
(2) Manually define the song part length by appending the bars in square brackets at the end of the song part name e.g. Intro [3].

image

// Auto Advance Song Parts
// Author: @rank13
// Features:
//    (1) Define default bar lengths for song part names e.g. 4 bars for 'Intro'
//    (2) Define custom bar lengths by appending the length in square brackets to the end of the song part name e.g. Intro [3]

Var 
    DefaultSongPartNames : String Array = ["Intro", "Verse", "Chorus"]
    DefaultBars : Integer Array = [4, 8, 4] // These lengths correspond to the song part names above
    BarCounter : Integer = 0
    CurrentBarLength : Integer = 1
    UnspecifiedBarLength : Integer = 9999 // If there is no length specified, it will use this. 9999 used as the equivalent of 'play forever'
    DisplayScriptLogger : Boolean = true // Change to false to hide the script logger entries

Function PrintToLogger(text : String)
    If DisplayScriptLogger Then
        Print(text)
    End
End

// Calculate the bar length for the current song part
Function GetBarLengthFromSongPartName()
    Var songPartName : String = GetCurrentSongPartName()
        barFound : Boolean = false
        bars : String = StringAfterFirstOccurrence (songPartName, "[", false, true)
        i : Integer
    PrintToLogger("Song Part: " + GetCurrentSongPartName())
    If bars != "" Then
        bars = StringUpToFirstOccurrence(bars, "]", false, true)
        CurrentBarLength = StringToInt(bars)
    Else
        For i = 0; i < Size(DefaultSongPartNames); i = i + 1 Do
            If DefaultSongPartNames[i] == songPartName Then
                CurrentBarLength = DefaultBars[i]
                barFound = true
            End
        End
        If !barFound Then CurrentBarLength = UnspecifiedBarLength End
    End
    PrintToLogger("Bar Length: "+CurrentBarLength)
End

// Called when beat changes
On BeatChanged(bar : integer, beat : integer)
    If beat == 1 Then
        PrintToLogger("Bar: " + bar)
        BarCounter = BarCounter + 1
        If BarCounter > CurrentBarLength Then
            SongNextPart()
        End
    End
End

// Called when you switch to another songpart
On Songpart(oldSongpartIndex : integer, newSongpartIndex : integer)
    BarCounter = 1
    GetBarLengthFromSongPartName()
End

// Called on attempt to change playhead status
On SystemEvent(newValue : double) Matching PlayheadStateChanged
    If newValue == 1.0 Then
        BarCounter = 0
        GetBarLengthFromSongPartName()
    End
End

How are you triggering this currently from GP? Are you using Max?

I use LiveGrabber by Showsync. Showsync | Free Tools for Ableton Live

It allows me to use the GetCurrentSongPartName() to trigger an Ableton scene with a matching name. However, as described, I can’t get them to trigger absolutely simultaneously. It makes sense, being that Ableton’s global quantize requires you to trigger the change inside of whatever global quantization setting you have set. Being even a fraction late will round up to the next quantization value.

I was hoping to use GP in the way that Ableton uses follow actions to trigger both changes at the same time and have the “follow action” length controlled locally by widgets in the song parts. Your method above parsing the song part names is very clever, but it would defeat my goal of having “on the fly” control over the auto advance threshold. I wish to be able to adjust the number of times the Ableton scene loops dynamically; perhaps I want song part 2 to loop for 4 bars normally, but have the freedom to extend it to 8 or 12 bars at any time.

Without the follow actions being exposed in the Live API, the best I can do is a manual scene trigger via OSC and the LiveGrabber method mentioned above. You can globally toggle follow actions on and off in Ableton with standard MIDI control, but I can see no way to adjust follow actions on the fly.

Manual scene triggers via OSC and LiveGrabber require the scene change request to be sent a beat before the global quantization setting, so my song part ends up changing a beat before the backing track changes. I tried turning turning the global quantize off in Ableton and using your quantized song part method in GP, but the timing would eventually drift. Thusly, I haven’t been able to successfully and predictably have the Ableton scene launch exactly in time with the song part change in GP.

Knowing that you’re using the song part name in your OSC message, I don’t see too much of a challenge for the script to send out the OSC message on the beat prior to when it triggers the next song part change.

You would grab the next song part name using the GetSongPartName function.

GetSongPartName(GetCurrentSongPart() + 1)

Sometimes the simple solution is the hardest for me to see. I was using the OSC message in an OnSongPartChanged callback which was preventing me from controlling when the OSC message is sent; it was always tied to the beat that the song part changes. I’m going to rework this when I get home but I feel you may have hit it on the head! :grin:

What I’m considering is to extend the Quantize Song Part scripts to allow the concept of auto advance.

I think the ‘Global parameter assignment’ feature could allow this to work e.g. there is a bar length knob in the global panel that will communicate with my quantize script. If you set up widgets in the local rackspace and map them to this global parameter, it allows you to change the bar value from your local rackspaces/variations (and therefore song parts).

You can also use the widget scaling feature to set defined bar ranges or toggle values e.g. to set a button widget to toggle between 4 bars and 8 bars.

I haven’t updated the script yet, but this shows the front-end interaction. Watch the ‘Auto Advance’ knob in the global panel as I change the song part or widget.

Quantize Song Part Auto Advance

YES YES YES. And then it could be further customized to send the OSC command to Ableton 1 beat earlier than the SongNextPart().

That would be the aim, yes.

This would be fantastic. I’ve been studying your original script line by line.

For those interested, a working solution here:

2 Likes