Punch in/out


Is is possible to start playback at some timeline position and drop in and out of recording later in the timeline? Essentially punch in / punch out, but in my case I know beforehand where recording should start and stop.

Of course I could just start recording earlier but I don’t want the new material to replace the contents of the track during the count-in/pre-roll.

I’m working on MIDI recording for the moment but I guess that the mechanics is essentially the same for audio recording as well?



I was going to ask a similar question (except I don’t know the punch out time before hand) :slight_smile:

@erikronstrom Similar, I was trying to get on the fly PunchInOut recording working. I did bump into something that might be what you want, though.

My Punch In/Out Problem: - Continuous playback with on demand start/stop recording
Using the supplied Recording Demo I found that if it’s already playing, hitting record would trigger the creation of hundreds of tiny (784bytes) wav files whilst grinding everything to a halt.

What I tried:
1. Rapidly stop playback and start recording

edit.getTransport().stop (false, false); //Sneakily stopping before recording.
edit.getTransport().record (false); //Starts playback and recording again.

This kind of works, but clearly isn’t right and also results in a tiny blip in audio playback.

2. Playing, then hitting record with PunchInOut
I also experimented with setting edit.recordingPunchInOut.setValue(true, nullptr), where I set the in and out points slightly later than the start time. By pressing play, then record, it would play fine until it got to the record in-point, then it would all grind to a halt again. So clearly thats not the answer for me either.

3. Moving punchInOut during record.
Finally I tried replacing Play with Record+PunchInOut and EditTimeRange(1000.f,1001.f). Then whilst it was playing having a test button suddenly move the EditTimeRange to 2.f and 4.f. I was hoping TE would notice this change and start recording in the newly moved range - kind of like a fake punchInOut recording. That didn’t happen so sadly this idea didn’t work at all.

I don’t know what else to try. I’m pretty sure I must be missing something obvious but I can’t see what.

@erikronstrom I might be misunderstanding but it seems like what you need is:

edit.recordingPunchInOut.setValue(true, nullptr);
edit.getTransport().setLoopRange(te::EditTimeRange(double start, double end))
edit.getTransport().record (false);

Note the lack of play command. It will still play though, and when it gets to the in point it should start recording.



Thanks, this is what I was looking for! However, while it seems to work well with audio I can’t get it to work with MIDI recordings:

