JMSL Tutorial: MusicShape and DimensionNameSpace

Let's look at JMSL's DimensionNameSpace interface. DimensionNameSpace is a powerful notion which maps dimension numbers to names.  This is an important section, so check it out carefully.

A six dimensional DimensionNameSpace might assign names as follows.
dimension  name
"duration"
"pitch"
"amplitude"
"hold"
"modamp"
"cutoff"

NOTE: The first four names above (duration, pitch, amplitude, and hold) are considered standards by a number of JMSL tools.  They are frequently identified as "invariants".
 

DimensionNameSpace also includes the notion of upper limit, lower limit, and default value per dimension.
For example:
dimension  name  low limit  high limit default
"amplitude"  0.0  1.0 0.25

MusicShape implements DimensionNameSpace, resulting in a rows and columns where each column (dimension) is indexed 0, 1, 2, 3... and has a name (like "duration", "pitch", "amplitude", "hold" ...).

To give a name to a MusicShape's dimension, simply do this:
myMusicShape.setDimensionName(4, "modAmp");

To set limits and default valus, do this:
myMusicShape.setLimits(4, 0, 1000); // dim, low, high
myMusicShape.lsetDefault(4, 1.0), // dim, default
 
 

TRANSLATING BETWEEN DIMENSION NAME SPACES

JMSL also has a DimensionNameSpaceTranslator class which translates an array of double[] from one DimensionNameSpace to another.  It is assumed that the length of the array equal the number of dimensions, and that double[i] contians a value that corresponds to dimension i.  MusicShape uses arrays of double[] to store its elements, so this translation is a fundamental operation.

One DimensionNameSpace may share names with another in some dimensions but not all (for example "rate" in one name space might be found in dimension 6 while it is found in dimension 5 of another space).   The idea is to preserve the meaning of "rate" when an array of data is translated from one DimensionNameSpace to another.

Let's examine two DimensionNameSpaces and see what translation would do.

DimensionNameSpace built by SynthNoteAllPortsInstrument for com.softsynth.jsyn.circuits.FilteredSawtoothBL
0 duration
1 pitch
2 amplitude
3 hold
4 cutoff
5 resonance
6 Rate

DimensionNameSpace built by SynthNoteAllPortsInstrument for com.punosmusic.circuitgrabbag.NewPlucker
0 duration
1 pitch
2 amplitude
3 hold
4 feedback
5 rate

TRANSLATING BETWEEN THESE TWO:
Note that besides the four common names "duration", "pitch", "amplitude", and "hold", that the special name "rate" is found in both DimensionNameSpaces (name comparison is case insensitive so Rate == rate).  DimensionNameSpaceTranslator will preserve these common names when translating a double[] from one name space to the other.

For example, if the following double[] were described in the first DimensionNameSpace:

{ 1, 60, 0.5, 0.5, 1000, 0.60, 0.1 } // before translation, 0.1 "rate" is in dim 6
and if it were translated to the latter DimensionNameSpace, the translation will preserve the first four dimensions, as well as the rate = 0.1 value.  It will substitute the DimensionNameSpace's default value for "feedback",  producing the array:
{1, 60, 0.5, 0.5, <defaultfeedbackvalue>, 0.1 } // after translation, 0.1 "rate" is in dim 5, "feedback" is not common so default is used
Invariants: Some dimensions may be so important that you don't want to risk mistranslations. It would be very unhelpful to penalize the choice of "dur" versus "duration" as a dimension name for dimension 0, and insist on matching names. To address this, DimensionNameSpaceTranslator supports the idea of a dimensional "invariant".  If dimensions 0, 1, 2, and 3 are set as invariants, the DimensionNameSpaceTranslator will simply copy them in place without examining and matching their dimension names.  For example, myDimNameSpaceTranslator.addInvariant(0) specifies that dimension 0 should be copied from position 0 of one array to position 0 of the other, regardless of naming.  To give a working example, in JMSL's Score, when a user copies and pastes notes between staves with different instruments, its DimensionnameSpaceTranslator maintains dimensions 0..3 as invariants since duration, pitch, amplitude, and hold are required to be in dimensions 0..3 respectively.

WHY IS TRANSLATING BETWEEN DIMENSION NAME SPACES USEFUL?

