1/*
2 * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.media.sound;
27
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.net.URL;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
39import java.util.Stack;
40
41import javax.sound.midi.Instrument;
42import javax.sound.midi.Patch;
43import javax.sound.midi.Soundbank;
44import javax.sound.midi.SoundbankResource;
45import javax.sound.sampled.AudioFormat;
46import javax.sound.sampled.AudioFormat.Encoding;
47import javax.sound.sampled.AudioInputStream;
48import javax.sound.sampled.AudioSystem;
49
50/**
51 * A DLS Level 1 and Level 2 soundbank reader (from files/url/streams).
52 *
53 * @author Karl Helgason
54 */
55public final class DLSSoundbank implements Soundbank {
56
57    private static class DLSID {
58        long i1;
59        int s1;
60        int s2;
61        int x1;
62        int x2;
63        int x3;
64        int x4;
65        int x5;
66        int x6;
67        int x7;
68        int x8;
69
70        private DLSID() {
71        }
72
73        DLSID(long i1, int s1, int s2, int x1, int x2, int x3, int x4,
74                int x5, int x6, int x7, int x8) {
75            this.i1 = i1;
76            this.s1 = s1;
77            this.s2 = s2;
78            this.x1 = x1;
79            this.x2 = x2;
80            this.x3 = x3;
81            this.x4 = x4;
82            this.x5 = x5;
83            this.x6 = x6;
84            this.x7 = x7;
85            this.x8 = x8;
86        }
87
88        public static DLSID read(RIFFReader riff) throws IOException {
89            DLSID d = new DLSID();
90            d.i1 = riff.readUnsignedInt();
91            d.s1 = riff.readUnsignedShort();
92            d.s2 = riff.readUnsignedShort();
93            d.x1 = riff.readUnsignedByte();
94            d.x2 = riff.readUnsignedByte();
95            d.x3 = riff.readUnsignedByte();
96            d.x4 = riff.readUnsignedByte();
97            d.x5 = riff.readUnsignedByte();
98            d.x6 = riff.readUnsignedByte();
99            d.x7 = riff.readUnsignedByte();
100            d.x8 = riff.readUnsignedByte();
101            return d;
102        }
103
104        @Override
105        public int hashCode() {
106            return (int)i1;
107        }
108
109        @Override
110        public boolean equals(Object obj) {
111            if (!(obj instanceof DLSID)) {
112                return false;
113            }
114            DLSID t = (DLSID) obj;
115            return i1 == t.i1 && s1 == t.s1 && s2 == t.s2
116                && x1 == t.x1 && x2 == t.x2 && x3 == t.x3 && x4 == t.x4
117                && x5 == t.x5 && x6 == t.x6 && x7 == t.x7 && x8 == t.x8;
118        }
119    }
120
121    /** X = X & Y */
122    private static final int DLS_CDL_AND = 0x0001;
123    /** X = X | Y */
124    private static final int DLS_CDL_OR = 0x0002;
125    /** X = X ^ Y */
126    private static final int DLS_CDL_XOR = 0x0003;
127    /** X = X + Y */
128    private static final int DLS_CDL_ADD = 0x0004;
129    /** X = X - Y */
130    private static final int DLS_CDL_SUBTRACT = 0x0005;
131    /** X = X * Y */
132    private static final int DLS_CDL_MULTIPLY = 0x0006;
133    /** X = X / Y */
134    private static final int DLS_CDL_DIVIDE = 0x0007;
135    /** X = X && Y */
136    private static final int DLS_CDL_LOGICAL_AND = 0x0008;
137    /** X = X || Y */
138    private static final int DLS_CDL_LOGICAL_OR = 0x0009;
139    /** X = (X < Y) */
140    private static final int DLS_CDL_LT = 0x000A;
141    /** X = (X <= Y) */
142    private static final int DLS_CDL_LE = 0x000B;
143    /** X = (X > Y) */
144    private static final int DLS_CDL_GT = 0x000C;
145    /** X = (X >= Y) */
146    private static final int DLS_CDL_GE = 0x000D;
147    /** X = (X == Y) */
148    private static final int DLS_CDL_EQ = 0x000E;
149    /** X = !X */
150    private static final int DLS_CDL_NOT = 0x000F;
151    /** 32-bit constant */
152    private static final int DLS_CDL_CONST = 0x0010;
153    /** 32-bit value returned from query */
154    private static final int DLS_CDL_QUERY = 0x0011;
155    /** 32-bit value returned from query */
156    private static final int DLS_CDL_QUERYSUPPORTED = 0x0012;
157
158    private static final DLSID DLSID_GMInHardware = new DLSID(0x178f2f24,
159            0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
160    private static final DLSID DLSID_GSInHardware = new DLSID(0x178f2f25,
161            0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
162    private static final DLSID DLSID_XGInHardware = new DLSID(0x178f2f26,
163            0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
164    private static final DLSID DLSID_SupportsDLS1 = new DLSID(0x178f2f27,
165            0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
166    private static final DLSID DLSID_SupportsDLS2 = new DLSID(0xf14599e5,
167            0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6);
168    private static final DLSID DLSID_SampleMemorySize = new DLSID(0x178f2f28,
169            0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
170    private static final DLSID DLSID_ManufacturersID = new DLSID(0xb03e1181,
171            0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
172    private static final DLSID DLSID_ProductID = new DLSID(0xb03e1182,
173            0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
174    private static final DLSID DLSID_SamplePlaybackRate = new DLSID(0x2a91f713,
175            0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
176
177    private long major = -1;
178    private long minor = -1;
179
180    private final DLSInfo info = new DLSInfo();
181
182    private final List<DLSInstrument> instruments = new ArrayList<>();
183    private final List<DLSSample> samples = new ArrayList<>();
184
185    private boolean largeFormat = false;
186    private File sampleFile;
187
188    public DLSSoundbank() {
189    }
190
191    public DLSSoundbank(URL url) throws IOException {
192        InputStream is = url.openStream();
193        try {
194            readSoundbank(is);
195        } finally {
196            is.close();
197        }
198    }
199
200    public DLSSoundbank(File file) throws IOException {
201        largeFormat = true;
202        sampleFile = file;
203        InputStream is = new FileInputStream(file);
204        try {
205            readSoundbank(is);
206        } finally {
207            is.close();
208        }
209    }
210
211    public DLSSoundbank(InputStream inputstream) throws IOException {
212        readSoundbank(inputstream);
213    }
214
215    private void readSoundbank(InputStream inputstream) throws IOException {
216        RIFFReader riff = new RIFFReader(inputstream);
217        if (!riff.getFormat().equals("RIFF")) {
218            throw new RIFFInvalidFormatException(
219                    "Input stream is not a valid RIFF stream!");
220        }
221        if (!riff.getType().equals("DLS ")) {
222            throw new RIFFInvalidFormatException(
223                    "Input stream is not a valid DLS soundbank!");
224        }
225        while (riff.hasNextChunk()) {
226            RIFFReader chunk = riff.nextChunk();
227            if (chunk.getFormat().equals("LIST")) {
228                if (chunk.getType().equals("INFO"))
229                    readInfoChunk(chunk);
230                if (chunk.getType().equals("lins"))
231                    readLinsChunk(chunk);
232                if (chunk.getType().equals("wvpl"))
233                    readWvplChunk(chunk);
234            } else {
235                if (chunk.getFormat().equals("cdl ")) {
236                    if (!readCdlChunk(chunk)) {
237                        throw new RIFFInvalidFormatException(
238                                "DLS file isn't supported!");
239                    }
240                }
241                if (chunk.getFormat().equals("colh")) {
242                    // skipped because we will load the entire bank into memory
243                    // long instrumentcount = chunk.readUnsignedInt();
244                    // System.out.println("instrumentcount = "+ instrumentcount);
245                }
246                if (chunk.getFormat().equals("ptbl")) {
247                    // Pool Table Chunk
248                    // skipped because we will load the entire bank into memory
249                }
250                if (chunk.getFormat().equals("vers")) {
251                    major = chunk.readUnsignedInt();
252                    minor = chunk.readUnsignedInt();
253                }
254            }
255        }
256
257        for (Map.Entry<DLSRegion, Long> entry : temp_rgnassign.entrySet()) {
258            entry.getKey().sample = samples.get((int)entry.getValue().longValue());
259        }
260
261        temp_rgnassign = null;
262    }
263
264    private boolean cdlIsQuerySupported(DLSID uuid) {
265        return uuid.equals(DLSID_GMInHardware)
266            || uuid.equals(DLSID_GSInHardware)
267            || uuid.equals(DLSID_XGInHardware)
268            || uuid.equals(DLSID_SupportsDLS1)
269            || uuid.equals(DLSID_SupportsDLS2)
270            || uuid.equals(DLSID_SampleMemorySize)
271            || uuid.equals(DLSID_ManufacturersID)
272            || uuid.equals(DLSID_ProductID)
273            || uuid.equals(DLSID_SamplePlaybackRate);
274    }
275
276    private long cdlQuery(DLSID uuid) {
277        if (uuid.equals(DLSID_GMInHardware))
278            return 1;
279        if (uuid.equals(DLSID_GSInHardware))
280            return 0;
281        if (uuid.equals(DLSID_XGInHardware))
282            return 0;
283        if (uuid.equals(DLSID_SupportsDLS1))
284            return 1;
285        if (uuid.equals(DLSID_SupportsDLS2))
286            return 1;
287        if (uuid.equals(DLSID_SampleMemorySize))
288            return Runtime.getRuntime().totalMemory();
289        if (uuid.equals(DLSID_ManufacturersID))
290            return 0;
291        if (uuid.equals(DLSID_ProductID))
292            return 0;
293        if (uuid.equals(DLSID_SamplePlaybackRate))
294            return 44100;
295        return 0;
296    }
297
298
299    // Reading cdl-ck Chunk
300    // "cdl " chunk can only appear inside : DLS,lart,lar2,rgn,rgn2
301    private boolean readCdlChunk(RIFFReader riff) throws IOException {
302
303        DLSID uuid;
304        long x;
305        long y;
306        Stack<Long> stack = new Stack<>();
307
308        while (riff.available() != 0) {
309            int opcode = riff.readUnsignedShort();
310            switch (opcode) {
311            case DLS_CDL_AND:
312                x = stack.pop();
313                y = stack.pop();
314                stack.push(Long.valueOf(((x != 0) && (y != 0)) ? 1 : 0));
315                break;
316            case DLS_CDL_OR:
317                x = stack.pop();
318                y = stack.pop();
319                stack.push(Long.valueOf(((x != 0) || (y != 0)) ? 1 : 0));
320                break;
321            case DLS_CDL_XOR:
322                x = stack.pop();
323                y = stack.pop();
324                stack.push(Long.valueOf(((x != 0) ^ (y != 0)) ? 1 : 0));
325                break;
326            case DLS_CDL_ADD:
327                x = stack.pop();
328                y = stack.pop();
329                stack.push(Long.valueOf(x + y));
330                break;
331            case DLS_CDL_SUBTRACT:
332                x = stack.pop();
333                y = stack.pop();
334                stack.push(Long.valueOf(x - y));
335                break;
336            case DLS_CDL_MULTIPLY:
337                x = stack.pop();
338                y = stack.pop();
339                stack.push(Long.valueOf(x * y));
340                break;
341            case DLS_CDL_DIVIDE:
342                x = stack.pop();
343                y = stack.pop();
344                stack.push(Long.valueOf(x / y));
345                break;
346            case DLS_CDL_LOGICAL_AND:
347                x = stack.pop();
348                y = stack.pop();
349                stack.push(Long.valueOf(((x != 0) && (y != 0)) ? 1 : 0));
350                break;
351            case DLS_CDL_LOGICAL_OR:
352                x = stack.pop();
353                y = stack.pop();
354                stack.push(Long.valueOf(((x != 0) || (y != 0)) ? 1 : 0));
355                break;
356            case DLS_CDL_LT:
357                x = stack.pop();
358                y = stack.pop();
359                stack.push(Long.valueOf((x < y) ? 1 : 0));
360                break;
361            case DLS_CDL_LE:
362                x = stack.pop();
363                y = stack.pop();
364                stack.push(Long.valueOf((x <= y) ? 1 : 0));
365                break;
366            case DLS_CDL_GT:
367                x = stack.pop();
368                y = stack.pop();
369                stack.push(Long.valueOf((x > y) ? 1 : 0));
370                break;
371            case DLS_CDL_GE:
372                x = stack.pop();
373                y = stack.pop();
374                stack.push(Long.valueOf((x >= y) ? 1 : 0));
375                break;
376            case DLS_CDL_EQ:
377                x = stack.pop();
378                y = stack.pop();
379                stack.push(Long.valueOf((x == y) ? 1 : 0));
380                break;
381            case DLS_CDL_NOT:
382                x = stack.pop();
383                y = stack.pop();
384                stack.push(Long.valueOf((x == 0) ? 1 : 0));
385                break;
386            case DLS_CDL_CONST:
387                stack.push(Long.valueOf(riff.readUnsignedInt()));
388                break;
389            case DLS_CDL_QUERY:
390                uuid = DLSID.read(riff);
391                stack.push(cdlQuery(uuid));
392                break;
393            case DLS_CDL_QUERYSUPPORTED:
394                uuid = DLSID.read(riff);
395                stack.push(Long.valueOf(cdlIsQuerySupported(uuid) ? 1 : 0));
396                break;
397            default:
398                break;
399            }
400        }
401        if (stack.isEmpty())
402            return false;
403
404        return stack.pop() == 1;
405    }
406
407    private void readInfoChunk(RIFFReader riff) throws IOException {
408        info.name = null;
409        while (riff.hasNextChunk()) {
410            RIFFReader chunk = riff.nextChunk();
411            String format = chunk.getFormat();
412            if (format.equals("INAM"))
413                info.name = chunk.readString(chunk.available());
414            else if (format.equals("ICRD"))
415                info.creationDate = chunk.readString(chunk.available());
416            else if (format.equals("IENG"))
417                info.engineers = chunk.readString(chunk.available());
418            else if (format.equals("IPRD"))
419                info.product = chunk.readString(chunk.available());
420            else if (format.equals("ICOP"))
421                info.copyright = chunk.readString(chunk.available());
422            else if (format.equals("ICMT"))
423                info.comments = chunk.readString(chunk.available());
424            else if (format.equals("ISFT"))
425                info.tools = chunk.readString(chunk.available());
426            else if (format.equals("IARL"))
427                info.archival_location = chunk.readString(chunk.available());
428            else if (format.equals("IART"))
429                info.artist = chunk.readString(chunk.available());
430            else if (format.equals("ICMS"))
431                info.commissioned = chunk.readString(chunk.available());
432            else if (format.equals("IGNR"))
433                info.genre = chunk.readString(chunk.available());
434            else if (format.equals("IKEY"))
435                info.keywords = chunk.readString(chunk.available());
436            else if (format.equals("IMED"))
437                info.medium = chunk.readString(chunk.available());
438            else if (format.equals("ISBJ"))
439                info.subject = chunk.readString(chunk.available());
440            else if (format.equals("ISRC"))
441                info.source = chunk.readString(chunk.available());
442            else if (format.equals("ISRF"))
443                info.source_form = chunk.readString(chunk.available());
444            else if (format.equals("ITCH"))
445                info.technician = chunk.readString(chunk.available());
446        }
447    }
448
449    private void readLinsChunk(RIFFReader riff) throws IOException {
450        while (riff.hasNextChunk()) {
451            RIFFReader chunk = riff.nextChunk();
452            if (chunk.getFormat().equals("LIST")) {
453                if (chunk.getType().equals("ins "))
454                    readInsChunk(chunk);
455            }
456        }
457    }
458
459    private void readInsChunk(RIFFReader riff) throws IOException {
460        DLSInstrument instrument = new DLSInstrument(this);
461
462        while (riff.hasNextChunk()) {
463            RIFFReader chunk = riff.nextChunk();
464            String format = chunk.getFormat();
465            if (format.equals("LIST")) {
466                if (chunk.getType().equals("INFO")) {
467                    readInsInfoChunk(instrument, chunk);
468                }
469                if (chunk.getType().equals("lrgn")) {
470                    while (chunk.hasNextChunk()) {
471                        RIFFReader subchunk = chunk.nextChunk();
472                        if (subchunk.getFormat().equals("LIST")) {
473                            if (subchunk.getType().equals("rgn ")) {
474                                DLSRegion split = new DLSRegion();
475                                if (readRgnChunk(split, subchunk))
476                                    instrument.getRegions().add(split);
477                            }
478                            if (subchunk.getType().equals("rgn2")) {
479                                // support for DLS level 2 regions
480                                DLSRegion split = new DLSRegion();
481                                if (readRgnChunk(split, subchunk))
482                                    instrument.getRegions().add(split);
483                            }
484                        }
485                    }
486                }
487                if (chunk.getType().equals("lart")) {
488                    List<DLSModulator> modlist = new ArrayList<>();
489                    while (chunk.hasNextChunk()) {
490                        RIFFReader subchunk = chunk.nextChunk();
491                        if (chunk.getFormat().equals("cdl ")) {
492                            if (!readCdlChunk(chunk)) {
493                                modlist.clear();
494                                break;
495                            }
496                        }
497                        if (subchunk.getFormat().equals("art1"))
498                            readArt1Chunk(modlist, subchunk);
499                    }
500                    instrument.getModulators().addAll(modlist);
501                }
502                if (chunk.getType().equals("lar2")) {
503                    // support for DLS level 2 ART
504                    List<DLSModulator> modlist = new ArrayList<>();
505                    while (chunk.hasNextChunk()) {
506                        RIFFReader subchunk = chunk.nextChunk();
507                        if (chunk.getFormat().equals("cdl ")) {
508                            if (!readCdlChunk(chunk)) {
509                                modlist.clear();
510                                break;
511                            }
512                        }
513                        if (subchunk.getFormat().equals("art2"))
514                            readArt2Chunk(modlist, subchunk);
515                    }
516                    instrument.getModulators().addAll(modlist);
517                }
518            } else {
519                if (format.equals("dlid")) {
520                    instrument.guid = new byte[16];
521                    chunk.readFully(instrument.guid);
522                }
523                if (format.equals("insh")) {
524                    chunk.readUnsignedInt(); // Read Region Count - ignored
525
526                    int bank = chunk.read();             // LSB
527                    bank += (chunk.read() & 127) << 7;   // MSB
528                    chunk.read(); // Read Reserved byte
529                    int drumins = chunk.read();          // Drum Instrument
530
531                    int id = chunk.read() & 127; // Read only first 7 bits
532                    chunk.read(); // Read Reserved byte
533                    chunk.read(); // Read Reserved byte
534                    chunk.read(); // Read Reserved byte
535
536                    instrument.bank = bank;
537                    instrument.preset = id;
538                    instrument.druminstrument = (drumins & 128) > 0;
539                    //System.out.println("bank="+bank+" drumkit="+drumkit
540                    //        +" id="+id);
541                }
542
543            }
544        }
545        instruments.add(instrument);
546    }
547
548    private void readArt1Chunk(List<DLSModulator> modulators, RIFFReader riff)
549            throws IOException {
550        long size = riff.readUnsignedInt();
551        long count = riff.readUnsignedInt();
552
553        if (size - 8 != 0)
554            riff.skip(size - 8);
555
556        for (int i = 0; i < count; i++) {
557            DLSModulator modulator = new DLSModulator();
558            modulator.version = 1;
559            modulator.source = riff.readUnsignedShort();
560            modulator.control = riff.readUnsignedShort();
561            modulator.destination = riff.readUnsignedShort();
562            modulator.transform = riff.readUnsignedShort();
563            modulator.scale = riff.readInt();
564            modulators.add(modulator);
565        }
566    }
567
568    private void readArt2Chunk(List<DLSModulator> modulators, RIFFReader riff)
569            throws IOException {
570        long size = riff.readUnsignedInt();
571        long count = riff.readUnsignedInt();
572
573        if (size - 8 != 0)
574            riff.skip(size - 8);
575
576        for (int i = 0; i < count; i++) {
577            DLSModulator modulator = new DLSModulator();
578            modulator.version = 2;
579            modulator.source = riff.readUnsignedShort();
580            modulator.control = riff.readUnsignedShort();
581            modulator.destination = riff.readUnsignedShort();
582            modulator.transform = riff.readUnsignedShort();
583            modulator.scale = riff.readInt();
584            modulators.add(modulator);
585        }
586    }
587
588    private Map<DLSRegion, Long> temp_rgnassign = new HashMap<>();
589
590    private boolean readRgnChunk(DLSRegion split, RIFFReader riff)
591            throws IOException {
592        while (riff.hasNextChunk()) {
593            RIFFReader chunk = riff.nextChunk();
594            String format = chunk.getFormat();
595            if (format.equals("LIST")) {
596                if (chunk.getType().equals("lart")) {
597                    List<DLSModulator> modlist = new ArrayList<>();
598                    while (chunk.hasNextChunk()) {
599                        RIFFReader subchunk = chunk.nextChunk();
600                        if (chunk.getFormat().equals("cdl ")) {
601                            if (!readCdlChunk(chunk)) {
602                                modlist.clear();
603                                break;
604                            }
605                        }
606                        if (subchunk.getFormat().equals("art1"))
607                            readArt1Chunk(modlist, subchunk);
608                    }
609                    split.getModulators().addAll(modlist);
610                }
611                if (chunk.getType().equals("lar2")) {
612                    // support for DLS level 2 ART
613                    List<DLSModulator> modlist = new ArrayList<>();
614                    while (chunk.hasNextChunk()) {
615                        RIFFReader subchunk = chunk.nextChunk();
616                        if (chunk.getFormat().equals("cdl ")) {
617                            if (!readCdlChunk(chunk)) {
618                                modlist.clear();
619                                break;
620                            }
621                        }
622                        if (subchunk.getFormat().equals("art2"))
623                            readArt2Chunk(modlist, subchunk);
624                    }
625                    split.getModulators().addAll(modlist);
626                }
627            } else {
628
629                if (format.equals("cdl ")) {
630                    if (!readCdlChunk(chunk))
631                        return false;
632                }
633                if (format.equals("rgnh")) {
634                    split.keyfrom = chunk.readUnsignedShort();
635                    split.keyto = chunk.readUnsignedShort();
636                    split.velfrom = chunk.readUnsignedShort();
637                    split.velto = chunk.readUnsignedShort();
638                    split.options = chunk.readUnsignedShort();
639                    split.exclusiveClass = chunk.readUnsignedShort();
640                }
641                if (format.equals("wlnk")) {
642                    split.fusoptions = chunk.readUnsignedShort();
643                    split.phasegroup = chunk.readUnsignedShort();
644                    split.channel = chunk.readUnsignedInt();
645                    long sampleid = chunk.readUnsignedInt();
646                    temp_rgnassign.put(split, sampleid);
647                }
648                if (format.equals("wsmp")) {
649                    split.sampleoptions = new DLSSampleOptions();
650                    readWsmpChunk(split.sampleoptions, chunk);
651                }
652            }
653        }
654        return true;
655    }
656
657    private void readWsmpChunk(DLSSampleOptions sampleOptions, RIFFReader riff)
658            throws IOException {
659        long size = riff.readUnsignedInt();
660        sampleOptions.unitynote = riff.readUnsignedShort();
661        sampleOptions.finetune = riff.readShort();
662        sampleOptions.attenuation = riff.readInt();
663        sampleOptions.options = riff.readUnsignedInt();
664        long loops = riff.readInt();
665
666        if (size > 20)
667            riff.skip(size - 20);
668
669        for (int i = 0; i < loops; i++) {
670            DLSSampleLoop loop = new DLSSampleLoop();
671            long size2 = riff.readUnsignedInt();
672            loop.type = riff.readUnsignedInt();
673            loop.start = riff.readUnsignedInt();
674            loop.length = riff.readUnsignedInt();
675            sampleOptions.loops.add(loop);
676            if (size2 > 16)
677                riff.skip(size2 - 16);
678        }
679    }
680
681    private void readInsInfoChunk(DLSInstrument dlsinstrument, RIFFReader riff)
682            throws IOException {
683        dlsinstrument.info.name = null;
684        while (riff.hasNextChunk()) {
685            RIFFReader chunk = riff.nextChunk();
686            String format = chunk.getFormat();
687            if (format.equals("INAM")) {
688                dlsinstrument.info.name = chunk.readString(chunk.available());
689            } else if (format.equals("ICRD")) {
690                dlsinstrument.info.creationDate =
691                        chunk.readString(chunk.available());
692            } else if (format.equals("IENG")) {
693                dlsinstrument.info.engineers =
694                        chunk.readString(chunk.available());
695            } else if (format.equals("IPRD")) {
696                dlsinstrument.info.product = chunk.readString(chunk.available());
697            } else if (format.equals("ICOP")) {
698                dlsinstrument.info.copyright =
699                        chunk.readString(chunk.available());
700            } else if (format.equals("ICMT")) {
701                dlsinstrument.info.comments =
702                        chunk.readString(chunk.available());
703            } else if (format.equals("ISFT")) {
704                dlsinstrument.info.tools = chunk.readString(chunk.available());
705            } else if (format.equals("IARL")) {
706                dlsinstrument.info.archival_location =
707                        chunk.readString(chunk.available());
708            } else if (format.equals("IART")) {
709                dlsinstrument.info.artist = chunk.readString(chunk.available());
710            } else if (format.equals("ICMS")) {
711                dlsinstrument.info.commissioned =
712                        chunk.readString(chunk.available());
713            } else if (format.equals("IGNR")) {
714                dlsinstrument.info.genre = chunk.readString(chunk.available());
715            } else if (format.equals("IKEY")) {
716                dlsinstrument.info.keywords =
717                        chunk.readString(chunk.available());
718            } else if (format.equals("IMED")) {
719                dlsinstrument.info.medium = chunk.readString(chunk.available());
720            } else if (format.equals("ISBJ")) {
721                dlsinstrument.info.subject = chunk.readString(chunk.available());
722            } else if (format.equals("ISRC")) {
723                dlsinstrument.info.source = chunk.readString(chunk.available());
724            } else if (format.equals("ISRF")) {
725                dlsinstrument.info.source_form =
726                        chunk.readString(chunk.available());
727            } else if (format.equals("ITCH")) {
728                dlsinstrument.info.technician =
729                        chunk.readString(chunk.available());
730            }
731        }
732    }
733
734    private void readWvplChunk(RIFFReader riff) throws IOException {
735        while (riff.hasNextChunk()) {
736            RIFFReader chunk = riff.nextChunk();
737            if (chunk.getFormat().equals("LIST")) {
738                if (chunk.getType().equals("wave"))
739                    readWaveChunk(chunk);
740            }
741        }
742    }
743
744    private void readWaveChunk(RIFFReader riff) throws IOException {
745        DLSSample sample = new DLSSample(this);
746
747        while (riff.hasNextChunk()) {
748            RIFFReader chunk = riff.nextChunk();
749            String format = chunk.getFormat();
750            if (format.equals("LIST")) {
751                if (chunk.getType().equals("INFO")) {
752                    readWaveInfoChunk(sample, chunk);
753                }
754            } else {
755                if (format.equals("dlid")) {
756                    sample.guid = new byte[16];
757                    chunk.readFully(sample.guid);
758                }
759
760                if (format.equals("fmt ")) {
761                    int sampleformat = chunk.readUnsignedShort();
762                    if (sampleformat != 1 && sampleformat != 3) {
763                        throw new RIFFInvalidDataException(
764                                "Only PCM samples are supported!");
765                    }
766                    int channels = chunk.readUnsignedShort();
767                    long samplerate = chunk.readUnsignedInt();
768                    // bytes per sec
769                    /* long framerate = */ chunk.readUnsignedInt();
770                    // block align, framesize
771                    int framesize = chunk.readUnsignedShort();
772                    int bits = chunk.readUnsignedShort();
773                    AudioFormat audioformat = null;
774                    if (sampleformat == 1) {
775                        if (bits == 8) {
776                            audioformat = new AudioFormat(
777                                    Encoding.PCM_UNSIGNED, samplerate, bits,
778                                    channels, framesize, samplerate, false);
779                        } else {
780                            audioformat = new AudioFormat(
781                                    Encoding.PCM_SIGNED, samplerate, bits,
782                                    channels, framesize, samplerate, false);
783                        }
784                    }
785                    if (sampleformat == 3) {
786                        audioformat = new AudioFormat(
787                                Encoding.PCM_FLOAT, samplerate, bits,
788                                channels, framesize, samplerate, false);
789                    }
790
791                    sample.format = audioformat;
792                }
793
794                if (format.equals("data")) {
795                    if (largeFormat) {
796                        sample.setData(new ModelByteBuffer(sampleFile,
797                                chunk.getFilePointer(), chunk.available()));
798                    } else {
799                        byte[] buffer = new byte[chunk.available()];
800                        //  chunk.read(buffer);
801                        sample.setData(buffer);
802
803                        int read = 0;
804                        int avail = chunk.available();
805                        while (read != avail) {
806                            if (avail - read > 65536) {
807                                chunk.readFully(buffer, read, 65536);
808                                read += 65536;
809                            } else {
810                                chunk.readFully(buffer, read, avail - read);
811                                read = avail;
812                            }
813                        }
814                    }
815                }
816
817                if (format.equals("wsmp")) {
818                    sample.sampleoptions = new DLSSampleOptions();
819                    readWsmpChunk(sample.sampleoptions, chunk);
820                }
821            }
822        }
823
824        samples.add(sample);
825
826    }
827
828    private void readWaveInfoChunk(DLSSample dlssample, RIFFReader riff)
829            throws IOException {
830        dlssample.info.name = null;
831        while (riff.hasNextChunk()) {
832            RIFFReader chunk = riff.nextChunk();
833            String format = chunk.getFormat();
834            if (format.equals("INAM")) {
835                dlssample.info.name = chunk.readString(chunk.available());
836            } else if (format.equals("ICRD")) {
837                dlssample.info.creationDate =
838                        chunk.readString(chunk.available());
839            } else if (format.equals("IENG")) {
840                dlssample.info.engineers = chunk.readString(chunk.available());
841            } else if (format.equals("IPRD")) {
842                dlssample.info.product = chunk.readString(chunk.available());
843            } else if (format.equals("ICOP")) {
844                dlssample.info.copyright = chunk.readString(chunk.available());
845            } else if (format.equals("ICMT")) {
846                dlssample.info.comments = chunk.readString(chunk.available());
847            } else if (format.equals("ISFT")) {
848                dlssample.info.tools = chunk.readString(chunk.available());
849            } else if (format.equals("IARL")) {
850                dlssample.info.archival_location =
851                        chunk.readString(chunk.available());
852            } else if (format.equals("IART")) {
853                dlssample.info.artist = chunk.readString(chunk.available());
854            } else if (format.equals("ICMS")) {
855                dlssample.info.commissioned =
856                        chunk.readString(chunk.available());
857            } else if (format.equals("IGNR")) {
858                dlssample.info.genre = chunk.readString(chunk.available());
859            } else if (format.equals("IKEY")) {
860                dlssample.info.keywords = chunk.readString(chunk.available());
861            } else if (format.equals("IMED")) {
862                dlssample.info.medium = chunk.readString(chunk.available());
863            } else if (format.equals("ISBJ")) {
864                dlssample.info.subject = chunk.readString(chunk.available());
865            } else if (format.equals("ISRC")) {
866                dlssample.info.source = chunk.readString(chunk.available());
867            } else if (format.equals("ISRF")) {
868                dlssample.info.source_form = chunk.readString(chunk.available());
869            } else if (format.equals("ITCH")) {
870                dlssample.info.technician = chunk.readString(chunk.available());
871            }
872        }
873    }
874
875    public void save(String name) throws IOException {
876        writeSoundbank(new RIFFWriter(name, "DLS "));
877    }
878
879    public void save(File file) throws IOException {
880        writeSoundbank(new RIFFWriter(file, "DLS "));
881    }
882
883    public void save(OutputStream out) throws IOException {
884        writeSoundbank(new RIFFWriter(out, "DLS "));
885    }
886
887    private void writeSoundbank(RIFFWriter writer) throws IOException {
888        RIFFWriter colh_chunk = writer.writeChunk("colh");
889        colh_chunk.writeUnsignedInt(instruments.size());
890
891        if (major != -1 && minor != -1) {
892            RIFFWriter vers_chunk = writer.writeChunk("vers");
893            vers_chunk.writeUnsignedInt(major);
894            vers_chunk.writeUnsignedInt(minor);
895        }
896
897        writeInstruments(writer.writeList("lins"));
898
899        RIFFWriter ptbl = writer.writeChunk("ptbl");
900        ptbl.writeUnsignedInt(8);
901        ptbl.writeUnsignedInt(samples.size());
902        long ptbl_offset = writer.getFilePointer();
903        for (int i = 0; i < samples.size(); i++)
904            ptbl.writeUnsignedInt(0);
905
906        RIFFWriter wvpl = writer.writeList("wvpl");
907        long off = wvpl.getFilePointer();
908        List<Long> offsettable = new ArrayList<>();
909        for (DLSSample sample : samples) {
910            offsettable.add(Long.valueOf(wvpl.getFilePointer() - off));
911            writeSample(wvpl.writeList("wave"), sample);
912        }
913
914        // small cheat, we are going to rewrite data back in wvpl
915        long bak = writer.getFilePointer();
916        writer.seek(ptbl_offset);
917        writer.setWriteOverride(true);
918        for (Long offset : offsettable)
919            writer.writeUnsignedInt(offset.longValue());
920        writer.setWriteOverride(false);
921        writer.seek(bak);
922
923        writeInfo(writer.writeList("INFO"), info);
924
925        writer.close();
926    }
927
928    private void writeSample(RIFFWriter writer, DLSSample sample)
929            throws IOException {
930
931        AudioFormat audioformat = sample.getFormat();
932
933        Encoding encoding = audioformat.getEncoding();
934        float sampleRate = audioformat.getSampleRate();
935        int sampleSizeInBits = audioformat.getSampleSizeInBits();
936        int channels = audioformat.getChannels();
937        int frameSize = audioformat.getFrameSize();
938        float frameRate = audioformat.getFrameRate();
939        boolean bigEndian = audioformat.isBigEndian();
940
941        boolean convert_needed = false;
942
943        if (audioformat.getSampleSizeInBits() == 8) {
944            if (!encoding.equals(Encoding.PCM_UNSIGNED)) {
945                encoding = Encoding.PCM_UNSIGNED;
946                convert_needed = true;
947            }
948        } else {
949            if (!encoding.equals(Encoding.PCM_SIGNED)) {
950                encoding = Encoding.PCM_SIGNED;
951                convert_needed = true;
952            }
953            if (bigEndian) {
954                bigEndian = false;
955                convert_needed = true;
956            }
957        }
958
959        if (convert_needed) {
960            audioformat = new AudioFormat(encoding, sampleRate,
961                    sampleSizeInBits, channels, frameSize, frameRate, bigEndian);
962        }
963
964        // fmt
965        RIFFWriter fmt_chunk = writer.writeChunk("fmt ");
966        int sampleformat = 0;
967        if (audioformat.getEncoding().equals(Encoding.PCM_UNSIGNED))
968            sampleformat = 1;
969        else if (audioformat.getEncoding().equals(Encoding.PCM_SIGNED))
970            sampleformat = 1;
971        else if (audioformat.getEncoding().equals(Encoding.PCM_FLOAT))
972            sampleformat = 3;
973
974        fmt_chunk.writeUnsignedShort(sampleformat);
975        fmt_chunk.writeUnsignedShort(audioformat.getChannels());
976        fmt_chunk.writeUnsignedInt((long) audioformat.getSampleRate());
977        long srate = ((long)audioformat.getFrameRate())*audioformat.getFrameSize();
978        fmt_chunk.writeUnsignedInt(srate);
979        fmt_chunk.writeUnsignedShort(audioformat.getFrameSize());
980        fmt_chunk.writeUnsignedShort(audioformat.getSampleSizeInBits());
981        fmt_chunk.write(0);
982        fmt_chunk.write(0);
983
984        writeSampleOptions(writer.writeChunk("wsmp"), sample.sampleoptions);
985
986        if (convert_needed) {
987            RIFFWriter data_chunk = writer.writeChunk("data");
988            AudioInputStream stream = AudioSystem.getAudioInputStream(
989                    audioformat, (AudioInputStream)sample.getData());
990            byte[] buff = new byte[1024];
991            int ret;
992            while ((ret = stream.read(buff)) != -1) {
993                data_chunk.write(buff, 0, ret);
994            }
995        } else {
996            RIFFWriter data_chunk = writer.writeChunk("data");
997            ModelByteBuffer databuff = sample.getDataBuffer();
998            databuff.writeTo(data_chunk);
999            /*
1000            data_chunk.write(databuff.array(),
1001            databuff.arrayOffset(),
1002            databuff.capacity());
1003             */
1004        }
1005
1006        writeInfo(writer.writeList("INFO"), sample.info);
1007    }
1008
1009    private void writeInstruments(RIFFWriter writer) throws IOException {
1010        for (DLSInstrument instrument : instruments) {
1011            writeInstrument(writer.writeList("ins "), instrument);
1012        }
1013    }
1014
1015    private void writeInstrument(RIFFWriter writer, DLSInstrument instrument)
1016            throws IOException {
1017
1018        int art1_count = 0;
1019        int art2_count = 0;
1020        for (DLSModulator modulator : instrument.getModulators()) {
1021            if (modulator.version == 1)
1022                art1_count++;
1023            if (modulator.version == 2)
1024                art2_count++;
1025        }
1026        for (DLSRegion region : instrument.regions) {
1027            for (DLSModulator modulator : region.getModulators()) {
1028                if (modulator.version == 1)
1029                    art1_count++;
1030                if (modulator.version == 2)
1031                    art2_count++;
1032            }
1033        }
1034
1035        int version = 1;
1036        if (art2_count > 0)
1037            version = 2;
1038
1039        RIFFWriter insh_chunk = writer.writeChunk("insh");
1040        insh_chunk.writeUnsignedInt(instrument.getRegions().size());
1041        insh_chunk.writeUnsignedInt(instrument.bank +
1042                (instrument.druminstrument ? 2147483648L : 0));
1043        insh_chunk.writeUnsignedInt(instrument.preset);
1044
1045        RIFFWriter lrgn = writer.writeList("lrgn");
1046        for (DLSRegion region: instrument.regions)
1047            writeRegion(lrgn, region, version);
1048
1049        writeArticulators(writer, instrument.getModulators());
1050
1051        writeInfo(writer.writeList("INFO"), instrument.info);
1052
1053    }
1054
1055    private void writeArticulators(RIFFWriter writer,
1056            List<DLSModulator> modulators) throws IOException {
1057        int art1_count = 0;
1058        int art2_count = 0;
1059        for (DLSModulator modulator : modulators) {
1060            if (modulator.version == 1)
1061                art1_count++;
1062            if (modulator.version == 2)
1063                art2_count++;
1064        }
1065        if (art1_count > 0) {
1066            RIFFWriter lar1 = writer.writeList("lart");
1067            RIFFWriter art1 = lar1.writeChunk("art1");
1068            art1.writeUnsignedInt(8);
1069            art1.writeUnsignedInt(art1_count);
1070            for (DLSModulator modulator : modulators) {
1071                if (modulator.version == 1) {
1072                    art1.writeUnsignedShort(modulator.source);
1073                    art1.writeUnsignedShort(modulator.control);
1074                    art1.writeUnsignedShort(modulator.destination);
1075                    art1.writeUnsignedShort(modulator.transform);
1076                    art1.writeInt(modulator.scale);
1077                }
1078            }
1079        }
1080        if (art2_count > 0) {
1081            RIFFWriter lar2 = writer.writeList("lar2");
1082            RIFFWriter art2 = lar2.writeChunk("art2");
1083            art2.writeUnsignedInt(8);
1084            art2.writeUnsignedInt(art2_count);
1085            for (DLSModulator modulator : modulators) {
1086                if (modulator.version == 2) {
1087                    art2.writeUnsignedShort(modulator.source);
1088                    art2.writeUnsignedShort(modulator.control);
1089                    art2.writeUnsignedShort(modulator.destination);
1090                    art2.writeUnsignedShort(modulator.transform);
1091                    art2.writeInt(modulator.scale);
1092                }
1093            }
1094        }
1095    }
1096
1097    private void writeRegion(RIFFWriter writer, DLSRegion region, int version)
1098            throws IOException {
1099        RIFFWriter rgns = null;
1100        if (version == 1)
1101            rgns = writer.writeList("rgn ");
1102        if (version == 2)
1103            rgns = writer.writeList("rgn2");
1104        if (rgns == null)
1105            return;
1106
1107        RIFFWriter rgnh = rgns.writeChunk("rgnh");
1108        rgnh.writeUnsignedShort(region.keyfrom);
1109        rgnh.writeUnsignedShort(region.keyto);
1110        rgnh.writeUnsignedShort(region.velfrom);
1111        rgnh.writeUnsignedShort(region.velto);
1112        rgnh.writeUnsignedShort(region.options);
1113        rgnh.writeUnsignedShort(region.exclusiveClass);
1114
1115        if (region.sampleoptions != null)
1116            writeSampleOptions(rgns.writeChunk("wsmp"), region.sampleoptions);
1117
1118        if (region.sample != null) {
1119            if (samples.indexOf(region.sample) != -1) {
1120                RIFFWriter wlnk = rgns.writeChunk("wlnk");
1121                wlnk.writeUnsignedShort(region.fusoptions);
1122                wlnk.writeUnsignedShort(region.phasegroup);
1123                wlnk.writeUnsignedInt(region.channel);
1124                wlnk.writeUnsignedInt(samples.indexOf(region.sample));
1125            }
1126        }
1127        writeArticulators(rgns, region.getModulators());
1128        rgns.close();
1129    }
1130
1131    private void writeSampleOptions(RIFFWriter wsmp,
1132            DLSSampleOptions sampleoptions) throws IOException {
1133        wsmp.writeUnsignedInt(20);
1134        wsmp.writeUnsignedShort(sampleoptions.unitynote);
1135        wsmp.writeShort(sampleoptions.finetune);
1136        wsmp.writeInt(sampleoptions.attenuation);
1137        wsmp.writeUnsignedInt(sampleoptions.options);
1138        wsmp.writeInt(sampleoptions.loops.size());
1139
1140        for (DLSSampleLoop loop : sampleoptions.loops) {
1141            wsmp.writeUnsignedInt(16);
1142            wsmp.writeUnsignedInt(loop.type);
1143            wsmp.writeUnsignedInt(loop.start);
1144            wsmp.writeUnsignedInt(loop.length);
1145        }
1146    }
1147
1148    private void writeInfoStringChunk(RIFFWriter writer,
1149            String name, String value) throws IOException {
1150        if (value == null)
1151            return;
1152        RIFFWriter chunk = writer.writeChunk(name);
1153        chunk.writeString(value);
1154        int len = value.getBytes("ascii").length;
1155        chunk.write(0);
1156        len++;
1157        if (len % 2 != 0)
1158            chunk.write(0);
1159    }
1160
1161    private void writeInfo(RIFFWriter writer, DLSInfo info) throws IOException {
1162        writeInfoStringChunk(writer, "INAM", info.name);
1163        writeInfoStringChunk(writer, "ICRD", info.creationDate);
1164        writeInfoStringChunk(writer, "IENG", info.engineers);
1165        writeInfoStringChunk(writer, "IPRD", info.product);
1166        writeInfoStringChunk(writer, "ICOP", info.copyright);
1167        writeInfoStringChunk(writer, "ICMT", info.comments);
1168        writeInfoStringChunk(writer, "ISFT", info.tools);
1169        writeInfoStringChunk(writer, "IARL", info.archival_location);
1170        writeInfoStringChunk(writer, "IART", info.artist);
1171        writeInfoStringChunk(writer, "ICMS", info.commissioned);
1172        writeInfoStringChunk(writer, "IGNR", info.genre);
1173        writeInfoStringChunk(writer, "IKEY", info.keywords);
1174        writeInfoStringChunk(writer, "IMED", info.medium);
1175        writeInfoStringChunk(writer, "ISBJ", info.subject);
1176        writeInfoStringChunk(writer, "ISRC", info.source);
1177        writeInfoStringChunk(writer, "ISRF", info.source_form);
1178        writeInfoStringChunk(writer, "ITCH", info.technician);
1179    }
1180
1181    public DLSInfo getInfo() {
1182        return info;
1183    }
1184
1185    @Override
1186    public String getName() {
1187        return info.name;
1188    }
1189
1190    @Override
1191    public String getVersion() {
1192        return major + "." + minor;
1193    }
1194
1195    @Override
1196    public String getVendor() {
1197        return info.engineers;
1198    }
1199
1200    @Override
1201    public String getDescription() {
1202        return info.comments;
1203    }
1204
1205    public void setName(String s) {
1206        info.name = s;
1207    }
1208
1209    public void setVendor(String s) {
1210        info.engineers = s;
1211    }
1212
1213    public void setDescription(String s) {
1214        info.comments = s;
1215    }
1216
1217    @Override
1218    public SoundbankResource[] getResources() {
1219        SoundbankResource[] resources = new SoundbankResource[samples.size()];
1220        int j = 0;
1221        for (int i = 0; i < samples.size(); i++)
1222            resources[j++] = samples.get(i);
1223        return resources;
1224    }
1225
1226    @Override
1227    public DLSInstrument[] getInstruments() {
1228        DLSInstrument[] inslist_array =
1229                instruments.toArray(new DLSInstrument[instruments.size()]);
1230        Arrays.sort(inslist_array, new ModelInstrumentComparator());
1231        return inslist_array;
1232    }
1233
1234    public DLSSample[] getSamples() {
1235        return samples.toArray(new DLSSample[samples.size()]);
1236    }
1237
1238    @Override
1239    public Instrument getInstrument(Patch patch) {
1240        int program = patch.getProgram();
1241        int bank = patch.getBank();
1242        boolean percussion = false;
1243        if (patch instanceof ModelPatch)
1244            percussion = ((ModelPatch) patch).isPercussion();
1245        for (Instrument instrument : instruments) {
1246            Patch patch2 = instrument.getPatch();
1247            int program2 = patch2.getProgram();
1248            int bank2 = patch2.getBank();
1249            if (program == program2 && bank == bank2) {
1250                boolean percussion2 = false;
1251                if (patch2 instanceof ModelPatch)
1252                    percussion2 = ((ModelPatch) patch2).isPercussion();
1253                if (percussion == percussion2)
1254                    return instrument;
1255            }
1256        }
1257        return null;
1258    }
1259
1260    public void addResource(SoundbankResource resource) {
1261        if (resource instanceof DLSInstrument)
1262            instruments.add((DLSInstrument) resource);
1263        if (resource instanceof DLSSample)
1264            samples.add((DLSSample) resource);
1265    }
1266
1267    public void removeResource(SoundbankResource resource) {
1268        if (resource instanceof DLSInstrument)
1269            instruments.remove(resource);
1270        if (resource instanceof DLSSample)
1271            samples.remove(resource);
1272    }
1273
1274    public void addInstrument(DLSInstrument resource) {
1275        instruments.add(resource);
1276    }
1277
1278    public void removeInstrument(DLSInstrument resource) {
1279        instruments.remove(resource);
1280    }
1281
1282    public long getMajor() {
1283        return major;
1284    }
1285
1286    public void setMajor(long major) {
1287        this.major = major;
1288    }
1289
1290    public long getMinor() {
1291        return minor;
1292    }
1293
1294    public void setMinor(long minor) {
1295        this.minor = minor;
1296    }
1297}
1298