Difference between revisions of "Modedular.ck"

From CSWiki
Jump to: navigation, search
m
m (Reverted edit of Blogsdna, changed back to last version by Kijjaz)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This is the code for the version 0.2 testing
+
Update: This is the code for the version 0.3 testing
  
  // Modedular version 0.2 testing
+
  // Modedular version 0.3 testing
  // by Kijjasak Triyanond (kijjaz) kijjaz@yahoo.com  
+
  // by Kijjasak Triyanond (kijjaz) kijjaz@yahoo.com   
 
// [note] this is still quite experimental, so some functions might have problems
 
// some more features will be added soon.
 
// [fixes]
 
// * now the note function's code had been cleaned up
 
//  and is greatly reduced (in size) and optimized (generalized); the output is the same.
 
// * i've decided that the normal rotation should not rotate the whole mode data,
 
//  but instead set the offset of the readhead while using note function
 
//   the rotation can by 'Applied' so that it really rotate the interval array.
 
// * now chord is fully functional, check the test code to see chord in action         
 
 
   
 
   
 
  // licence: Attribution-Share Alike 3.0
 
  // licence: Attribution-Share Alike 3.0
Line 19: Line 9:
 
  //    * to Remix — to adapt the work
 
  //    * to Remix — to adapt the work
 
  // Under the following conditions:
 
  // Under the following conditions:
  //    * Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
+
  //    * Attribution. You must attribute the work in the manner specified by the author or licensor
  //    * Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license.
+
//      (but not in any way that suggests that they endorse you or your use of the work).
 +
  //    * Share Alike. If you alter, transform, or build upon this work,
 +
//      you may distribute the resulting work only under the same, similar or a compatible license.  
 
   
 
   
 +
// Brief Manual:
 +
//    * Modes are kept as an int array of intervals --> intervals[]
 +
//    * Octave Size is the sum of all the intervals,
 +
// Octave Size is automatically calculated when a mode is set.
 +
//    * .update() recalculates Octave Size from the interval array
 +
// (important: if you change the value in intervals[] from outside, .update() should be executed after that)
 +
//    * .set(int interval[]) copies the given array into its interval array
 +
//    * .set(string preset) can set the mode to some preset modes, see below
 +
//    * .get(int result[]) copies the interval array to an outside array
 +
//    * .note(int pitch) returns interger note number (in semitone), pitch starts from 1
 +
// (for example, if we have church mode (7 notes per octave), 1 2 3 4 5 6 7 8 is do re mi fa sol la ti do)
 +
//    * .note(int pitch, int octave) also applies octave shift (can be negative) to the result.
 +
//    * .chord(int root, int degree[], int result[])
 +
//      creates notes from the supplies chord degrees and copy to result
 +
//      and .chord(int root, int octave, int degree[], int result[]) with chord offset
 +
//    * .rotate(int x) moves the root position x times (can be negative)
 +
//    * .setRotate(int x) moves the root position to position x (can be negative)
 +
//    * .rotateApply() applies current rotation to the interval array
 +
//    * .rotateApply(int x) moves the root position to position x then performs rotateApply()
 +
//    Mode Operations:
 +
//    * .retrograde() does the retrograde
 +
//    * .invert() invert all intervals (multiply by -1)
 +
//    * .trim(int begin, int end) trim the interval array to the selected area (from begin to end)
 +
//    * addLeft(int input[]) and addRight(int input[])
 +
//      insert more interval values in front of or behind the current interval arrays
 +
//    * .addValue(int input[]), .subtractValue(int input[]), multiplyValue(int input[]), divideValue(int input[])
 +
//      add or subtract or multiply or divide each cell of the interval array with the input array
 +
