Questions about the new clip launcher

I’m trying to figure out the new feature clip launcher feature at the develop-branch. Maybe I’m too early with my questions…

I checked classes like ClipSlotList, ClipSlot, SceneList, Scene, LaunchHandle
and came up with this structure:

Here is my interpretation of it:
Scene is managed by SceneList. Scene creates ClipSlots to all available AudioTracks. A Clip lives in a ClipSlot and is managed by the ClipSlotList. An AudioTrack owns the ClipSlotList. If I want to launch a clip, I should use the LaunchHandle thats belongs to the Clip.

For a simple test I wrote this code:

edit->getSceneList().insertScene(0);
audioTrack1->getClipSlotList().getClipSlots()[0]->setClip(audioClip1);
audioTrack1->getClipSlotList().getClipSlots()[0]->getClip()->getLaunchHandle()->play( tracktion::MonotonicBeat(  { 0_bp } ) );

Is this the right direction? Did I miss something?

No, that’s correct.
The MonotonicBeat is the point in the future that you want to launch it though. If you want to launch it immediately, just pass an empty optional i.e. play ({}).
Although a MonotonicBeat (0_bp) will probably do the same thing as 0_bp is likely to be in the past and hence launched immediately.

It is a bit early to rely on the exact APIs and behaviour though. It won’t be fixed until we release v3.0 of the Engine.

Also note that the feature/launcher branch is a technical preview so can’t be commercially licensed yet.

Thanks for the hint @dave96!
In my code example the ->getClip() doesn’t work correct. I’m getting an error
JUCE Message Thread (1): EXC_BAD_ACCESS (code=1, address=0x0)
Should I call something between ->setClip() and ->getClip ?

Do you have a rough roadmap or schedule for v3.0?

For my project, I will publish it open source.

Can you step through the SceneList::insertScene and see if it’s actually adding the slots to the ClipSlotLists?

Can you call auto clipSlots = audioTrack1->getClipSlotList().getClipSlots() and see what it’s contents is?

@dave96 Yes, I think this part is ok…

Here is the result:

Below is the complete code:

Can you step in to the setClip call and see if it’s adding the clip state?

Do you mean this spot here?

@dave96 I checked the setClip-section again. Seems like dynamic_cast<ClipTrack*> (&newParent) fails in my case…

bool Clip::moveTo (ClipOwner& newParent)
{
    if (auto to = dynamic_cast<ClipTrack*> (&newParent))
    {
        if (canBeAddedTo (*to))
        {
            if (parent != to)
            {
                if (! to->isFrozen (Track::anyFreeze))
                {
                    Clip::Ptr refHolder (this);
                    to->addClip (this);
                }
            }
            return true;
        }
    }
    return false;
}

I’m getting true if I check it with tracktion::Clip::isClipState(stepClip1->state);

Does this work for you:

Yes, it works better. But I have another issue here:

void ClipSlot::setClip (Clip* c)
{
    if (c == nullptr)
        return;

    if (auto existingClip = getClip())
        existingClip->removeFromParent();

    jassert (findClipForID (c->itemID) == nullptr);  <== No matching function for call to 'findClipForID'

    auto um = c->getUndoManager();
    c->state.getParent().removeChild (c->state, um);
    state.addChild (c->state, -1, um);
}

It works when I comment this out: jassert (findClipForID (c->itemID) == nullptr);

Now I’m playing around with the LaunchHandle. If I start a clip (while the transport is playing) I hear a very short part (like a short click sound) of it. My approach is to launch and stop a clip with a quantisation of 1 bar. The clip should loop until I stop it. This is my code:

switch(launchHandle->getPlayingStatus())
{
    case tracktion::LaunchHandle::PlayState::playing:
    {
        launchHandle->stop( tracktion::MonotonicBeat( {} ) );
        break;
    }
    case tracktion::LaunchHandle::PlayState::stopped:
    {
        launchHandle->play( tracktion::MonotonicBeat( {} ) );
        break;
    }
}

Should I use other function like the FollowActions?

jassert (findClipForID (c->itemID) == nullptr); <== No matching function for call to 'findClipForID
I thought this was fixed? Can you grab the latest and try again please?


tracktion::MonotonicBeat( {} ) means, “as soon as possible”, i.e. not in the future so it sounds like you’re starting and stopping it straight away.

You probably want to look at Edit::getLaunchQuantisation()

@dave96 Congratulation to the Tracktion 13 launch! The new clip launcher works well!


I got the latest version but didn’t help:

As I mentioned, it works for me if I comment the line out.


I think I figure it out how the new tracktion::MonotonicBeat works. It’s like a parallel beat based timeline - right? And the clip launcher syncs the clip start and stop to it.

