X-Git-Url: https://ruin.nu/git/?p=moosique.git;a=blobdiff_plain;f=Moosique.java;h=68bfd1799fd4ed00e07d1e2cc6e9aa891a3b285d;hp=5ad1afe9d93b8ae55a9201e6c1464eed52dfd0a5;hb=e49cbedbb512d2cbadb19f9b4b84c1b0ef39ff9c;hpb=22067aec9dd544828d90ff39a3a6d40cdfc7ea9f diff --git a/Moosique.java b/Moosique.java index 5ad1afe..68bfd17 100644 --- a/Moosique.java +++ b/Moosique.java @@ -20,6 +20,8 @@ public class Moosique { private static Receiver receiver; private static MidiChannel[] channels; private static MidiChannel activeChannel; + private static Track recordTrack = null; + private static MooTrackView recordTrackView = null; private static ArrayList copyBuffer, emptyTracks, timeSignatures, tempoChanges; private static TreeSet selection; @@ -31,7 +33,7 @@ public class Moosique { private static long editPosition; private static boolean makeGUI = true, initSound = true, edited = false, drawEmptyTracks = false; public static final int DEFAULT_RESOLUTION = 96, DEFAULT_TRACKS = 4; - public static final int WHOLE_NOTE = 0, HALF_NOTE = 1, QUARTER_NOTE = 2, EIGHTH_NOTE = 3, SIXTEENTH_NOTE = 4; + public static final int WHOLE_NOTE = 16, HALF_NOTE = 8, QUARTER_NOTE = 4, EIGHTH_NOTE = 2, SIXTEENTH_NOTE = 1; /** * Starts the application. @@ -52,20 +54,22 @@ public class Moosique { // Acquires MIDI devices and connects them. out("Initializing MIDI devices.", false); try { - // Configures sequencer + // Configures sequencer. sequencer = MidiSystem.getSequencer(); advanceStatus(); sequencer.open(); sequencer.addMetaEventListener(new SongEndListener()); - // Configures synthesizer + // Configures synthesizer. synthesizer = MidiSystem.getSynthesizer(); advanceStatus(); synthesizer.open(); - // Configures receiver, transmitter and channels. + // Connects devices. receiver = synthesizer.getReceiver(); sequencer.getTransmitter().setReceiver(receiver); + + // Configures channels. channels = synthesizer.getChannels(); setActiveChannel(0); } catch (MidiUnavailableException e) { @@ -166,41 +170,6 @@ public class Moosique { return gui; } - /** - * Calculates the position (measures, beats, ticks) in the current sequence for the given tick position. - * @param tickPosition the tick position for which to calculate the position - * @return an array of integers where index 0 is measures, 1 is beats and 2 is ticks. - */ - public static int[] getPositionForTicks(long tickPosition) { - int ticksPerBeat = seq.getResolution(), sigs = timeSignatures.size(), beatsPerMeasure = 4; - long measures = 0, beats = 0, ticks = 0; - if (sigs > 1) { - /* - Iterator it = timeSignatures.iterator(); - MidiEvent lastTSEvent = (MidiEvent)it.next(); - if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick(); - while(it.hasNext()) { - MidiEvent nextTSEvent = (MidiEvent)it.next(); - long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick(); - ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); - beatsPerMeasure = ts[0] * (4 / ts[1]); - tickPos += ((beatsPerMeasure * measures + beats) * res + ticks); - } - */ - } else { - if (sigs == 1) { - MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0); - int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData()); - beatsPerMeasure = ts[0] * (4 / ts[1]); - } - measures = tickPosition / (beatsPerMeasure * ticksPerBeat); - beats = (tickPosition - measures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat; - ticks = tickPosition - measures * beatsPerMeasure * ticksPerBeat - beats * ticksPerBeat; - } - int[] pos = {(int)measures + 1, (int)beats + 1, (int)ticks + 1}; - return pos; - } - /** * Returns the receiver of the current sequencer. * @return the receiver @@ -225,73 +194,6 @@ public class Moosique { return sequencer; } - /** - * Returns the tempo in the given tick position. - * @param tick the tick position for which to return the tempo - * @return the tempo at the specified tick position - */ - public static int getTempo(long tick) { - if (tempoChanges.size() == 0) return 120; - MidiEvent tempoEvent = (MidiEvent)tempoChanges.get(0); - if (tempoChanges.size() > 1) { - for (int i = 1; i < tempoChanges.size(); i++) { - MidiEvent nextTempoEvent = (MidiEvent)tempoChanges.get(i); - if (nextTempoEvent.getTick() < tick && nextTempoEvent.getTick() > tempoEvent.getTick()) - tempoEvent = nextTempoEvent; - } - } - return decodeTempo(((MetaMessage)tempoEvent.getMessage()).getData()); - } - - /** - * Calculates the tick position in the current sequence for the given position (measures, beats, ticks). - * @return the tick position. - */ - public static long getTicksForPosition(int measures, int beats, int ticks) { - int res = seq.getResolution(); - long tickPos = 0; - switch (timeSignatures.size()) { - case 0: - tickPos = (4 * measures + beats) * res + ticks; - case 1: - MidiEvent TSEvent = (MidiEvent)timeSignatures.get(0); - int[] ts = decodeTimeSig(((MetaMessage)TSEvent.getMessage()).getData()); - int beatsPerMeasure = ts[0] * (4 / ts[1]); - tickPos = (beatsPerMeasure * measures + beats) * res + ticks; - default: - Iterator it = timeSignatures.iterator(); - MidiEvent lastTSEvent = (MidiEvent)it.next(); - if (lastTSEvent.getTick() != 0) tickPos += (int)lastTSEvent.getTick(); - while(it.hasNext()) { - MidiEvent nextTSEvent = (MidiEvent)it.next(); - long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick(); - ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); - beatsPerMeasure = ts[0] * (4 / ts[1]); - tickPos += ((beatsPerMeasure * measures + beats) * res + ticks); - } - } - return tickPos; - } - - /** - * Returns the time signature in the given tick position. - * @param tick the tick position for which to return the time signature - * @return an array of two integers where [0] is the numerator and [1] the denominator - */ - public static int[] getTimeSig(long tick) { - int[] ts = {4, 4}; - if (timeSignatures.size() == 0) return ts; - MidiEvent timeSigEvent = (MidiEvent)timeSignatures.get(0); - if (timeSignatures.size() > 1) { - for (int i = 1; i < timeSignatures.size(); i++) { - MidiEvent nextTimeSigEvent = (MidiEvent)timeSignatures.get(i); - if (nextTimeSigEvent.getTick() < tick && nextTimeSigEvent.getTick() > timeSigEvent.getTick()) - timeSigEvent = nextTimeSigEvent; - } - } - return decodeTimeSig(((MetaMessage)timeSigEvent.getMessage()).getData()); - } - /** * Returns true if the current sequence has been edited. * @return the tick position @@ -431,75 +333,6 @@ public class Moosique { - /* *** - ** ENCODING / DECODING METHODS ** - *** */ - - - - - - - - - /** - * Returns the byte array for the given tempo. - * @param tempo the tempo in beats per minute - * @return an array of bytes representing the given tempo - */ - public static byte[] encodeTempo(int tempo) { - int microSecsPerQuarter = 60000000 / tempo; - byte[] b = new byte[3]; - b[0] = (byte)(microSecsPerQuarter / 65536); - b[1] = (byte)((microSecsPerQuarter - (b[0] * 65536)) / 256); - b[2] = (byte)(microSecsPerQuarter - (b[0] * 65536) - (b[1] * 256)); - return b; - } - - /** - * Returns the tempo for the given byte array. - * @param an array of three bytes representing the tempo - * @return the tempo in beats per minute - */ - public static int decodeTempo(byte[] bytes) { - return 60000000 / (bytes[0] * 65536 + bytes[1] * 256 + bytes[2]); - } - - /** - * Returns the byte array for the given time signature. - * @param numerator the numerator of the time signature - * @param denominator the denominator of the time signature - * @return an array of bytes representing the given time signature - */ - public static byte[] encodeTimeSig(int numerator, int denominator) { - byte[] b = { - (byte)numerator, - (byte)(Math.log(denominator) / Math.log(2)), // logarithm of denominator in base 2 - (byte)96, - (byte)8 - }; - return b; - } - /** - * Returns the time signature for the given byte array. - * @param an array of four bytes representing the time signature - * @return an array of two integers where [0] is the numerator and [1] the denominator - */ - public static int[] decodeTimeSig(byte[] bytes) { - int[] t = { - (int)bytes[0], - (int)(1 << bytes[1]) - }; - return t; - } - - - - - - - - /* *** ** SELECTION METHODS ** *** */ @@ -637,6 +470,221 @@ public class Moosique { sequencer.setTickPosition(editPosition); player = null; gui.update((long)0); + + if (recordTrack != null && recordTrackView != null) { + recordTrackView.disableKeyboardRecording(); + sequencer.stopRecording(); + sequencer.recordDisable(recordTrack); +// if (quantize) recordTrackView.placeNewNotes(quantize( +// convertTrack(recordTrack), SIXTEENTH_NOTE, true, true)); +// else recordTrackView.placeNewNotes(Moosique.convertTrack(recordTrack)); + } + } + + /** + * Enables recording to the given track. + */ + public static boolean record(Track track) { +/* try { + sequencer.recordEnable(track, channel); + sequencer.startRecording(); + } catch(Exception e) { + return false; + } + recordTrackView = gui.getView().getTrackView(track); + recordTrackView.enableKeyboardRecording(); + Moosique.setEdited(); +*/ + return true; + } + + + + + + + + + /* *** + ** TEMPO & TIME SIGNATURE METHODS ** + *** */ + + + + + + + + + /** + * Returns the byte array for the given tempo. + * @param tempo the tempo in beats per minute + * @return an array of bytes representing the given tempo + */ + public static byte[] encodeTempo(int tempo) { + int microSecsPerQuarter = 60000000 / tempo; + byte[] b = new byte[3]; + b[0] = (byte)(microSecsPerQuarter / 65536); + b[1] = (byte)((microSecsPerQuarter - (b[0] * 65536)) / 256); + b[2] = (byte)(microSecsPerQuarter - (b[0] * 65536) - (b[1] * 256)); + return b; + } + + /** + * Returns the tempo for the given byte array. + * @param an array of three bytes representing the tempo + * @return the tempo in beats per minute + */ + public static int decodeTempo(byte[] bytes) { + return 60000000 / (bytes[0] * 65536 + bytes[1] * 256 + bytes[2]); + } + + /** + * Returns the tempo in the given tick position. + * @param tick the tick position for which to return the tempo + * @return the tempo at the specified tick position + */ + public static int getTempo(long tick) { + if (tempoChanges.size() == 0) return 120; + MidiEvent tempoEvent = (MidiEvent)tempoChanges.get(0); + if (tempoChanges.size() > 1) { + for (int i = 1; i < tempoChanges.size(); i++) { + MidiEvent nextTempoEvent = (MidiEvent)tempoChanges.get(i); + if (nextTempoEvent.getTick() < tick && nextTempoEvent.getTick() > tempoEvent.getTick()) + tempoEvent = nextTempoEvent; + } + } + return decodeTempo(((MetaMessage)tempoEvent.getMessage()).getData()); + } + + /** + * Returns the byte array for the given time signature. + * @param numerator the numerator of the time signature + * @param denominator the denominator of the time signature + * @return an array of bytes representing the given time signature + */ + public static byte[] encodeTimeSig(int numerator, int denominator) { + byte[] b = { + (byte)numerator, + (byte)(Math.log(denominator) / Math.log(2)), // logarithm of denominator in base 2 + (byte)96, + (byte)8 + }; + return b; + } + /** + * Returns the time signature for the given byte array. + * @param an array of four bytes representing the time signature + * @return an array of two integers where [0] is the numerator and [1] the denominator + */ + public static int[] decodeTimeSig(byte[] bytes) { + int[] t = { + (int)bytes[0], + (int)(1 << bytes[1]) + }; + return t; + } + + /** + * Returns the time signature in the given tick position. + * @param tick the tick position for which to return the time signature + * @return an array of two integers where [0] is the numerator and [1] the denominator + */ + public static int[] getTimeSig(long tick) { + int[] ts = {4, 4}; + if (timeSignatures.size() == 0) return ts; + MidiEvent timeSigEvent = (MidiEvent)timeSignatures.get(0); + if (timeSignatures.size() > 1) { + for (int i = 1; i < timeSignatures.size(); i++) { + MidiEvent nextTimeSigEvent = (MidiEvent)timeSignatures.get(i); + if (nextTimeSigEvent.getTick() < tick && nextTimeSigEvent.getTick() > timeSigEvent.getTick()) + timeSigEvent = nextTimeSigEvent; + } + } + return decodeTimeSig(((MetaMessage)timeSigEvent.getMessage()).getData()); + } + + /** + * Calculates the position (measures, beats, ticks) in the current sequence for the given tick position. + * @param tickPosition the tick position for which to calculate the position + * @return an array of integers where index 0 is measures, 1 is beats and 2 is ticks. + */ + public static int[] getPositionForTicks(long tickPosition) { + int ticksPerBeat = seq.getResolution(); + long measures = 0, beats = 0, ticks = 0; + + // Counts for each time signature change up to the last one before the given tick position. + Iterator it = timeSignatures.iterator(); + MidiEvent lastTSEvent = (MidiEvent)it.next(); + while(it.hasNext()) { + MidiEvent nextTSEvent = (MidiEvent)it.next(); + if (nextTSEvent.getTick() > tickPosition) break; + long tickDiff = nextTSEvent.getTick() - lastTSEvent.getTick(); + int[] ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); + int beatsPerMeasure = ts[0] * (4 / ts[1]); + long thisTSMeasures = tickDiff / (beatsPerMeasure * ticksPerBeat); + measures += thisTSMeasures; + long thisTSBeats = (tickDiff - thisTSMeasures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat; + beats += thisTSBeats; + ticks += tickDiff - thisTSMeasures * beatsPerMeasure * ticksPerBeat - thisTSBeats * ticksPerBeat; + lastTSEvent = nextTSEvent; + } + + // Counts from the given tick position to the last time signature change before it. + long tickDiff = tickPosition - lastTSEvent.getTick(); + int[] ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); + int beatsPerMeasure = ts[0] * (4 / ts[1]); + long thisTSMeasures = tickDiff / (beatsPerMeasure * ticksPerBeat); + measures += thisTSMeasures; + long thisTSBeats = (tickDiff - thisTSMeasures * beatsPerMeasure * ticksPerBeat) / ticksPerBeat; + beats += thisTSBeats; + ticks += tickDiff - thisTSMeasures * beatsPerMeasure * ticksPerBeat - thisTSBeats * ticksPerBeat; + + // Corrects any overflows. + if (ticks > ticksPerBeat) { + beats += Math.floor(ticks / ticksPerBeat); + ticks = ticks % ticksPerBeat; + } + if (beats > beatsPerMeasure) { + measures += Math.floor(beats / beatsPerMeasure); + beats = beats % beatsPerMeasure; + } + + // Returns the calculated values. + int[] pos = {(int)measures, (int)beats, (int)ticks}; + return pos; + } + + /** + * Calculates the tick position in the current sequence for the given position (measures, beats, ticks). + * @param measures the measure of the current position + * @param beats the beat of the current position + * @param tick the tick of the current position + * @return the tick position. + */ + public static long getTicksForPosition(int measures, int beats, int ticks) { + int res = seq.getResolution(); + int[] lastTSPos = new int[3]; + long tickPosition = 0; + + // Counts for each time signature change up to the last one before the given tick position. + Iterator it = timeSignatures.iterator(); + MidiEvent lastTSEvent = (MidiEvent)it.next(); + while(it.hasNext()) { + MidiEvent nextTSEvent = (MidiEvent)it.next(); + int[] nextTSPos = getPositionForTicks(nextTSEvent.getTick()); + if (nextTSPos[0] >= measures) break; + lastTSPos = nextTSPos; + lastTSEvent = nextTSEvent; + } + + // Counts from the given tick position to the last time signature change before it. + int measdiff = measures - lastTSPos[0]; + int[] ts = decodeTimeSig(((MetaMessage)lastTSEvent.getMessage()).getData()); + int beatsPerMeasure = ts[0] * (4 / ts[1]); + tickPosition = lastTSEvent.getTick() + (beatsPerMeasure * measures + beats) * res + ticks; + + return tickPosition; } @@ -684,7 +732,7 @@ public class Moosique { // Reinitializes sequence variables file = null; - reinitializeVariables(); + reinitializeLists(); // Sends the sequence to the GUI. if (gui != null) gui.setSequence(seq, null); @@ -718,7 +766,7 @@ public class Moosique { * @param quantize whether to round locations and durations in the track to nearest 16th * @return a list of the created MooNotes */ - public static List convertTrack(Track track, boolean quantize) { + public static List convertTrack(Track track) { // Searches the track for NoteOn and NoteOff events ArrayList noteOns = new ArrayList(track.size() / 2); ArrayList noteOffs = new ArrayList(track.size() / 2); @@ -773,7 +821,6 @@ public class Moosique { iOn.remove(); } } - if (quantize) quantize(newMooNotes, SIXTEENTH_NOTE, true, true); return newMooNotes; } @@ -789,7 +836,7 @@ public class Moosique { edited = false; Track[] tracks = seq.getTracks(); - reinitializeVariables(); + reinitializeLists(); // Searches track 0 for changes in tempo and time signature. MidiEvent event; @@ -808,9 +855,25 @@ public class Moosique { Collections.sort(tempoChanges, c); Collections.sort(timeSignatures, c); + try { + // If no time signature specified at tick 0, adds the standard one. + if (timeSignatures.size() == 0 || ((MidiEvent)timeSignatures.get(0)).getTick() != 0) { + MetaMessage timeSigMsg = new MetaMessage(); + timeSigMsg.setMessage(88, encodeTimeSig(4, 4), 4); + timeSignatures.add(0, new MidiEvent(timeSigMsg, (long)0)); + } + + // If no tempo specified at tick 0, adds the standard one. + if (tempoChanges.size() == 0 || ((MidiEvent)tempoChanges.get(0)).getTick() != 0) { + MetaMessage tempoMsg = new MetaMessage(); + tempoMsg.setMessage(81, encodeTempo(120), 3); + tempoChanges.add(0, new MidiEvent(tempoMsg, (long)0)); + } + } catch (Exception e) {} + // Converts tracks. for (int i = 0; i < tracks.length; i++) { - convertTrack(tracks[i], false); + convertTrack(tracks[i]); } // Sends sequence to GUI and sequencer, then returns @@ -828,21 +891,35 @@ public class Moosique { * @param location whether the quantize should affect the location of the note * @param duration whether the quantize should affect the duration of the note */ - public static void quantize(List notes, int resolution, boolean location, boolean duration) { - // Math.round(mn.getTick() / ticksPerSixteenth); + public static List quantize(List notes, int resolution, boolean location, boolean duration) { + Iterator it = notes.iterator(); + int noteSize = resolution * seq.getResolution() / 4; + while(it.hasNext()) { + MidiEvent note = (MidiEvent)it.next(); + if (note instanceof MooNote) { + MooNote mn = (MooNote)note; + if (location) mn.setTick(Math.round(mn.getTick() / noteSize) * noteSize); + if (duration) { + int length = Math.round(mn.getDuration() / noteSize) * noteSize; + if (length < noteSize) length = noteSize; + mn.setDuration(length); + } + } + } + return notes; } /** - * Reinitializes sequence-specific variables. + * Reinitializes sequence-specific collections. */ - private static void reinitializeVariables() { - emptyTracks = new ArrayList(); - timeSignatures = new ArrayList(); - tempoChanges = new ArrayList(); - trackSolo = new HashMap(); - trackMute = new HashMap(); - copyBuffer = new ArrayList(); - selection = new TreeSet(); + private static void reinitializeLists() { + emptyTracks = new ArrayList(); + timeSignatures = new ArrayList(); + tempoChanges = new ArrayList(); + copyBuffer = new ArrayList(); + trackSolo = new HashMap(); + trackMute = new HashMap(); + selection = new TreeSet(); } /**