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 
  26 package com.sun.media.sound;
  27 
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.io.OutputStream;
  33 import java.net.URL;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.HashMap;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Stack;
  40 
  41 import javax.sound.midi.Instrument;
  42 import javax.sound.midi.Patch;
  43 import javax.sound.midi.Soundbank;
  44 import javax.sound.midi.SoundbankResource;
  45 import javax.sound.sampled.AudioFormat;
  46 import javax.sound.sampled.AudioFormat.Encoding;
  47 import javax.sound.sampled.AudioInputStream;
  48 import 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  */
  55 public 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 }