JUCE Assertion failure in juce_MidiMessageSequence.cpp:114
(lldb) bt
* thread #1, name = 'JUCE Message Thread', queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x00000001001eff22 Gaudiamus`juce::MidiMessageSequence::getIndexOfMatchingKeyUp(this=0x0000000103f18f40, index=6) const at juce_MidiMessageSequence.cpp:114
    frame #1: 0x00000001001f08b6 Gaudiamus`juce::MidiMessageSequence::deleteEvent(this=0x0000000103f18f40, index=6, deleteMatchingNoteUp=true) at juce_MidiMessageSequence.cpp:187
    frame #2: 0x00000001010758ea Gaudiamus`tracktion_engine::MidiInputDeviceInstanceBase::applyLastRecordingToEdit(this=0x0000000103f18e40, recordedRange=(start = 4, end = 18.013242630385488), isLooping=false, loopRange=(start = 8, end = 14), discardRecordings=false, selectionManager=0x0000000000000000) at tracktion_MidiInputDevice.cpp:940
    frame #3: 0x0000000100f85e74 Gaudiamus`tracktion_engine::EditPlaybackContext::stopRecording(this=0x0000000103f41370, in=0x0000000103f18e40, recordedRange=(start = 4, end = 18.013242630385488), discardRecordings=false) at tracktion_EditPlaybackContext.cpp:636
    frame #4: 0x0000000100f86175 Gaudiamus`tracktion_engine::EditPlaybackContext::recordingFinished(this=0x0000000103f41370, recordedRange=(start = 4, end = 18.013242630385488), discardRecordings=false) at tracktion_EditPlaybackContext.cpp:649
    frame #5: 0x0000000100f9a15f Gaudiamus`tracktion_engine::TransportControl::performStop(this=0x0000000103d0d9c0) at tracktion_TransportControl.cpp:1472
    frame #6: 0x00000001010beaa8 Gaudiamus`tracktion_engine::TransportControl::TransportState::valueTreePropertyChanged(this=0x0000000117973c00, v=0x00007ffeefbfe220, i=0x0000000117973c28) at tracktion_TransportControl.cpp:235
    frame #7: 0x00000001005f538b Gaudiamus`juce::ValueTree::SharedObject::sendPropertyChangeMessage(this=0x00007ffeefbfe180, l=0x0000000117973c00)::'lambda'(juce::ValueTree::Listener&)::operator()(juce::ValueTree::Listener&) const at juce_ValueTree.cpp:100
    frame #8: 0x00000001005f534b Gaudiamus`void juce::ListenerList<juce::ValueTree::Listener, juce::Array<juce::ValueTree::Listener*, juce::DummyCriticalSection, 0> >::callExcluding<juce::ValueTree::SharedObject::sendPropertyChangeMessage(this=0x0000000117974248, listenerToExclude=0x0000000000000000, callback=0x00007ffeefbfe180)::'lambda'(juce::ValueTree::Listener&)&>(juce::ValueTree::Listener*, juce::ValueTree::SharedObject::sendPropertyChangeMessage(juce::Identifier const&, juce::ValueTree::Listener*)::'lambda'(juce::ValueTree::Listener&)&&&) at juce_ListenerList.h:140
    frame #9: 0x00000001005f527b Gaudiamus`void juce::ValueTree::SharedObject::callListeners<juce::ValueTree::SharedObject::sendPropertyChangeMessage(juce::Identifier const&, juce::ValueTree::Listener*)::'lambda'(juce::ValueTree::Listener&)>(this=0x00006000000a9fc0, listenerToExclude=0x0000000000000000, fn=(anonymous class) @ 0x00007ffeefbfe180)::'lambda'(juce::ValueTree::Listener&)) const at juce_ValueTree.cpp:85
    frame #10: 0x00000001005f5170 Gaudiamus`void juce::ValueTree::SharedObject::callListenersForAllParents<juce::ValueTree::SharedObject::sendPropertyChangeMessage(juce::Identifier const&, juce::ValueTree::Listener*)::'lambda'(juce::ValueTree::Listener&)>(this=0x00006000000a9fc0, listenerToExclude=0x0000000000000000, fn=(anonymous class) @ 0x00007ffeefbfe1d0)::'lambda'(juce::ValueTree::Listener&)) const at juce_ValueTree.cpp:94
    frame #11: 0x00000001005e5fd4 Gaudiamus`juce::ValueTree::SharedObject::sendPropertyChangeMessage(this=0x00006000000a9fc0, property=0x0000000117973c28, listenerToExclude=0x0000000000000000) at juce_ValueTree.cpp:100
    frame #12: 0x00000001005e40c8 Gaudiamus`juce::ValueTree::SharedObject::setProperty(this=0x00006000000a9fc0, name=0x0000000117973c28, newValue=0x00007ffeefbfe428, undoManager=0x0000000000000000, listenerToExclude=0x0000000000000000) at juce_ValueTree.cpp:138
    frame #13: 0x00000001005e4052 Gaudiamus`juce::ValueTree::setPropertyExcludingListener(this=0x0000000117973c10, listenerToExclude=0x0000000000000000, name=0x0000000117973c28, newValue=0x00007ffeefbfe428, undoManager=0x0000000000000000) at juce_ValueTree.cpp:769
    frame #14: 0x00000001005e3f3d Gaudiamus`juce::ValueTree::setProperty(this=0x0000000117973c10, name=0x0000000117973c28, newValue=0x00007ffeefbfe428, undoManager=0x0000000000000000) at juce_ValueTree.cpp:759
    frame #15: 0x0000000100137de9 Gaudiamus`juce::CachedValue<bool>::setValue(this=0x0000000117973c08, newValue=0x00007ffeefbfe4cb, undoManagerToUse=0x0000000000000000) at juce_CachedValue.h:251
    frame #16: 0x0000000100122eb3 Gaudiamus`juce::CachedValue<bool>::operator=(this=0x0000000117973c08, newValue=0x00007ffeefbfe4cb) at juce_CachedValue.h:241
    frame #17: 0x0000000100f93085 Gaudiamus`tracktion_engine::TransportControl::TransportState::stop(this=0x0000000117973c00, discardRecordings_=false, clearDevices_=false, canSendMMCStop_=true, invertReturnToStartPosSelection_=false) at tracktion_TransportControl.cpp:157
    frame #18: 0x0000000100f914dc Gaudiamus`tracktion_engine::TransportControl::stop(this=0x0000000103d0d9c0, discardRecordings=false, clearDevices=false, canSendMMCStop=true, invertReturnToStartPosSelection=false) at tracktion_TransportControl.cpp:817
  * frame #19: 0x000000010013ddfb Gaudiamus`MainComponent::MainComponent(this=0x000000010484bf68)::$_3::operator()() const at MainComponent.cpp:62
    frame #20: 0x000000010013dcad Gaudiamus`void std::__1::__invoke_void_return_wrapper<void>::__call<MainComponent::MainComponent()::$_3&>(MainComponent::MainComponent()::$_3&&&) [inlined] decltype(__f=0x000000010484bf68)::$_3&>(fp)(std::__1::forward<>(fp0))) std::__1::__invoke<MainComponent::MainComponent()::$_3&>(MainComponent::MainComponent()::$_3&&&) at type_traits:4428
    frame #21: 0x000000010013dc9c Gaudiamus`void std::__1::__invoke_void_return_wrapper<void>::__call<MainComponent::MainComponent(__args=0x000000010484bf68)::$_3&>(MainComponent::MainComponent()::$_3&&&) at __functional_base:349
    frame #22: 0x000000010013dbc9 Gaudiamus`std::__1::__function::__func<MainComponent::MainComponent()::$_3, std::__1::allocator<MainComponent::MainComponent()::$_3>, void ()>::operator(this=0x000000010484bf60)() at functional:1562
    frame #23: 0x0000000100143346 Gaudiamus`std::__1::function<void ()>::operator(this=0x000000010484bf60)() const at functional:1913
    frame #24: 0x0000000100803276 Gaudiamus`juce::Button::sendClickMessage(this=0x000000010484be90, modifiers=0x00007ffeefbfe888) at juce_Button.cpp:407
    frame #25: 0x00000001008039b4 Gaudiamus`juce::Button::internalClickCallback(this=0x000000010484be90, modifiers=0x00007ffeefbfe888) at juce_Button.cpp:350
    frame #26: 0x00000001008041d0 Gaudiamus`juce::Button::mouseUp(this=0x000000010484be90, e=0x00007ffeefbfe878) at juce_Button.cpp:470
    frame #27: 0x00000001007ef142 Gaudiamus`juce::Component::internalMouseUp(this=0x000000010484be90, source=MouseInputSource @ 0x00007ffeefbfe998, relativePos=(x = 99.4335937, y = 12.9570313), time=(millisSinceEpoch = 1631184091134), oldModifiers=(flags = 16), pressure=0, orientation=0, rotation=0, tiltX=0, tiltY=0) at juce_Component.cpp:2458
    frame #28: 0x00000001008faa2e Gaudiamus`juce::MouseInputSourceInternal::sendMouseUp(this=0x00006040001e0400, comp=0x000000010484be90, screenPos=(x = 941.433593, y = 59.9570313), time=(millisSinceEpoch = 1631184091134), oldMods=(flags = 16)) at juce_MouseInputSource.cpp:154
    frame #29: 0x00000001008f9c2b Gaudiamus`juce::MouseInputSourceInternal::setButtons(this=0x00006040001e0400, screenPos=(x = 941.433593, y = 59.9570313), time=(millisSinceEpoch = 1631184091134), newButtonState=(flags = 0)) at juce_MouseInputSource.cpp:196
    frame #30: 0x00000001007f4076 Gaudiamus`juce::MouseInputSourceInternal::handleEvent(this=0x00006040001e0400, newPeer=0x0000604000188130, positionWithinPeer=(x = 701.433593, y = 14.9570313), time=(millisSinceEpoch = 1631184091134), newMods=(flags = 0), newPressure=0, newOrientation=0, pen=(rotation = 0, tiltX = 0, tiltY = 0)) at juce_MouseInputSource.cpp:333
    frame #31: 0x00000001007f3dc2 Gaudiamus`juce::MouseInputSource::handleEvent(this=0x00007ffeefbfec68, peer=0x0000604000188130, pos=(x = 701.433593, y = 14.9570313), time=1631184091134, mods=(flags = 0), pressure=0, orientation=0, penDetails=0x00007ffeefbfecb8) at juce_MouseInputSource.cpp:633
    frame #32: 0x00000001008d7e5c Gaudiamus`juce::ComponentPeer::handleMouseEvent(this=0x0000604000188130, type=mouse, pos=(x = 701.433593, y = 14.9570313), newMods=(flags = 0), newPressure=0, newOrientation=0, time=1631184091134, pen=(rotation = 0, tiltX = 0, tiltY = 0), touchIndex=0) at juce_ComponentPeer.cpp:87
    frame #33: 0x00000001009891e2 Gaudiamus`juce::NSViewComponentPeer::sendMouseEvent(this=0x0000604000188130, ev=0x0000600000124c40) at juce_mac_NSViewComponentPeer.mm:747
    frame #34: 0x000000010098c5fe Gaudiamus`juce::NSViewComponentPeer::redirectMouseUp(this=0x0000604000188130, ev=0x0000600000124c40) at juce_mac_NSViewComponentPeer.mm:611
    frame #35: 0x0000000100987b89 Gaudiamus`juce::JuceNSViewClass::asyncMouseUp(self=0x0000604000122d00, (null)="mouseUp:", ev=0x0000600000124c40) at juce_mac_NSViewComponentPeer.mm:1796
    frame #36: 0x0000000100985c37 Gaudiamus`juce::JuceNSViewClass::mouseUp(self=0x0000604000122d00, s="mouseUp:", ev=0x0000600000124c40) at juce_mac_NSViewComponentPeer.mm:1782
    frame #37: 0x00007fff34392836 AppKit`-[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 1961
    frame #38: 0x00007fff34391c70 AppKit`-[NSWindow(NSEventRouting) sendEvent:] + 497
    frame #39: 0x00007fff341f3236 AppKit`-[NSApplication(NSEvent) sendEvent:] + 2462
    frame #40: 0x00007fff33a5387d AppKit`-[NSApplication run] + 812
    frame #41: 0x00000001006a2052 Gaudiamus`juce::MessageManager::runDispatchLoop(this=0x0000604000247410) at juce_mac_MessageManager.mm:362
    frame #42: 0x00000001006a1e44 Gaudiamus`juce::JUCEApplicationBase::main() at juce_ApplicationBase.cpp:263
    frame #43: 0x00000001006a1a0c Gaudiamus`juce::JUCEApplicationBase::main(argc=3, argv=0x00007ffeefbff770) at juce_ApplicationBase.cpp:241
    frame #44: 0x0000000100180db3 Gaudiamus`main(argc=3, argv=0x00007ffeefbff770) at Main.cpp:160
    frame #45: 0x00007fff5e48e015 libdyld.dylib`start + 1

