这只是为了补充已经提供和接受的答案(我给了+1)。
You can use wavetables as an alternative to running trig functions on the fly--sort of half sampled/half synthesized. I also use a sine wave table with six independent cursors pointing into it for FM synthesis, and have duplicated several Yamaha DX7 patches this way. But this is all done via javax.sound.sampled. Once a soft-synth has been built, you might want to control it with the midi library classes.
Let's say you populate a 1K array with floats for a single sine wave.
If you "play" the wave table by incrementing and looping through it and extracting each array member in turn (to write to the sound card via a SourceDataLine), you will get a pitch directly related to your sample rate. For 44100 samples per second, the 1024-member array will cycle 44100/1024 = 43.066... times in to fill that "second" with data (a very low pitch--roughly 43 Hz). If you skip every second table member, the pitch is twice that, etc. To get the pitch 440, one needs to find the correct "increment" to use to step through the wave table array, which can be found:
incr = (size of waveTable * desired pitch) / sample rate
For example
(1024 * 440 ) / 44100 gives an increment of: 10.21678...
Thus, if the first value from the waveTable is at array location 0, the second value to be used would be in between locations 10 and 11. To get a value that lies in between two array locations, use linear interpolation.
I use this method, with javax.sound.sampled libraries, for a "Theremin" at this link. There is a keyboard displayed, but you can easily hear/see microtonal control as you move the mouse across the keys.
http://www.hexara.com/VSL/JTheremin.htm
In the above, the mouse position (called via MouseMotionListener) is used to calculate desired pitch via this function:
return Math.pow(2, ((mouseX + tuningLoc) / (octaveWidth)));
where octaveWidth is the number of pixels that covers an octave.