//      or use (int input[], int offset) to set offset for the operations 
 +
 
  class Modedular
 
  class Modedular
 
  {
 
  {
 
     [0] @=> int intervals[]; // array of intervals in the mode
 
     [0] @=> int intervals[]; // array of intervals in the mode
 
     0 => int octaveSize; // octave size in semitones
 
     0 => int octaveSize; // octave size in semitones
     0 => int rotationOffset; // for easy mode rotation
+
     0 => int rootPosition; // for easy mode rotation
       
+
   
 +
    // - - - Initialization
 
     fun int update()
 
     fun int update()
 
     {
 
     {
Line 55: Line 77:
 
         if (input == "harmonic minor") set([2,1,2,2,1,3,1]);
 
         if (input == "harmonic minor") set([2,1,2,2,1,3,1]);
 
         if (input == "melodic minor") set([2,1,2,2,2,2,1]);
 
         if (input == "melodic minor") set([2,1,2,2,2,2,1]);
 +
       
 +
        if (input == "major pentatonic") set([2, 2, 3, 2, 3]);
 +
        if (input == "minor pentatonic") set([3, 2, 2, 3, 2]);
 +
       
 +
        if (input == "wholetone") set([2,2,2,2,2,2]);
 +
        if (input == "whole-half") set([2,1]);
 +
        if (input == "half-whole") set([1,2]);
 +
        // maybe this is a joke or something, but theoretically, yes it is this..
 +
        if (input == "chromatic") set([1]);
 
         update();
 
         update();
 
     }
 
     }
 
     fun void get(int input[])
 
     fun void get(int input[])
 
     {
 
     {
         // use this to copy to an outside array
+
         // use this to copy to an outside array      
         new int[input.cap()] @=> input;
+
         for(int i; i < input.cap() && i < intervals.cap(); i++) intervals[i] => input[i];
        for(int i; i < input.cap(); i++) intervals[i] => input[i];
 
 
     }
 
     }
 
      
 
      
 +
    // - - - Acquiring note
 
     fun int note(int pitch)
 
     fun int note(int pitch)
 
     {
 
     {
Line 80: Line 111:
 
         // calculate semitones for the pitch
 
         // calculate semitones for the pitch
 
         // with rootPosition for easy mode rotation
 
         // with rootPosition for easy mode rotation
         for(int i; i < pitch; i++) intervals[(i + rotationOffset) % intervals.cap()] +=> sum;  
+
         for(int i; i < pitch; i++) intervals[(i + rootPosition) % intervals.cap()] +=> sum;  
 
         octave * octaveSize +=> sum; // select desired octave
 
         octave * octaveSize +=> sum; // select desired octave
 
         return sum; // and we'll have the result in semitone
 
         return sum; // and we'll have the result in semitone
Line 90: Line 121:
 
     }   
 
     }   
 
      
 
      
 +
    // - - - RotationZ
 
     fun void rotate(int x)
 
     fun void rotate(int x)
 
     {
 
     {
         // rotate the mode x times      
+
         // rotate the mode x times
         x +=> rotationOffset;
+
         x +=> rootPosition;
         (rotationOffset - (rotationOffset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rotationOffset;
+
         (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
 
     }
 
     }
    fun void setRotate(int x)
+
      fun void setRotate(int x)
 
     {
 
     {
 
         // reset rotation point to x
 
         // reset rotation point to x
         x => rotationOffset;
+
         x => rootPosition;
         (rotationOffset - (rotationOffset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rotationOffset;
+
         (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
 
     }
 
     }
 
     fun void rotateApply()
 
     fun void rotateApply()
 
     {
 
     {
         // use current rotation offset to really rotate the interval array.
+
         // update current rotation into the interval array
        // then reset the rotation offset.
+
         int dummy[intervals.cap()];       
         int dummy[intervals.cap()];
+
        (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
         for(int i; i < intervals.cap(); i++) intervals[(i + rotationOffset) % intervals.cap()] => dummy[i];
+
         for(int i; i < intervals.cap(); i++) intervals[(i + rootPosition) % intervals.cap()] => dummy[i];
 
         for(int i; i < intervals.cap(); i++) dummy[i] => intervals[i];
 
         for(int i; i < intervals.cap(); i++) dummy[i] => intervals[i];
         0 => rotationOffset;
+
         0 => rootPosition; // and clear the rootPosition
 
     }
 
     }
 
     fun void rotateApply(int x)
 
     fun void rotateApply(int x)
 
     {
 
     {
         // use in supplied number as the rotation offset, then really rotate the interval array.
+
         // move root position to x and update current rotation into the interval array right away
        // then reset the rotation offset, also.       
 
 
         setRotate(x);
 
         setRotate(x);
 
         rotateApply();
 
         rotateApply();
 
     }
 
     }
   
+
   
     fun void chord(int root, int positions[], int result[])
+
    // - - - Chord Formation
 +
     fun void chord(int root, int degree[], int result[])
 
     {
 
     {
 
         // make a chord from position list (chord degrees)
 
         // make a chord from position list (chord degrees)
         for(int i; i < positions.cap() && i < result.cap(); i++)
+
         for(int i; i < degree.cap() && i < result.cap(); i++)
 
         {
 
         {
             note(root + positions[i] - 1) => result[i];
+
             note(root-1 + degree[i]) => result[i];
 
         }
 
         }
 
     }
 
     }
     fun void chord(int root, int octave, int positions[], int result[])
+
     fun void chord(int root, int octave, int degree[], int result[])
 
     {
 
     {
 
         // make a chord from position list, with octave
 
         // make a chord from position list, with octave
         for(int i; i < positions.cap() && i < result.cap(); i++)
+
         for(int i; i < degree.cap() && i < result.cap(); i++)
 
         {
 
         {
             note(root + positions[i] - 1) + octave * octaveSize => result[i];
+
             note(root-1 + degree[i]) + octave * octaveSize => result[i];
 
         }
 
         }
     }  
+
     }
 +
   
 +
    // - - - Operation
 +
    fun void retrograde()
 +
    {
 +
        int dummy;
 +
        // swap all the left part with all the right part: an easy way to retrograde
 +
        for(int i; i < intervals.cap()/2; i++)
 +
        {
 +
            intervals[i] => dummy;
 +
            intervals[intervals.cap()-1 - i] => intervals[i];
 +
            dummy => intervals[intervals.cap()-1 - i];
 +
        }
 +
    }
 +
    fun void invert()
 +
    {
 +
        // this is one thing we should have: invertion of the intervals
 +
        for(int i; i < intervals.cap(); i++) -1 *=> intervals[i];
 +
        update();
 +
    }
 +
    fun void trim(int begin, int end)
 +
    {
 +
        // this can trim the interval array by selecting a section.
 +
        // but we should prevent array-out-of-bound
 +
        end++;
 +
        if (begin < 0) 0 => begin;
 +
        if (end >= intervals.cap()) intervals.cap()-1 => end;
 +
        if (begin > end) 0 => begin => end;
 +
        int dummy[end-begin];
 +
        // start trimming
 +
        for(int i; i < end-begin; i++) intervals[begin + i] => dummy[i];
 +
        set(dummy); // and it's all done
 +
    }
 +
   
 +
    fun void addLeft(int input[])
 +
    {
 +
        // add a new set of intervals in front of the current array
 +
        int dummy[intervals.cap() + input.cap()];
 +
        for(int i; i < input.cap(); i++) input[i] => dummy[i];
 +
        for(int i; i < intervals.cap(); i++) intervals[i] => dummy[input.cap() + i];
 +
        set(dummy); // and it's all done
 +
        <<< intervals.cap() >>>;
 +
    }
 +
    fun void addRight(int input[])
 +
    {
 +
        // add a new set of intervals in front of the current array
 +
        int dummy[intervals.cap() + input.cap()];
 +
        for(int i; i < intervals.cap(); i++) intervals[i] => dummy[i];
 +
        for(int i; i < input.cap(); i++) input[i] => dummy[intervals.cap() + i];
 +
        set(dummy); // and it's all done
 +
        <<< intervals.cap() >>>;
 +
    }
 +
    fun void addValue(int input[])
 +
    {
 +
        // add a set of values to the original intervals
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] +=> intervals[i];
 +
        update();
 +
    }
 +
    fun void addValue(int input[], int offset)
 +
    {
 +
        // addValue with offset
 +
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] +=> intervals[(i + offset) % intervals.cap()];
 +
        update();
 +
    }
 +
    fun void subtractValue(int input[])
 +
    {
 +
        // subtract a set of values to the original intervals
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] -=> intervals[i];
 +
        update();
 +
    }
 +
    fun void subtractValue(int input[], int offset)
 +
    {
 +
        // subtractValue with offset
 +
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] -=> intervals[(i + offset) % intervals.cap()];
 +
        update();
 +
    }
 +
    fun void multiplyValue(int input[])
 +
    {
 +
        // multiply a set of values to the original intervals
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] *=> intervals[i];
 +
        update();
 +
    }
 +
    fun void multiplyValue(int input[], int offset)
 +
    {
 +
        // multiplyValue with offset
 +
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] *=> intervals[(i + offset) % intervals.cap()];
 +
        update();
 +
    }
 +
    fun void divideValue(int input[])
 +
    {
 +
        // divide a set of values from the original intervals
 +
        // division by zero will result in no change
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++)
 +
            if (input[i] != 0) input[i] /=> intervals[i];
 +
        update();
 +
    }
 +
    fun void divideValue(int input[], int offset)
 +
    {
 +
        // divideValue with offset
 +
        // division by zero will result in no change
 +
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
 +
        for(int i; i < intervals.cap() && i < input.cap(); i++)
 +
            if (input[i] != 0) input[i] /=> intervals[(i + offset) % intervals.cap()];
 +
        update();
 +
    }       
 
  }
 
  }
  