Actually after more testing it does work with MIDI, apart from this intermittent crash!

Thanks, this sounds like an issue we need to look in to.

This is the correct way to punch record if you know the times before hand.

If you don’t know the times before had you have to start recording like so:
transportControl.record (false, true)
and then when you arm an input it will punch in. Stopping recording or disarming the input will stop recording.

@erikronstrom this isn’t a crash but an assertion. It’s telling you that there is a note-off event with no note-on. Could this be because you’ve started recording whilst holding a note?
Apart from the assertion, I think the recording will complete but that note will be discarded.

1 Like

@dave96 thanks so much! Gah I should have thought of simply arming/disarming during the record. :man_facepalming:

I’ll try it as soon as I wake up properly though I suspect this will likely work.

I appreciate the answer and the clarifications. Awesome :slight_smile:


this isn’t a crash but an assertion. It’s telling you that there is a note-off event with no note-on. Could this be because you’ve started recording whilst holding a note?

Possibly – I will try and verify that.

Apart from the assertion, I think the recording will complete but that note will be discarded.

Does that mean that I can/should just comment out the assertion?

Well you can do but it just wont be there in release builds so you don’t have to.
That’s of course if that is the reason the assertion is being triggered. If it’s something else, commenting out the assertion will hide that cause.

But this could well happen during normal usage, couldn’t it? If the user starts recording with a MIDI key pressed. But of course I could try and avoid that during development…

