b5b456348dcff6018d9a73c5bc4b968e62978b24
[ardour.git] / libs / evoral / src / SMFReader.cpp
1 /* This file is part of Evoral.
2  * Copyright(C) 2008 Dave Robillard <http://drobilla.net>
3  * Copyright(C) 2000-2008 Paul Davis
4  *
5  * Evoral is free software; you can redistribute it and/or modify it under the
6  * terms of the GNU General Public License as published by the Free Software
7  * Foundation; either version 2 of the License, or(at your option) any later
8  * version.
9  *
10  * Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <cstring>
20 #include <cstdio>
21 #include <cassert>
22 #include <iostream>
23 #include <glibmm/miscutils.h>
24
25 #include "evoral/midi_util.h"
26 #include "evoral/SMFReader.hpp"
27
28 using namespace std;
29
30 namespace Evoral {
31
32
33 SMFReader::SMFReader(const string& filename)
34         : _fd(NULL)
35         , _ppqn(0)
36         , _track(0)
37         , _track_size(0)
38 {
39         if (filename.length() > 0) {
40                 open(filename);
41         }
42 }
43
44
45 SMFReader::~SMFReader()
46 {
47         if (_fd)
48                 close();
49 }
50
51
52 bool
53 SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
54 {
55         if (_fd)
56                 throw logic_error("Attempt to start new read while write in progress.");
57
58         cout << "Opening SMF file " << filename << " for reading." << endl;
59
60         _fd = fopen(filename.c_str(), "r+");
61
62         if (_fd) {
63                 // Read type (bytes 8..9)
64                 fseek(_fd, 0, SEEK_SET);
65                 char mthd[5];
66                 mthd[4] = '\0';
67                 fread(mthd, 1, 4, _fd);
68                 if (strcmp(mthd, "MThd")) {
69                         cerr << filename << " is not an SMF file, aborting." << endl;
70                         fclose(_fd);
71                         _fd = NULL;
72                         return false;
73                 }
74
75                 // Read type (bytes 8..9)
76                 fseek(_fd, 8, SEEK_SET);
77                 uint16_t type_be = 0;
78                 fread(&type_be, 2, 1, _fd);
79                 _type = GUINT16_FROM_BE(type_be);
80
81                 // Read number of tracks (bytes 10..11)
82                 uint16_t num_tracks_be = 0;
83                 fread(&num_tracks_be, 2, 1, _fd);
84                 _num_tracks = GUINT16_FROM_BE(num_tracks_be);
85
86                 // Read PPQN (bytes 12..13)
87                 uint16_t ppqn_be = 0;
88                 fread(&ppqn_be, 2, 1, _fd);
89                 _ppqn = GUINT16_FROM_BE(ppqn_be);
90
91                 // TODO: Absolute (SMPTE seconds) time support
92                 if ((_ppqn & 0x8000) != 0)
93                         throw UnsupportedTime();
94
95                 seek_to_track(1);
96
97                 return true;
98         } else {
99                 return false;
100         }
101 }
102
103
104 /** Seek to the start of a given track, starting from 1.
105  * Returns true if specified track was found.
106  */
107 bool
108 SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
109 {
110         if (track == 0)
111                 throw logic_error("Seek to track 0 out of range (must be >= 1)");
112
113         if (!_fd)
114                 throw logic_error("Attempt to seek to track on unopened SMF file.");
115
116         unsigned track_pos = 0;
117
118         fseek(_fd, 14, SEEK_SET);
119         char id[5];
120         id[4] = '\0';
121         uint32_t chunk_size = 0;
122
123         while (!feof(_fd)) {
124                 fread(id, 1, 4, _fd);
125
126                 if (!strcmp(id, "MTrk")) {
127                         ++track_pos;
128                 } else {
129                         std::cerr << "Unknown chunk ID " << id << endl;
130                 }
131
132                 uint32_t chunk_size_be;
133                 fread(&chunk_size_be, 4, 1, _fd);
134                 chunk_size = GUINT32_FROM_BE(chunk_size_be);
135
136                 if (track_pos == track)
137                         break;
138
139                 fseek(_fd, chunk_size, SEEK_CUR);
140         }
141
142         if (!feof(_fd) && track_pos == track) {
143                 _track = track;
144                 _track_size = chunk_size;
145                 return true;
146         } else {
147                 return false;
148         }
149 }
150
151
152 /** Read an event from the current position in file.
153  *
154  * File position MUST be at the beginning of a delta time, or this will die very messily.
155  * ev.buffer must be of size ev.size, and large enough for the event.  The returned event
156  * will have it's time field set to it's delta time (so it's the caller's responsibility
157  * to keep track of delta time, even for ignored events).
158  *
159  * Returns event length (including status byte) on success, 0 if event was
160  * skipped (eg a meta event), or -1 on EOF (or end of track).
161  *
162  * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
163  * set to the actual size of the event.
164  */
165 int
166 SMFReader::read_event(size_t    buf_len,
167                       uint8_t*  buf,
168                       uint32_t* ev_size,
169                       uint32_t* delta_time)
170                 throw (std::logic_error, PrematureEOF, CorruptFile)
171 {
172         if (_track == 0)
173                 throw logic_error("Attempt to read from unopened SMF file");
174
175         if (!_fd || feof(_fd)) {
176                 return -1;
177         }
178
179         assert(buf_len > 0);
180         assert(buf);
181         assert(ev_size);
182         assert(delta_time);
183
184         // Running status state
185         static uint8_t  last_status = 0;
186         static uint32_t last_size   = 0;
187
188         *delta_time = read_var_len(_fd);
189         int status = fgetc(_fd);
190         if (status == EOF)
191                 throw PrematureEOF();
192         else if (status > 0xFF)
193                 throw CorruptFile();
194
195         if (status < 0x80) {
196                 if (last_status == 0)
197                         throw CorruptFile();
198                 status = last_status;
199                 *ev_size = last_size;
200                 fseek(_fd, -1, SEEK_CUR);
201         } else {
202                 last_status = status;
203                 *ev_size = midi_event_size(status);
204                 last_size = *ev_size;
205         }
206
207         buf[0] = (uint8_t)status;
208
209         if (status == 0xFF) {
210                 *ev_size = 0;
211                 if (feof(_fd))
212                         throw PrematureEOF();
213                 uint8_t type = fgetc(_fd);
214                 const uint32_t size = read_var_len(_fd);
215                 /*cerr.flags(ios::hex);
216                 cerr << "SMF - meta 0x" << (int)type << ", size = ";
217                 cerr.flags(ios::dec);
218                 cerr << size << endl;*/
219
220                 if ((uint8_t)type == 0x2F) {
221                         return -1; // we hit the logical EOF anyway...
222                 } else {
223                         fseek(_fd, size, SEEK_CUR);
224                         return 0;
225                 }
226         }
227
228         if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
229                 //cerr << "SMF - Skipping event" << endl;
230                 // Skip event, return 0
231                 fseek(_fd, *ev_size - 1, SEEK_CUR);
232                 return 0;
233         } else {
234                 // Read event, return size
235                 if (ferror(_fd))
236                         throw CorruptFile();
237
238                 fread(buf+1, 1, *ev_size - 1, _fd);
239
240                 if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
241                         buf[0] = (0x80 | (buf[0] & 0x0F));
242                         buf[2] = 0x40;
243                 }
244
245                 return *ev_size;
246         }
247 }
248
249
250 void
251 SMFReader::close()
252 {
253         if (_fd)
254                 fclose(_fd);
255
256         _fd = NULL;
257 }
258
259
260 uint32_t
261 SMFReader::read_var_len(FILE* fd) throw (PrematureEOF)
262 {
263         if (feof(fd))
264                 throw PrematureEOF();
265
266         uint32_t value;
267         uint8_t  c;
268
269         if ( (value = getc(fd)) & 0x80 ) {
270                 value &= 0x7F;
271                 do {
272                         if (feof(fd))
273                                 throw PrematureEOF();
274                         value = (value << 7) + ((c = getc(fd)) & 0x7F);
275                 } while (c & 0x80);
276         }
277
278         return value;
279 }
280
281
282 } // namespace Evoral
283