I found a method that seems to work.
The main problem I was having was trying to use "Clip" for nonstreaming sources and "SourceDataLine" for streaming sources. Theoretically, this would be the best way, but these two classes do not work interchangably, and managing both types was getting to be a headache. The only way I can get around this is to use SourceDataLine for every source -- in other words "stream" everything. For a "nonstreaming" source, all the data will be queued to play at once, whereas in a "streaming" source, buffers are used to queue the data as usual. This may not be the ideal method, but it works.
Another problem I was having was that when instantiating a SourceDataLine, it takes "format" as a parameter (which, for my purposes, is unknown until the user actually wants to play a source).
A third problem is that I read online that there can be any number of SourceDataLines created, but trying to "open()" more than 32 simultaneously results in an exception. I haven't actually tested this, but I've read the same thing from more than one website. It seems reasonable that the maximum number of SourceDataLines would be constant since the Javasound "Mixer" is "software-rendered", from what I understand. I plan to test to make sure the number 32 can be trusted here even if other programs with sound are running. Worse case scenario, it may turn out that, as with OpenAL, I'll just have to live with the uncertainty of how many sources can be played simultaniously. Either way, I will write my code to "catch" any exceptions that happen due to running out of voices.
So my solution works like this: I create a mixer when Javasound initializes, and I create a list of 32 SourceDataLine varriables (they are all "null" to start with). "Loading" a sound file stores its format and byte data for later use, but it does nothing to the SourceDataLines. Then, whenever a source is to be played, a SourceDataLine is instantiated with the correct format, set to the correct gain and pan information, and supplied with byte data for whatever sound is to be played. If the end of the list is reached and no SourceDataLines are empty, the program loops back to the first SourceDataLine, stops and closes it, reinstantiates it with the new format, changes the gain and pan, and supplies it with the new source data.
Preliminary tests show this method to be working, so I am now in the process of moving the code over to my SoundSystem library. There will also be some minor tweaking for things like "priority sources" and the like. I will let everyone know how this goes. I am nearly ready to run some final tests and release the SoundSystem library (finally!)
--EDIT--
Oh, BTW, I do not have a working formula for calculating pan in the Javasound library. Basically I have vectors for souce position and listener position, and normal vectors for look-at point and up direction. From that I need to calculate a float between -1 (left speaker) and 1 (right speaker). My math skills are far to poor to handle a problem like this, so I have been looking for a formula online that someone else has written (so far without luck). In the mean time, Javasound will just not have panning 3D sources. BTW, if any of you are more "mathematically endowed" than me, suggestions would be greatly appreciated.
--EDIT 2--
I found someone to help me out with the panning formula. The psudocode they gave me is:
vec3 side = cross(listener.up, listener.look_at);
side.normalize();
float x = dot(source.position - listener.position, side);
float z = dot(source.position - listener.position, listener.look_at);
float angle = atan2(x, z);
// One way you could compute the panning value from the angle:
float pan = sin(angle);
I am going to try and implement this to see if it works (crossing my fingers).