//  JS OBJECT -  -  -  GNOELALEXMAY -  -  -  -  21-02-2009
//
//  OBJECT FOR CREATING ARPEGGIATED SEQUENCES FOR ALGORITHMIC COMPOSITION
//
//  FOR COLLABORATIVE MODULE - 2009

//    OBJECT INFORMATION >>>>>>>>

//    A flexible playbar-arpeggiator that transposes an array of pitches to fit a set range.
//    The arp loop can play at any speed in either direction, outputting the 'nearest' note 
//    to its current address.
//    A note is output when the js object receives a 'bang' from the Max patch.
//    When a note is output, the playbar address is updated by the arp_multiplier amount (float pos/neg)
//    The playbar is cycles within a range set from the Max patch (0-8 by default)
//    The note range that is output can be implemented in two modes (range_mode 0/1)
//    MODE 0:  If a note goes out of the set range, it will appear from the other end of the range
//    MODE 1:  If a note goes out of range, it will be moved back, octave-by-octave until...
//    ...it is back in range, and then output
//
//    MODE 0 is more esoteric, and can create unexpected note-values (especially if the 
//    ...range is not an exact octave)
//    MODE 1 will keep the original notes, only moving their octave.
//    If MODE 1 is used with a note-range less than 1 octave, some notes may be bounced 
//    back below the minimum range
//
//    The arpeggiator can hold as many notes as you like, a loop through a user-defined section
//    ...so a whole composition can be held, playing through different sections throught the composition
//
//    the parameter, 'chord_offset n' can be set from the Max patch, allowing transposition to occurr
//    within the set range - useful for chord inversions.
//
//    NOTE VALUES are output through outlet 1
//    An 'unadjusted' note value is output through outlet 2 (read directly from the note-array)
//    The address of the current note is output through outlet 3.



inlets=1;
outlets=3;


//    >>>>>>VARIABLES

arpLen=8;                                      //    LENGTH OF FULL ARP ARRAY ADDRESS LOOP
arpStart=0;                                    //    START POINT OF ARP_OUTPUT ADDRESS LOOP
arpEnd=8;                                     //    END POINT OF ARP_OUTPUT ADDRESS LOOP
arpMult=1;                                     //    MULTIPLIER OF ARP_OUTPUT ADDRESS
modLow=0;                                   //    LOW VALUE OF MODULO RANGE ( LOWEST OUTPUT VALUE)
modHi=12;                                    //    HIGH VALUE OF MODULO RANGE (HIGHEST OUTPUT VALUE)
arpN=0;                                        //    CURRENT OUTPUT ADDRESS (WHICH arpArr VALUE IS OUTPUT)
offset=0;                                       //    CURRENT 'CHORD_OFFSET' (ADDED TO OUTPUT)
mode=0;                                       //    CURRENT MODE***   hmmm....

//    >>>>>>>>>>>ARRAYS

arpArr = new Array(arpLen);                //  ARP_ARRAY - Contains the notes used by the arpeggiator
initARP(1, 4, 7, 4, 9);
tempArr= new Array(arpLen);              //  TEMP_ARRAY - Remembers notes when changing array length



//    >>>>>>>>>>>FUNCTIONS>>>>>>>>>>>>

//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET ARP_LENGTH 

function arp_length() {
    if(arguments.length) {
        tempArr=new Array(arpLen);
        for(i=0;i<arpLen;i++) {
            tempArr[i]=arpArr[i]; }                                 //    WRITE ARP_ARRAY TO TEMP_ARRAY ARRAY

        arpLen=arguments[0];                                    //   SET NEW ARP_LENGTH
        if(arpLen<1) arpLen=1;                                  //    MAKE SURE ITS MORE THAN 0
        arpArr=new Array(arpLen);                            //    MAKE NEW ARP_ARRAY to LENGTH
        for(i=0;i<arpLen;i++) {
            if(tempArr[i]) {
                 arpArr[i]=tempArr[i] }                             //    WRITE TEMP_ARRAY BACK INTO ARP_ARRAY
                 else arpArr[i]=0;
                }
    }
    //  *************************  post("ARP_ARRAY LENGTH: "+arpLen+"\n");

    
}