Translating arrays of double[] between DimensionNameSpaces is a very fluid and flexible way of dealing with musical data.  It permits data to be described and interpreted without conforming to a strict specification, and permits the semantics of this data to be preserved wherever it can as it flow between interpreters.

For example, consider the action of copying and pasting musical notes from one staff to another. The notes in the source staff contain synthesis parameters appropriate for the instrument assigned to that staff.  The destination staff may be assigned to a different instrument which may have all, some, or none of its synthesis parameters in common with the first instrument.  By translating each note's data when pasting into the destination staff, we preserve as many semantics as possible, additionally ensuring that the order of the data in the array is in the order required by the destination instrument.

MusicShape uses DimensionNameSpaceTranslator to translate its data from its own DimensionNameSpace to the DimensionNameSpace of its Instrument.  Just before it calls instrument.play(), it queries the instrument for its DimensionNameSpace. If the Instrument's DimensionNameSpace is not null, MusicShape translates the double[] it passes to play() so the values conform to the instrument's DimensionNameSpace.  In pseudo code, this action looks something like this:
 

for each element in the MusicShape {
    fetch one element of double[] data
    if the MusicShape's instrument's DimensionNameSpace is not null {
        translate the double[] from the MusicShape's DimensionNameSpace to the Instrument's DimensionNameSpace.
    }
    hand the translated double[] to instrument.play()
}
To give a concrete example, consider building a MusicShape whose Instrument's DimensionNameSpace is defined by the input ports on a JSyn SynthNote (see com.softsynth.jmsl.jsyn.SynthNoteAllPortsInstrument).  You may know that "modfreq" is one of the input ports on the SynthNote, but you don't care whether this name is mapped to dimension 4, 5, 6 ....  Since MusicShape will translate its data to conform to the instrument's DimensionNameSpace you can simply do the following:
myMusicShape.setDimensionName(4, "modfreq");
...and add data to myMusicShape, assured that dimension 4 will be interpreted as "modfreq".  Even if the Instrument's DimensionNameSpace assigns "modfreq" to dimension 5, the translator will relocate it from the MusicShape's dimension 4 to the Instrument's dimension 5, just in time to play() it.
 
 

Creating a custom DimensionNameSpace

Let's say we have an Instrument which plays excerpts from a soundfile.  We could define its DimensionNameSpace as follows:

...
  DimensionNameSpace dns = new DimensionNameSpaceAdapter();

  dns.setDimensionName(0, "duration");
  dns.setLimits(0, 0, 8); // dim, low, high
  dns.setDefault(0, 1.0); // dim, default

  dns.setDimensionName(1, "startIndex");
  dns.setLimits(1, 0, 44100 * 3600); // dim, low, high
  dns.setDefault(1, 0); // dim, default

  dns.setDimensionName(2, "numFrames");
  dns.setLimits(2, 0, 44100 * 3600); // dim, low, high
  dns.setDefault(2, 1); // dim, default

  setDimensionNameSpace(dns);
 ...
 

Getting and setting array values using names instead of indexes
One of the advantages DimensionNameSpace lends to the JMSL API is the ability to set and get values in an array by name.

You could create a MusicShape compatible with the Instrument described above as follows:
MusicShape s = new MusicShape(ins.getDimensionNameSpace());

You could generate and add data without knowing the dimension indexes, as follows:

for (int i = 0; i < 8; i++) {
   double[] data = new double[s.dimension()];
   data[s.getDimension("duration")] = JMSLRandom.choose(); //0..1
   data[s.getDimension("startIndex")] = 0;
   data[s.getDimension("numFrames")] = JMSLRandom.choose(100, 2048);
   s.add(data);
  }
 

The instrument's play() method could extract data from the double[] parameter by name as well, as is shown here:

public double play(double playTime, double timeStretch, double[] dar) {
  // retrieve data values without knowing the dimension numbers
  double duration = dar[getDimensionNameSpace().getDimension("duration")];
  int startIndex = (int) dar[getDimensionNameSpace().getDimension("startIndex")];
  int numFrames = (int) dar[getDimensionNameSpace().getDimension("numFrames")];
...
 
 
Previous Tutorial Index Tutorial Contents Next


  (C) 1997 Phil Burk and Nick Didkovsky, All Rights Reserved
  JMSL is based upon HMSL (C) Phil Burk, Larry Polansky and David Rosenboom.