midish icon indicating copy to clipboard operation
midish copied to clipboard

Continuous playback when switching from recording mode to playback mode when looping

Open SilvioDylan opened this issue 1 year ago • 4 comments

Hello,

Is it possible to keep the playback seamless when switching between recording and playback modes when the looping mode is active? I want to use this as a live midi looper on a raspberry pi but the rewinds that occur when you switch between these modes don't feel nice for my use case. Keeping it on the recording mode is an option but ideally I would like to try out ideas in the playback mode before committing them to the loop.

I don't know if you are still maintaining this project but thanks nevertheless for this awesome tool!

SilvioDylan avatar Sep 16 '24 15:09 SilvioDylan

On Mon, Sep 16, 2024 at 08:16:11AM -0700, SilvioDylan wrote:

Hello,

Is it possible to keep the playback seamless when switching between recording and playback modes when the looping mode is active? I want to use this as a live midi looper on a raspberry pi but the rewinds that occur when you switch between these modes don't feel nice for my use case. Keeping it on the recording mode is an option but ideally I would like to try out ideas in the playback mode before committing them to the loop.

I don't know if you are still maintaining this project but thanks nevertheless for this awesome tool!

Hi,

I'm still maintaining midish, but I've less time these days, unfortunately, especially this week :-/

What you describe is on my long and ever growing TODO list. The elementary blocks to switch between playback and recording modes in real-time are already there, so the change doesn't seem that difficult.

Let me know if you want to look at this more in depth, I'll send you code references and more details about the internals.

ratchov avatar Sep 17 '24 07:09 ratchov

It is commendable that you are still maintaining this project after so many years, awesome! :D

I would be interested in looking into this but I can't promise much because I've never done any C before. So the code references and other details would be very helpful.

I already took a look at the source code and tried out some stuff. I have a few questions about some things but feel free to leave these unanswered if you don't have the time.

  1. From my understanding the main culprit behind the issue is the mux_stopreq() and mux_startreq() functions that are called when switching modes (in the song.c file). mux_startreq() is also preceded by a song_goto() call. I'm still not sure if the rewind occurs because of the song_goto() call or because of stopping and restarting the midi clock.
  2. Is it necessary to stop and start the clock when switching modes? I tried not restarting the clock when switching between record and playback modes, and it kind of got rid of the rewinds. However, when I do this the recordings are not added to the loop when switching from record mode to playback mode, and the terminal freezes without errors after a few switches between the two modes.
  3. I guess the last question is how I can make sure the record track is transferred to the playback track when switching from record mode to playback mode. Is just calling song_mergerec() enough?

SilvioDylan avatar Sep 17 '24 08:09 SilvioDylan

On Tue, Sep 17, 2024 at 01:49:07AM -0700, SilvioDylan wrote:

It is commendable that you are still maintaining this project after so many years, awesome! :D

I would be interested in looking into this but I can't promise much because I've never done any C++ before. So the code references and other details would be very helpful.

I already took a look at the source code and tried out some stuff. I have a few questions about some things but feel free to leave these unanswered if you don't have the time.

AFAICS, play and rec modes are very similar:

Basically, the record mode is like the play mode, but there's an extra temporary track (struct song->rec) where recorded events are stored and read from in loop mode. The logic is that every input event is tagged with TAG_OFF, TAG_PLAY or TAG_REC. Events with the TAG_REC are stored in the "rec" track, others are played or discarded (tag TAG_PLAY, TAG_OFF).

From this stand-point, we could make the play and rec mode the same, except that in the play mode we'd never use TAG_REC, so no events would be stored in the temporary track. Then, to switch to recording mode, just start tagging events with TAG_REC.

Once performance is stopped, the temporary "rec" track is merged into the current track. If no events were recorded, "rec" is empty and there are no changes to the current track.

The central routines are:

song_ticplay()

called at every clock tick to play the events. In rec mode, it
also plays the temporary "rec" track for the loop mode to
work.

song_onev()

called for every incoming event. It's in charge to tag the
event with TAG_PLAY, TAG_REC or TAG_OFF, so here's where most
of the logic happens.

song_setmode()

does the initialization, may need some changes for the
playmode to use the temporary "rec" track, not 100% sure

There's also a central "state" structure to which every event is matched and where the tag is stored. For instance, for a note-on event, this structure is allocated and the tag is set. Then, the next corresponding aftertouch and note-off events inherit the same structure, so the events inherit the tag (amongst other information) and follow the same path as the note-on.

The user interface must be changed as well to permit the "r" function while in play mode.

song_record()

is called by the interpreter to start recording. This routine
will need to be modified to check if we're already playing,
and if so to skip the "destructive" calls to song_goto(),
mux_startreq() and alike.

song_play()

same changes, for record->playback transition.

