alda-lang / alda-server-clj

A Clojure implementation of an Alda server
Other
2 stars 2 forks source link

MIDI audio is delayed after suspending/restarting processes, pt. 2 #5

Closed daveyarwood closed 7 years ago

daveyarwood commented 7 years ago

Continuing from: https://github.com/alda-lang/alda/issues/160

This issue was mostly fixed -- see that issue for context. However, since I upgraded my Macbook from Mavericks to Sierra, I've started seeing the issue again. I think the fix for the issue above may work on some systems and not others, depending on how the OS handles suspending and resuming processes when it goes into sleep mode.

I think maybe a better workaround would be to implement a life span (maybe 15-30 mins? we could randomize it so that the workers don't all quit at the same time) for worker processes, after which time the worker quits and the server spawns a new one to replace it. This would at least ensure that if your computer is awake for long enough (i.e. 15+ minutes), you are guaranteed to have a worker available that isn't stale from before your computer woke up.

jgkamat commented 7 years ago

I can see the process faling to restart occasionally, but I can't reproduce it reliably. Maybe reducing SUSPEND-INTERVAL would help? (I'm not sure if you're testing by suspending <10s). Another thing to note is that I might have found another way to work around this bug, by closing/opening the synth. Maybe instead of restarting the entire process whenever we sleep, we can just close/open the synth whenever a worker finishes playing a part. I'm not sure if that would be less/more resource intensive.

I also noticed that I don't see a delay if a note is being played before the suspend starts (so maybe we could play a very long silent note, and that would 'solve' it too?)

diff --git a/src/jmdae/Main.java b/src/jmdae/Main.java
index 1b869f1..96acd58 100644
--- a/src/jmdae/Main.java
+++ b/src/jmdae/Main.java
@@ -35,6 +35,15 @@ public class Main {
   }

   public static void playAndPrintNotes() {
+         // System.out.println(synth.getLatency());
+         // System.out.println(synth.getMicrosecondPosition());
+         try {
+                 synth.close();
+                 synth.open();
+                 Thread.sleep(2000);
+         } catch (Exception e) {
+                 e.printStackTrace();
+         }
     System.out.println("Playing notes...");

     int[] notes = {60, 62, 64};

The 2000 ms sleep is overkill, I'm not sure what is reasonable, but it needs to wait for the device to come back online. fix-sleep-bug.txt

daveyarwood commented 7 years ago

I think you're right! I tried closing and reopening the same Synthesizer instance like you mentioned, and it appears to fix the issue, although we still have to sleep for some amount of time before playing in order to avoid timing issues immediately after the synth is opened. This is not great, because we need Alda to be able to start playing a score immediately upon receiving an alda play command.

I don't think this much better than what we're doing right now, i.e. letting idle workers die and spawning new ones. I actually prefer what we have now, because it ensures that the worker process that plays your score is a recently created one, which potentially helps us avoid memory issues, etc. related to using processes that have been up for a long time.

Maybe instead of restarting the entire process whenever we sleep, we can just close/open the synth whenever a worker finishes playing a part.

I like this idea, but unfortunately I don't think it will work. Even if workers close and re-open their synths after playing each score, it would still be possible for the process to be interrupted (e.g. laptop lid closed and reopened) before it gets its next request to play a score, and the audio would be delayed.

The best workaround I can think of right now is to have each worker retire after some amount of time like 15-30 minutes, regardless of whether the process was interrupted.

daveyarwood commented 7 years ago

Fixed in 0.1.7; each worker now has a 15-20 minute lifespan. I think this is a decent workaround.