1 : <?php
2 :
3 : /**
4 : * \Midi\Parsing\EventParser
5 : *
6 : * @package Midi
7 : * @subpackage Parsing
8 : * @copyright © 2009 Tommy Montgomery <http://phpmidiparser.com/>
9 : * @since 1.0
10 : */
11 :
12 : namespace Midi\Parsing;
13 :
14 : use \Midi\Event;
15 : use \Midi\Util\Util;
16 :
17 : /**
18 : * Class for parsing MIDI events
19 : *
20 : * @package Midi
21 : * @subpackage Parsing
22 : * @since 1.0
23 : */
24 1 : class EventParser extends Parser {
25 :
26 : /**
27 : * The current continuation event type
28 : *
29 : * @var int|null
30 : */
31 : protected $continuationEvent;
32 :
33 : /**
34 : * Constructor
35 : *
36 : * @since 1.0
37 : */
38 : public function __construct() {
39 40 : parent::__construct();
40 :
41 40 : $this->continuationEvent = null;
42 40 : }
43 :
44 : /**
45 : * Channel event factory
46 : *
47 : * @since 1.0
48 : *
49 : * @param int $type See {@link EventType}
50 : * @param int $channel Valid values: 0-15
51 : * @param int $param1
52 : * @param int|null $param2
53 : * @throws {@link MidiException}
54 : * @return ChannelEvent
55 : */
56 : public function getChannelEvent($type, $channel, $param1, $param2 = null) {
57 : switch ($type) {
58 2 : case Event\EventType::NOTE_OFF:
59 1 : $class = '\Midi\Event\NoteOffEvent';
60 1 : break;
61 2 : case Event\EventType::NOTE_ON:
62 1 : $class = '\Midi\Event\NoteOnEvent';
63 1 : break;
64 2 : case Event\EventType::NOTE_AFTERTOUCH:
65 1 : $class = '\Midi\Event\NoteAftertouchEvent';
66 1 : break;
67 2 : case Event\EventType::CONTROLLER:
68 1 : $class = '\Midi\Event\ControllerEvent';
69 1 : break;
70 2 : case Event\EventType::PROGRAM_CHANGE:
71 1 : $class = '\Midi\Event\ProgramChangeEvent';
72 1 : break;
73 2 : case Event\EventType::CHANNEL_AFTERTOUCH:
74 1 : $class = '\Midi\Event\ChannelAftertouchEvent';
75 1 : break;
76 2 : case Event\EventType::PITCH_BEND:
77 1 : $class = '\Midi\Event\PitchBendEvent';
78 1 : break;
79 1 : default:
80 1 : throw new \Midi\MidiException('Invalid channel event');
81 1 : }
82 :
83 1 : return new $class($channel, $param1, $param2);
84 : }
85 :
86 : /**
87 : * Meta event factory
88 : *
89 : * If the event type does not exist, then an {@link UnknownMetaEvent}
90 : * object is returned.
91 : *
92 : * @since 1.0
93 : * @uses Util::unpack()
94 : *
95 : * @param int $type See {@link MetaEventType}
96 : * @param string|binary $data
97 : * @return MetaEvent
98 : */
99 : public function getMetaEvent($type, $data) {
100 : switch ($type) {
101 2 : case Event\MetaEventType::SEQUENCE_NUMBER:
102 1 : $data = Util::unpack($data);
103 1 : return new Event\SequenceNumberEvent($data[0], $data[1]);
104 2 : case Event\MetaEventType::TEXT_EVENT:
105 1 : return new Event\TextEvent($data);
106 2 : case Event\MetaEventType::COPYRIGHT_NOTICE:
107 1 : return new Event\CopyrightNoticeEvent($data);
108 2 : case Event\MetaEventType::TRACK_NAME:
109 1 : return new Event\TrackNameEvent($data);
110 2 : case Event\MetaEventType::INSTRUMENT_NAME:
111 1 : return new Event\InstrumentNameEvent($data);
112 2 : case Event\MetaEventType::LYRICS:
113 1 : return new Event\LyricsEvent($data);
114 2 : case Event\MetaEventType::MARKER:
115 1 : return new Event\MarkerEvent($data);
116 2 : case Event\MetaEventType::CUE_POINT:
117 1 : return new Event\CuePointEvent($data);
118 2 : case Event\MetaEventType::END_OF_TRACK:
119 1 : return new Event\EndOfTrackEvent();
120 2 : case Event\MetaEventType::CHANNEL_PREFIX:
121 1 : $data = Util::unpack($data);
122 1 : return new Event\ChannelPrefixEvent($data[0]);
123 2 : case Event\MetaEventType::SET_TEMPO:
124 1 : $data = Util::unpack($data);
125 1 : $mpqn = ($data[0] << 16) | ($data[1] << 8) | $data[2];
126 1 : return new Event\SetTempoEvent($mpqn);
127 2 : case Event\MetaEventType::SMPTE_OFFSET:
128 1 : $data = Util::unpack($data);
129 1 : $frameRate = ($data[0] >> 5) & 0xFF;
130 1 : $hour = $data[0] & 0x1F;
131 1 : $minute = $data[1];
132 1 : $second = $data[2];
133 1 : $frame = $data[3];
134 1 : $subFrame = $data[4];
135 1 : return new Event\SmpteOffsetEvent($frameRate, $hour, $minute, $second, $frame, $subFrame);
136 2 : case Event\MetaEventType::TIME_SIGNATURE:
137 1 : $data = Util::unpack($data);
138 1 : return new Event\TimeSignatureEvent($data[0], pow(2, $data[1]), $data[2], $data[3]);
139 2 : case Event\MetaEventType::KEY_SIGNATURE:
140 1 : $data = Util::unpack($data);
141 1 : return new Event\KeySignatureEvent($data[0], $data[1]);
142 2 : case Event\MetaEventType::SEQUENCER_SPECIFIC:
143 1 : return new Event\SequencerSpecificEvent($data);
144 1 : default:
145 1 : return new Event\UnknownMetaEvent($data);
146 1 : }
147 : }
148 :
149 : /**
150 : * System exclusive event factory
151 : *
152 : * @since 1.0
153 : *
154 : * @param string|binary $data
155 : * @return SystemExclusiveEvent
156 : */
157 : public function getSystemExclusiveEvent($data) {
158 1 : return new Event\SystemExclusiveEvent($data);
159 : }
160 :
161 : /**
162 : * @since 1.0
163 : * @uses read()
164 : * @uses Util::unpack()
165 : * @uses parseChannelEvent()
166 : * @uses parseMetaEvent()
167 : * @uses parseSystemExclusiveEvent()
168 : *
169 : * @throws {@link ParseException}
170 : * @return Event
171 : */
172 : public function parse() {
173 6 : $byte = $this->read(1, true);
174 :
175 6 : $eventType = Util::unpack($byte);
176 6 : $eventType = $eventType[0];
177 6 : $isContinuation = false;
178 :
179 6 : if ($eventType < 0x80) {
180 3 : if ($this->continuationEvent === null) {
181 1 : throw new ParseException('Invalid event: first byte must be greater than or equal to 0x80');
182 : } else {
183 2 : $eventType = $this->continuationEvent;
184 2 : $isContinuation = true;
185 : //rewind one byte so that parseChannelEvent() doesn't throw an exception
186 : //when it can't find two more bytes
187 2 : $this->file->fseek(-1, SEEK_CUR);
188 : }
189 2 : } else {
190 5 : $this->continuationEvent = $eventType;
191 : }
192 :
193 5 : if ($eventType < 0xF0) {
194 2 : return $this->parseChannelEvent($eventType, $isContinuation);
195 3 : } else if ($eventType === 0xFF) {
196 1 : return $this->parseMetaEvent();
197 2 : } else if ($eventType === 0xF0) {
198 1 : return $this->parseSystemExclusiveEvent();
199 : }
200 :
201 1 : throw new ParseException('Unsupported event type: 0x' . strtoupper(dechex($eventType)));
202 : }
203 :
204 : /**
205 : * Parses the buffer stream for a channel event
206 : *
207 : * @since 1.0
208 : * @uses Util::unpack()
209 : * @uses getChannelEvent()
210 : *
211 : * @param int $eventType See {@link EventType}
212 : * @param bool $isContinuation Whether the event is a continuation of a previous event
213 : * @return ChannelEvent
214 : */
215 : protected function parseChannelEvent($eventType, $isContinuation) {
216 2 : $type = $eventType & 0xF0;
217 2 : if ($type === 0xC0 || $type === 0xD0) {
218 1 : $data = Util::unpack($this->read(1, true));
219 1 : $data[1] = null;
220 1 : } else {
221 1 : $data = Util::unpack($this->read(2, true));
222 : }
223 :
224 2 : $event = $this->getChannelEvent($eventType & 0xF0, $eventType & 0x0F, $data[0], $data[1]);
225 2 : if ($isContinuation) {
226 2 : $event->setContinuation(true);
227 2 : }
228 :
229 2 : return $event;
230 : }
231 :
232 : /**
233 : * Parses the buffer stream for a meta event
234 : *
235 : * @since 1.0
236 : * @uses read()
237 : * @uses Util::unpack()
238 : * @uses getDelta()
239 : * @uses getMetaEvent()
240 : *
241 : * @return MetaEvent
242 : */
243 : protected function parseMetaEvent() {
244 1 : $metaEventType = Util::unpack($this->read(1, true));
245 1 : $metaEventType = $metaEventType[0];
246 :
247 1 : $length = $this->getDelta();
248 1 : $data = $this->read($length, true);
249 1 : return $this->getMetaEvent($metaEventType, $data);
250 : }
251 :
252 : /**
253 : * Parses the buffer stream for a system exclusive event
254 : *
255 : * @since 1.0
256 : * @uses getDelta()
257 : * @uses read()
258 : * @uses getSystemExclusiveEvent()
259 : *
260 : * @return SystemExclusiveEvent
261 : */
262 : protected function parseSystemExclusiveEvent() {
263 1 : $length = $this->getDelta();
264 1 : $data = $this->read($length, true);
265 1 : return $this->getSystemExclusiveEvent(str_split($data));
266 : }
267 :
268 : }
269 :
|