Yes, which is why you can just continue past it.
The problem is the assertion is there to catch other possibly unexpected behaviour but it’s catching this perhaps expected behaviour.

There’s also an argument that perhaps if a key is held when recording starts that it should create a note-on event at that time. I’d have to do some research as to whether that’s the best/canonical approach though which I don’t really have the capacity to do at the moment.

Hey there, hope you’re well!

Could you possibly clarify something please?:

If you don’t know the times before had you have to start recording like so:
transportControl.record (false, true)
and then when you arm an input it will punch in. Stopping recording or disarming the input will stop recording.

By “arm” do you mean:
myWaveInputDeviceInstance->setRecordingEnabled(*track, true);

myWaveInputDeviceInstance->setTargetTrack(*track, trackIndex, true);


My challenge is I’d like to monitor the input through a desired track in question, but punch in/out recording on the fly during playback. If any of the above are not set I can’t hear (monitor) anything.

Based on what you’ve said I could launch transportControl.record (false, true) which would let me start “recording” without anything being recorded to disk so that’s good. Of course when I set the above to true then yes, I can hear and record at the same time which is also great. But how do I hear (monitor) but not record?

I hope this question makes sense! Any advice would be very much appreciated :slight_smile:
Cheers, J

Looking at Reaper to sanity check my expectations: I can have a track armed and monitorable, hit transport-play and hear the session as expected and the monitored track, but then I can hit transport-record to punch in a new audio recording, and hit it again to punch out. I can repeat this as often as I want.

