3

I'm trying to to use Java Sound API in a Scala SBT-managed project.

Here is a toy app that plays a note.

import javax.sound.midi._

object MyMain extends App {
  val infos = MidiSystem.getMidiDeviceInfo()
  println( "[DEBUG] midi devices found: " + infos.length )

  val myMsg = new ShortMessage;
  // Start playing the note Middle C (60),
  // moderately loud (velocity = 93).
  myMsg.setMessage(ShortMessage.NOTE_ON, 0, 60, 93);
  val timeStamp = -1;
  val rcvr : Receiver = MidiSystem.getReceiver();
  rcvr.send(myMsg, timeStamp);

  readChar()  // give time to play note
}

When I execute run in SBT, I get the javax.sound.midi.MidiUnavailableException because infos.length returns 0. That said, when I run the app in IntelliJ, two devices are found and the note plays just fine.

What does SBT need to know to make it run? Is there something which needs to be added to the classpath? I noticed that IntelliJ attaches a whole bunch of jars to the execution command (however, removing those of the jars that come from jdk/jre/lib/ had no effect, while the others are scala related or IntelliJ related).

StokedOver9k
  • 129
  • 1
  • 9
  • Did you create theIntelliJ project using SBT? This should at least in theory give the same classpath for both. – sorencito Sep 07 '13 at 19:45
  • Yes: `gen-idea`. How would I look at the command SBT uses to run the program (so I can compare with IntelliJ's command)? – StokedOver9k Sep 07 '13 at 20:42
  • What version of java are you using (for both SBT and IntelliJ if they are different)? – gourlaysama Sep 07 '13 at 21:21
  • In IntelliJ "Project SDK" is listed as "1.7 (java version "1.6.27")". As to SBT, I'm not too sure that I'm getting this the correct way, but here's what I did: `show javaHome` returns `None`, thus I assume that it uses the default java; `java -version` gives me 1.7.0_25. Are these the values you're asking for? – StokedOver9k Sep 07 '13 at 22:51
  • Curiously, IntelliJ uses /usr/lib/jvm/default-java/bin/java which is an OpenJDK which I believed until now that I totally got rid of with "apt-get purge". – StokedOver9k Sep 07 '13 at 22:56
  • Ok, changed IntelliJ's SDK to Oracle's implementation that's on my path (java -version) - the app still works in IntelliJ and not in SBT. – StokedOver9k Sep 07 '13 at 23:18
  • Ok, so if they both use the same java version, then this could be a problem with SPI: the java sound API uses Service Provider Interface to auto-discover sound components. Maybe SBT's classloader messes with that. Try adding `fork in run := true` in `build.sbt`. – gourlaysama Sep 08 '13 at 12:09
  • Yes! `for in run := true` worked. Curiously, this breaks the `readChar` with a "Exception in thread "main" java.io.EOFException: Console has reached end of input", but that's ok. Does this option make SBT start a new JVM for running the program? If so, will I run into issues because of that? For example, I see that now ^C while the note is playing kills SBT along with the app instead of just killing the app. – StokedOver9k Sep 08 '13 at 16:26
  • @StokedOver9k I posted it as an answer, see below. – gourlaysama Sep 08 '13 at 16:45

1 Answers1

5

SBT runs your application in-process, with some classloader magic, which probably prevents MidiSystem from finding (using SPI) the sound components.

You can try forking a new JVM to run your application: fork in run := true. See Forking in the documentation.

Note that:

  • by default it does not redirect input to the application. You can add that with:

    connectInput in run := true
    
  • the forked JVM can be killed by any external tool without problem (using kill or any kind of task manager)
gourlaysama
  • 11,240
  • 3
  • 44
  • 51
  • Great! That took care of the second problem too. I haven't had much experience with SBT, so I'm wondering if forking on run is somewhat of a hack. Would a more proper solution be to figure out how to influence SBT's JVM or this the right way to deal with problems of this sort? – StokedOver9k Sep 08 '13 at 17:58