//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET CURRENT  ARP_COUNT - arpN VALUE

function setcount(val) {
    if(val<0) arp=0;
    arpN=(val%arpEnd);
    post("SET CURRENT ARP-COUNT TO "+arpN+"\n");
}

//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET ARP_OUT START

function arp_start() {
    if(arguments.length) {
        arpStart=arguments[0];
        if(arpStart<0) arpStart=0;
    }
    //  *************************  post("ARP_OUT START: "+arpStart+"\n");
}


//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET ARP_OUT END

function arp_end() {
    if(arguments.length) {
        arpEnd=arguments[0];
        if(arpEnd<1) arpEnd=1;
        if(arpEnd>=arpLen) arpEnd=arpLen-1;
    }
    //  *************************  post("ARP_OUT END: "+arpEnd+"\n");
}


//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET ARP_OUT MULTIPLIER

function arp_multiplier() {
    if(arguments.length) {
        arpMult=arguments[0];
    }
    //  *************************  post("ARP_OUT MULTIPLIER: "+arpMult+"\n");
}


//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET NOTE_RANGE

function range() {
    if(arguments.length==2) {                                           //    IF RANGE IS RECIEVED AS A PAIR
        modLow=arguments[0];
        modHi=arguments[1];
        if(modHi<=modLow) modHi=modLow+1;               //    IF THEY OVERLAP, RESET HIGH TO LOW+1
    }
    //  *************************  post("NOTE_RANGE: "+modLow+" to "+modHi+"\n");
}

//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET CHORD_OFFSET

function chord_offset() {                                                         
    if(arguments.length) {
        offset=arguments[0];
    }
    //  *************************  post("CHORD_OFFSET: "+offset+"\n");
}


//    >>>>  >>>>  >>>>  >>>>  ******MAX_INPUT:  SET MOD-RANGE MODE

function range_mode() {                                                         
    if(arguments.length) {
        if(mode!=0) mode=1;                                    //    IF MODE IS NOT 0, MODE IS 1
        mode=arguments[0];
    }
    //  *************************  post("RANGE-MODE: "+mode+"\n");
}



//    >>>>  >>>>  >>>>  THE initARP FUNCTION <<<<  <<<<  <<<<
//
//    >>>>  >>>>  >>>>  FILL ARP_ARRAY WITH ARGUMENTS

function initARP() {
    if(arguments.length) {
        len=arguments.length;
        args = new Array(len);
        arpArr=new Array(arpLen);
        for(i=0;i<len;i++) {
            args[i]=arguments[i];
        }
        for(i=0;i<arpLen;i++) {
            arpArr[i]=args[i%len];
        }
        //  *************************  post("ARP_ARRAY: "+arpArr+"\n");
        return 0;
    }
    for(i=0;i<arpLen;i++) {
        arpArr[i]=0;
    }
    //  *************************  post("ARP_ARRAY: "+arpArr+"\n");
}



//    >>>>  >>>>  >>>>  THE BANG FUNCTION <<<<  <<<<  <<<<
//
//    >>>>  >>>>  >>>>  OUTPUT CURRENT ARP_NOTE

function bang() {
    n=Math.round(arpN);                                        //    ROUND CURRENT ADRESS TO INT 'N' 
                                                                             //    (due to FLOAT Multiplier allowing partial changes)
    //  post("ADDRESS: "+n+"\n");
    a=arpArr[n];                                                      //    STORE CURRENT ARP_VALUE IN VARIABLE 'A'
    a=a+offset;                                                       //    ADD CHORD_OFFSET

    if(mode==0) b=inRangeA(a);                           //    CHECK MOD_RANGE: MODE_1 - STORE IN 'B'
    if(mode==1) b=inRangeB(a);                           //    CHECK MOD_RANGE: MODE_2 - STORE IN 'B'

    outlet(0, b);                                                       //    OUTPUT VALUE
    outlet(1, arpArr[n]);                                           //    OUTPUT UN-ADJUSTED VALUE
    outlet(2, n);                                                       //    OUTPUT 'N' ADDRESS
    // post("OUT: "+b+"\n");
    count();
}