This functionality is what I’m trying to achieve with tracktion engine.

There’s several steps to this but it should become obvious why they’re all needed.

  1. First the InputDevice needs to be enabled. This is so the Edit can see the InputDeviceInstances which it uses. I think by default all devices are enabled so you can likely skip this step (but useful to put here for future visitors)
  2. Secondly you need to assign an input to a track using InputDeviceInstance::setTargetTrack as you’ve seen. This sets what track the input should go to.
  3. If you want to monitor input you’ll need to ensure “InputDevice::isEndToEndEnabled” is returning true. You can use WaveInputDevice::setEndToEnd (true) or InputDevice::flipEndToEnd. For WaveInputDevice, the input also needs to be record enabled to monitor unless your EngineBehaviour::monitorAudioInputsWithoutRecordEnable() returns true.
  4. Finally you need to call InputDeviceInstance::setRecordingEnabled to ensure the content is recorded to Clips when TransportControl::record is called.

It seems like a lot but can be boiled down to “enable, assign, monitor, arm, record”. Hope that helps.

Hey there @dave96 this is great thanks for the run down.

However taking all the steps, when I call TransportControl::record I start recording content to a clip.

“But what if I don’t want to record content?”, I ask.

I call InputDeviceInstance::setRecordingEnabled(false). Now I can’t hear what I’m monitoring.

“But why are you calling TransportControl::record instead of ::play?”, anyone else may ask.

Because if I try it that way, and then randomly decide to hit record whilst playback is in progress I get thousands of those tiny wav files and the program grinds to a slow crawl/halt. Per this bug report/post I made:

That all said, I wasn’t aware of this EngineBehaviour::monitorAudioInputsWithoutRecordEnable() and it seems like a big clue towards what I want to achieve.

If you have any more advice on how to monitor during playback then start recording to a clip on the fly, stop recording to said clip whilst enjoying uninterupted playback throughout I would be immensely grateful.

Again, thank you very much for your input already especially during the holiday season!

Well then! Thanks very much for that clue (and all the other info) @dave96. Indeed overriding EngineBehaviour::monitorAudioInputsWithoutRecordEnable() is exactly what I needed. I’m now getting the behaviour I wanted.

For the benefit of those who may follow:

  • If you want to be able modify EngineBehaviour via a subclass, have seen info on the forum and don’t know how to do it, here’s a way.

You may have been using engine like I was, like this
te::Engine engine { ProjectInfo::projectName };

Now you want to modify some EngineBehaviour?

  1. Create a subclass of it like this (I’m using my case as an example of an override, but look at the EngineBehaviour code to see all the fun stuff available):
namespace te = tracktion_engine;

class MyEngineBehaviour : public te::EngineBehaviour
    bool monitorAudioInputsWithoutRecordEnable() override { return true; }
  1. Make use of one of the other constructors for the::engine instead, like this
te::Engine engine { ProjectInfo::projectName, std::make_unique<te::UIBehaviour>(), std::unique_ptr<te::EngineBehaviour>(new MyEngineBehaviour()) };

This should get you up and running.

  • Side point. You may have wondered why I didn’t do this:
te::Engine engine { ProjectInfo::projectName, std::make_unique<te::UIBehaviour>(), std::make_unique<MyEngineBehaviour>() };

This actually worked for me first, but I had a perky IDE wiggly red warning in AppCode that I couldn’t get rid of. Thanks to @dave-elton for finding an IDE-friendly approach.

Glad you got it working. I might make EngineBehaviour::monitorAudioInputsWithoutRecordEnable return true by default in the future as it’s most likely the behaviour people want.

1 Like