/****************************************************************/
/*      DJ-Tech CDJ101 controller script                        */
/*      For Mixxx version 1.11                                  */
/*      Author: zestoi                                          */
/****************************************************************/
//
// pitch slider works as you'd expect
// holding down the push button works as a "shift" to activate secondary functions
// needs the cdj101 to be on midi channel 1 for deck1 and 2 for deck2
//
// track playing:
//
// * surface of jog wheel: scratch
// * edge of jog wheel: tempo bend
// * shift + edge of jog wheel: fine tempo bend
//
// track not playing:
//
// * click push button: load a track
// * surface of jog wheel: scratch thru track
// * shift + surface of jog wheel: scan through track quickly
// * edge of jog wheel: jog though track slowly
// * shift + cue: move beatgrid
//
// track playing or not:
//
// * rotate push button: navigate tracks
// * shift + rotate push button: switch between playlists
// * cue button: default cue behaviour
// * play: toggle play
// * shift + play: sync
//

DJTechCDJ101 = {};
DJTechCDJ101.pushmaxtime = 200;
DJTechCDJ101.pushed = false;
DJTechCDJ101.pushedon = 0;
DJTechCDJ101.outer2inner = false;
DJTechCDJ101.scratch_timeout = 100;

DJTechCDJ101.init = function(id) {
    engine.connectControl("[Channel1]", "play", "DJTechCDJ101.play_feedback_deck1");
    engine.connectControl("[Channel2]", "play", "DJTechCDJ101.play_feedback_deck2");
    engine.connectControl("[Channel1]", "cue_default", "DJTechCDJ101.cue_feedback_deck1");
    engine.connectControl("[Channel2]", "cue_default", "DJTechCDJ101.cue_feedback_deck2");
}

DJTechCDJ101.shutdown = function() {}

//
// utility function
//

DJTechCDJ101.mtime = function()
{
    var t = new Date();
    return t.getTime();
}

//
// cue+play led feedback
//

DJTechCDJ101.play_feedback_deck1 = function(value)
{
    midi.sendShortMsg(0x90, 0x2a, value > 0 ? 0x7f : 0);
}

DJTechCDJ101.play_feedback_deck2 = function(value)
{
    midi.sendShortMsg(0x91, 0x2a, value > 0 ? 0x7f : 0);
}

DJTechCDJ101.cue_feedback_deck1 = function(value)
{
    midi.sendShortMsg(0x90, 0x2b, value > 0 ? 0x7f : 0);
}

DJTechCDJ101.cue_feedback_deck2 = function(value)
{
    midi.sendShortMsg(0x91, 0x2b, value > 0 ? 0x7f : 0);
}

//
// translate 14bit pitchbend message into pitch control
//

DJTechCDJ101.pitch = function(channel, lsb, msb, status, group)
{
    engine.setValue(group, "rate", script.pitch(lsb, msb, status)); // not working for some reason
    //engine.setValue(group, "rate", (8192 - (msb << 7 | lsb)) / 8192);
}

DJTechCDJ101.beatjump = function(group, jump)
{
    jump = jump * 120 / engine.getValue(group, "bpm") / engine.getValue(group, "track_samples") * engine.getValue(group, "track_samplerate");
    engine.setValue(group, "playposition", engine.getValue(group, "playposition") + jump);
}

//
// hold down encoder and turn to change playlists
// turn while not pushed in to scroll through tracks
// click to load track
//

DJTechCDJ101.browse = function(channel, control, value, status, group)
{
    if (DJTechCDJ101.pushed) {
        engine.setValue("[Playlist]", value == 0x41 ? "SelectNextPlaylist" : "SelectPrevPlaylist", 1);
    }
    else {
        engine.setValue("[Playlist]", value == 0x41 ? "SelectNextTrack" : "SelectPrevTrack", 1);
    }
}

DJTechCDJ101.push = function(channel, control, value, status, group)
{
    if (value > 0) {
        DJTechCDJ101.pushed = true;
        DJTechCDJ101.pushedon = DJTechCDJ101.mtime();
    }
    else {

        //
        // load selected track if released quickly enough
        //

        if (DJTechCDJ101.mtime() - DJTechCDJ101.pushedon < DJTechCDJ101.pushmaxtime) {
            engine.setValue(group, "LoadSelectedTrack", 1);
        }

        DJTechCDJ101.pushed = false;
        DJTechCDJ101.pushedon = 0;
    }
}

//
// when deck is not playing and the push button is held, pressing cue will move the beatgrid
//

DJTechCDJ101.cue = function(channel, control, value, status, group)
{
    if (DJTechCDJ101.pushed && !engine.getValue(group, "play")) {
        engine.setValue(group, "beats_translate_curpos", value > 0 ? 1 : 0);
    }
    else {
        engine.setValue(group, "cue_default", value > 0 ? 1 : 0);
    }
}

//
// when the push button is held, pressing play will sync
//

DJTechCDJ101.play = function(channel, control, value, status, group)
{
    if (DJTechCDJ101.pushed) {
        engine.setValue(group, "beatsync", value > 0 ? 1 : 0);
    }
    else if (value > 0) {
        //
        // we want play to toggle
        //

        engine.setValue(group, "play", !engine.getValue(group, "play"));
    }
}

//
// when deck is playing either enable or disable scratch mode, no action otherwise
//

DJTechCDJ101.jogtouch = function(channel, control, value, status, group)
{
    var deck = parseInt(group.substring(8,9));

    if (value > 0) {
        engine.scratchEnable(deck, 143, 45, 0.125, 0.125/32);
    }
    else {
        //
        // don't disable right away in case of spin backs if playing and also morph
        // outer jog movements into top jog movements during this time
        //

        if (!engine.getValue(group, "play")) {
            DJTechCDJ101.finishScratch(deck, false);
        }
        else {
            DJTechCDJ101.outer2inner = true;
            DJTechCDJ101.scratch_timer = engine.beginTimer(DJTechCDJ101.scratch_timeout, () => DJTechCDJ101.finishScratch(deck, true));
        }
    }
}

DJTechCDJ101.finishScratch = function(deck, stop_timer)
{
    if (stop_timer) {
        engine.stopTimer(DJTechCDJ101.scratch_timer);
    }
    engine.scratchDisable(deck);
    DJTechCDJ101.outer2inner = false;
}

//
// use outer part of jog to pitchbend when in play mode and fine track seek when not
//

DJTechCDJ101.jogouter = function(channel, control, value, status, group)
{
    //
    // when touch surface has been release for a short time convert
    // outer jog movements into surface jog ones for spinbacks etc
    //

    if (DJTechCDJ101.outer2inner) {
        return DJTechCDJ101.jogtop(channel, control, value, status, group);
    }

    //
    // scale down based on whether we are playing and the shift is held down
    //

    value = (value - 0x40) / 2;
    if (DJTechCDJ101.pushed) value /= 2.5;
    if (!engine.getValue(group, "play")) value /= 2.5;
    engine.setValue(group, "jog", value);
}

//
// track playing: top of jog scratches
// track not playing: seek thru track (using same scratch ticks and gives more positive response) or seek faster through track when push button held down
//

DJTechCDJ101.jogtop = function(channel, control, value, status, group)
{
    value -= 0x40;
    if (!engine.getValue(group, "play") && DJTechCDJ101.pushed) {
        DJTechCDJ101.beatjump(group, value);
    }
    else {
        engine.scratchTick(parseInt(group.substring(8,9)), value);
    }
}
