/[svn]/libgig/trunk/src/SF.cpp
ViewVC logotype

Contents of /libgig/trunk/src/SF.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2020 - (show annotations) (download)
Fri Oct 30 16:25:27 2009 UTC (14 years, 3 months ago) by iliev
File size: 46016 byte(s)
* sf2: 24bit support
* sf2: loop support
* sf2: implemented overridingRootKey
* sf2: implemented instrument global region
* sf2: bugfix: some regions were ignored

1 /***************************************************************************
2 * *
3 * libsf2 - C++ cross-platform SF2 format file access library *
4 * *
5 * Copyright (C) 2009 Grigor Iliev <grigor@grigoriliev.com> *
6 * Copyright (C) 2009 Christian Schoenebeck *
7 * Copyright (C) 2009 Andreas Persson *
8 * *
9 * This library is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 * This library is distributed in the hope that it will be useful, *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17 * GNU General Public License for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License *
20 * along with this library; if not, write to the Free Software *
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
22 * MA 02111-1307 USA *
23 ***************************************************************************/
24
25 #include <vector>
26
27 #include "RIFF.h"
28
29 #include "SF.h"
30
31 #include "helper.h"
32 #include <math.h>
33
34 #define _1200TH_ROOT_OF_2 1.000577789506555
35 #define _200TH_ROOT_OF_10 1.011579454259899
36
37 namespace sf2 {
38 double ToSeconds(int Timecents) {
39 if (Timecents == 0) return 1.0;
40 if (Timecents == -32768) return 0.0;
41 return pow(_1200TH_ROOT_OF_2, Timecents);
42 }
43
44 double ToPermilles(int Centibels) {
45 if (Centibels == 0) return 1000.0;
46 if (Centibels < 0) return 0.0;
47 return pow(_200TH_ROOT_OF_10, Centibels);
48 }
49
50 RIFF::Chunk* GetMandatoryChunk(RIFF::List* list, uint32_t chunkId) {
51 RIFF::Chunk* ck = list->GetSubChunk(chunkId);
52 if(ck == NULL) throw Exception("Mandatory chunk in RIFF list chunk not found: " + ToString(chunkId));
53 return ck;
54 }
55
56 void LoadString(RIFF::Chunk* ck, std::string& s, int strLength) {
57 if(ck == NULL) return;
58 char* buf = new char[strLength];
59 int len = 0;
60 for(int i = 0; i < strLength; i++) {
61 buf[i] = ck->ReadInt8();
62 if(buf[i] == 0 && !len) len = i;
63 }
64 if(!len) len = strLength;
65 s.assign(buf, len);
66 delete [] buf;
67 }
68
69 /**
70 * Throws an error if the chunk is NULL or
71 * the chunk data size is less than size (in bytes).
72 */
73 void VerifySize(RIFF::Chunk* ck, int size) {
74 if (ck == NULL) throw Exception("NULL chunk");
75 if (ck->GetSize() < size) {
76 throw Exception("Invalid chunk size. Chunk ID: " + ToString(ck->GetChunkID()));
77 }
78 }
79
80 Modulator::Modulator(SFModulator mod) {
81 Type = mod >> 10; // The last 6 bits
82 Polarity = mod & (1 << 9);
83 Direction = mod & (1 << 8);
84 MidiPalete = mod & (1 << 7); // two paletes - general or MIDI
85 Index = mod & 0x7f; /* index field */;
86
87 }
88
89 ModulatorItem::ModulatorItem(ModList& mod) :
90 ModSrcOper(Modulator(mod.ModSrcOper)),
91 ModAmtSrcOper(Modulator(mod.ModAmtSrcOper))
92 {
93
94 }
95
96 Version::Version(RIFF::Chunk* ck) {
97 if(ck != NULL) VerifySize(ck, 4);
98 Major = ck ? ck->ReadUint16() : 0;
99 Minor = ck ? ck->ReadUint16() : 0;
100 }
101
102 // *************** Info ***************
103 // *
104
105 /** @brief Constructor.
106 *
107 * Initializes the info strings with values provided by an INFO list chunk.
108 *
109 * @param list - pointer to a list chunk which contains an INFO list chunk
110 */
111 Info::Info(RIFF::List* list) {
112 if (list) {
113 RIFF::List* lstINFO = list->GetSubList(LIST_TYPE_INFO);
114 if (lstINFO) {
115 pVer = new Version(GetMandatoryChunk(lstINFO, CHUNK_ID_IFIL));
116 LoadString(CHUNK_ID_ISNG, lstINFO, SoundEngine);
117 LoadString(CHUNK_ID_INAM, lstINFO, BankName);
118 LoadString(CHUNK_ID_IROM, lstINFO, RomName);
119 pRomVer = new Version(lstINFO->GetSubChunk(CHUNK_ID_IVER));
120 LoadString(CHUNK_ID_ICRD, lstINFO, CreationDate);
121 LoadString(CHUNK_ID_IENG, lstINFO, Engineers);
122 LoadString(CHUNK_ID_IPRD, lstINFO, Product);
123 LoadString(CHUNK_ID_ICOP, lstINFO, Copyright);
124 LoadString(CHUNK_ID_ICMT, lstINFO, Comments);
125 LoadString(CHUNK_ID_ISFT, lstINFO, Software);
126
127 }
128 }
129 }
130
131 Info::~Info() {
132 delete pVer;
133 delete pRomVer;
134 }
135
136 /** @brief Load given INFO field.
137 *
138 * Load INFO field from INFO chunk with chunk ID \a ChunkID from INFO
139 * list chunk \a lstINFO and save value to \a s.
140 */
141 void Info::LoadString(uint32_t ChunkID, RIFF::List* lstINFO, String& s) {
142 RIFF::Chunk* ck = lstINFO->GetSubChunk(ChunkID);
143 ::LoadString(ck, s); // function from helper.h
144 }
145
146 Sample::Sample(RIFF::Chunk* ck, RIFF::Chunk* pCkSmpl, RIFF::Chunk* pCkSm24) {
147 this->pCkSmpl = pCkSmpl;
148 this->pCkSm24 = pCkSm24;
149
150 LoadString(ck, Name, 20);
151 Start = ck->ReadInt32();
152 End = ck->ReadInt32();
153 StartLoop = ck->ReadInt32();
154 EndLoop = ck->ReadInt32();
155 SampleRate = ck->ReadInt32();
156 OriginalPitch = ck->ReadInt8();
157 PitchCorrection = ck->ReadInt8();
158 SampleLink = ck->ReadInt16();
159 SampleType = ck->ReadInt16();
160
161 if (Start > End || !pCkSmpl || pCkSmpl->GetSize() <= End) {
162 throw Exception("Broken SF2 file (invalid sample info)");
163 }
164
165 ChannelCount = 1;
166 switch(SampleType) {
167 case 0 : // terminal sample
168 case sf2::Sample::MONO_SAMPLE :
169 case sf2::Sample::ROM_MONO_SAMPLE : break;
170 case sf2::Sample::RIGHT_SAMPLE :
171 case sf2::Sample::LEFT_SAMPLE :
172 case sf2::Sample::ROM_RIGHT_SAMPLE :
173 case sf2::Sample::ROM_LEFT_SAMPLE : ChannelCount = 2; break;
174 case sf2::Sample::LINKED_SAMPLE :
175 case sf2::Sample::ROM_LINKED_SAMPLE : std::cerr << "Linked samples not implemented yet"; break;
176 default: throw Exception("Broken SF2 file (invalid sample type)");
177 }
178
179 RAMCache.Size = 0;
180 RAMCache.pStart = NULL;
181 RAMCache.NullExtensionSize = 0;
182 }
183
184 int Sample::GetChannelCount() {
185 return ChannelCount;
186 }
187
188 long Sample::GetTotalFrameCount() {
189 return (End - Start);
190 }
191
192 /**
193 * @returns The frame size in bytes
194 */
195 int Sample::GetFrameSize() {
196 return ChannelCount * ((pCkSm24 != NULL) ? 3 : 2);
197 }
198
199 bool Sample::HasLoops() {
200 return StartLoop != 0 && EndLoop != 0;
201 }
202
203 /**
204 * Reads \a SampleCount number of sample points from the position stored
205 * in \a pPlaybackState into the buffer pointed by \a pBuffer and moves
206 * the position within the sample respectively, this method honors the
207 * looping informations of the sample (if any). Use this
208 * method if you don't want to load the sample into RAM, thus for disk
209 * streaming. All this methods needs to know to proceed with streaming
210 * for the next time you call this method is stored in \a pPlaybackState.
211 * You have to allocate and initialize the playback_state_t structure by
212 * yourself before you use it to stream a sample:
213 * @code
214 * PlaybackState playbackstate;
215 * playbackstate.position = 0;
216 * playbackstate.reverse = false;
217 * playbackstate.loop_cycles_left = pSample->LoopPlayCount;
218 * @endcode
219 * You don't have to take care of things like if there is actually a loop
220 * defined or if the current read position is located within a loop area.
221 * The method already handles such cases by itself.
222 *
223 * @param pBuffer destination buffer
224 * @param FrameCount number of sample points to read
225 * @param pPlaybackState will be used to store and reload the playback
226 * state for the next ReadAndLoop() call
227 * @returns number of successfully read sample points
228 */
229 unsigned long Sample::ReadAndLoop (
230 void* pBuffer,
231 unsigned long FrameCount,
232 PlaybackState* pPlaybackState,
233 Region* pRegion
234 ) {
235 // TODO: startAddrsCoarseOffset, endAddrsCoarseOffset
236 unsigned long samplestoread = FrameCount, totalreadsamples = 0, readsamples, samplestoloopend;
237 uint8_t* pDst = (uint8_t*) pBuffer;
238 SetPos(pPlaybackState->position);
239 if (pRegion->HasLoop) {
240 do {
241 samplestoloopend = pRegion->LoopEnd - GetPos();
242 readsamples = Read(&pDst[totalreadsamples * GetFrameSize()], Min(samplestoread, samplestoloopend));
243 samplestoread -= readsamples;
244 totalreadsamples += readsamples;
245 if (readsamples == samplestoloopend) {
246 SetPos(pRegion->LoopStart);
247 }
248 } while (samplestoread && readsamples);
249 } else {
250 totalreadsamples = Read(pBuffer, FrameCount);
251 }
252
253 pPlaybackState->position = GetPos();
254
255 return totalreadsamples;
256 }
257
258 Region::Region() {
259 pSample = NULL;
260 pInstrument = NULL;
261 loKey = hiKey = NONE;
262 minVel = maxVel = NONE;
263 startAddrsOffset = startAddrsCoarseOffset = endAddrsOffset = endAddrsCoarseOffset = 0;
264 startloopAddrsOffset = startloopAddrsCoarseOffset = endloopAddrsOffset = endloopAddrsCoarseOffset = 0;
265 pan = fineTune = coarseTune = 0;
266 overridingRootKey = -1; // -1 means not used
267
268 HasLoop = false;
269 LoopStart = LoopEnd = 0;
270
271 EG1PreAttackDelay = EG1Attack = EG1Hold = EG1Decay = EG1Release = ToSeconds(-12000);
272 EG1Sustain = 0;
273 EG2PreAttackDelay = EG2Attack = EG2Hold = EG2Decay = EG2Release = ToSeconds(-12000);
274 EG2Sustain = 0;
275 }
276
277 int Region::GetUnityNote() {
278 return overridingRootKey != -1 ? overridingRootKey : pSample->OriginalPitch;
279 }
280
281 void Region::SetGenerator(sf2::File* pFile, GenList& Gen) {
282 switch(Gen.GenOper) {
283 case START_ADDRS_OFFSET:
284 startAddrsOffset = Gen.GenAmount.wAmount;
285 break;
286 case END_ADDRS_OFFSET:
287 if (Gen.GenAmount.shAmount <= 0) {
288 endAddrsOffset = Gen.GenAmount.shAmount;
289 } else {
290 std::cerr << "Ignoring invalid endAddrsOffset" << std::endl;
291 }
292 break;
293 case STARTLOOP_ADDRS_OFFSET:
294 startloopAddrsOffset = Gen.GenAmount.shAmount;
295 LoopStart += startloopAddrsOffset;
296 break;
297 case ENDLOOP_ADDRS_OFFSET:
298 endloopAddrsOffset = Gen.GenAmount.shAmount;
299 LoopEnd += endloopAddrsOffset;
300 break;
301 case START_ADDRS_COARSE_OFFSET:
302 startAddrsCoarseOffset = Gen.GenAmount.wAmount;
303 break;
304 case MOD_LFO_TO_PITCH:
305 break;
306 case VIB_LFO_TO_PITCH:
307 break;
308 case MOD_ENV_TO_PITCH:
309 break;
310 case INITIAL_FILTER_FC:
311 break;
312 case INITIAL_FILTER_Q:
313 break;
314 case MOD_LFO_TO_FILTER_FC:
315 break;
316 case MOD_ENV_TO_FILTER_FC:
317 break;
318 case END_ADDRS_COARSE_OFFSET:
319 endAddrsCoarseOffset = Gen.GenAmount.wAmount;
320 break;
321 case MOD_LFO_TO_VOLUME:
322 break;
323 case CHORUS_EFFECTS_SEND:
324 break;
325 case REVERB_EFFECTS_SEND:
326 break;
327 case PAN:
328 pan = Gen.GenAmount.shAmount;
329 pan * 64; pan /= 500;
330 if (pan < -64) pan = -64;
331 if (pan > 63) pan = 63;
332 break;
333 case DELAY_MOD_LFO:
334 break;
335 case FREQ_MOD_LFO:
336 break;
337 case DELAY_VIB_LFO:
338 break;
339 case FREQ_VIB_LFO:
340 break;
341 case DELAY_MOD_ENV:
342 EG2PreAttackDelay = ToSeconds(Gen.GenAmount.shAmount);
343 break;
344 case ATTACK_MOD_ENV:
345 EG2Attack = ToSeconds(Gen.GenAmount.shAmount);
346 break;
347 case HOLD_MOD_ENV:
348 EG2Hold = ToSeconds(Gen.GenAmount.shAmount);
349 break;
350 case DECAY_MOD_ENV:
351 EG2Decay = ToSeconds(Gen.GenAmount.shAmount);
352 break;
353 case SUSTAIN_MOD_ENV:
354 EG2Sustain = 1000 - Gen.GenAmount.shAmount;
355 break;
356 case RELEASEMODENV:
357 EG2Release = ToSeconds(Gen.GenAmount.shAmount);
358 break;
359 case KEYNUM_TO_MOD_ENV_HOLD:
360 break;
361 case KEYNUM_TO_MOD_ENV_DECAY:
362 break;
363 case DELAY_VOL_ENV:
364 EG1PreAttackDelay = ToSeconds(Gen.GenAmount.shAmount);
365 break;
366 case ATTACK_VOL_ENV:
367 EG1Attack = ToSeconds(Gen.GenAmount.shAmount);
368 break;
369 case HOLD_VOL_ENV:
370 EG1Hold = ToSeconds(Gen.GenAmount.shAmount);
371 break;
372 case DECAY_VOL_ENV:
373 EG1Decay = ToSeconds(Gen.GenAmount.shAmount);
374 break;
375 case SUSTAIN_VOL_ENV:
376 EG1Sustain = ToPermilles(Gen.GenAmount.shAmount);
377 break;
378 case RELEASE_VOL_ENV:
379 EG1Release = ToSeconds(Gen.GenAmount.shAmount);
380 break;
381 case KEYNUM_TO_VOL_ENV_HOLD:
382 break;
383 case KEYNUM_TO_VOL_ENV_DECAY:
384 break;
385 case INSTRUMENT: {
386 uint16_t id = Gen.GenAmount.wAmount;
387 if (id >= pFile->Instruments.size()) {
388 throw Exception("Broken SF2 file (missing instruments)");
389 }
390 pInstrument = pFile->Instruments[id];
391 break;
392 }
393 case KEY_RANGE:
394 loKey = Gen.GenAmount.ranges.byLo;
395 hiKey = Gen.GenAmount.ranges.byHi;
396 break;
397 case VEL_RANGE:
398 minVel = Gen.GenAmount.ranges.byLo;
399 maxVel = Gen.GenAmount.ranges.byHi;
400 break;
401 case STARTLOOP_ADDRS_COARSE_OFFSET:
402 startloopAddrsCoarseOffset = Gen.GenAmount.wAmount;
403 LoopStart += startloopAddrsCoarseOffset * 32768;
404 break;
405 case KEYNUM:
406 break;
407 case VELOCITY:
408 break;
409 case INITIAL_ATTENUATION:
410 break;
411 case ENDLOOP_ADDRS_COARSE_OFFSET:
412 endloopAddrsCoarseOffset = Gen.GenAmount.wAmount;
413 LoopEnd += endloopAddrsCoarseOffset * 32768;
414 break;
415 case COARSE_TUNE:
416 coarseTune = Gen.GenAmount.shAmount;
417 break;
418 case FINE_TUNE:
419 fineTune = Gen.GenAmount.shAmount;
420 break;
421 case SAMPLE_ID: {
422 uint16_t sid = Gen.GenAmount.wAmount;
423 if (sid >= pFile->Samples.size()) {
424 throw Exception("Broken SF2 file (missing samples)");
425 }
426 pSample = pFile->Samples[sid];
427
428 if (HasLoop) {
429 LoopStart += pSample->StartLoop;
430 LoopEnd += pSample->EndLoop;
431 if ( LoopStart < pSample->Start || LoopStart > pSample->End ||
432 LoopStart > LoopEnd || LoopEnd > pSample->End ) {
433 throw Exception("Broken SF2 file (invalid loops)");
434 }
435 LoopStart -= pSample->Start; // Relative to the sample start
436 LoopEnd -= pSample->Start; // Relative to the sample start
437 }
438 break;
439 }
440 case SAMPLE_MODES:
441 HasLoop = Gen.GenAmount.wAmount & 1;
442 // TODO: 3 indicates a sound which loops for the duration of key depression
443 // then proceeds to play the remainder of the sample.
444 break;
445 case SCALE_TUNING:
446 break;
447 case EXCLUSIVE_CLASS:
448 break;
449 case OVERRIDING_ROOT_KEY:
450 overridingRootKey = Gen.GenAmount.shAmount;
451 break;
452 }
453 }
454
455 void Region::SetModulator(sf2::File* pFile, ModList& Mod) {
456 modulators.push_back(ModulatorItem(Mod));
457 /*switch(srcType) {
458 case NO_CONTROLLER:
459 break;
460 case NOTE_ON_VELOCITY:
461 break;
462 case NOTE_ON_KEY_NUMBER:
463 break;
464 case POLY_PRESSURE:
465 break;
466 case CHANNEL_PRESSURE:
467 break;
468 case PITCH_WHEEL:
469 break;
470 case PITCH_WHEEL_SENSITIVITY:
471 break;
472 case LINK:
473 break;
474 default: std::cout << "Unknown controller source: " << srcType << std::endl;
475 }*/
476 }
477
478 InstrumentBase::InstrumentBase(sf2::File* pFile) {
479 this->pFile = pFile;
480 pGlobalRegion = NULL;
481 }
482
483 InstrumentBase::~InstrumentBase() {
484 if (pGlobalRegion) delete pGlobalRegion;
485 for (int i = regions.size() - 1; i >= 0; i--) {
486 if (regions[i]) delete (regions[i]);
487 }
488 }
489
490 int InstrumentBase::GetRegionCount() {
491 return regions.size();
492 }
493
494 Region* InstrumentBase::GetRegion(int idx) {
495 if (idx < 0 || idx >= GetRegionCount()) {
496 throw Exception("Region index out of bounds");
497 }
498
499 return regions[idx];
500 }
501
502 std::vector<Region*> InstrumentBase::GetRegionsOnKey(int key, uint8_t vel) {
503 std::vector<Region*> v;
504 for (int i = 0; i < GetRegionCount(); i++) {
505 Region* r = GetRegion(i);
506 if (
507 key >= r->loKey && key <= r->hiKey &&
508 ((r->minVel == NONE && r->maxVel == NONE) || (vel >= r->minVel && vel <= r->maxVel))
509 ) {
510 v.push_back(r);
511 }
512 }
513
514 return v;
515 }
516
517 Instrument::Instrument(sf2::File* pFile, RIFF::Chunk* ck) : InstrumentBase(pFile) {
518 this->pFile = pFile;
519 LoadString(ck, Name, 20);
520 InstBagNdx = ck->ReadInt16();
521 }
522
523 Instrument::~Instrument() {
524
525 }
526
527 Region* Instrument::CreateRegion() {
528 Region* r = new Region;
529 if (pGlobalRegion != NULL) {
530 r->loKey = pGlobalRegion->loKey;
531 r->hiKey = pGlobalRegion->hiKey;
532 r->minVel = pGlobalRegion->minVel;
533 r->maxVel = pGlobalRegion->maxVel;
534 r->pan = pGlobalRegion->pan;
535 r->fineTune = pGlobalRegion->fineTune;
536 r->coarseTune = pGlobalRegion->coarseTune;
537 r->overridingRootKey = pGlobalRegion->overridingRootKey;
538 r->startAddrsOffset = pGlobalRegion->startAddrsOffset;
539 r->startAddrsCoarseOffset = pGlobalRegion->startAddrsCoarseOffset;
540 r->endAddrsOffset = pGlobalRegion->endAddrsOffset;
541 r->endAddrsCoarseOffset = pGlobalRegion->endAddrsCoarseOffset;
542 r->startloopAddrsOffset = pGlobalRegion->startloopAddrsOffset;
543 r->startloopAddrsCoarseOffset = pGlobalRegion->startloopAddrsCoarseOffset;
544 r->endloopAddrsOffset = pGlobalRegion->endloopAddrsOffset;
545 r->endloopAddrsCoarseOffset = pGlobalRegion->endloopAddrsCoarseOffset;
546
547 r->EG1PreAttackDelay = pGlobalRegion->EG1PreAttackDelay;
548 r->EG1Attack = pGlobalRegion->EG1Attack;
549 r->EG1Hold = pGlobalRegion->EG1Hold;
550 r->EG1Decay = pGlobalRegion->EG1Decay;
551 r->EG1Sustain = pGlobalRegion->EG1Sustain;
552 r->EG1Release = pGlobalRegion->EG1Release;
553
554 r->EG2PreAttackDelay = pGlobalRegion->EG2PreAttackDelay;
555 r->EG2Attack = pGlobalRegion->EG2Attack;
556 r->EG2Hold = pGlobalRegion->EG2Hold;
557 r->EG2Decay = pGlobalRegion->EG2Decay;
558 r->EG2Sustain = pGlobalRegion->EG2Sustain;
559 r->EG2Release = pGlobalRegion->EG2Release;
560
561 r->HasLoop = pGlobalRegion->HasLoop;
562 r->LoopStart = pGlobalRegion->LoopStart;
563 r->LoopEnd = pGlobalRegion->LoopEnd;
564 }
565
566 return r;
567 }
568
569 void Instrument::DeleteRegion(Region* pRegion) {
570 for (int i = 0; i < regions.size(); i++) {
571 if (regions[i] == pRegion) {
572 delete pRegion;
573 regions[i] = NULL;
574 return;
575 }
576 }
577
578 std::cerr << "Can't remove unknown Region" << std::endl;
579 }
580
581 void Instrument::LoadRegions(int idx1, int idx2) {
582 for (int i = idx1; i < idx2; i++) {
583 int gIdx1 = pFile->InstBags[i].InstGenNdx;
584 int gIdx2 = pFile->InstBags[i + 1].InstGenNdx;
585
586 if (gIdx1 < 0 || gIdx2 < 0 || gIdx1 > gIdx2 || gIdx2 >= pFile->InstGenLists.size()) {
587 throw Exception("Broken SF2 file (invalid InstGenNdx)");
588 }
589
590 int mIdx1 = pFile->InstBags[i].InstModNdx;
591 int mIdx2 = pFile->InstBags[i + 1].InstModNdx;
592
593 if (mIdx1 < 0 || mIdx2 < 0 || mIdx1 > mIdx2 || mIdx2 >= pFile->InstModLists.size()) {
594 throw Exception("Broken SF2 file (invalid InstModNdx)");
595 }
596
597 Region* reg = CreateRegion();
598
599 for (int j = gIdx1; j < gIdx2; j++) {
600 reg->SetGenerator(pFile, pFile->InstGenLists[j]);
601 // TODO: ignore generators following a sampleID generator
602 }
603
604 for (int j = mIdx1; j < mIdx2; j++) {
605 reg->SetModulator(pFile, pFile->InstModLists[j]);
606 }
607
608 if (reg->pSample == NULL) {
609 if (i == idx1 && idx2 - idx1 > 1) {
610 pGlobalRegion = reg; // global zone
611 } else {
612 std::cerr << "Ignoring instrument's region without sample" << std::endl;
613 delete reg;
614 }
615 } else {
616 regions.push_back(reg);
617 }
618 }
619 }
620
621 Preset::Preset(sf2::File* pFile, RIFF::Chunk* ck): InstrumentBase(pFile) {
622 this->pFile = pFile;
623 LoadString(ck, Name, 20);
624 PresetNum = ck->ReadInt16();
625 Bank = ck->ReadInt16();
626 PresetBagNdx = ck->ReadInt16();
627 Library = ck->ReadInt32();
628 Genre = ck->ReadInt32();
629 Morphology = ck->ReadInt32();
630 }
631
632 Preset::~Preset() {
633
634 }
635
636 void Preset::LoadRegions(int idx1, int idx2) {
637 for (int i = idx1; i < idx2; i++) {
638 int gIdx1 = pFile->PresetBags[i].GenNdx;
639 int gIdx2 = pFile->PresetBags[i + 1].GenNdx;
640
641 if (gIdx1 < 0 || gIdx2 < 0 || gIdx1 > gIdx2 || gIdx2 >= pFile->PresetGenLists.size()) {
642 throw Exception("Broken SF2 file (invalid PresetGenNdx)");
643 }
644
645 Region* reg = new Region;
646
647 for (int j = gIdx1; j < gIdx2; j++) {
648 reg->SetGenerator(pFile, pFile->PresetGenLists[j]);
649 }
650 if (reg->pInstrument == NULL) {
651 if (i == idx1 && idx2 - idx1 > 1) {
652 pGlobalRegion = reg; // global zone
653 } else {
654 std::cerr << "Ignoring preset's region without instrument" << std::endl;
655 delete reg;
656 }
657 } else {
658 regions.push_back(reg);
659 }
660 }
661 }
662
663 /** @brief Constructor.
664 *
665 * Load an existing SF2 file.
666 *
667 * @param pRIFF - pointer to a RIFF file which is actually the SF2 file
668 * to load
669 * @throws Exception if given file is not a SF2 file, expected chunks
670 * are missing
671 */
672 File::File(RIFF::File* pRIFF) {
673 if (!pRIFF) throw Exception("NULL pointer reference to RIFF::File object.");
674 this->pRIFF = pRIFF;
675
676 if (pRIFF->GetListType() != RIFF_TYPE_SF2) {
677 throw Exception("Not a SF2 file");
678 }
679
680 pInfo = new Info(pRIFF);
681 if (pInfo->pVer->Major != 2) {
682 throw Exception("Unsupported version: " + ToString(pInfo->pVer->Major));
683 }
684
685 RIFF::List* lstSDTA = pRIFF->GetSubList(LIST_TYPE_SDTA);
686 if (lstSDTA == NULL) {
687 throw Exception("Broken SF2 file (missing sdta)");
688 }
689
690 RIFF::Chunk* pCkSmpl = lstSDTA->GetSubChunk(CHUNK_ID_SMPL);
691 RIFF::Chunk* pCkSm24 = lstSDTA->GetSubChunk(CHUNK_ID_SM24);
692 if (pCkSmpl != NULL && pCkSm24 != NULL) {
693 long l = pCkSmpl->GetSize() / 2;
694 if (l%2) l++;
695 if (pCkSm24->GetSize() != l) {
696 pCkSm24 = NULL; // ignoring sm24 due to invalid size
697 }
698 }
699
700 RIFF::List* lstPDTA = pRIFF->GetSubList(LIST_TYPE_PDTA);
701 if (lstPDTA == NULL) {
702 throw Exception("Broken SF2 file (missing pdta)");
703 }
704
705 RIFF::Chunk* ck = lstPDTA->GetSubChunk(CHUNK_ID_PHDR);
706 if (ck->GetSize() < 38) {
707 throw Exception("Broken SF2 file (broken phdr)");
708 }
709
710 int count = ck->GetSize() / 38;
711 for (int i = 0; i < count; i++) {
712 Presets.push_back(new Preset(this, ck));
713 }
714
715 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_PBAG);
716 if (ck->GetSize() < 4 || (ck->GetSize() % 4)) {
717 throw Exception("Broken SF2 file (broken pbag)");
718 }
719
720 count = ck->GetSize() / 4;
721 for (int i = 0; i < count; i++) {
722 PresetBag pb;
723 pb.GenNdx = ck->ReadInt16();
724 pb.ModNdx = ck->ReadInt16();
725 PresetBags.push_back(pb);
726 }
727 //std::cout << "Preset bags: " << PresetBags.size() << std::endl;
728
729 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_PMOD);
730 if (ck->GetSize() % 10) {
731 throw Exception("Broken SF2 file (broken pmod)");
732 }
733
734 count = ck->GetSize() / 10;
735 for (int i = 0; i < count; i++) {
736 ModList ml;
737 ml.ModSrcOper = ck->ReadInt16();
738 ml.ModDestOper = ck->ReadInt16();
739 ml.ModAmount = ck->ReadInt16();
740 ml.ModAmtSrcOper = ck->ReadInt16();
741 ml.ModTransOper = ck->ReadInt16();
742 PresetModLists.push_back(ml);
743 }
744 //std::cout << "Preset mod lists: " << PresetModLists.size() << std::endl;
745
746 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_PGEN);
747 if (ck->GetSize() < 4 || (ck->GetSize() % 4)) {
748 throw Exception("Broken SF2 file (broken pgen)");
749 }
750
751 count = ck->GetSize() / 4;
752 for (int i = 0; i < count; i++) {
753 GenList gl;
754 gl.GenOper = ck->ReadInt16();
755 gl.GenAmount.wAmount = ck->ReadInt16();
756 PresetGenLists.push_back(gl);
757 }
758 //std::cout << "Preset gen lists: " << PresetGenLists.size() << std::endl;
759
760 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_INST);
761 if (ck->GetSize() < (22 * 2) || (ck->GetSize() % 22)) {
762 throw Exception("Broken SF2 file (broken inst)");
763 }
764 count = ck->GetSize() / 22;
765 for (int i = 0; i < count; i++) {
766 Instruments.push_back(new Instrument(this, ck));
767 }
768
769 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_IBAG);
770 if (ck->GetSize() < 4 || (ck->GetSize() % 4)) {
771 throw Exception("Broken SF2 file (broken ibag)");
772 }
773
774 count = ck->GetSize() / 4;
775 for (int i = 0; i < count; i++) {
776 InstBag ib;
777 ib.InstGenNdx = ck->ReadInt16();
778 ib.InstModNdx = ck->ReadInt16();
779 InstBags.push_back(ib);
780 }
781 //std::cout << "Instrument bags: " << InstBags.size() << std::endl;
782
783 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_IMOD);
784 if (ck->GetSize() % 10) {
785 throw Exception("Broken SF2 file (broken imod)");
786 }
787
788 count = ck->GetSize() / 10;
789 for (int i = 0; i < count; i++) {
790 ModList ml;
791 ml.ModSrcOper = ck->ReadInt16();
792 ml.ModDestOper = ck->ReadInt16();
793 ml.ModAmount = ck->ReadInt16();
794 ml.ModAmtSrcOper = ck->ReadInt16();
795 ml.ModTransOper = ck->ReadInt16();
796 InstModLists.push_back(ml);
797 }
798 //std::cout << "Instrument mod lists: " << InstModLists.size() << std::endl;
799
800 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_IGEN);
801 if (ck->GetSize() < 4 || (ck->GetSize() % 4)) {
802 throw Exception("Broken SF2 file (broken igen)");
803 }
804
805 count = ck->GetSize() / 4;
806 for (int i = 0; i < count; i++) {
807 GenList gl;
808 gl.GenOper = ck->ReadInt16();
809 gl.GenAmount.wAmount = ck->ReadInt16();
810 InstGenLists.push_back(gl);
811 }
812 //std::cout << "Instrument gen lists: " << InstGenLists.size() << std::endl;
813
814 ck = GetMandatoryChunk(lstPDTA, CHUNK_ID_SHDR);
815 if ((ck->GetSize() % 46)) {
816 throw Exception("Broken SF2 file (broken shdr)");
817 }
818 count = ck->GetSize() / 46;
819 for (int i = 0; i < count; i++) {
820 Samples.push_back(new Sample(ck, pCkSmpl, pCkSm24));
821 }
822
823 // Loading instrument regions
824 for (int i = 0; i < Instruments.size() - 1; i++) {
825 Instrument* instr = Instruments[i];
826 int x1 = instr->InstBagNdx;
827 int x2 = Instruments[i + 1]->InstBagNdx;
828 if (x1 < 0 || x2 < 0 || x1 > x2 || x2 >= InstBags.size()) {
829 throw Exception("Broken SF2 file (invalid InstBagNdx)");
830 }
831
832 instr->LoadRegions(x1, x2);
833 }
834
835 // Loading preset regions
836 for (int i = 0; i < Presets.size() - 1; i++) {
837 Preset* preset = Presets[i];
838 int x1 = preset->PresetBagNdx;
839 int x2 = Presets[i + 1]->PresetBagNdx;
840 if (x1 < 0 || x2 < 0 || x1 > x2 || x2 >= PresetBags.size()) {
841 throw Exception("Broken SF2 file (invalid PresetBagNdx)");
842 }
843
844 preset->LoadRegions(x1, x2);
845 }
846 }
847
848 File::~File() {
849 delete pInfo;
850 for (int i = Presets.size() - 1; i >= 0; i--) {
851 if (Presets[i]) delete (Presets[i]);
852 }
853 for (int i = Instruments.size() - 1; i >= 0; i--) {
854 if (Instruments[i]) delete (Instruments[i]);
855 }
856 for (int i = Samples.size() - 1; i >= 0; i--) {
857 if (Samples[i]) delete (Samples[i]);
858 }
859 }
860
861 int File::GetPresetCount() {
862 return Presets.size() - 1; // exclude terminal preset (EOP)
863 }
864
865 Preset* File::GetPreset(int idx) {
866 if (idx < 0 || idx >= GetPresetCount()) {
867 throw Exception("Preset index out of bounds");
868 }
869
870 return Presets[idx];
871 }
872
873 int File::GetInstrumentCount() {
874 return Instruments.size() - 1; // exclude terminal instrument (EOI)
875 }
876
877 Instrument* File::GetInstrument(int idx) {
878 if (idx < 0 || idx >= GetInstrumentCount()) {
879 throw Exception("Instrument index out of bounds");
880 }
881
882 return Instruments[idx];
883 }
884
885 void File::DeleteInstrument(Instrument* pInstrument) {
886 for (int i = 0; i < GetPresetCount(); i++) {
887 Preset* p = GetPreset(i);
888 if (p == NULL) continue;
889 for (int j = p->GetRegionCount() - 1; j >= 0 ; j--) {
890 if (p->GetRegion(j) && p->GetRegion(j)->pInstrument == pInstrument) {
891 p->GetRegion(j)->pInstrument = NULL;
892 }
893 }
894 }
895
896 for (int i = 0; i < GetInstrumentCount(); i++) {
897 if (GetInstrument(i) == pInstrument) {
898 Instruments[i] = NULL;
899 delete pInstrument;
900 }
901 }
902 }
903
904 int File::GetSampleCount() {
905 return Samples.size() - 1; // exclude terminal sample (EOS)
906 }
907
908 Sample* File::GetSample(int idx) {
909 if (idx < 0 || idx >= GetSampleCount()) {
910 throw Exception("Sample index out of bounds");
911 }
912
913 return Samples[idx];
914 }
915
916 void File::DeleteSample(Sample* pSample) {
917 // Sanity check
918 for (int i = GetInstrumentCount() - 1; i >= 0; i--) {
919 Instrument* pInstr = GetInstrument(i);
920 if (pInstr == NULL) continue;
921
922 for (int j = pInstr->GetRegionCount() - 1; j >= 0 ; j--) {
923 if (pInstr->GetRegion(j) && pInstr->GetRegion(j)->GetSample() == pSample) {
924 std::cerr << "Deleting sample which is still in use" << std::endl;
925 }
926 }
927 }
928 ///////
929
930 for (int i = 0; i < GetSampleCount(); i++) {
931 if (Samples[i] == pSample) {
932 delete pSample;
933 Samples[i] = NULL;
934 return;
935 }
936 }
937
938 throw Exception("Unknown sample: " + pSample->Name);
939 }
940
941 bool File::HasSamples() {
942 for (int i = 0; i < GetSampleCount(); i++) {
943 if (Samples[i] != NULL) return true;
944 }
945
946 return false;
947 }
948
949 /**
950 * Loads the whole sample wave into RAM. Use
951 * ReleaseSampleData() to free the memory if you don't need the cached
952 * sample data anymore.
953 *
954 * @returns buffer_t structure with start address and size of the buffer
955 * in bytes
956 * @see ReleaseSampleData(), Read(), SetPos()
957 */
958 Sample::buffer_t Sample::LoadSampleData() {
959 return LoadSampleDataWithNullSamplesExtension(GetTotalFrameCount(), 0); // 0 amount of NullSamples
960 }
961
962 /**
963 * Reads and caches the first \a SampleCount
964 * numbers of SamplePoints in RAM. Use ReleaseSampleData() to free the
965 * memory space if you don't need the cached samples anymore.
966 * Read the <i>Size</i> member of the <i>buffer_t</i> structure
967 * that will be returned to determine the actual cached samples, but note
968 * that the size is given in bytes! You get the number of actually cached
969 * samples by dividing it by the frame size of the sample:
970 * @code
971 * buffer_t buf = pSample->LoadSampleData(acquired_samples);
972 * long cachedsamples = buf.Size / pSample->FrameSize;
973 * @endcode
974 *
975 * @param SampleCount - number of sample points to load into RAM
976 * @returns buffer_t structure with start address and size of
977 * the cached sample data in bytes
978 * @see ReleaseSampleData(), Read(), SetPos()
979 */
980 Sample::buffer_t Sample::LoadSampleData(unsigned long SampleCount) {
981 return LoadSampleDataWithNullSamplesExtension(SampleCount, 0); // 0 amount of NullSamples
982 }
983
984 /**
985 * Loads the whole sample wave into RAM. Use
986 * ReleaseSampleData() to free the memory if you don't need the cached
987 * sample data anymore.
988 * The method will add \a NullSamplesCount silence samples past the
989 * official buffer end (this won't affect the 'Size' member of the
990 * buffer_t structure, that means 'Size' always reflects the size of the
991 * actual sample data, the buffer might be bigger though). Silence
992 * samples past the official buffer are needed for differential
993 * algorithms that always have to take subsequent samples into account
994 * (resampling/interpolation would be an important example) and avoids
995 * memory access faults in such cases.
996 *
997 * @param NullSamplesCount - number of silence samples the buffer should
998 * be extended past it's data end
999 * @returns buffer_t structure with start address and
1000 * size of the buffer in bytes
1001 * @see ReleaseSampleData(), Read(), SetPos()
1002 */
1003 Sample::buffer_t Sample::LoadSampleDataWithNullSamplesExtension(uint NullSamplesCount) {
1004 return LoadSampleDataWithNullSamplesExtension(GetTotalFrameCount(), NullSamplesCount);
1005 }
1006
1007 /**
1008 * Reads and caches the first \a SampleCount
1009 * numbers of SamplePoints in RAM. Use ReleaseSampleData() to free the
1010 * memory space if you don't need the cached samples anymore.
1011 * Read the <i>Size</i> member of the <i>buffer_t</i> structure
1012 * that will be returned to determine the actual cached samples, but note
1013 * that the size is given in bytes! You get the number of actually cached
1014 * samples by dividing it by the frame size of the sample:
1015 * @code
1016 * buffer_t buf = pSample->LoadSampleDataWithNullSamplesExtension(acquired_samples, null_samples);
1017 * long cachedsamples = buf.Size / pSample->FrameSize;
1018 * @endcode
1019 * The method will add \a NullSamplesCount silence samples past the
1020 * official buffer end (this won't affect the 'Size' member of the
1021 * buffer_t structure, that means 'Size' always reflects the size of the
1022 * actual sample data, the buffer might be bigger though). Silence
1023 * samples past the official buffer are needed for differential
1024 * algorithms that always have to take subsequent samples into account
1025 * (resampling/interpolation would be an important example) and avoids
1026 * memory access faults in such cases.
1027 *
1028 * @param SampleCount - number of sample points to load into RAM
1029 * @param NullSamplesCount - number of silence samples the buffer should
1030 * be extended past it's data end
1031 * @returns buffer_t structure with start address and
1032 * size of the cached sample data in bytes
1033 * @see ReleaseSampleData(), Read(), SetPos()
1034 */
1035 Sample::buffer_t Sample::LoadSampleDataWithNullSamplesExtension(unsigned long SampleCount, uint NullSamplesCount) {
1036 if (SampleCount > GetTotalFrameCount()) SampleCount = GetTotalFrameCount();
1037 if (RAMCache.pStart) delete[] (int8_t*) RAMCache.pStart;
1038 unsigned long allocationsize = (SampleCount + NullSamplesCount) * GetFrameSize();
1039 SetPos(0); // reset read position to begin of sample
1040 RAMCache.pStart = new int8_t[allocationsize];
1041 RAMCache.Size = Read(RAMCache.pStart, SampleCount) * GetFrameSize();
1042 RAMCache.NullExtensionSize = allocationsize - RAMCache.Size;
1043 // fill the remaining buffer space with silence samples
1044 memset((int8_t*)RAMCache.pStart + RAMCache.Size, 0, RAMCache.NullExtensionSize);
1045 return GetCache();
1046 }
1047
1048 /**
1049 * Returns current cached sample points. A buffer_t structure will be
1050 * returned which contains address pointer to the begin of the cache and
1051 * the size of the cached sample data in bytes. Use
1052 * <i>LoadSampleData()</i> to cache a specific amount of sample points in
1053 * RAM.
1054 *
1055 * @returns buffer_t structure with current cached sample points
1056 * @see LoadSampleData();
1057 */
1058 Sample::buffer_t Sample::GetCache() {
1059 // return a copy of the buffer_t structure
1060 buffer_t result;
1061 result.Size = this->RAMCache.Size;
1062 result.pStart = this->RAMCache.pStart;
1063 result.NullExtensionSize = this->RAMCache.NullExtensionSize;
1064 return result;
1065 }
1066
1067 /**
1068 * Frees the cached sample from RAM if loaded with
1069 * <i>LoadSampleData()</i> previously.
1070 *
1071 * @see LoadSampleData();
1072 */
1073 void Sample::ReleaseSampleData() {
1074 if (RAMCache.pStart) delete[] (int8_t*) RAMCache.pStart;
1075 RAMCache.pStart = NULL;
1076 RAMCache.Size = 0;
1077 RAMCache.NullExtensionSize = 0;
1078 }
1079
1080 /**
1081 * Sets the position within the sample (in sample points, not in
1082 * bytes). Use this method and <i>Read()</i> if you don't want to load
1083 * the sample into RAM, thus for disk streaming.
1084 *
1085 * @param SampleCount number of sample points to jump
1086 * @returns the new sample position
1087 * @see Read()
1088 */
1089 unsigned long Sample::SetPos(unsigned long SampleCount) {
1090 pCkSmpl->SetPos((Start * 2) + (SampleCount * 2), RIFF::stream_start);
1091 if(pCkSm24) pCkSm24->SetPos(Start + SampleCount, RIFF::stream_start);
1092 return SampleCount;
1093 }
1094
1095 /**
1096 * Returns the current position in the sample (in sample points).
1097 */
1098 unsigned long Sample::GetPos() {
1099 return (pCkSmpl->GetPos() - (Start * 2)) / 2;
1100 }
1101
1102 /**
1103 * Reads \a SampleCount number of sample points from the current
1104 * position into the buffer pointed by \a pBuffer and increments the
1105 * position within the sample. Use this method
1106 * and <i>SetPos()</i> if you don't want to load the sample into RAM,
1107 * thus for disk streaming.
1108 *
1109 * For 16 bit samples, the data in the buffer will be int16_t
1110 * (using native endianness). For 24 bit, the buffer will
1111 * contain three bytes per sample, little-endian.
1112 *
1113 * @param pBuffer destination buffer
1114 * @param SampleCount number of sample points to read
1115 * @returns number of successfully read sample points
1116 * @see SetPos()
1117 */
1118 unsigned long Sample::Read(void* pBuffer, unsigned long SampleCount) {
1119 // TODO: startAddrsCoarseOffset, endAddrsCoarseOffset
1120 if (SampleCount == 0) return 0;
1121 long pos = GetPos();
1122 if (pos + SampleCount > GetTotalFrameCount()) SampleCount = GetTotalFrameCount() - pos;
1123
1124 if (GetFrameSize() / GetChannelCount() == 3 /* 24 bit */) {
1125 uint8_t* pBuf = (uint8_t*)pBuffer;
1126 if (SampleType == MONO_SAMPLE || SampleType == ROM_MONO_SAMPLE) {
1127 pCkSmpl->Read(pBuf, SampleCount, 2);
1128 pCkSm24->Read(pBuf + SampleCount * 2, SampleCount, 1);
1129 for (int i = SampleCount - 1; i >= 0; i--) {
1130 pBuf[i*3] = pBuf[(SampleCount * 2) + i];
1131 pBuf[i*3 + 2] = pBuf[i*2 + 1];
1132 pBuf[i*3 + 1] = pBuf[i*2];
1133 }
1134 } else if (SampleType == LEFT_SAMPLE || SampleType == ROM_LEFT_SAMPLE) {
1135 pCkSmpl->Read(pBuf, SampleCount, 2);
1136 pCkSm24->Read(pBuf + SampleCount * 2, SampleCount, 1);
1137 for (int i = SampleCount - 1; i >= 0; i--) {
1138 pBuf[i*6] = pBuf[(SampleCount * 2) + i];
1139 pBuf[i*6 + 2] = pBuf[i*2 + 1];
1140 pBuf[i*6 + 1] = pBuf[i*2];
1141 pBuf[i*6 + 3] = pBuf[i*6 + 4] = pBuf[i*6 + 5] = 0;
1142 }
1143 } else if (SampleType == RIGHT_SAMPLE || SampleType == ROM_RIGHT_SAMPLE) {
1144 pCkSmpl->Read(pBuf, SampleCount, 2);
1145 pCkSm24->Read(pBuf + SampleCount * 2, SampleCount, 1);
1146 for (int i = SampleCount - 1; i >= 0; i--) {
1147 pBuf[i*6 + 3] = pBuf[(SampleCount * 2) + i];
1148 pBuf[i*6 + 5] = pBuf[i*2 + 1];
1149 pBuf[i*6 + 4] = pBuf[i*2];
1150 pBuf[i*6] = pBuf[i*6 + 1] = pBuf[i*6 + 2] = 0;
1151 }
1152 }
1153 } else {
1154 if (SampleType == MONO_SAMPLE || SampleType == ROM_MONO_SAMPLE) {
1155 return pCkSmpl->Read(pBuffer, SampleCount, 2);
1156 }
1157
1158 int16_t* pBuf = (int16_t*)pBuffer;
1159 if (SampleType == LEFT_SAMPLE || SampleType == ROM_LEFT_SAMPLE) {
1160 pCkSmpl->Read(pBuf, SampleCount, 2);
1161 for (int i = SampleCount - 1; i >= 0; i--) {
1162 pBuf[i*2] = pBuf[i];
1163 pBuf[i*2 + 1] = 0;
1164 }
1165 } else if (SampleType == RIGHT_SAMPLE || SampleType == ROM_RIGHT_SAMPLE) {
1166 pCkSmpl->Read(pBuf, SampleCount, 2);
1167 for (int i = SampleCount - 1; i >= 0; i--) {
1168 pBuf[i*2] = 0;
1169 pBuf[i*2 + 1] = pBuf[i];
1170 }
1171 }
1172 }
1173
1174 if (pCkSmpl->GetPos() > (End * 2)) {
1175 std::cerr << "Read after the sample end. This is a BUG!" << std::endl;
1176 std::cerr << "Current position: " << GetPos() << std::endl;
1177 std::cerr << "Total number of frames: " << GetTotalFrameCount() << std::endl << std::endl;
1178 }
1179 return SampleCount;
1180 }
1181
1182
1183 // *************** functions ***************
1184 // *
1185
1186 /**
1187 * Returns the name of this C++ library.
1188 */
1189 String libraryName() {
1190 return PACKAGE;
1191 }
1192
1193 /**
1194 * Returns version of this C++ library.
1195 */
1196 String libraryVersion() {
1197 return VERSION;
1198 }
1199
1200 } // namespace sf2

  ViewVC Help
Powered by ViewVC