1 package net.kreatious.pianoleopard.midi;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Optional;
8 import java.util.concurrent.CopyOnWriteArrayList;
9 import java.util.function.Consumer;
10
11 import javax.sound.midi.MidiDevice;
12 import javax.sound.midi.MidiDevice.Info;
13 import javax.sound.midi.MidiMessage;
14 import javax.sound.midi.MidiUnavailableException;
15 import javax.sound.midi.Receiver;
16
17 import net.kreatious.pianoleopard.intervalset.IntervalSet;
18 import net.kreatious.pianoleopard.midi.event.Event;
19 import net.kreatious.pianoleopard.midi.event.EventFactory;
20 import net.kreatious.pianoleopard.midi.event.EventPair;
21 import net.kreatious.pianoleopard.midi.event.NoteEvent;
22 import net.kreatious.pianoleopard.midi.event.PedalEvent;
23 import net.kreatious.pianoleopard.midi.track.ParsedSequence;
24 import net.kreatious.pianoleopard.midi.track.ParsedTrack;
25
26
27
28
29
30
31 public class InputModel implements AutoCloseable, ParsedTrack {
32 private Optional<MidiDevice> input = Optional.empty();
33 private final UserNoteRecorder userRecorder = new UserNoteRecorder();
34
35 private final IntervalSet<EventPair<NoteEvent>> notes = new IntervalSet<>();
36 private final IntervalSet<EventPair<PedalEvent>> pedals = new IntervalSet<>();
37
38 private final List<Consumer<? super Info>> inputDeviceListeners = new CopyOnWriteArrayList<>();
39 private final List<Consumer<? super Event>> inputListeners = new CopyOnWriteArrayList<>();
40
41 private InputModel(MidiDevice input) throws MidiUnavailableException {
42 setInputDevice(input);
43 }
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public static InputModel create(OutputModel outputModel) throws MidiUnavailableException {
59 final InputModel input = new InputModel(new InitialMidiDevice());
60 outputModel.addOpenListener(input::setCurrentSequence);
61 outputModel.addPlayListener(input.userRecorder::clear);
62 outputModel.addCurrentTimeListener(input.userRecorder::setCurrentTime);
63 return input;
64 }
65
66 private void setCurrentSequence(@SuppressWarnings("unused") ParsedSequence sequence) {
67 userRecorder.clear();
68 }
69
70 private final class UserNoteRecorder implements Receiver, ParsedTrack {
71 private final Map<Object, NoteEvent> onNotes = new HashMap<>();
72 private final Map<Object, PedalEvent> onPedals = new HashMap<>();
73
74 private long currentTime;
75
76 private synchronized void setCurrentTime(long time) {
77 currentTime = time;
78 }
79
80 @Override
81 public synchronized void send(MidiMessage message, long timeStamp) {
82 EventFactory.create(message, currentTime).ifPresent(this::userPressedEvent);
83 }
84
85 private void userPressedEvent(Event event) {
86 if (event instanceof NoteEvent) {
87 userPressedEvent((NoteEvent) event, onNotes, notes);
88 } else if (event instanceof PedalEvent) {
89 userPressedEvent((PedalEvent) event, onPedals, pedals);
90 }
91 inputListeners.forEach(listener -> listener.accept(event));
92 }
93
94 private <K extends Event> void userPressedEvent(K event, Map<Object, K> onEvents,
95 IntervalSet<EventPair<K>> fullEvents) {
96 synchronized (fullEvents) {
97 final long eventTime = event.getTime();
98 if (event.isOn()) {
99 onEvents.put(event.getSlot(), event);
100 } else {
101 Optional.ofNullable(onEvents.remove(event.getSlot())).ifPresent(onEvent -> {
102 fullEvents.put(onEvent.getTime(), eventTime, new EventPair<>(onEvent, event));
103 });
104 }
105 }
106 }
107
108 @Override
109 public Iterable<EventPair<NoteEvent>> getNotePairs(long low, long high) {
110 return getPairs(low, high, onNotes, notes);
111 }
112
113 @Override
114 public Iterable<EventPair<PedalEvent>> getPedalPairs(long low, long high) {
115 return getPairs(low, high, onPedals, pedals);
116 }
117
118 private synchronized <K extends Event> Iterable<EventPair<K>> getPairs(long low, long high,
119 Map<Object, K> onEvents, IntervalSet<EventPair<K>> fullEvents) {
120 synchronized (fullEvents) {
121 final List<EventPair<K>> result = new ArrayList<>();
122 fullEvents.subSet(low, high).forEach(result::add);
123 onEvents.values().forEach(event -> result.add(new EventPair<>(event, event.createOff(currentTime))));
124 return result;
125 }
126 }
127
128 void clear() {
129 synchronized (notes) {
130 notes.clear();
131 onNotes.clear();
132 }
133 synchronized (pedals) {
134 pedals.clear();
135 onPedals.clear();
136 }
137 }
138
139 @Override
140 public void close() {
141
142 }
143 }
144
145
146
147
148
149
150
151
152
153 public void setInputDevice(MidiDevice input) throws MidiUnavailableException {
154 this.input.ifPresent(MidiDevice::close);
155 this.input = Optional.of(input);
156
157 input.open();
158 input.getTransmitter().setReceiver(userRecorder);
159 userRecorder.clear();
160 inputDeviceListeners.forEach(listener -> listener.accept(input.getDeviceInfo()));
161 }
162
163
164
165
166
167
168
169 public void addInputDeviceListener(Consumer<? super Info> listener) {
170 inputDeviceListeners.add(listener);
171 }
172
173
174
175
176
177
178
179 public void addInputListener(Consumer<? super Event> listener) {
180 inputListeners.add(listener);
181 }
182
183 @Override
184 public void close() {
185 input.ifPresent(MidiDevice::close);
186 }
187
188 @Override
189 public Iterable<EventPair<NoteEvent>> getNotePairs(long low, long high) {
190 return userRecorder.getNotePairs(low, high);
191 }
192
193 @Override
194 public Iterable<EventPair<PedalEvent>> getPedalPairs(long low, long high) {
195 return userRecorder.getPedalPairs(low, high);
196 }
197 }