1 /*
2 * @(#)Instrument.h 3.00 3 August 1999
3 *
4 * Copyright (c) 2000 Pete Goodliffe (pete@cthree.org)
5 *
6 * This file is part of TSE3 - the Trax Sequencer Engine version 3.00.
7 *
8 * This library is modifiable/redistributable under the terms of the GNU
9 * General Public License.
10 *
11 * You should have received a copy of the GNU General Public License along
12 * with this program; see the file COPYING. If not, write to the Free Software
13 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14 *
15 */
16
17 #include "tse3/ins/Instrument.h"
18
19 #include "tse3/Progress.h"
20
21 #include <fstream>
22 #include <iostream>
23 #include <sstream>
24 #include <algorithm>
25
26 using namespace TSE3;
27 using namespace TSE3::Ins;
28
29 namespace
30 {
31 /**
32 * The .ins file comment divider. We use this when we write .ins files.
33 *
34 * This is the standard divider from one of Cakewalk's files.
35 */
36 const char *ins_divider = "; ----------------------------------"
37 "------------------------------------\n";
38
39 /**
40 * .ins files come from Windows/MS-DOS. They have ASCII 13 at the ends of
41 * the lines. We need to strip them out before we handle the line. That's
42 * what this we functon does.
43 *
44 * We also need to strip comments from lines. So we do that here too.
45 */
clean_string(std::string & str)46 void clean_string(std::string &str)
47 {
48 if (str[str.length()-1] == 13) // for empty lines?
49 {
50 str = str.substr(0, str.length()-1);
51 }
52 if (str.find(';') != str.npos)
53 {
54 str = str.substr(0,str.find(';'));
55 }
56 }
57 }
58
59
60 /******************************************************************************
61 * Voice class
62 *****************************************************************************/
63
Voice(int bank,int patch)64 Voice::Voice(int bank, int patch)
65 {
66 first = bank;
67 second = patch;
68 }
69
70
Voice(int bankMSB,int bankLSB,int patch)71 Voice::Voice(int bankMSB, int bankLSB, int patch)
72 {
73 first = (bankMSB << 7) + bankLSB;
74 second = patch;
75 }
76
77
operator <(const Voice & v) const78 int Voice::operator <(const Voice &v) const
79 {
80 if (first != v.first)
81 {
82 return first < v.first;
83 }
84 else
85 {
86 return second < v.second;
87 }
88 }
89
90
91 /******************************************************************************
92 * Instrument class
93 *****************************************************************************/
94
Instrument(const std::string & title,const std::string & filename,TSE3::Progress * p)95 Instrument::Instrument(const std::string &title, const std::string &filename,
96 TSE3::Progress *p)
97 : _title(title), _filename(filename),
98 _bankSelMethod(0), _useNotesAsControllers(false),
99 _control(0), _rpn(0), _nrpn(0)
100 {
101 std::ifstream in(filename.c_str());
102 if (in.good())
103 {
104 load(in, p);
105 }
106 }
107
108
setTitle(const std::string & n)109 void Instrument::setTitle(const std::string &n)
110 {
111 _title = n;
112 }
113
114
setBankSelMethod(int b)115 void Instrument::setBankSelMethod(int b)
116 {
117 if (b <= BankSelMethod_Normal || b >= BankSelMethod_Patch)
118 return;
119 _bankSelMethod = b;
120 }
121
122
setUseNotesAsControllers(bool u)123 void Instrument::setUseNotesAsControllers(bool u)
124 {
125 _useNotesAsControllers = u;
126 }
127
128
bankLSB(int index) const129 int Instrument::bankLSB(int index) const
130 {
131 return banks[index] == -1 ? -1 : banks[index] & 0xff;
132 }
133
134
bankMSB(int index) const135 int Instrument::bankMSB(int index) const
136 {
137 return banks[index] == -1 ? -1 : banks[index] >> 7;
138 }
139
140
bank(const Voice &) const141 int Instrument::bank(const Voice &/*voice*/) const
142 {
143 return -2;
144 // XXX
145 #warning This is not implemented
146 }
147
148
patchForBank(int bank) const149 PatchData *Instrument::patchForBank(int bank) const
150 {
151 std::vector<int>::const_iterator i
152 = std::find(banks.begin(), banks.end(), bank);
153
154 if (i == banks.end() && bank != -1)
155 {
156 // Ok, so that failed. Perhaps there's a catch-all patch set?
157 i = std::find(banks.begin(), banks.end(), -1);
158 }
159
160 if (i != banks.end())
161 {
162 return patches[i-banks.begin()];
163 }
164 else
165 {
166 return 0;
167 }
168 }
169
170
patchForBank(int bankLSB,int bankMSB) const171 PatchData *Instrument::patchForBank(int bankLSB, int bankMSB) const
172 {
173 return patchForBank(bankFromBytes(bankLSB, bankMSB));
174 }
175
176
keyForVoice(const Voice & voice) const177 NoteData *Instrument::keyForVoice(const Voice &voice) const
178 {
179 std::vector<std::pair<Voice, NoteData*> >::const_iterator i = keys.begin();
180 while (i != keys.end() && i->first != voice) i++;
181
182 if (i != keys.end())
183 {
184 return i->second;
185 }
186 else
187 {
188 return 0;
189 }
190 }
191
192
isDrum(const Voice & voice) const193 bool Instrument::isDrum(const Voice &voice) const
194 {
195 return
196 std::find(drumFlags.begin(), drumFlags.end(), voice) != drumFlags.end();
197 }
198
199
200 /******************************************************************************
201 * Instrument class saving/loading
202 *****************************************************************************/
203
load(std::istream & in,TSE3::Progress * progress)204 void Instrument::load(std::istream &in, TSE3::Progress *progress)
205 {
206 if (progress)
207 {
208 progress->progressRange(0, 100);
209 progress->progress(0);
210 }
211
212 // 1. Find the ".Instrument Definitions" line (0% - 10%)
213
214 in.seekg(0, std::ios::beg);
215 std::string line;
216 while (!in.eof() && line != ".Instrument Definitions")
217 {
218 std::getline(in, line);
219 clean_string(line);
220 }
221 if (line != ".Instrument Definitions") return;
222 if (progress)
223 {
224 progress->progress(10);
225 }
226
227 // 2. Find the instrument definition section (10% - 20%)
228
229 std::string matchstr = std::string("[") + _title + std::string("]");
230 while (!in.eof() && line != matchstr)
231 {
232 std::getline(in, line);
233 clean_string(line);
234 }
235 if (progress)
236 {
237 progress->progress(20);
238 }
239
240 // 3. Parse each of the bits in it (20% - 100%)
241
242 std::streampos defnFilePos = in.tellg();
243 std::streampos defnEndPos = defnFilePos;
244 if (progress)
245 {
246 // Find how big this section is for %age
247 while (!in.eof() && line.size() != 0)
248 {
249 std::getline(in, line);
250 clean_string(line);
251 if (line[0] == '[') line ="";
252 }
253 defnEndPos = in.tellg();
254 in.seekg(defnFilePos, std::ios::beg);
255 }
256
257 line = " ";
258 while (!in.eof() && line.size() != 0)
259 {
260 if (progress)
261 {
262 progress->progress(20
263 + ((in.tellg()-defnFilePos) * 80
264 / (defnEndPos - defnFilePos)));
265 }
266 std::getline(in, line);
267 clean_string(line);
268 if (line[0] != '[')
269 {
270 parseLine(line, in);
271 }
272 else
273 {
274 line = "";
275 }
276 }
277 if (progress)
278 {
279 progress->progress(100);
280 }
281 }
282
283
parseLine(const std::string & line,std::istream & in)284 void Instrument::parseLine(const std::string &line, std::istream &in)
285 {
286 if (line == "UseNotesAsControllers=1")
287 {
288 _useNotesAsControllers = true;
289 }
290 else if (line.substr(0,8) == "Control=")
291 {
292 std::string title(line.substr(8));
293 delete _control;
294 _control = new ControlData(title, in);
295 }
296 else if (line.substr(0,4) == "RPN=")
297 {
298 std::string title(line.substr(4));
299 delete _rpn;
300 _rpn = new RpnData(title, in);
301 }
302 else if (line.substr(0,5) == "NRPN=")
303 {
304 std::string title(line.substr(5));
305 delete _nrpn;
306 _nrpn = new NrpnData(title, in);
307 }
308 else if (line.substr(0,14) == "BankSelMethod=")
309 {
310 std::istringstream si(line.c_str()+14);
311 si >> _bankSelMethod;
312 }
313 else if (line.substr(0,6) == "Patch[")
314 {
315 std::string bank_s = line.substr(6, line.find(']')-6);
316 int bank = -1;
317 if (bank_s != "*")
318 {
319 std::istringstream si(line.c_str()+6);
320 si >> bank;
321 }
322 std::string title(line.substr(line.find('=')+1));
323 banks.push_back(bank);
324 patches.push_back(new PatchData(title, in));
325 }
326 else if (line.substr(0,4) == "Key[")
327 {
328 std::string bank_s(line.substr(4,line.find(',')-4));
329 int a = line.find(',')+1;
330 std::string patch_s(line.substr(a, line.find(']')-a));
331 int bank = -1, patch = -1;
332 if (bank_s != "*")
333 {
334 std::istringstream si(bank_s);
335 si >> bank;
336 }
337 if (patch_s != "*")
338 {
339 std::istringstream si(patch_s);
340 si >> patch;
341 }
342 std::string title(line.substr(line.find('=')+1));
343
344 // This has been split onto a separate line to keep gcc happy
345 std::pair<Voice, NoteData *> pr
346 (Voice(bank,patch), new NoteData(title, in));
347 keys.push_back(pr);
348 }
349 else if (line.substr(0,5) == "Drum[")
350 {
351 std::string bank_s(line.substr(5,line.find(',')-5));
352 int a = line.find(',')+1;
353 std::string patch_s(line.substr(a, line.find(']')-a));
354 int bank = -1, patch = -1;
355 if (bank_s != "*")
356 {
357 std::istringstream si(bank_s);
358 si >> bank;
359 }
360 if (patch_s != "*")
361 {
362 std::istringstream si(patch_s);
363 si >> patch;
364 }
365 std::string title(line.substr(line.find('=')+1));
366 drumFlags.push_back(Voice(bank,patch));
367 }
368 }
369
370
write(std::ostream & out)371 void Instrument::write(std::ostream &out)
372 {
373 out << "\n"
374 << ins_divider
375 << "; Instrument definition file save by TSE3 library\n"
376 << "; Defines the " << _title << " instrument only\n"
377 << "; Pete Goodliffe\n\n";
378
379 out << ins_divider << "\n.Patch Names\n\n";
380 {
381 std::vector<PatchData*>::iterator ip = patches.begin();
382 while (ip != patches.end())
383 {
384 (*ip)->write(out);
385 ++ip;
386 }
387 }
388
389 out << ins_divider << "\n.Note Names\n\n";
390 {
391 std::vector<std::pair<Voice,NoteData*> >::iterator i = keys.begin();
392 while (i != keys.end())
393 {
394 (*i).second->write(out);
395 ++i;
396 }
397 }
398
399 out << ins_divider << "\n.Controller Names\n\n";
400 if (_control) _control->write(out);
401
402 out << ins_divider << "\n.RPN Names\n\n";
403 // hmm?
404
405 out << ins_divider << "\n.NRPN Names\n\n";
406 if (_nrpn) _nrpn->write(out);
407
408 out << ins_divider << "\n.Instrument Definitions\n\n";
409 out << "[" << _title << "]\n";
410 if (_useNotesAsControllers) out << "UseNotesAsControllers=1\n";
411 if (_control) out << "Control=" << _control->title() << "\n";
412 if (_nrpn) out << "NRPN=" << _nrpn->title() << "\n";
413 if (_bankSelMethod) out << "BankSelMethod=" << _bankSelMethod
414 << "\n";
415 {
416 std::vector<PatchData*>::iterator ip = patches.begin();
417 std::vector<int>::iterator ib = banks.begin();
418 while (ip != patches.end())
419 {
420 out << "Patch[";
421 if (*ib == -1)
422 out << "*";
423 else
424 out << *ib;
425 out << "]=" << (*ip)->title() << "\n";
426 ++ip;
427 ++ib;
428 }
429 }
430 {
431 std::vector<std::pair<Voice,NoteData*> >::iterator i = keys.begin();
432 while (i != keys.end())
433 {
434 out << "Key[";
435 if ((*i).first.bank() == -1)
436 out << "*";
437 else
438 out << (*i).first.bank();
439 out << ",";
440 if ((*i).first.patch() == -1)
441 out << "*";
442 else
443 out << (*i).first.patch();
444 out << "]=" << (*i).second->title() << "\n";
445 ++i;
446 }
447 }
448 {
449 std::vector<Voice>::iterator i = drumFlags.begin();
450 while (i != drumFlags.end())
451 {
452 out << "Drum[";
453 if ((*i).bank() == -1)
454 out << "*";
455 else
456 out << (*i).bank();
457 out << ",";
458 if ((*i).patch() == -1)
459 out << "*";
460 else
461 out << (*i).patch();
462 out << "]=1\n";
463 ++i;
464 }
465 }
466 out << "\n";
467 }
468
469
470 /******************************************************************************
471 * InstrumentData class
472 *****************************************************************************/
473
474 std::string InstrumentData::empty;
475
InstrumentData(std::string const & title,std::string const & insHeading,std::istream & in)476 InstrumentData::InstrumentData(std::string const &title,
477 std::string const &insHeading,
478 std::istream &in)
479 : insHeading(insHeading), _title(title)
480 {
481 for (int n = 0; n < 128; ++n) _names[n] = 0;
482 load(_title, in);
483 }
484
485
load(const std::string & secname,std::istream & in)486 void InstrumentData::load(const std::string &secname, std::istream &in)
487 {
488 //out << "Loading ["<<secname<<"] from "<<insHeading<<"\n";
489 std::streampos fpos = in.tellg();
490 in.seekg(0, std::ios::beg);
491 std::string line;
492 bool success = false;
493 while (!in.eof() && line != insHeading)
494 {
495 std::getline(in, line);
496 clean_string(line);
497 }
498 if (line == insHeading)
499 {
500 line = "";
501 std::string matchstr = "[" + secname + "]";
502 while (!in.eof()
503 && line != matchstr
504 && (line.size() == 0 || line[0] != '.'))
505 {
506 std::getline(in, line);
507 clean_string(line);
508 }
509
510 if (line == matchstr)
511 {
512 line = "";
513 success = true;
514 while (!in.eof()
515 && (line.size() == 0 || (line[0] != '.' && line[0] != '[')))
516 {
517 std::getline(in, line);
518 clean_string(line);
519 if (line.substr(0, 7) == "BasedOn") load(line.substr(8), in);
520 if (line.find('=') != line.npos)
521 {
522 int no = 0;
523 {
524 std::istringstream si(line);
525 si >> no;
526 }
527 std::string title = line.substr(line.find('=')+1);
528 delete _names[no];
529 _names[no] = new std::string(title);
530 }
531 }
532 }
533 }
534 in.seekg(fpos, std::ios::beg);
535 if (!success)
536 {
537 std::cerr << "TSE3: Failed to load data [" << secname.c_str()
538 << "] from instrument file section "<< insHeading << "\n";
539 }
540 }
541
542
write(std::ostream & out) const543 void InstrumentData::write(std::ostream &out) const
544 {
545 out << "[" << _title << "]\n";
546 for (int n = 0; n < 128; ++n)
547 if (_names[n]) out << n << "=" << *(_names[n]) << "\n";
548 out << "\n";
549 }
550
551
552 /******************************************************************************
553 * CakewalkInstrumentFile class
554 *****************************************************************************/
555
CakewalkInstrumentFile(const std::string & filename)556 CakewalkInstrumentFile::CakewalkInstrumentFile(const std::string &filename)
557 : filename(filename), searched_yet(false)
558 {
559 }
560
561
instruments(TSE3::Progress * progress)562 const std::list<std::string> &CakewalkInstrumentFile::instruments
563 (TSE3::Progress *progress)
564 {
565 if (!searched_yet)
566 {
567 size_t progressCount = 0;
568 searched_yet = true;
569 std::ifstream in(filename.c_str());
570 if (!in.good())
571 {
572 return ins;
573 }
574 if (progress)
575 {
576 in.seekg(0, std::ios::end);
577 progress->progressRange(0, in.tellg());
578 in.seekg(0, std::ios::beg);
579 }
580 std::string line;
581 while (!in.eof() && line != ".Instrument Definitions")
582 {
583 std::getline(in, line);
584 clean_string(line);
585 if (progress && !(progressCount%20))
586 {
587 progress->progress(in.tellg());
588 }
589 progressCount++;
590 }
591 if (line != ".Instrument Definitions") return ins;
592 while (!in.eof())
593 {
594 std::getline(in, line);
595 clean_string(line);
596 if (line.size() && line[0] == '[')
597 {
598 ins.push_back(line.substr(1,line.size()-2));
599 }
600 if (progress && !(progressCount%20))
601 {
602 progress->progress(in.tellg());
603 }
604 progressCount++;
605 }
606 }
607 return ins;
608 }
609
610
instrument(const std::string & title,TSE3::Progress * p)611 Instrument *CakewalkInstrumentFile::instrument(const std::string &title,
612 TSE3::Progress *p)
613 {
614 return new Instrument(title, filename, p);
615 }
616