package org.deeplearning4j.examples.recurrent.character.melodl4j;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Track;
/*
* @author Donald A. Smith
*
* A NoteSequence is a sequence of notes played on a single track.
*
*/
public class NoteSequence implements Comparable<NoteSequence> {
private static boolean trace = false;
private long startTick;
private int trackNumber;
private int channel;
private List<Note> notes = new ArrayList<Note>();
private List<InstrumentChange> instrumentChanges = new ArrayList<InstrumentChange>();
private final int resolution;
private int instrument;
private double averageNoteDuration = -1;
public NoteSequence(long startTick, int track, int channel, int resolution) {
this.startTick = startTick;
this.trackNumber = track;
this.channel = channel;
this.resolution = resolution;
}
public int getResolution() {
return resolution;
}
public int getMaxRawNote() {
int max = Integer.MIN_VALUE;
for (Note note : getNotes()) {
int val = note.getRawNote();
if (val > max) {
max = val;
}
}
return max;
}
public double getAverageNoteDuration() {
if (averageNoteDuration < 0) {
averageNoteDuration = getAverageNoteDurationExpensive();
}
return averageNoteDuration;
}
public double getAverageNoteDurationExpensive() {
long sumDurations = 0;
int noteCount = 0;
for (Note note : getNotes()) {
noteCount++;
sumDurations += note.getDuration();
}
return noteCount == 0 ? 0 : (0.0 + sumDurations) / noteCount;
}
public int getMaxPitchGapAbsolute() {
int max = Integer.MIN_VALUE;
int lastPitch = Integer.MIN_VALUE;
for (Note note : getNotes()) {
int val = note.getRawNote();
if (lastPitch == Integer.MIN_VALUE) {
lastPitch = val;
continue;
}
int gap = Math.abs(val - lastPitch);
if (gap > max) {
max = gap;
}
lastPitch = val;
}
return max;
}
public int getMinRawNote() {
int min = Integer.MAX_VALUE;
for (Note note : getNotes()) {
int val = note.getRawNote();
if (val < min) {
min = val;
}
}
return min;
}
public boolean isValid() {
int min = getMinPitch();
int max = getMaxPitch();
if (min >= max || min < 0 || getMaxPitchGapAbsolute() > 16) {
return false;
}
return true;
}
public void addInstrumentChange(int instrumentNumber, long startTick) {
if (instrumentNumber == instrument) {
if (trace) {
System.out.println("Duplicate instrument change to " + Midi2MelodyStrings.programs[instrumentNumber]);
}
return;
}
if (trace) {
System.out.println("Adding instrument change for " + instrumentNumber
+ " (" + Midi2MelodyStrings.programs[instrumentNumber] + ") for channel " + channel + " at tick " + startTick);
}
instrumentChanges.add(new InstrumentChange(instrumentNumber, startTick, channel));
instrument = instrumentNumber;
}
public Sequence toSequence() throws InvalidMidiDataException {
Sequence sequence = new Sequence(Sequence.PPQ, resolution);
Track track = sequence.createTrack();
if (trace) {
System.out.println("Playing track " + trackNumber + ", channel " + channel);
}
for (InstrumentChange change : instrumentChanges) {
change.addMidiEvents(track);
}
for (Note note : notes) {
note.addMidiEvents(track);
}
return sequence;
}
public void play(Sequencer sequencer) throws MidiUnavailableException, InvalidMidiDataException {
Sequence sequence = toSequence();
sequencer.setSequence(sequence);
sequencer.setTickPosition(0);
sequencer.open();
sequencer.start();
}
public long getStartTick() {
return startTick;
}
public long getEndTick() {
return notes.get(notes.size() - 1).getEndTick();
}
public int getNumberOfDistinctPitches() {
int counts[] = new int[128];
int count = 0;
for (Note note : getNotes()) {
if (counts[note.getRawNote()] == 0) {
count++;
}
counts[note.getRawNote()]++;
}
if (count < 3) {
System.out.print(count + " ");
}
return count;
}
public long getDuration() {
return getEndTick() - getStartTick();
}
public double getProportionSilence() {
long totalDuration = getDuration();
long totalRests = 0;
Note lastNote = null;
for (Note note : notes) {
if (lastNote != null) {
totalRests += note.getStartTick() - lastNote.getEndTick();
}
lastNote = note;
}
return (0.0 + totalRests) / totalDuration;
}
public long getLongestNoteDuration() {
long longest = 0;
for (Note note : getNotes()) {
if (note.getDuration() > longest) {
longest = note.getDuration();
}
}
return longest;
}
public Iterable<Note> getNotes() {
final Iterator<Note> iterator = notes.iterator();
return new Iterable<Note>() {
@Override
public Iterator<Note> iterator() {
return iterator;
}
};
}
public long getShortetNoteDuration() {
long shortest = Long.MAX_VALUE;
for (Note note : getNotes()) {
long duration = note.getDuration();
if (duration > 0 && duration < shortest) {
shortest = note.getDuration();
}
}
return shortest;
}
public long getLongestRest() {
long longest = 0;
Note lastNote = notes.get(0);
for (Note note : notes) {
long rest = note.getStartTick() - lastNote.getEndTick();
if (rest > longest) {
longest = rest;
}
lastNote = note;
}
return longest;
}
public int getTrack() {
return trackNumber;
}
public int getChannel() {
return channel;
}
public void verifyMonotonicIncreasing() {
NoteOrInstrumentChange previous = null;
for (NoteOrInstrumentChange noteOrInstrumentChange : notes) {
if (previous != null) {
if (previous.getStartTick() > noteOrInstrumentChange.getStartTick()) {
System.err.println("Not monitonic: " + previous + " and " + noteOrInstrumentChange);
}
}
previous = noteOrInstrumentChange;
}
}
// Return count removed
public int removeAllButHigherOrLowerNotes(boolean higher) {
//throw new RuntimeException("Not implemented yet");
int countRemoved = 0;
Iterator<Note> iterator = notes.iterator();
while (iterator.hasNext()) {
Note note = iterator.next();
if (aHigherOrLowerPitchedNoteOverlapsThisNote(note, higher)) {
iterator.remove();
countRemoved++;
}
}
return countRemoved;
}
private boolean aHigherOrLowerPitchedNoteOverlapsThisNote(Note note1, boolean higher) {
for (Note note2 : getNotes()) {
if (note2.getStartTick() >= note1.getEndTick()) {
break;
}
if ((higher ? note2.getRawNote() > note1.getRawNote() : note2.getRawNote() < note1.getRawNote()) && ticksOverlapInTime(note1, note2)) {
return true;
}
}
return false;
}
private boolean ticksDontOverlapInTime(Note note1, Note note2) {
return note1.getEndTick() <= note2.getStartTick() || note2.getEndTick() <= note1.getStartTick();
}
private boolean ticksOverlapInTime(Note note1, Note note2) {
return !ticksDontOverlapInTime(note1, note2);
}
public int countOfNotesHavingPolyphony() {
verifyMonotonicIncreasing();
Set<Note> notesOn = new HashSet<Note>();
int count = 0;
for (Note note : getNotes()) {
Iterator<Note> iterator = notesOn.iterator();
while (iterator.hasNext()) {
Note onNote = iterator.next();
if (note.getStartTick() >= onNote.endTick()) {
iterator.remove();
} else {
count++;
}
}
notesOn.add(note);
}
return count;
}
@Override
public int compareTo(NoteSequence other) {
int diff = trackNumber - other.trackNumber;
if (diff != 0) {
return diff;
}
diff = channel - other.channel;
if (diff != 0) {
return diff;
}
long diffLong = startTick - other.startTick;
if (diffLong > 0) {
return 1;
}
if (diffLong < 0) {
return -1;
}
return 0;
}
public boolean equals(Object other) {
return compareTo((NoteSequence) other) == 0;
}
public void removeLeadingSilence() {
long firstRealNoteTick = getFirstRealNoteTick();
if (firstRealNoteTick >= 0) {
for (NoteOrInstrumentChange note : notes) {
if (note.getStartTick() >= firstRealNoteTick) {
note.setStartTick(1 + note.getStartTick() - firstRealNoteTick);
}
}
}
}
private long getFirstRealNoteTick() {
return notes.size() > 0 ? notes.get(0).getStartTick() : -1;
}
public void add(Note note) {
averageNoteDuration = -1;
notes.add(note);
}
public long getLastTick() {
return notes.isEmpty() ? 0L : notes.get(notes.size() - 1).getStartTick();
}
public void toString(StringBuilder sb, boolean verbose) {
sb.append("NoteSequence with track = " + trackNumber);
sb.append(", channel = " + channel);
sb.append(", noteCount = " + notes.size());
sb.append(", count of polyphonic notes = " + countOfNotesHavingPolyphony());
sb.append(", startTick = " + startTick);
sb.append(", and " + notes.size() + " notes");
if (verbose) {
for (NoteOrInstrumentChange note : notes) {
sb.append(" ");
sb.append(note);
sb.append("\n");
}
}
}
public int getMinPitch() {
int minPitch = Integer.MAX_VALUE;
for (Note note : notes) {
if (note.getRawNote() < minPitch) {
minPitch = note.getRawNote();
}
}
return minPitch;
}
public int getMaxPitch() {
int maxPitch = 0;
for (Note note : notes) {
if (note.getRawNote() > maxPitch) {
maxPitch = note.getRawNote();
}
}
return maxPitch;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb, false);
return sb.toString();
}
public String toString(boolean verbose) {
StringBuilder sb = new StringBuilder();
toString(sb, verbose);
return sb.toString();
}
public int getLength() {
return notes.size();
}
public Note get(int i) {
return notes.get(i);
}
public int getLengthOfLongestSequenceOfRepeatedNotes() {
int count = 0;
int max = 0;
int lastRawNote = -1;
for (Note note : getNotes()) {
int rawNote = note.getRawNote();
if (rawNote == lastRawNote) {
count++;
if (count > max) {
max = count;
}
} else {
count = 0;
}
lastRawNote = rawNote;
}
return max;
}
public int getNumberOfRepeatedNotes() {
int count = 0;
int lastRawNote = -1;
for (Note note : getNotes()) {
int rawNote = note.getRawNote();
if (rawNote == lastRawNote) {
count++;
}
lastRawNote = rawNote;
}
return count;
}
public double getNumberOfNotes() {
return notes.size();
}
}