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