User Manual: (draft)
+
A a new brief user manual is integrated in the comment in the code. A more detailed one will be posted soon!
  
* Use '''.set''' to set a new mode. The mode's data is formed by a serie of intervals (in semitones).
+
And this is the test code for testing the class. It's also a tutorial/introduction.
For example, .set( [2, 2, 1, 2, 2, 2, 1] ) will create Ionian mode or Major Scale.
 
Other kinds and length of a scale can be created also for example, .set( [2, 1] ) creates a Whole-Half diminished scale.
 
* '''.set''' can be used to set the mode to a preset by passing a string with the mode's name.
 
For example, .set("harmonic minor") would make the mode a harmonic minor instantly.
 
Current mode names are: lydian, ionian, mixolydian, dorian, aeolian, phrygian, locrian, harmonic minor, melodic minor
 
* Use '''.get''' to copy the interval array.
 
int result[];
 
A.get(result);
 
would create a new array and assign to result with the value copied from the mode's interval array
 
* Function '''.note(int pitch)''' would return semitones (in integer) that can be added to a midi note value. The result will be the note in the mode in the key of the midi note value that we add.[br]
 
Note: the pitch value starts from 1, not 0. for example, 1 3 5 7 in ionian mode forms a major 7th chord.
 
* '''.note''' can also accept octave adjustment by using '''.note(int pitch, int octave)'''
 
* '''.rotate(int x)''' will rotate the mode x times and the result can be read by '''.note''' and '''.chord''' (the interval array would stay the same, the rotation does not change the original array, it changes only the "Root Position" value)
 
* '''.setRotate(int x)''' set the "Root Position" directly
 
* '''.rotateApply()''' will update the current rotation to the interval array itself, thus changing the interval array
 
* '''.rotateApply(int x)''' will set the "Root Position" at the x position then update to the interval array right away.
 
* '''.chord(int root, int ChordDegrees[], int result[])''' - specify root as the root of the chord. ChordDegrees contains a serie of chord degrees to create, the result (notes of the chord) would be copied into result.[br]
 
For example, if we have '''.set("ionian")''', and use '''.chord''' with root = 1, ChordDegree = [1, 3, 5, 7], the result would be a major 7th chord on the tonic.
 
