View Javadoc
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 }