• ¶

    script.xponent

  • ¶

    Mixxx script file for the M-Audio Xponent controller. The script is based on the Mixco framework. The numbers in the following picture will be used in the script to describe the functionallity of the controls.

    Xponent Layout

    {assert}  = require '../mixco/util'
    script    = require '../mixco/script'
    
    c = require '../mixco/control'
    b = require '../mixco/behaviour'
    v = require '../mixco/value'
    
    ledButtonFixed = ->
        c.ledButton(arguments...).option
            process: (ev) ->
                if ev.message() == c.MIDI_NOTE_OFF
                    ev.value = 0
    
    script.register module,
    
        name: 'maudio_xponent'
        info:
            name: '[mixco] M-Audio Xponent'
            author: 'Juan Pedro Bolivar Puente <raskolnikov@gnu.org>'
            description:
                """
                Controller mapping for the M-Audio Xponent DJ controller.
                """
            forums: ''
            wiki: ''
  • ¶

    Global section

  • ¶

    Controls that do not have a per-deck functionality.

        constructor: ->
            ccId = (cc) -> c.ccIds cc, 2
            g  = "[Master]"
  • ¶
    • 27. Pre-hear mix.
            c.knob(ccId 0x0D).does g, "headMix"
  • ¶
    • 39. Crossfader.
            c.slider(ccId 0x07).does g, "crossfader"
  • ¶

    Effects

    Most of knobs and buttons in 24 and 25 are dedicated to effects. Some of them are mapped per-deck —see later— but the *flanger parameters are mapped globally:

    • The first knob of the left deck is the LFO depth.
            c.slider(0x0c, 0x00).does b.soft "[Flanger]", "lfoDepth"
  • ¶
    • The first knob of the right deck is the LFO delay.
            c.slider(0x0c, 0x01).does b.soft "[Flanger]", "lfoDelay"
  • ¶
    • The second knob of the right deck is the LFO period.
            c.slider(0x0d, 0x01).does b.soft "[Flanger]", "lfoPeriod"
  • ¶
    • 16. When in MIDI mode, the touch pad can be used to control the depth and delay for the flanger effect on the Y and X axis *respectively.
            c.control(0x09, 0x02).does "[Flanger]", "lfoDepth"
            c.control(0x08, 0x02).does "[Flanger]", "lfoDelay"
  • ¶
    • 17. and 18. When in MIDI mode, the touch pad buttons toggle the flanger effect for the left and right channel respectively.
            c.button(c.noteOnIds 0x00, 0x02).does "[Channel1]", "flanger"
            c.button(c.noteOnIds 0x01, 0x02).does "[Channel2]", "flanger"
  • ¶

    Microphone

    • The second knob the left deck controls the microphone volume.
            c.slider(0x0d, 0x00).does b.soft "[Microphone]", "volume"
  • ¶
    • The second button of the left deck controls the microphone enable.
            c.ledButton(c.noteOnIds 0x0d, 0x00).does \
                "[Microphone]", "talkover"
  • ¶

    Per deck controls

  • ¶

    We add the two decks with the addDeck(idx) function. In the Xponent, each MIDI message is repeated per-deck on a different channel.

            @decks = b.chooser()
            @addDeck 0
            @addDeck 1
    
        addDeck: (i) ->
            assert i in [0, 1]
            g  = "[Channel#{i+1}]"
            ccId = (cc) -> c.ccIds cc, i
            noteId = (note) -> c.noteIds note, i
            noteOnId = (note) -> c.noteOnIds note, i
  • ¶
    • 15. Shift. It changes the behaviour of some controls. Note that there is a shift button per-deck, which only affects the controls of that deck.
            shift = b.modifier()
            ledButtonFixed(noteId 0x2C).does shift
  • ¶
    • 12. Pre-Fade Listen. Select which deck goes to the pre-hear.
            c.ledButton(noteOnId 0x14).does @decks.add g, "pfl"
  • ¶

    The mixer

    • 20. Filter and gain kills.
            c.ledButton(noteId 0x08).does g, "filterLowKill"
            c.ledButton(noteId 0x09).does g, "filterMidKill"
            c.ledButton(noteId 0x0A).does g, "filterHighKill"
            c.ledButton(noteId 0x0B).does g, "pregain_toggle"
  • ¶
    • 22. Mixer EQ and gain.
            c.knob(ccId 0x08).does g, "filterLow"
            c.knob(ccId 0x09).does g, "filterMid"
            c.knob(ccId 0x0A).does g, "filterHigh"
            c.knob(ccId 0x0B).does b.soft g, "pregain"
  • ¶
    • 23. Per deck volume meters.
            c.meter(c.ccIds 0x12+i, 3)
                .does b.mapOut(g, "VuMeter").meter()
  • ¶
    • 34. Sync button. Adjust pitch and aligns grids to beatmatch both tracks. When shift is pressed, it only adjusts pitch, not phase.
            ledButtonFixed(noteId 0x02)
                .when(shift, g, "beatsync_tempo")
                .else g, "beatsync"
  • ¶
    • 33. Deck volume.
            c.slider(ccId 0x07).does b.soft g, "volume"
  • ¶
    • 38. Punch-in/transform. While pressed, lets this track be heard overriding the corssfader.
            ledButtonFixed(noteId 0x07).does b.punchIn (0.5-i)
  • ¶

    The transport section

    • 29. Song progress indication. When it approches the end of the playing song it starts blinking.
            c.meter(c.ccIds 0x14+i, 3).does b.playhead g
  • ¶
    • 30. Back and forward.
            c.ledButton(noteId 0x21).does g, "back"
            c.ledButton(noteId 0x22).does g, "fwd"
  • ¶
    • 31. Includes several buttons…

    • The top buttons with numbers are the hotcues. On first press, sets the hotcue. On second press, jumps to hotcue. When shift is held, deletes the hotcue point.

            for idx in [0..4]
                ledButtonFixed(noteId(0x17 + idx))
                    .when(shift,
                          g, "hotcue_#{idx+1}_clear",
                          g, "hotcue_#{idx+1}_enabled")
                    .else g, "hotcue_#{idx+1}_activate",
                          g, "hotcue_#{idx+1}_enabled"
  • ¶
    • The little arrow buttons do beatjump — jump forward or back by one beat. When shift is pressed, they select the previous or next item of the browser sidebar.
            ledButtonFixed(noteId 0x1C)
                .when(shift, "[Playlist]", "SelectPrevPlaylist")
                .else b.beatJump g, -1
            ledButtonFixed(noteId 0x1D)
                .when(shift, "[Playlist]", "SelectNextPlaylist")
                .else b.beatJump g, 1
  • ¶
    • The lock button does key lock — i.e. makes tempo changes independent of pitch. When shift is pressed, it expands/collapses the selected browser item.
            ledButtonFixed(noteId 0x1E)
                .when(shift, "[Playlist]", "ToggleSelectedSidebarItem")
                .else g, "keylock"
  • ¶
    • The plus (+) button moves the beat grid to align with the current play position.
            c.ledButton(noteId 0x1F).does g, "beats_translate_curpos"
  • ¶
    • The minus (-) button plays the track in reverese.
            c.ledButton(noteId 0x20).does g, "reverse"
  • ¶
    • 35. Cue button.
            c.ledButton(noteId 0x23).does g, "cue_default"
  • ¶
    • 37. Play/pause button.
            c.ledButton(noteOnId 0x24).does g, "play"
  • ¶

    The looping section

    • 36. This includes several controls to manage loops…

    • The in and out buttons set the loop start and end to the current playing position. When shift is pressed, they halve and double the current loop size respectively.

            ledButtonFixed(noteId 0x29)
                .when(shift, g, "loop_halve")
                .else g, "loop_in"
            ledButtonFixed(noteId 0x2B)
                .when(shift, g, "loop_double")
                .else g, "loop_out"
  • ¶
    • The loop toggles the current loop on/off whenever there is a loop selected.
            c.ledButton(noteId 0x2A).does \
                g, "reloop_exit", g, "loop_enabled"
  • ¶
    • The numbers set and trigger a loop of 4, 8, 16 and 32 beats respectively. When shift, they set loops of 1/8, 1/2, 1 or 2 long.
            ledButtonFixed(noteId 0x25)
                .when(shift, g, "beatloop_0.125_activate",
                      g, "beatloop_0.125_enabled")
                .else g, "beatloop_4_activate",
                      g, "beatloop_4_enabled"
            ledButtonFixed(noteId 0x26)
                .when(shift, g, "beatloop_0.5_activate",
                      g, "beatloop_0.5_enabled")
                .else g, "beatloop_8_activate",
                      g, "beatloop_8_enabled"
            ledButtonFixed(noteId 0x27)
                .when(shift, g, "beatloop_1_activate",
                      g, "beatloop_1_enabled")
                .else g, "beatloop_16_activate",
                      g, "beatloop_16_enabled"
            ledButtonFixed(noteId 0x28)
                .when(shift, g, "beatloop_2_activate",
                      g, "beatloop_2_enabled")
                .else g, "beatloop_32_activate",
                      g, "beatloop_32_enabled"
  • ¶

    Effects

    • In the 24 group, the first button enables the flanger effect for each deck.
            c.ledButton(noteOnId 0x0c).does g, "flanger"
  • ¶
    • The third knob and button in 24 and 25 enable a beat loop effect, similar to those of the looping section but with resolution controllable with a knob for more drastic effects.
            beatloop = b.beatEffect g
            c.knob(ccId 0x0e).does beatloop.selector()
            c.ledButton(noteOnId 0x0e).does beatloop
  • ¶
    • The fourth knob and button in 24 and 25 enable a beat roll effect. It works like the beat loop, but the playing position is restored when turned off.
            beatroll = b.beatEffect g, 'roll'
            c.knob(ccId 0x0f).does beatroll.selector()
            c.ledButton(noteOnId 0x0f).does beatroll
  • ¶

    The wheel and pitch section

    • 10. Toggles scratch mode.
            scratchMode = b.switch()
            c.ledButton(noteOnId 0x15).does scratchMode
  • ¶
    • 11. The wheel does different functions…

      • When the deck is stopped, it moves the play position.

      • When scrach mode is on, it will stop the song when touched on top and control the track play like a vinyl when moved.

      • Otherwise, it can be used to nudge the playing speed up or down to synch the phase of tracks when the track is playing.

      • When shift is pressed, it will scroll through the current list of tracks in the browser.

            selectTrackKnobTransform = do ->
                toggle = 1
                (val) ->
                    val = val - 64
                    toggle -= 1
                    if toggle < 0 then toggle = 3
                    if toggle == 0 then val.sign() else null
    
            ledButtonFixed(noteId 0x16)
                .when v.and(v.not(shift), scratchMode), b.scratchEnable i+1
    
            c.knob(ccId 0x16)
                .when(shift, b.map("[Playlist]", "SelectTrackKnob")
                    .transform selectTrackKnobTransform)
                .else.when(scratchMode,
                    b.scratchTick(i+1).options.spread64)
                .else b.map(g, "jog").transform (v) -> (v - 64) / 8
  • ¶
    • 26. Temporarily nudges the pitch down or up. When shift, they do it in a smaller ammount.
            ledButtonFixed(noteId 0x10)
                .when(shift, g, "rate_temp_down_small")
                .else g, "rate_temp_down"
            ledButtonFixed(noteId 0x11)
                .when(shift, g, "rate_temp_up_small")
                .else g, "rate_temp_up"
  • ¶
    • 32. Pitch slider, adjusts playing speed.
            c.slider(c.pbIds i).does b.soft g, "rate"
  • ¶
    • 21. Custom effects that include…

    • The big cross (X) button simulates a brake effect as if the turntable was turned off suddenly. On shift, it ejects the track from the deck.

            ledButtonFixed(noteId 0x12)
                .when(shift, g, "eject")
                .else b.brake i+1
  • ¶
    • The big minus (—) button simulates a backspin effect as if the vinyl was launched backwards. On shift, it loads the selected track in the browser into the deck.
            ledButtonFixed(noteId 0x13)
                .when(shift, g, "LoadSelectedTrack")
                .else b.spinback i+1
  • ¶

    Initialization

  • ¶

    Unlike old Mixxx versions, this script initializes the device properly for light feedback. The trick of holding the two and key button on initalization are no longer required.

        preinit: ->
            msg = [0xF0, 0x00, 0x20, 0x08, 0x00, 0x00, 0x63, 0x0E,
                   0x16, 0x40, 0x00, 0x01, 0xF7]
            @mixxx.midi.sendSysexMsg msg, msg.length
    
        init: ->
            @decks.activate 0
    
        postshutdown: ->
            msg = [0xF0, 0x00, 0x20, 0x08, 0x00, 0x00, 0x63, 0x0E,
                   0x16, 0x40, 0x00, 0x00, 0xF7]
            @mixxx.midi.sendSysexMsg msg, msg.length
  • ¶

    License

  • ¶

    Copyright (C) 2013 Juan Pedro Bolívar Puente

    This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.