<commit_before>package com.digero.maestro.midi;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;

import com.digero.common.midi.IMidiConstants;
import com.digero.common.midi.KeySignature;
import com.digero.common.midi.MidiConstants;
import com.digero.common.midi.Note;
import com.digero.common.midi.TimeSignature;
import com.digero.maestro.abc.TimingInfo;
import com.sun.media.sound.MidiUtils;

public class TrackInfo implements IMidiConstants {
	private SequenceInfo sequenceInfo;

	private Track track;
	private int trackNumber;
	private String name;
	private TimeSignature timeSignature = null;
	private KeySignature keySignature = null;
	private Set<Integer> instruments;
	private List<NoteEvent> noteEvents;
	private SortedSet<Integer> notesInUse;
	private boolean isDrumTrack;

	@SuppressWarnings("unchecked")
	TrackInfo(SequenceInfo parent, Track track, int trackNumber, MidiUtils.TempoCache tempoCache,
			SequenceDataCache sequenceCache) throws InvalidMidiDataException {
		this.sequenceInfo = parent;
		this.track = track;
		this.trackNumber = trackNumber;

		Sequence song = sequenceInfo.getSequence();

		instruments = new HashSet<Integer>();
		noteEvents = new ArrayList<NoteEvent>();
		notesInUse = new TreeSet<Integer>();
		List<NoteEvent>[] notesOn = new List[16];
		int notesNotTurnedOff = 0;

		int[] pitchBend = new int[16];
		for (int j = 0, sz = track.size(); j < sz; j++) {
			MidiEvent evt = track.get(j);
			MidiMessage msg = evt.getMessage();

			if (msg instanceof ShortMessage) {
				ShortMessage m = (ShortMessage) msg;
				int cmd = m.getCommand();
				int c = m.getChannel();

				if (noteEvents.isEmpty())
					isDrumTrack = (c == DRUM_CHANNEL);
				else if (isDrumTrack != (c == DRUM_CHANNEL))
					System.err.println("Track contains both notes and drums");

				if (notesOn[c] == null)
					notesOn[c] = new ArrayList<NoteEvent>();

				if (cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF) {
					int noteId = m.getData1() + (isDrumTrack ? 0 : pitchBend[c]);
					int velocity = m.getData2() * sequenceCache.getVolume(c, evt.getTick()) / MAX_VOLUME;
					long micros = MidiUtils.tick2microsecond(song, evt.getTick(), tempoCache);

					if (cmd == ShortMessage.NOTE_ON && velocity > 0) {
						Note note = Note.fromId(noteId);
						if (note == null)
							throw new InvalidMidiDataException("Encountered unrecognized note ID: " + noteId);

						NoteEvent ne = new NoteEvent(note, velocity, micros, micros);

						Iterator<NoteEvent> onIter = notesOn[c].iterator();
						while (onIter.hasNext()) {
							NoteEvent on = onIter.next();
							if (on.note.id == ne.note.id) {
								onIter.remove();
								noteEvents.remove(on);
								notesNotTurnedOff++;
								break;
							}
						}

						if (!isDrumTrack) {
							instruments.add(sequenceCache.getInstrument(c, evt.getTick()));
						}
						noteEvents.add(ne);
						notesInUse.add(ne.note.id);
						notesOn[c].add(ne);
					}
					else {
						Iterator<NoteEvent> iter = notesOn[c].iterator();
						while (iter.hasNext()) {
							NoteEvent ne = iter.next();
							if (ne.note.id == noteId) {
								iter.remove();
								ne.endMicros = micros;
								break;
							}
						}
					}
				}
				else if (cmd == ShortMessage.PITCH_BEND && !isDrumTrack) {
					final int STEP_SIZE = ((1 << 14) - 1) / 4;
					int bend = ((m.getData1() | (m.getData2() << 7)) + STEP_SIZE / 2) / STEP_SIZE - 2;
					long micros = MidiUtils.tick2microsecond(song, evt.getTick(), tempoCache);

					int bendNew = sequenceCache.getPitchBend(evt);

					//System.out.println("[" + c + " @ " +Util.formatDuration(micros) + "] Bend Old: " + bend + " / New: " + bendNew);

					bend = bendNew;

					if (bend != pitchBend[c]) {
						List<NoteEvent> bentNotes = new ArrayList<NoteEvent>();
						for (NoteEvent ne : notesOn[c]) {
							ne.endMicros = micros;
							// If the note is too short, just skip it
							if (ne.getLength() < TimingInfo.SHORTEST_NOTE_MICROS) {
								noteEvents.remove(ne);
								micros = ne.startMicros;
							}

							Note bn = Note.fromId(ne.note.id + bend - pitchBend[c]);
							NoteEvent bne = new NoteEvent(bn, ne.velocity, micros, micros);
							noteEvents.add(bne);
							bentNotes.add(bne);
						}
						notesOn[c] = bentNotes;
						pitchBend[c] = bend;
					}
				}
			}
			else if (msg instanceof MetaMessage) {
				MetaMessage m = (MetaMessage) msg;
				int type = m.getType();

				if (type == META_TRACK_NAME && name == null) {
					try {
						byte[] data = m.getData();
						String tmp = new String(data, 0, data.length, "US-ASCII").trim();
						if (tmp.length() > 0 && !tmp.equalsIgnoreCase("untitled")
								&& !tmp.equalsIgnoreCase("WinJammer Demo"))
							name = tmp;
					}
					catch (UnsupportedEncodingException ex) {
						// Ignore.  This should never happen...
					}
				}
				else if (type == META_KEY_SIGNATURE && keySignature == null) {
					keySignature = new KeySignature(m);
				}
				else if (type == META_TIME_SIGNATURE && timeSignature == null) {
					timeSignature = new TimeSignature(m);<commit_msg>Workaround bug where notes can get "bent" out of the 0-127 range.<commit_after>package com.digero.maestro.midi;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;

import com.digero.common.midi.IMidiConstants;
import com.digero.common.midi.KeySignature;
import com.digero.common.midi.MidiConstants;
import com.digero.common.midi.Note;
import com.digero.common.midi.TimeSignature;
import com.digero.maestro.abc.TimingInfo;
import com.sun.media.sound.MidiUtils;

public class TrackInfo implements IMidiConstants {
	private SequenceInfo sequenceInfo;

	private Track track;
	private int trackNumber;
	private String name;
	private TimeSignature timeSignature = null;
	private KeySignature keySignature = null;
	private Set<Integer> instruments;
	private List<NoteEvent> noteEvents;
	private SortedSet<Integer> notesInUse;
	private boolean isDrumTrack;

	@SuppressWarnings("unchecked")
	TrackInfo(SequenceInfo parent, Track track, int trackNumber, MidiUtils.TempoCache tempoCache,
			SequenceDataCache sequenceCache) throws InvalidMidiDataException {
		this.sequenceInfo = parent;
		this.track = track;
		this.trackNumber = trackNumber;

		Sequence song = sequenceInfo.getSequence();

		instruments = new HashSet<Integer>();
		noteEvents = new ArrayList<NoteEvent>();
		notesInUse = new TreeSet<Integer>();
		List<NoteEvent>[] notesOn = new List[16];
		int notesNotTurnedOff = 0;

		int[] pitchBend = new int[16];
		for (int j = 0, sz = track.size(); j < sz; j++) {
			MidiEvent evt = track.get(j);
			MidiMessage msg = evt.getMessage();

			if (msg instanceof ShortMessage) {
				ShortMessage m = (ShortMessage) msg;
				int cmd = m.getCommand();
				int c = m.getChannel();

				if (noteEvents.isEmpty())
					isDrumTrack = (c == DRUM_CHANNEL);
				else if (isDrumTrack != (c == DRUM_CHANNEL))
					System.err.println("Track contains both notes and drums");

				if (notesOn[c] == null)
					notesOn[c] = new ArrayList<NoteEvent>();

				if (cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF) {
					int noteId = m.getData1() + (isDrumTrack ? 0 : pitchBend[c]);
					int velocity = m.getData2() * sequenceCache.getVolume(c, evt.getTick()) / MAX_VOLUME;
					long micros = MidiUtils.tick2microsecond(song, evt.getTick(), tempoCache);

					if (cmd == ShortMessage.NOTE_ON && velocity > 0) {
						Note note = Note.fromId(noteId);
						if (note == null) {
//							throw new InvalidMidiDataException("Encountered unrecognized note ID: " + noteId);
							continue; // Note was probably bent out of range. Not great, but not a reason to fail.
						}

						NoteEvent ne = new NoteEvent(note, velocity, micros, micros);

						Iterator<NoteEvent> onIter = notesOn[c].iterator();
						while (onIter.hasNext()) {
							NoteEvent on = onIter.next();
							if (on.note.id == ne.note.id) {
								onIter.remove();
								noteEvents.remove(on);
								notesNotTurnedOff++;
								break;
							}
						}

						if (!isDrumTrack) {
							instruments.add(sequenceCache.getInstrument(c, evt.getTick()));
						}
						noteEvents.add(ne);
						notesInUse.add(ne.note.id);
						notesOn[c].add(ne);
					}
					else {
						Iterator<NoteEvent> iter = notesOn[c].iterator();
						while (iter.hasNext()) {
							NoteEvent ne = iter.next();
							if (ne.note.id == noteId) {
								iter.remove();
								ne.endMicros = micros;
								break;
							}
						}
					}
				}
				else if (cmd == ShortMessage.PITCH_BEND && !isDrumTrack) {
					final int STEP_SIZE = ((1 << 14) - 1) / 4;
					int bend = ((m.getData1() | (m.getData2() << 7)) + STEP_SIZE / 2) / STEP_SIZE - 2;
					long micros = MidiUtils.tick2microsecond(song, evt.getTick(), tempoCache);

					int bendNew = sequenceCache.getPitchBend(evt);

					//System.out.println("[" + c + " @ " +Util.formatDuration(micros) + "] Bend Old: " + bend + " / New: " + bendNew);

					bend = bendNew;

					if (bend != pitchBend[c]) {
						List<NoteEvent> bentNotes = new ArrayList<NoteEvent>();
						for (NoteEvent ne : notesOn[c]) {
							ne.endMicros = micros;
							// If the note is too short, just skip it
							if (ne.getLength() < TimingInfo.SHORTEST_NOTE_MICROS) {
								noteEvents.remove(ne);
								micros = ne.startMicros;
							}

							Note bn = Note.fromId(ne.note.id + bend - pitchBend[c]);
							// If bn is null , the note was bent out of the 0-127 range. 
							// Not much we can do except skip it.
							if (bn != null) {
								NoteEvent bne = new NoteEvent(bn, ne.velocity, micros, micros);
								noteEvents.add(bne);
								bentNotes.add(bne);
							}
						}
						notesOn[c] = bentNotes;
						pitchBend[c] = bend;
					}
				}
			}
			else if (msg instanceof MetaMessage) {
				MetaMessage m = (MetaMessage) msg;
				int type = m.getType();

				if (type == META_TRACK_NAME && name == null) {
					try {
						byte[] data = m.getData();
						String tmp = new String(data, 0, data.length, "US-ASCII").trim();
						if (tmp.length() > 0 && !tmp.equalsIgnoreCase("untitled")
								&& !tmp.equalsIgnoreCase("WinJammer Demo"))
							name = tmp;
					}
					catch (UnsupportedEncodingException ex) {
						// Ignore.  This should never happen...
					}
				}
				else if (type == META_KEY_SIGNATURE && keySignature == null) {
					keySignature = new KeySignature(m);
				}
				else if (type == META_TIME_SIGNATURE && timeSignature == null) {
					timeSignature = new TimeSignature(m);
{'fraction_non_alphanumeric': 0.1305226174791392, 'fraction_numerical': 0.004918752744839701, 'mean_word_length': 3.103063063063063, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '921967', 'n_tokens_mistral': 4738, 'n_tokens_neox': 3715, 'n_words': 870}