* '''.chord(int root, int octave, int ChordDegrees[], int result[])''' is also possible. use the octave parameter to adjust octave.
 
  
Have fun playing with modes.
+
  // - - - test code: to test all the features
Some improvements will be added soon!
 
 
 
An example code for the usage of the class (with some focus on rotation and chord):
 
 
 
  60 => int baseNote; // use middle C as base note
 
 
   
 
   
  Flute s1 => NRev rev => dac; // prepare flute and reverb for performance
+
  Modedular A, B, C;
 +
A.set("harmonic minor"); // prepare A as a harmonic minor mode
 +
B.set([2, 2, 2, 1, 2, 1, 2]); // prepare B as lydian mixolydian (dominant lydian)
 +
C.set([3, 1, 2, 1, 3, 2]); // prepare C as something.. hmm kinda like a major blues, with 6 intervals
 
   
 
   
  Clarinet s2[4]; // prepare clarinet orchestra
+
  Flute s1 => NRev rev => dac;
  for(int i; i < 4; i++)
+
s1.gain(.5);
 +
rev.mix(.15);
 +
 +
Flute s2[6];
 +
  for(int i; i < 6; i++)
 
  {
 
  {
 
     s2[i] => rev;
 
     s2[i] => rev;
     s2[i].gain(.1);
+
     s2[i].gain(.2);
 
  }
 
  }
 
   
 
   
s1.gain(.5);
 
rev.mix(.15);
 
 
   
 
   
  Modedular A;
+
  60 => int baseNote;
 
   
 
   
  fun void PlayScale()
+
// functions for easy playing
 +
  fun void PlayScale(StkInstrument S, Modedular M)
 
  {
 
  {
 
     for(1 => int i; i <= 15; i++)
 
     for(1 => int i; i <= 15; i++)
 +
        {
 +
            M.note(i) + baseNote => Std.mtof => s1.freq;
 +
            S.noteOn(.5);
 +
            200::ms => now;
 +
            S.noteOff(.5);
 +
            50::ms => now;
 +
        }
 +
      second => now; // a little pause
 +
}
 +
fun void PlayChord(int notes[])
 +
{
 +
    for(int i; i < notes.cap(); i++)
 
     {
 
     {
         A.note(i) + baseNote => Std.mtof => s1.freq;
+
         notes[i] + baseNote => Std.mtof => s2[i].freq;
         s1.noteOn(.2);
+
         s2[i].noteOn(.7);
        200::ms => now;
 
        s1.noteOff(.2);
 
        50::ms => now;
 
 
     }
 
     }
 +
    second => now;
 +
    for(int i; i < notes.cap(); i++) s2[i].noteOff(.7);
 +
    50::ms => now;
 
  }
 
  }
 
   
 
   
  <<< "play notes from C ionain mode.", "" >>>;
+
// let's perform each feature
  A.set("ionian");
+
<<< "play scale A","" >>>; PlayScale(s1, A);
  PlayScale();
+
<<< "play scale B","" >>>; PlayScale(s1, B);
second => now;
+
  <<< "play scale C","" >>>; PlayScale(s1, C);
 +
  int mode1[A.intervals.cap()];
 +
<<< "get scale from A","" >>>; A.get(mode1);
 +
  <<< "display the acquired mode intervals:","" >>>;
 +
for(int i; i < mode1.cap(); i++) <<< "interval ", i, ": ", mode1[i], " semitones." >>>;
 
   
 
   
  <<< "rotate mode by +1.", "" >>>;
+
  <<< "make some chords.. first, use [1,3,5] form on scale A","" >>>;
A.rotate(1);
+
  int chord[3];
PlayScale();
+
// chord degree = note number .. like 1 3 5 is a triad .. 1 3 5 7 a seventh chord
<<< "oh, correct. That was dorian, so the rotation works fine", "" >>>;
 
  second => now;
 
 
   
 
   
  <<< "now rotate it by -3 and let's see.", "" >>>;
+
  for(1 => int i; i <= 8; i++)
A.rotate(-3);
 
PlayScale();
 
<<< "that is aeolian. Okay the rotation works correctly", "" >>>;
 
second => now;
 
 
<<< "take a look at the interval array: ", "" >>>;
 
for(int i; i < A.intervals.cap(); i++) <<< "interval ", i, " : ", A.intervals[i] >>>;
 
<<< "ah! after rotating it around, it still stays as an ionian the same,", "" >>>;
 
<<< "let's try applying the rotation and see that the change is update in the interval array.", "" >>>;
 
A.rotateApply();
 
for(int i; i < A.intervals.cap(); i++) <<< "interval ", i, " : ", A.intervals[i] >>>;
 
PlayScale();
 
second => now;
 
 
// - - - chord testing
 
// although using Note is simple enough to use as chord creator, but I'm introducing a chord idea:
 
 
fun void PlayChord(int semitones[])
 
 
  {
 
  {
     for(int i; i < 4; i++)
+
     A.chord(i, [1, 3, 5], chord); // this will create notes (in semitone) and put in the 'chord' array
    {
+
     PlayChord(chord);
        semitones[i] + baseNote => Std.mtof => s2[i].freq;
 
        s2[i].noteOn(.8);
 
     }
 
    1200::ms => now;
 
    for(int i; i < 4; i++) s2[i].noteOff(.8);
 
    200::ms => now;
 
   
 
 
  }
 
  }
 +