With this code I can synced launch / stop the clip with 1 bar quantisation (4_bd) :

auto& ts = edit->tempoSequence;
auto& tc = edit->getTransport();            
tracktion::MonotonicBeat startSyncBeat;
tracktion::MonotonicBeat endSyncBeat;

const auto currentBarsBeats = ts.toBarsAndBeats (tc.getPosition());
const auto barStartBeat = ts.toBeats (tracktion::tempo::BarsAndBeats { .bars = currentBarsBeats.bars });
startSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd };
endSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd }; 
.
.
.
switch(launchHandle->getPlayingStatus())
{
    case tracktion::LaunchHandle::PlayState::playing:
    {
        DBG("barStartBeat - Stop:" + juce::String( endSyncBeat.v.inBeats() ) );
        launchHandle->stop( endSyncBeat );
        break;
    }
    case tracktion::LaunchHandle::PlayState::stopped:
    {
        DBG("barStartBeat - Start:" + juce::String( startSyncBeat.v.inBeats() ) );
        launchHandle->play( startSyncBeat );
        break;
    }
}
                        

Currently the clips are played as one shots. How can I loop it? This function didn’t help:
stepClip->setLoopRangeBeats( {0_bp, 4_bp} );

I’m exploring the Waveform 13. I can’t find the right place to set clips in a loop mode.

@dave96 Can you give me a hint where the “Loop” checkbox activate the loop, please?

image

Thanks!

“Loop” is just if the loop length is non-zero.
If you set the loop length in beats, you also need to enable “auto-tempo”.

Sorry I’m being slow to respond but the clip launcher branch isn’t really ready for public consumption yet so I can’t really prioritise it this soon after a major app launch I’m afraid.

If I understand you correctly, does ‘non-zero’ mean a value greater than zero?.
My code ( stepClip->setLoopRangeBeats( {0_bp, 4_bp} ); ) should work - right?
I can’t set up “auto-tempo” for StepClip. I only found it for AudioClipBase.

No worries about the delayed response, I’m grateful for your insight and guidance. :slight_smile:

I think that should work… Maybe debug in LoopingMidiNode to see if it’s not getting the correct loop region?

Finally it works! :grinning:

This is my quick’n’dirty solution for a clip launch button:

clipLaunchButton2.onClick = [this]
{
    auto& ts = edit->tempoSequence;
    auto& tc = edit->getTransport();
    
    tracktion::MonotonicBeat startSyncBeat;
    tracktion::MonotonicBeat endSyncBeat;
    const auto currentBarsBeats = ts.toBarsAndBeats (tc.getPosition());
    const auto barStartBeat = ts.toBeats (tracktion::tempo::BarsAndBeats { .bars = currentBarsBeats.bars });
    startSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd };
    endSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd };

    stopAllClips(endSyncBeat);
    audioTrack1->getClipSlotList().getClipSlots()[1]->getClip()->setLoopRangeBeats( { 0_bp, 4_bp } );
    audioTrack1->getClipSlotList().getClipSlots()[1]->getClip()->getLaunchHandle()->play( startSyncBeat );
};

I schedule a stop clip for every clip in the ClipSlotList before launching another clip in the slot list. Or is there a build in function for this clip launch behaviour?

void stopAllClips(std::optional<tracktion::MonotonicBeat> endSyncBeat)
{
    for( auto clipSlot : audioTrack1->getClipSlotList().getClipSlots() )
    {
        if(clipSlot)
        {
            if(auto clip = clipSlot->getClip())
            {
                if(auto launchHandle = clip->getLaunchHandle())
                {
                    launchHandle->stop( endSyncBeat) ;
                }
            }
        }
    }
}
1 Like

That’s pretty good, just a few quick comments.

  1. Rather than doing your manual toBarsAndBeats calculation, you can use the LaunchQuantisation class which effectively does this for you.

  2. This is just the same calculation so no point in doing it twice.

    startSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd };
    endSyncBeat = tracktion::MonotonicBeat { barStartBeat + 4_bd };
  1. No that’s what we do currently. You could optimise by keeping track of all the playing or queued clips but it wouldn’t be much more efficient.

Or is there a build in function for this clip launch behaviour?

  1. What you’ve got will probably only work once when play starts from the beginning. The MonotonicBeat continues during playback so you need to find the “current” MonotonicBeat and use that as a reference. In Waveform what we do is get the current sync point from the current sync point from the EditPlaybackContext::getSyncPoint(), find the next BeatPosition to launch at using LaunchQuantisation and SyncPoint::beat. Find the duration number of beats between the current beat position and the launch position, then add that on to the SyncPoint::MonotonicBeat to find out where to launch/stop the clips.
1 Like