NotePlayers in a Thread?

I’ve written a music composition program with playback support for up to four simultaneous voices (instruments), each executing from separate command stacks pre-parsed from four separate textual musical scores (one per instrument.) Each voice has its own NotePlayer and Timer to play notes at the proper intervals with a fifth timer monitoring the situation to preform cleanup after all voices have finished their parts.

I decided upon the timer-playback approach because I wanted to keep the UI responsive so I could continue to work during playback, but I have found that interacting with UI controls interrupts the instruments (temporarily) and often causes the instruments to become unsynchronized. The Timer approach seemed like such a brilliant idea at the time but in reality, doesn’t work so well in practice.

Is there any way to utilize NotePlayers from a secondary thread so I can playback smoothly while keeping the UI responsive? If I absolutely have to, I could plop my playback code into a loop and just show the beachball of significant inconvenience for several minutes during playback, but I’m trying to avoid that.

I just did the math on the 1/64th note triplet duration (the fastest note length that my program currently supports), and assuming a fairly typical tempo of 1/4 note = 100 bpm, each 1/64 triplet would have to play every 24.996 ms. That’s kinda quick for a timer to fire AND be able to execute all of the ensuing code prior to the next note coming due. The main thread loop approach is starting to look a bit better to me now. Or I could just pretend that 1/64th triplets don’t exist. Who plays notes that fast anyway? :wink:

Since NotePlayer is a control, I do not think you can use it from a thread. It would raise a ThreadAccessingUIException. Besides, as you noted, a thread may not be responsive enough.

An alternative to a thread if you want to keep the UI responsive is to delegate the note playing to another app that runs concurrently, a helper, launched from the main app when needed.

This is my problem exactly. I’m showing an image of a cartoon trombone playing the correct position with each midi pitch played. I wrote it first with the timer, which could not play overlapping notes, but I could change the a tempo slider. Then I switched to a method that uses app.SleepCurrentThread. I got the overlapping notes playing correctly, but I cannot change the slider. I get a beachball if I even click on the screen.

[quote=168991:@Robert Steely][/quote] Did you get it to work?

Where would I find out more about how to do this?

This is my routine with app.SleepCurrentThread, count is a local variable

dim n,d,count as integer dim s as NotationClass NotePlayer1.Instrument=78 count=1 While count<song.ubound s=song(count) n=GetFingering(s.pitch) gTrb=gInst(n) canvas1.invalidate NotePlayer1.PlayNote(s.pitch,s.vol) d=s.dur/gtempo App.SleepCurrentThread(d) count=count +1 wend count=0 MsgBox "end of song"

This is the original with Timer1, count is a global

dim n,d as integer dim s as NotationClass NotePlayer1.Instrument=78 if count>song.ubound then count=0 me.mode=0 MsgBox "end of song" end s=song(count) n=GetFingering(s.pitch) gTrb=gInst(n) canvas1.invalidate NotePlayer1.PlayNote(s.pitch,s.vol) d=s.dur/gtempo me.period=d count=count +1

[quote=213066:@Ruth Millard]@Michel Bujardet An alternative to a thread if you want to keep the UI responsive is to delegate the note playing to another app that runs concurrently, a helper, launched from the main app when needed.
Where would I find out more about how to do this?[/quote]

Long time no see. I had forgotten about this thread.

A helper app is just another Xojo program you call from your main program thread using a shell. Since it is a different program, it uses a different core of the processor and is entirely independent, therefore will not be slowed down by whatever you do.

Here is how it works.

The notes helper is a windowless app which sole purpose is to play music. I created mine with the NotePlayer example from the LR.

Everything happens in the App Open event.

  1. In the inspector, set the default window as None
  2. Add a Noteplayer1 as NotePlayer property to App.
  3. Add the Open event, I added the LR example inside. Note the last line Quit which closes the app when music is over :

Noteplayer1 = New NotePlayer NotePlayer1.Instrument = 1 // Notes for Do Re Mi Fa So La Ti Do // see http://en.wikipedia.org/wiki/Do-Re-Mi from The Sound of Music http://en.wikipedia.org/wiki/The_Sound_of_Music // (C, D, E, F, G, A, B, C) Dim doReMi(7) As Integer doReMi = Array(60, 62, 64, 65, 67, 69, 71, 60) For Each note As Integer In doReMi NotePlayer1.PlayNote(note, 100) // Pause to let note play App.SleepCurrentThread(500) Next quit

The Caller app has this in the Run event of Thread1 :

Sub Thread1Run(Sender as Thread) dim s as new shell dim f as FolderItem = GetFolderItem("").child("notes.app") s.Execute("open "+f.ShellPath) End Sub

Both apps are in the same folder, so the path to notes.app is based on that. For distribution, you can also place notes.app in the bundle, so everything is self contained. I used to place such apps in Resources, but I remember Sam Rowlands mentioned new recommendations from Apple about that. Unfortunately, I cannot locate that at the moment.

To point to Resources within the Caller bundle, here is the line :

dim f as folderitem = App.ExecutableFile.parent.parent.child("Resources").child("Notes.app")

This archive contains the two example projects and the builds. Open Caller and click the button to see it working.
Notes.zip

Last, you need to avoid displaying the icon in the dock for the helper app. See
https://forum.xojo.com/11024-how-to-start-app-with-no-window-and-without-icon-in-dock/0

I have used that in the notes program. See the OS X build settings Script1

Are you sure your code is executing in the context of a thread? A common misconception is that merely placing code in a thread object and then calling it directly will cause it to run in a separate thread. Your code must be called from the thread’s Run event and in response to a Thread.Run command. If you’re beach balling, it sounds like you’re actually sleeping the main thread.

@Ruth Millard - Your project sounds really interesting. (It sounds like you are a trombone professional, probably with some educational interests.)

Anyhow - I have recently been working on a similar project - except I have been using the MBS Audio/MIDI plugin - so I can’t give any real advice regarding the NotePlayer.

For playing - I used a while loop which calls the microseconds, checks the time against the eventList(myMidiNotes) and plays the event. (Each event has its own start or delta time.) This loop in placed in a method, and that method is in a Thread which is called whenever you want to play.

Using microseconds gives you access to pretty much any time resolution that you want, and it is all so much easier and more accurate than using a timer. As I understand it, the timer can easily be delayed by the OS, and any kind of latency with music sucks. If the timing gets messed up by doing too much UI work while the thread is running, you can just play around with the priority property on your Thread object.

Some UI items that I have found cause some timing delays were the popup menus if I were to click on them when the MIDI was playing.

I have had up to 6 MIDI tracks playing at quite high tempos ( > 400 BPM) and it has generally worked quite well (so far), but I haven’t pushed it to hard so far as performance is not my prime need.

That’s been my experience.

I am not a coding expert and my advice is worth what you have just paid me.