second => now;
 
   
 
   
  <<< "make 7th chord form: 1 3 5 7", "" >>>;
+
  <<< "let's try chord with more voices.. let's say.. form [1,4,7,9,11]. Jazzy!","" >>>;
  int ChordResult[4]; // will be used to keep notes produced from Modedular's chord function
+
  new int[5] @=> chord;
int ChordDegree[]; // will be used to specify chord form
 
 
[1, 3, 5, 7] @=> ChordDegree;
 
 
 
  for(1 => int i; i <= 8; i++)
 
  for(1 => int i; i <= 8; i++)
 
  {
 
  {
     A.chord(i, ChordDegree, ChordResult);
+
     A.chord(i, [1, 4, 7, 9, 11], chord);
     PlayChord(ChordResult);
+
     PlayChord(chord);
 
  }
 
  }
 
  second => now;
 
  second => now;
 
   
 
   
  <<< "try changing into another mode, and play with a different set of chord degrees", "" >>>;
+
  <<< "let's try that with a different scale: scale B.. woohoo.","" >>>;
A.set([1, 2, 1, 2, 2, 2, 2]); // set A to "Super Locrian" mode to play some jazzy tension chords
 
[1, 4, 5, 7 + 7] @=> ChordDegree; // change voicing form, notice the +7, this simply means up an octave
 
// (because this time, we have 7 tones per octave)
 
 
 
  for(1 => int i; i <= 8; i++)
 
  for(1 => int i; i <= 8; i++)
 
  {
 
  {
     A.chord(i, ChordDegree, ChordResult);
+
     B.chord(i, [1, 4, 7, 9, 11], chord);
     PlayChord(ChordResult);
+
     PlayChord(chord);
 
  }
 
  }
 
  second => now;
 
  second => now;
 +
 +
<<< "let's do some rotation: play scale A original:","" >>>; PlayScale(s1, A);
 +
<<< "we'll rotate it so that the mode will start from the next (second) root position instead.","" >>>;
 +
A.rotate(1);
 +
PlayScale(s1, A);
 +
<<< "rotate back","" >>>; A.rotate(-1); PlayScale(s1, A);
 +
<<< "rotate back another 2 times","" >>>; A.rotate(-2); PlayScale(s1, A);
 +
<<< "Apply the rotation to the interval array:","" >>>; A.rotateApply();
 +
<<< "and let's see the content of the interval array now:","" >>>;
 +
for(int i; i < A.intervals.cap(); i++) <<< "interval ", i, ": ", A.intervals[i], " semitones." >>>;
 +
second => now;
 +
 +
// - - - - -
 +
 
 +
<<< "alright! let's do some operations on the modes. this time we'll play with mode B","" >>>;
 +
<<< "original:","" >>>; PlayScale(s1, B);
 +
<<< "retrograde!","" >>>;
 +
B.retrograde(); PlayScale(s1, B);
 +
<<< "inverse!","" >>>;
 +
24 +=> baseNote; B.invert(); PlayScale(s1, B);
 +
<<< "retrograde again!","" >>>;
 +
B.retrograde(); PlayScale(s1, B);
 +
<<< "and inverse! (so this time it'd be back to the original)","" >>>;
 +
24 -=> baseNote; B.invert(); PlayScale(s1, B);
 +
 +
// - - - - -
 +
 +
<<< "add more intervals behind mode B. let's say.. an ionian. so it'd be a 24-semitone mode!","" >>>;
 +
B.addRight([2, 2, 1, 2, 2, 2, 1]);
 +
for(int i; i < B.intervals.cap(); i++) <<< "interval ", i, ": ", B.intervals[i], " semitones." >>>;
 +
PlayScale(s1, B);
 +
 +
<<< "trim it by selecting interval 5 to 11 (we'll get a 7-note mode)","" >>>;
 +
B.trim(5, 11);
 +
for(int i; i < B.intervals.cap(); i++) <<< "interval ", i, ": ", B.intervals[i], " semitones." >>>;
 +
PlayScale(s1, B);
 +
<<< "add 1 semitone to all the intervals!","" >>>;
 +
B.addValue([1,1,1,1,1,1,1]); PlayScale(s1, B);
 +
<<< "subtract 1 semitone from all the intervals!","" >>>;
 +
B.subtractValue([1,1,1,1,1,1,1]); PlayScale(s1, B);
 +
<<< "multiply 2 to all the intervals! (make it a 7-notes 24-semitone mode)","" >>>;
 +
B.multiplyValue([2,2,2,2,2,2,2]); PlayScale(s1, B);
 +
<<< "divide 2 from all the intervals!","" >>>;
 +
B.divideValue([2,2,2,2,2,2,2]); PlayScale(s1, B);
 +
 +
<<< "thanks for staying with us. comments and bug reports are very welcome. and thanks for testing", "" >>>;

Latest revision as of 09:30, 5 August 2008

Update: This is the code for the version 0.3 testing

// Modedular version 0.3 testing
// by Kijjasak Triyanond (kijjaz) kijjaz@yahoo.com   