There are probably other places that need to updated to make everything work, most are in song.c

  1. From my understanding the main culprit behind the issue is the mux_stopreq() and mux_startreq() functions that are called when switching modes (in the song.c file). mux_startreq() is also preceded by a song_goto() call. I'm still not sure if the rewind occurs because of the song_goto() call or because of stopping and restarting the midi clock.

starting/stopping the clock won't work as it will pause playback. Furthermore we don't wan't heavy changes, just enable/disable storing of the events in the track.

  1. Is it necessary to stop and start the clock when switching modes? I tried not restarting the clock when switching between record and playback modes, and it kind of got rid of the rewinds. However, when I do this the recordings are not added to the loop when switching from record mode to playback mode, and the terminal freezes without errors after a few switches between the two modes.

  2. I guess the last question is how I can make sure the record track is transferred to the playback track when switching from record mode to playback mode. Is just calling song_mergerec() enough?

song_mergerec() copies the temporary track to the current track; this is the last step after recording is over. So, song_mergerec() must be called when both playback and recording stop (ie. song->mode transition to SONG_IDLE or 0). So song_setmode() will need few changes as well.

ratchov avatar Sep 18 '24 07:09 ratchov

Thanks for all the helpful pointers. I will try to make something happen. Though my changes might end up being too hacky to merge...

On Wed, Sep 18, 2024, 09:23 Alexandre Ratchov @.***> wrote:

On Tue, Sep 17, 2024 at 01:49:07AM -0700, SilvioDylan wrote:

It is commendable that you are still maintaining this project after so many years, awesome! :D

I would be interested in looking into this but I can't promise much because I've never done any C++ before. So the code references and other details would be very helpful.

I already took a look at the source code and tried out some stuff. I have a few questions about some things but feel free to leave these unanswered if you don't have the time.

AFAICS, play and rec modes are very similar:

Basically, the record mode is like the play mode, but there's an extra temporary track (struct song->rec) where recorded events are stored and read from in loop mode. The logic is that every input event is tagged with TAG_OFF, TAG_PLAY or TAG_REC. Events with the TAG_REC are stored in the "rec" track, others are played or discarded (tag TAG_PLAY, TAG_OFF).

From this stand-point, we could make the play and rec mode the same, except that in the play mode we'd never use TAG_REC, so no events would be stored in the temporary track. Then, to switch to recording mode, just start tagging events with TAG_REC.

Once performance is stopped, the temporary "rec" track is merged into the current track. If no events were recorded, "rec" is empty and there are no changes to the current track.

The central routines are:

song_ticplay()

called at every clock tick to play the events. In rec mode, it also plays the temporary "rec" track for the loop mode to work.

song_onev()

called for every incoming event. It's in charge to tag the event with TAG_PLAY, TAG_REC or TAG_OFF, so here's where most of the logic happens.

song_setmode()

does the initialization, may need some changes for the playmode to use the temporary "rec" track, not 100% sure

There's also a central "state" structure to which every event is matched and where the tag is stored. For instance, for a note-on event, this structure is allocated and the tag is set. Then, the next corresponding aftertouch and note-off events inherit the same structure, so the events inherit the tag (amongst other information) and follow the same path as the note-on.

The user interface must be changed as well to permit the "r" function while in play mode.

song_record()

is called by the interpreter to start recording. This routine will need to be modified to check if we're already playing, and if so to skip the "destructive" calls to song_goto(), mux_startreq() and alike.

song_play()

same changes, for record->playback transition.

There are probably other places that need to updated to make everything work, most are in song.c

  1. From my understanding the main culprit behind the issue is the mux_stopreq() and mux_startreq() functions that are called when switching modes (in the song.c file). mux_startreq() is also preceded by a song_goto() call. I'm still not sure if the rewind occurs because of the song_goto() call or because of stopping and restarting the midi clock.

starting/stopping the clock won't work as it will pause playback. Furthermore we don't wan't heavy changes, just enable/disable storing of the events in the track.

  1. Is it necessary to stop and start the clock when switching modes? I tried not restarting the clock when switching between record and playback modes, and it kind of got rid of the rewinds. However, when I do this the recordings are not added to the loop when switching from record mode to playback mode, and the terminal freezes without errors after a few switches between the two modes.

  2. I guess the last question is how I can make sure the record track is transferred to the playback track when switching from record mode to playback mode. Is just calling song_mergerec() enough?

song_mergerec() copies the temporary track to the current track; this is the last step after recording is over. So, song_mergerec() must be called when both playback and recording stop (ie. song->mode transition to SONG_IDLE or 0). So song_setmode() will need few changes as well.

— Reply to this email directly, view it on GitHub https://github.com/ratchov/midish/issues/2#issuecomment-2357700044, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATECEKDSFYIZEMAQ7V5A2ETZXES7JAVCNFSM6AAAAABOJRA7PWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNJXG4YDAMBUGQ . You are receiving this because you authored the thread.Message ID: @.***>

SilvioDylan avatar Sep 20 '24 23:09 SilvioDylan