Yes it can be done, we just have to figure out how.
Campbell Ritchie wrote:Of course there is. Start by looking through the Java™ Tutorials.
Stephan van Hulst wrote:A melody is basically a Track containing a MidiEvent for each note that starts playing, and one for each note that stops playing.
When you've added all notes to a Track of a Sequence, you should hook up a Sequencer to an output device, and let it start the sequence.
Stephan van Hulst wrote:You could create your melody like this:
Kevin Simonson wrote:What imports do I need to make to get this to compile? In other words, where are {Fraction}, {Note}, {Key}, and {Melody} located? I couldn't find them in "https://docs.oracle.com/javase/8/docs/api". Are they part of Java 9?
Stephan van Hulst wrote:I would first create classes that represent keys, notes, times, fractions and melodies, before you convert them to MidiEvents. For instance, this is what a Note could look like:
..........snip..........
This should give you enough of an idea. I wrote this without a compiler, so it may contain errors, and you'll have to add some missing classes (Key and Fraction) and add exception declarations.
Kevin Simonson wrote:
The only thing these files tell me about class {Fraction} is that it has an {atTicks()} method that takes as parameters an {int} and a {Fraction}. What exactly does {atTicks()} do? Is it a simple multiplication?
Stephan van Hulst wrote:That would be asTicks(int ticksPerQuarterNote). I made a mistake, you don't need the timeSignature parameter. asTicks() converts the duration of a note specified in fractions of a bar, to duration of a note specified in ticks, where a tick is a length of time that depends on the resolution of the sequence.
Imagine that you want to play a note for the duration of a quaver. That's 1/8 of a bar. Let's say that the resolution of the sequence is 96 ticks per quarter note. That means 1/4 of a bar contains 96 ticks. A single quaver would then be 48 ticks. If you want to play a minim, which is 1/2 of a bar, it would have to last for 192 ticks.
Why perform this conversion? MidiEvent uses ticks to specify the time of the event.
What else does Fraction have? For now, it just needs a numerator() and a denominator().
Education won't help those who are proudly and willfully ignorant. They'll literally rather die before changing.
Tim Holloway wrote:OK, I'm late to the party and there's enough already written that I may have missed this part, but it looks like you're all giving advice on how to get Java to do MIDI sound. But this is a video game, so most likely that isn't what Kevin wants.
MIDI is a digital control protocol. Originally, you'd have used it to play an external musical instrument like a synthesizer. These days, many OS's have internal MIDI client capabilities as well. But that would be overkill for the average videogame.
The Java sound API also includes the ability to play digital sound samples. For a video game, you'd most commonly keep these samples in .wav files, although Java supports other formats as well.
I don't have anything lying around handy right now, but basically you'd just load the file into RAM and tell the API to play it.
You could also synthesize raw waveforms and play them, but it's easier to use pre-recorded samples.
Stephan van Hulst wrote:
Make your class final. Make your fields private and final. Normalize your fields in the constructor using BigInteger.gcd(). Your compareTo() method does not take overflow into account. You're better off using BigInteger instead of long. If you make a class Comparable, you should override equals() and hashCode() as well. Override the toString() method. Key should start with C, otherwise the noteNumber() formula will not work. I would also write out the sharps:
Kevin Simonson wrote:Does that look like it should work?
Stephan van Hulst wrote:
Kevin Simonson wrote:Does that look like it should work?
You should really write unit tests to test the correctness of your implementation.
Stephan van Hulst wrote:Yes, all this code does is convert notes to Midi events. The events still have to be sequenced and sent to a playback device.
MidiSystem.getSequencer() returns the system's default sequencer, hooked up to a default playback device. Create a new Sequence, add your Melody to it as a Track, and use Sequencer.setSequence() and Sequencer.start() to sequence your Melody.
Stephan van Hulst wrote:Yes, all this code does is convert notes to Midi events. The events still have to be sequenced and sent to a playback device.
MidiSystem.getSequencer() returns the system's default sequencer, hooked up to a default playback device. Create a new Sequence, add your Melody to it as a Track, and use Sequencer.setSequence() and Sequencer.start() to sequence your Melody.
Stephan van Hulst wrote:Create a new Sequence, add your Melody to it as a Track, and use Sequencer.setSequence() and Sequencer.start() to sequence your Melody.
Kevin Simonson wrote:Well, the only thing that creates {MidiEvent}s is {addAsTrackToSequence()}. Are you saying that after my program calls {melody.playNote()} five times, I can call {melody.addAsTrackToSequence()} and use that to make my melody sound?
There are two constructors that create {Sequence} objects, namely {Sequence ( float divisionType, int resolution)} and {Sequence ( float divisionType, int resolution, int numTracks)}. I'm guessing I want the first. What exactly do the {divisionType} and {resolution} arguments do, and can anybody give me any idea what values I'd want to set them to in order to generate my little melody?
I know how to create a {Track} object. How do I add that {Track} object to my {Sequence} object? I don't see a {Sequence} method for doing that.
Stephan van Hulst wrote:
Kevin Simonson wrote:Well, the only thing that creates {MidiEvent}s is {addAsTrackToSequence()}. Are you saying that after my program calls {melody.playNote()} five times, I can call {melody.addAsTrackToSequence()} and use that to make my melody sound?
Yes, that method converts the notes to Midi events, and adds those Midi events to a track that it creates itself. The Track is already part of a Sequence.
There are two constructors that create {Sequence} objects, namely {Sequence ( float divisionType, int resolution)} and {Sequence ( float divisionType, int resolution, int numTracks)}. I'm guessing I want the first. What exactly do the {divisionType} and {resolution} arguments do, and can anybody give me any idea what values I'd want to set them to in order to generate my little melody?
Division type determines the units that the resolution is given in. The two main kinds are "pulses per quarter", which is used for tempo-based Midi, and "frame rate", which is mostly used when playing Midi for a video. Indeed, we're only interested in tempo-based Midi, so for divisionType you should use Sequence.PPQ.
The resolution is exactly that: how precise you can time your Midi events. If you only have 2 pulses per quarter, you will only be able to time midi events at two different moments within a quarter. I believe 96 pulses per quarter is a fairly standard amount to use.
I know how to create a {Track} object. How do I add that {Track} object to my {Sequence} object? I don't see a {Sequence} method for doing that.
You don't have to. The addAsTrackToSequence() already does that. When it calls sequence.createTrack(), the track that is returned is already part of the sequence.
Stephan van Hulst wrote:You're almost there!
Stephan van Hulst wrote:The exception tells you exactly what's wrong. Your sequencer hasn't been opened. So you should try to open it.
Please note that Sequencer is AutoCloseable, so you should use it in a try-with-resources statement.
addAsTrackToSequence() also doesn't need the timeSignature parameter, so remove it.
Stephan van Hulst wrote:Actually, everything is working fine. The sequencer is now open, and it's sequencing the music. The message you get is just a warning, you can disregard it.
The final problem you're facing now is that music is sequenced and played asynchronously, and the program terminates before it has a chance to finish playing the music. You need to write some code that blocks until the music has stopped playing. For now, you can use Thread.sleep() after you've started the sequencer to listen if any music is being played. After you've made sure that it does, you can add a MetaEventListener to your sequencer to detect the end of the melody, and use Lock and Condition to block until you've detected the end.
Note that you don't have to call sequencer.close(). Because you're using try-with-resources, the sequencer is automatically closes as soon as you leave the try statement.
Kevin Simonson wrote:[...] I decreased {half} from 1/2 to 1/16 [...]
But I noticed that the first note seems to last twice as long as any of the other notes. Does anybody know why that might be the case?
Oh. Hi guys! Look at this tiny ad:
Gift giving made easy with the permaculture playing cards
https://coderanch.com/t/777758/Gift-giving-easy-permaculture-playing
|