// licence: Attribution-Share Alike 3.0
// You are free:
//    * to Share — to copy, distribute and transmit the work
//    * to Remix — to adapt the work
// Under the following conditions:
//    * Attribution. You must attribute the work in the manner specified by the author or licensor
//      (but not in any way that suggests that they endorse you or your use of the work).
//    * Share Alike. If you alter, transform, or build upon this work,
//      you may distribute the resulting work only under the same, similar or a compatible license. 

// Brief Manual:
//    * Modes are kept as an int array of intervals --> intervals[]
//    * Octave Size is the sum of all the intervals, 
// Octave Size is automatically calculated when a mode is set.
//    * .update() recalculates Octave Size from the interval array
// (important: if you change the value in intervals[] from outside, .update() should be executed after that)
//    * .set(int interval[]) copies the given array into its interval array
//    * .set(string preset) can set the mode to some preset modes, see below
//    * .get(int result[]) copies the interval array to an outside array
//    * .note(int pitch) returns interger note number (in semitone), pitch starts from 1
// (for example, if we have church mode (7 notes per octave), 1 2 3 4 5 6 7 8 is do re mi fa sol la ti do)
//    * .note(int pitch, int octave) also applies octave shift (can be negative) to the result.
//    * .chord(int root, int degree[], int result[]) 
//       creates notes from the supplies chord degrees and copy to result
//       and .chord(int root, int octave, int degree[], int result[]) with chord offset
//    * .rotate(int x) moves the root position x times (can be negative)
//    * .setRotate(int x) moves the root position to position x (can be negative)
//    * .rotateApply() applies current rotation to the interval array
//    * .rotateApply(int x) moves the root position to position x then performs rotateApply()
//    Mode Operations:
//    * .retrograde() does the retrograde
//    * .invert() invert all intervals (multiply by -1)
//    * .trim(int begin, int end) trim the interval array to the selected area (from begin to end)
//    * addLeft(int input[]) and addRight(int input[])
//      insert more interval values in front of or behind the current interval arrays
//    * .addValue(int input[]), .subtractValue(int input[]), multiplyValue(int input[]), divideValue(int input[])
//      add or subtract or multiply or divide each cell of the interval array with the input array
//      or use (int input[], int offset) to set offset for the operations  
class Modedular
{
    [0] @=> int intervals[]; // array of intervals in the mode
    0 => int octaveSize; // octave size in semitones
    0 => int rootPosition; // for easy mode rotation
    
    // - - - Initialization
    fun int update()
    {
        // use this to octave Octave Size
        0 => octaveSize;
        for(int i; i < intervals.cap(); i++) intervals[i] +=> octaveSize;
        return octaveSize;
    }
    fun void set(int input[])
    {
        // use this to copy intervals from the input array
        new int[input.cap()] @=> intervals;
        for(int i; i < input.cap(); i++) input[i] => intervals[i];
        update();
    }
    fun void set(string input)
    {
        // use this to set the mode to a preset value by a string
        if (input == "lydian") set([2,2,2,1,2,2,1]);
        if (input == "ionian") set([2,2,1,2,2,2,1]);
        if (input == "mixolydian") set([2,2,1,2,2,1,2]);
        if (input == "dorian") set([2,1,2,2,2,1,2]);
        if (input == "aeolian") set([2,1,2,2,1,2,2]);
        if (input == "phrygian") set([1,2,2,2,1,2,2]);
        if (input == "locrian") set([1,2,2,1,2,2,2]);
       
        if (input == "harmonic minor") set([2,1,2,2,1,3,1]);
        if (input == "melodic minor") set([2,1,2,2,2,2,1]);
        
        if (input == "major pentatonic") set([2, 2, 3, 2, 3]);
        if (input == "minor pentatonic") set([3, 2, 2, 3, 2]);
        
        if (input == "wholetone") set([2,2,2,2,2,2]);
        if (input == "whole-half") set([2,1]);
        if (input == "half-whole") set([1,2]);
        // maybe this is a joke or something, but theoretically, yes it is this..
        if (input == "chromatic") set([1]); 
        update();
    }
    fun void get(int input[])
    {
        // use this to copy to an outside array        
        for(int i; i < input.cap() && i < intervals.cap(); i++) intervals[i] => input[i];
    }
   
