1 package net.kreatious.pianoleopard.midi.track;
2
3 import static java.util.stream.Collectors.toList;
4
5 import java.io.File;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.List;
9 import java.util.Optional;
10 import java.util.concurrent.CopyOnWriteArrayList;
11 import java.util.stream.Stream;
12
13 import javax.sound.midi.InvalidMidiDataException;
14 import javax.sound.midi.Sequence;
15 import javax.sound.midi.Track;
16
17 import net.kreatious.pianoleopard.midi.event.TempoCache;
18
19 /**
20 * Represents a parsed MIDI sequence containing multiple parsed tracks
21 *
22 * @author Jay-R Studer
23 */
24 public class ParsedSequence {
25 private final List<ParsedTrack> inactiveTracks = new CopyOnWriteArrayList<>();
26 private final List<ParsedTrack> activeTracks = new CopyOnWriteArrayList<>();
27 private final List<ParsedTrack> tracks;
28 private final Sequence sequence;
29
30 /**
31 * Originally set to null to signify that the value has not been set -- this
32 * is contrary to the normal expectations for an optional field
33 */
34 private Optional<File> file = null;
35
36 private ParsedSequence(Sequence sequence, Track[] tracks, TempoCache cache) {
37 this.sequence = sequence;
38 this.tracks = Stream.of(tracks).map(track -> new ImmutableParsedTrack(track, cache)).collect(toList());
39 activeTracks.addAll(this.tracks);
40 }
41
42 /**
43 * Gets the file that this sequence was originally created from.
44 * <p>
45 * If the returned file is not empty, subsequent calls to this function are
46 * guaranteed to return the same value.
47 *
48 * @return An optional containing the original file this sequence was
49 * created with.
50 */
51 public Optional<File> getFile() {
52 return Optional.ofNullable(file).orElse(Optional.empty());
53 }
54
55 /**
56 * Sets the file that this sequence was originally created from.
57 * <p>
58 * This setter may only be called once per sequence. Subsequent calls will
59 * throw an exception.
60 *
61 * @param file
62 * the file for this sequence
63 * @throws IllegalStateException
64 * if the file has already been set.
65 */
66 public void setFile(Optional<File> file) {
67 if (this.file != null) {
68 throw new IllegalStateException("Cannot set the file to " + file + "; already set to " + this.file);
69 }
70 this.file = file;
71 }
72
73 /**
74 * Gets all tracks stored by this parsed MIDI sequence.
75 *
76 * @return a read only view of all parsed tracks contained in this sequence
77 */
78 public List<ParsedTrack> getTracks() {
79 return Collections.unmodifiableList(tracks);
80 }
81
82 /**
83 * Gets the active tracks stored by this parsed MIDI sequence.
84 * <p>
85 * Active tracks are those selected by the user for practice.
86 *
87 * @return a read only unordered view of the active parsed tracks contained
88 * in this sequence
89 */
90 public Collection<ParsedTrack> getActiveTracks() {
91 return Collections.unmodifiableCollection(activeTracks);
92 }
93
94 /**
95 * Gets the inactive tracks stored by this parsed MIDI sequence.
96 * <p>
97 * Inactive tracks are those not selected by the user for practice.
98 *
99 * @return a read only unordered view of the inactive parsed tracks
100 * contained in this sequence
101 */
102 public Collection<ParsedTrack> getInactiveTracks() {
103 return Collections.unmodifiableCollection(inactiveTracks);
104 }
105
106 /**
107 * Sets the specified track as active.
108 * <p>
109 * If the track is already active, no changes occur.
110 *
111 * @param track
112 * the parsed track in this sequence to modify
113 * @param active
114 * true if the track should be active, otherwise false
115 * @throws IllegalArgumentException
116 * if the track is not contained in this sequence
117 */
118 public void setTrackActive(ParsedTrack track, boolean active) {
119 if (!tracks.contains(track)) {
120 throw new IllegalArgumentException("Specified track is not contained by this container.");
121 }
122
123 if (active && inactiveTracks.contains(track)) {
124 inactiveTracks.remove(track);
125 activeTracks.add(track);
126 } else if (!active && activeTracks.contains(track)) {
127 activeTracks.remove(track);
128 inactiveTracks.add(track);
129 }
130 }
131
132 /**
133 * Gets the original MIDI sequence used to create this parsed MIDI sequence.
134 *
135 * @return the original MIDI {@link Sequence}
136 */
137 public Sequence getSequence() {
138 return sequence;
139 }
140
141 /**
142 * Returns an empty parsed sequence containing nothing.
143 *
144 * @return a new empty {@link ParsedSequence}
145 */
146 public static ParsedSequence createEmpty() {
147 try {
148 final Sequence sequence = new Sequence(Sequence.SMPTE_25, 1);
149 return new ParsedSequence(sequence, new Track[0], new TempoCache(sequence));
150 } catch (final InvalidMidiDataException e) {
151 throw new IllegalStateException(e);
152 }
153 }
154
155 /**
156 * Parses a MIDI sequence, arranging it by tracks
157 *
158 * @param sequence
159 * the sequence to parse
160 * @return a new {@link ParsedSequence}
161 */
162 public static ParsedSequence parseByTracks(Sequence sequence) {
163 return new ParsedSequence(sequence, sequence.getTracks(), new TempoCache(sequence));
164 }
165 }