//    >>>>  >>>>  >>>>  FUNCTION: COUNTER  <<<<  <<<<  <<<<

function count() {
    arpN=arpN+arpMult
    if(arpN>=arpEnd) {
        //  post("LOOP_END_REACHED! ...AT ADDRESS: "+arpN+"\n");
        arpN=arpN-(Math.abs(arpEnd-arpStart));        //    CYCLIC VERSION.....
        //  arpN=arpStart;                                                      //    FLAT VERSION - - just go to start
    }
    if(arpN<arpStart) {
        //  post("LOOP_START_REACHED! ...AT ADDRESS: "+arpN+"\n");
        arpN=arpN+((Math.abs(arpEnd-arpStart)))-1;        //    ******FIRST VERSION.....
        //  arpN=arpEnd-1;                                                      //    FLAT VERSION - - just go to end-1
    }
}

//    >>>>  >>>>  >>>>  FUNCTION: MOD_RANGE CHECK  MODE A  <<<<  <<<<  <<<<
//
//            >>>>  >>>  IF OUT OF RANGE, COUNT IN FROM OTHER END  <<<  <<<

function inRangeA(val) {
    if(val>modLow && val<modHi) return val;      //    IF ALREADY IN RANGE, JUST RETURN THE VALUE

    hi=Math.abs(modHi-modLow);                       //    ADJUST MOD RANGE TO START FROM ZERO (0 to hi)
    adj=val-modLow;                                            //    ADJUST VALUE SIMILARLY - STORE IN 'ADJ' (adjusted)
    
    if(adj<0) {                                                        //    IF LOWER THAN ZERO, THE MODULO WON'T
        do {                                                             //    WORK CORRECTLY....
             adj=hi+adj; }                                           //    ADD MOD-RANGE-AMOUNT...
            while(adj<0);                                           //    ...UNTIL VALUE IS POSITIVE
        }
    mod=adj%hi;                                                  //    DO MODULO ARITHMETIC - STORE IN 'MOD' (Modulo)
    val=mod+modLow;                                        //    RE-ADD THE LOW-RANGE VALUE (undo Adjust to Zero)

    return val;                                                        //    RETURN RANGE-DEPENDANT VALUE
}

//    >>>>  >>>>  >>>>  FUNCTION: MOD_RANGE CHECK  MODE B  <<<<  <<<<  <<<<
//
//            >>>>  >>>  IF OUT OF RANGE, COUNT IN FROM OTHER END  <<<  <<<

function inRangeB(val) {
    if(val>modLow && val<modHi) return val;          //    IF ALREADY IN RANGE, JUST RETURN THE VALUE

    while(val<modLow) {                                          //    IF TOO LOW, ADD OCTAVES 'TILL IN RANGE
        val=val+12;
    }
    
    while(val>modHi) {                                          //    IF TOO HIGH, SUBTRACT OCTAVES 'TILL IN RANGE
        val=val-12;
    }
                                                    //    THIS MEANS THAT IF LOW-HIGH MOD-RANGE IS LESS 
                                                    //    THAN AN OCTAVE, SOME NOTES MAY BE BOUNCED 
                                                    //    BACK DOWN BELOW THE LOW RANGE

    return val;                                                            //    RETURN RANGE-DEPENDANT VALUE
}



//    >>>>  >>>>  >>>>  FUNCTION:  OBJECT INFORMATION TO MAX WINDOW <<<<  <<<<  <<<<

function info() {
   post("Arp_ARRAY: "+arpArr+"\n");
   post("Arp_Length: "+arpLen+"\n");
   post("Arp_Loop-Start: "+arpStart+"\n");
   post("Arp_Loop-End: "+arpEnd+"\n");
   post("Arp_Loop-Multiplier: "+arpMult+"\n");
   post("Note-Range_Low: "+modLow+"\n");
   post("Note-Range_High: "+modHi+"\n");
   post("Current Arp_Address: "+arpN+"\n");
}