    // - - - Acquiring note
    fun int note(int pitch)
    {
        // use this to acquire note (calculated in semitones) from the mode
        // without octave input
        pitch--; // so user can start the first pitch from 1 instead of 0
        0 => int octave; // but we still have to use octave if pitch is negative
        
        // calculate pitch and octave for use the intervals array
        // by limiting pitch in rang 0..intervals.cap()-1 and adjust octave number
        if (pitch < 0) octave--;
        pitch / intervals.cap() +=> octave;
        (pitch - (pitch / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => pitch;
       
        0 => int sum;
        // calculate semitones for the pitch
        // with rootPosition for easy mode rotation
        for(int i; i < pitch; i++) intervals[(i + rootPosition) % intervals.cap()] +=> sum; 
        octave * octaveSize +=> sum; // select desired octave
        return sum; // and we'll have the result in semitone
    }
    fun int note(int pitch, int octave)
    {
        // note, with octave number also
        return note(pitch) + octave * octaveSize;
    }   
   
    // - - - RotationZ
    fun void rotate(int x)
    {
        // rotate the mode x times
        x +=> rootPosition;
        (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
    }
     fun void setRotate(int x)
    {
        // reset rotation point to x
        x => rootPosition;
        (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
    }
    fun void rotateApply()
    {
        // update current rotation into the interval array
        int dummy[intervals.cap()];        
        (rootPosition - (rootPosition / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => rootPosition;
        for(int i; i < intervals.cap(); i++) intervals[(i + rootPosition) % intervals.cap()] => dummy[i];
        for(int i; i < intervals.cap(); i++) dummy[i] => intervals[i];
        0 => rootPosition; // and clear the rootPosition
    }
    fun void rotateApply(int x)
    {
        // move root position to x and update current rotation into the interval array right away
        setRotate(x);
        rotateApply();
    }
    
    // - - - Chord Formation
    fun void chord(int root, int degree[], int result[])
    {
        // make a chord from position list (chord degrees)
        for(int i; i < degree.cap() && i < result.cap(); i++)
        {
            note(root-1 + degree[i]) => result[i];
        }
    }
    fun void chord(int root, int octave, int degree[], int result[])
    {
        // make a chord from position list, with octave
        for(int i; i < degree.cap() && i < result.cap(); i++)
        {
            note(root-1 + degree[i]) + octave * octaveSize => result[i];
        }
    }
    
    // - - - Operation
    fun void retrograde()
    {
        int dummy;
        // swap all the left part with all the right part: an easy way to retrograde
        for(int i; i < intervals.cap()/2; i++)
        {
            intervals[i] => dummy;
            intervals[intervals.cap()-1 - i] => intervals[i];
            dummy => intervals[intervals.cap()-1 - i];
        }
    }
    fun void invert()
    {
        // this is one thing we should have: invertion of the intervals
        for(int i; i < intervals.cap(); i++) -1 *=> intervals[i];
        update();
    }
    fun void trim(int begin, int end)
    {
        // this can trim the interval array by selecting a section.
        // but we should prevent array-out-of-bound
        end++;
        if (begin < 0) 0 => begin;
        if (end >= intervals.cap()) intervals.cap()-1 => end;
        if (begin > end) 0 => begin => end;
        int dummy[end-begin];
        // start trimming
        for(int i; i < end-begin; i++) intervals[begin + i] => dummy[i];
        set(dummy); // and it's all done
    }
    
    fun void addLeft(int input[])
    {
        // add a new set of intervals in front of the current array
        int dummy[intervals.cap() + input.cap()];
        for(int i; i < input.cap(); i++) input[i] => dummy[i];
        for(int i; i < intervals.cap(); i++) intervals[i] => dummy[input.cap() + i];
        set(dummy); // and it's all done
        <<< intervals.cap() >>>;
    }
    fun void addRight(int input[])
    {
        // add a new set of intervals in front of the current array
        int dummy[intervals.cap() + input.cap()];
        for(int i; i < intervals.cap(); i++) intervals[i] => dummy[i];
        for(int i; i < input.cap(); i++) input[i] => dummy[intervals.cap() + i];
        set(dummy); // and it's all done
        <<< intervals.cap() >>>;
    }
    fun void addValue(int input[])
    {
        // add a set of values to the original intervals
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] +=> intervals[i];
        update();
    }
    fun void addValue(int input[], int offset)
    {
        // addValue with offset
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] +=> intervals[(i + offset) % intervals.cap()];
        update();
    }
    fun void subtractValue(int input[])
    {
        // subtract a set of values to the original intervals
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] -=> intervals[i];
        update();
    }
    fun void subtractValue(int input[], int offset)
    {
        // subtractValue with offset
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] -=> intervals[(i + offset) % intervals.cap()];
        update();
    }
    fun void multiplyValue(int input[])
    {
        // multiply a set of values to the original intervals
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] *=> intervals[i];
        update();
    }
    fun void multiplyValue(int input[], int offset)
    {
        // multiplyValue with offset
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
        for(int i; i < intervals.cap() && i < input.cap(); i++) input[i] *=> intervals[(i + offset) % intervals.cap()];
        update();
    }
    fun void divideValue(int input[])
    {
        // divide a set of values from the original intervals
        // division by zero will result in no change
        for(int i; i < intervals.cap() && i < input.cap(); i++)
            if (input[i] != 0) input[i] /=> intervals[i];
        update();
    }
    fun void divideValue(int input[], int offset)
    {
        // divideValue with offset
        // division by zero will result in no change
        (offset - (offset / intervals.cap() - 1) * intervals.cap()) % intervals.cap() => offset;
        for(int i; i < intervals.cap() && i < input.cap(); i++)
            if (input[i] != 0) input[i] /=> intervals[(i + offset) % intervals.cap()];
        update();
    }        
}

A a new brief user manual is integrated in the comment in the code. A more detailed one will be posted soon!

And this is the test code for testing the class. It's also a tutorial/introduction.

// - - - test code: to test all the features

Modedular A, B, C;
A.set("harmonic minor"); // prepare A as a harmonic minor mode
B.set([2, 2, 2, 1, 2, 1, 2]); // prepare B as lydian mixolydian (dominant lydian)
C.set([3, 1, 2, 1, 3, 2]); // prepare C as something.. hmm kinda like a major blues, with 6 intervals

Flute s1 => NRev rev => dac;
s1.gain(.5);
rev.mix(.15);

Flute s2[6];
for(int i; i < 6; i++)
{
    s2[i] => rev;
    s2[i].gain(.2);
}


60 => int baseNote;

// functions for easy playing
fun void PlayScale(StkInstrument S, Modedular M)
{
    for(1 => int i; i <= 15; i++)
        {
            M.note(i) + baseNote => Std.mtof => s1.freq;
            S.noteOn(.5);
            200::ms => now;
            S.noteOff(.5);
            50::ms => now;
        }
     second => now; // a little pause
}
fun void PlayChord(int notes[])
{
    for(int i; i < notes.cap(); i++)
    {
        notes[i] + baseNote => Std.mtof => s2[i].freq;
        s2[i].noteOn(.7);
    }
    second => now;
    for(int i; i < notes.cap(); i++) s2[i].noteOff(.7);
    50::ms => now;
}

// let's perform each feature
<<< "play scale A","" >>>; PlayScale(s1, A);
<<< "play scale B","" >>>; PlayScale(s1, B);
<<< "play scale C","" >>>; PlayScale(s1, C);
int mode1[A.intervals.cap()];
<<< "get scale from A","" >>>; A.get(mode1);
<<< "display the acquired mode intervals:","" >>>;
for(int i; i < mode1.cap(); i++) <<< "interval ", i, ": ", mode1[i], " semitones." >>>;

<<< "make some chords.. first, use [1,3,5] form on scale A","" >>>;
int chord[3];
// chord degree = note number .. like 1 3 5 is a triad .. 1 3 5 7 a seventh chord

for(1 => int i; i <= 8; i++)
{
    A.chord(i, [1, 3, 5], chord); // this will create notes (in semitone) and put in the 'chord' array
    PlayChord(chord);
}
second => now;

<<< "let's try chord with more voices.. let's say.. form [1,4,7,9,11]. Jazzy!","" >>>;
new int[5] @=> chord;
for(1 => int i; i <= 8; i++)
{
    A.chord(i, [1, 4, 7, 9, 11], chord);
    PlayChord(chord);
}
second => now;

<<< "let's try that with a different scale: scale B.. woohoo.","" >>>;
for(1 => int i; i <= 8; i++)
{
    B.chord(i, [1, 4, 7, 9, 11], chord);
    PlayChord(chord);
}
second => now;

<<< "let's do some rotation: play scale A original:","" >>>; PlayScale(s1, A);
<<< "we'll rotate it so that the mode will start from the next (second) root position instead.","" >>>;
A.rotate(1);
PlayScale(s1, A);
<<< "rotate back","" >>>; A.rotate(-1); PlayScale(s1, A);
<<< "rotate back another 2 times","" >>>; A.rotate(-2); PlayScale(s1, A);
<<< "Apply the rotation to the interval array:","" >>>; A.rotateApply();
<<< "and let's see the content of the interval array now:","" >>>;
for(int i; i < A.intervals.cap(); i++) <<< "interval ", i, ": ", A.intervals[i], " semitones." >>>;
second => now;

// - - - - -
 
<<< "alright! let's do some operations on the modes. this time we'll play with mode B","" >>>;
<<< "original:","" >>>; PlayScale(s1, B);
<<< "retrograde!","" >>>;
B.retrograde(); PlayScale(s1, B);
<<< "inverse!","" >>>;
24 +=> baseNote; B.invert(); PlayScale(s1, B);
<<< "retrograde again!","" >>>;
B.retrograde(); PlayScale(s1, B);
<<< "and inverse! (so this time it'd be back to the original)","" >>>;
24 -=> baseNote; B.invert(); PlayScale(s1, B);

// - - - - -

<<< "add more intervals behind mode B. let's say.. an ionian. so it'd be a 24-semitone mode!","" >>>;
B.addRight([2, 2, 1, 2, 2, 2, 1]);
for(int i; i < B.intervals.cap(); i++) <<< "interval ", i, ": ", B.intervals[i], " semitones." >>>;
PlayScale(s1, B);

<<< "trim it by selecting interval 5 to 11 (we'll get a 7-note mode)","" >>>;
B.trim(5, 11);
for(int i; i < B.intervals.cap(); i++) <<< "interval ", i, ": ", B.intervals[i], " semitones." >>>;
PlayScale(s1, B);
<<< "add 1 semitone to all the intervals!","" >>>;
B.addValue([1,1,1,1,1,1,1]); PlayScale(s1, B);
<<< "subtract 1 semitone from all the intervals!","" >>>;
B.subtractValue([1,1,1,1,1,1,1]); PlayScale(s1, B);
<<< "multiply 2 to all the intervals! (make it a 7-notes 24-semitone mode)","" >>>;
B.multiplyValue([2,2,2,2,2,2,2]); PlayScale(s1, B);
<<< "divide 2 from all the intervals!","" >>>;
B.divideValue([2,2,2,2,2,2,2]); PlayScale(s1, B);

<<< "thanks for staying with us. comments and bug reports are very welcome. and thanks for testing", "" >>>;