RE: Editing a looping MusicTrack as it plays
RE: Editing a looping MusicTrack as it plays
- Subject: RE: Editing a looping MusicTrack as it plays
- From: David Hicks <email@hidden>
- Date: Mon, 23 Jan 2012 10:04:42 -0500
Neil,
I understand now that your problem has been associated with any tinkering
you do at the moment of loopback.
If I were to refine what I was suggesting in light of my current
understanding, it would be: "check if you are at the loopback point, and if
yes, avoid changing anything". (Yes, I know that sounds unacceptable.)
But the point of my guidance was/is this: almost anything one does with
CoreAudio or CoreMIDI, beyond the most simplistic, will ultimately require
reliance on information from the system: i.e. callbacks.
Thanks to Aran for jumping in. If, as your explanation suggests, the
loopback point is a no-man's-land where editing simply can't happen, Aran's
suggestion is the way to go.
It is (at least on OS X) relatively manageable to keep track of
sequence-time in order to add notes to a non-looping MusicTrack just before
they are to be played by the MusicPlayer. Played on time, without any hitch.
In other words you can give the user lots of time to change things before
you need to 'copy' a note from the base-pattern into the playing sequence,
just in time for it to play.
For what it's worth: in my app this amounts to 1) having a separate thread
for the purpose of playing MIDI and adding notes to the MusicTracks; 2)
attaching a callback to my AUGraph's MusicDevice (this callback, on a
system-created thread, gives me the critical HostTime at the start of play,
and periodically thereafter, i.e. every 512 samples, which I use to
coordinate with my MIDI thread).
So, at some experientially tiny time ahead of the sequence's playback (in my
app, a few music-ticks, calculated as a function of a variable
nanoseconds-per-beat, since BPM-tempo is user-variable), I do
MusicTrackNewMIDINoteEvent for whatever notes are on tap. (HostTimes, from
the MusicDevice callback, and from periodic calls to GetHostTime on the MIDI
thread, may be in nanoseconds, but if not are convertible to nanoseconds
using system functions. And thus from ns to music-ticks.)
HTH,
David
-----Original Message-----
From: Neil Wallace [mailto:email@hidden]
Sent: Sunday, January 22, 2012 9:07 AM
To: Aran Mulholland; David Hicks
Cc: email@hidden
Subject: RE: Editing a looping MusicTrack as it plays
Many thanks for the suggestions - thats certainly given me some things to
try out...
Aran - The approach you are suggesting seems like it would have a very bad
worst case for the time between adding a note to the sequence and it being
audible in the sequence (ie it could take a full bar for the note to appear)
- although I can try experimenting with how often the data gets copied from
the loop sequence into the continuous sequence.
David - The example code was just as small a sample as I could make to
demonstrate the problem (for instance in my real app I don't clear the whole
track instead I just remove the note at the time step I am editing) -
unfortunately I don't think the problem is to do with editing a note that is
just about to play as in my actual app this hasn't been a source of problems
- the problem only happens when you edit a note when the playhead is around
the loop point (the note being edited does not need to be anywhere near the
loop point). In fact I can also get the problem to occur by muting and
un-muting the track at the loop point.
I will have a bit more of a tinker based on both your suggestions and let
you know if I make any headway.
________________________________________
From: Aran Mulholland [email@hidden]
Sent: 21 January 2012 22:27
To: David Hicks
Cc: email@hidden; Neil Wallace
Subject: Re: Editing a looping MusicTrack as it plays
Hey Neil
I have been playing with the same stuff and have been thinking about a
way to get things working correctly. One theory that I have toyed with
is to not use the looping feature at all, to simply have a timeline of
events that continues to march on. I would store my loop in another
midi track and allow the user to edit that track, when a note gets
added or removed I would copy it to the main (playing) track. Just
before the end of every bar I would copy the next bar into the
currently playing track. Using a MusicTrackUserEvent I would know when
to copy the information.
Using MusicTrackNewUserEvent you can add events to a midi track that
will fire a callback (from the render thread). You can also add some
custom information to each event to differentiate between events. I
have been doing some other midi stuff and have wanted to have
notifications of certain events happening without continually querying
the timeline. What I have been doing is creating another pthread and
when the MusicTrackUserEvent fires i trigger a semaphore to wake up
the thread. Then I iterate the track that I am adding
MusicTrackUserEvents to and remove any events that are there and deal
with them appropriately. It gives me a way to process events without
having to process them on the render thread (big no no)
You could use this tech to fire notifications every 16th note for UI
refresh for example, which I assume would be a lot more efficient than
polling for the current time with an NSTimer. Or you could stop
editing of any notes around the loop points by putting them in the
queue as a MusicTrackUserEvent to be added to the main loop at the
correct time.
Aran.
On Sun, Jan 22, 2012 at 3:36 AM, David Hicks <email@hidden> wrote:
> Neil,
>
> I haven't seen any responses to your query yet, so until someone with more
> experience with looping MusicTracks weighs in, here's what occurs to me.
(I
> have some experience with populating MusicTracks on the fly, but not with
> looping.)
>
> It makes some sense to me that there might be a conflict with playing a
MIDI
> note event while it is being removed via MusicTrackClear. (I wouldn't
> necessarily expect the whole track to go silent, but who knows?) If I were
> in your shoes (not much documentation of looping MusicTracks, no input
from
> this list) I would keep track of where I was during playback of the
> MusicTrack, and to avoid Clearing any events at or near the current play
> position.
>
> At a minimum this would mean providing a callback function to the sequence
> (I assume you don't have one, from your code snippet), see documentation
> below from MusicSequence reference. The callback provides you with
> 'inStartSliceBeat' and 'inEndSliceBeat', which you can use to determine if
> an event you are about to clear is scheduled for immediate playback. If
so,
> don't clear these now. You could clear any other events you want to
without
> hazard, I would think, and add back in their new note-versions as well.
>
> I probably wouldn't do the Clear and Add during the callback itself. I
think
> I would instead add member variables, say mStartSliceBeat and
mEndSliceBeat,
> to my class, and these would get set from the callback. Then these member
> variables would be referenced in the main thread at the point where you
> normally do your MusicTrackClear (touchesBegan).
>
> HTH,
>
> David
>
>
> MusicSequenceSetUserCallback
>
> Registers a user callback function with a music sequence.
>
> OSStatus MusicSequenceSetUserCallback (
> MusicSequence inSequence,
> MusicSequenceUserCallback inCallback,
> void *inClientData
> );
>
> Parameters
>
> inSequence
>
> The music sequence that you want to add a user callback function to.
>
> inCallback
>
> A reference to your callback function. Use NULL to remove a registered
> callback function.
>
> inClientData
>
> Your data that the music sequence provides back to your callback function
> when it is invoked.
>
> Return Value
>
> A result code.
>
> Discussion
>
> The music sequence invokes your callback for each user event added to any
> music track owned by the sequence. If there is a callback registered, then
> UserEvents will be chased when MusicPlayerSetTime is called. In that case
> the inStartSliceBeat and inEndSliceBeat will both be the same value and
will
> be the beat that the player is chasing to.
>
> Usually, where the sequence data is being scheduled for playback, the
> following applies:
>
> inStartSliceBeat <= inEventTime < inEndSliceBeat
> The only exception to this is if the track that owns the MusicEvent is
> looping. In this case the start beat will still be less than the end beat
> (so your callback can still determine that it is playing, and what beats
are
> currently being scheduled), however, the inEventTime will be the original
> time-stamped time of the user event.
>
> Further down the MusicSequence reference we find this:
>
> MusicSequenceUserCallback
>
> typedef void (*MusicSequenceUserCallback) (
> void *inClientData,
> MusicSequence inSequence,
> MusicTrack inTrack,
> MusicTimeStamp inEventTime,
> const MusicEventUserData *inEventData,
> MusicTimeStamp inStartSliceBeat,
> MusicTimeStamp inEndSliceBeat
> );
>
> If you named your callback MyMusicSequenceUserCallback, you would declare
it
> like this:
>
> void MyMusicSequenceUserCallback (
> void *inClientData,
> MusicSequence inSequence,
> MusicTrack inTrack,
> MusicTimeStamp inEventTime,
> const MusicEventUserData *inEventData,
> MusicTimeStamp inStartSliceBeat,
> MusicTimeStamp inEndSliceBeat
> );
>
>
> _______________________________________________
> Do not post admin requests to the list. They will be ignored.
> Coreaudio-api mailing list (email@hidden)
> Help/Unsubscribe/Update your Subscription:
>
.com
>
> This email sent to email@hidden
>
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden