Claude Code Plugins

Community-maintained marketplace

Feedback

TypeScript and Node.js code samples for APC Mini MK2 development. Use when user needs "code example", "implementation", "easymidi", "@julusian/midi", "sample code", "how to implement", or wants working code for LED control, button handling, or MIDI communication.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name apc-code-samples
description TypeScript and Node.js code samples for APC Mini MK2 development. Use when user needs "code example", "implementation", "easymidi", "@julusian/midi", "sample code", "how to implement", or wants working code for LED control, button handling, or MIDI communication.

APC Mini MK2 Code Samples

Complete TypeScript/Node.js implementation examples for APC Mini MK2.

Library Setup

Using easymidi (Recommended for Rapid Development)

npm install easymidi
npm install -D @types/easymidi
import easymidi from 'easymidi';

// List available MIDI devices
console.log('Inputs:', easymidi.getInputs());
console.log('Outputs:', easymidi.getOutputs());

// Connect to APC Mini MK2
const output = new easymidi.Output('APC mini mk2');
const input = new easymidi.Input('APC mini mk2');

Using @julusian/midi (Recommended for Production)

npm install @julusian/midi
import midi from '@julusian/midi';

// List available devices
const output = new midi.Output();
const input = new midi.Input();

console.log('Output ports:', output.getPortCount());
for (let i = 0; i < output.getPortCount(); i++) {
  console.log(`  ${i}: ${output.getPortName(i)}`);
}

// Open port by name
function openOutputByName(name: string): midi.Output {
  const out = new midi.Output();
  for (let i = 0; i < out.getPortCount(); i++) {
    if (out.getPortName(i).includes(name)) {
      out.openPort(i);
      return out;
    }
  }
  throw new Error(`Device "${name}" not found`);
}

const apcOutput = openOutputByName('APC mini');

Basic LED Control

Set Single Pad Color (easymidi)

import easymidi from 'easymidi';

const output = new easymidi.Output('APC mini mk2');

// Set pad 0 to red at full brightness
output.send('noteon', {
  note: 0,
  velocity: 5,    // Red
  channel: 6      // 100% brightness
});

// Turn off pad
output.send('noteon', {
  note: 0,
  velocity: 0,
  channel: 0
});

Set Single Pad Color (@julusian/midi)

import midi from '@julusian/midi';

const output = new midi.Output();
output.openPort(0);

// Set pad 0 to red at full brightness
// Status byte: 0x96 = Note On, Channel 6
output.sendMessage([0x96, 0x00, 0x05]);

// Turn off pad
output.sendMessage([0x90, 0x00, 0x00]);

Complete APC Mini MK2 Class

import easymidi from 'easymidi';

interface PadOptions {
  brightness?: number;  // 0-6 for solid, 7-10 pulse, 11-15 blink
  color: number;        // 0-127 velocity
}

class APCMiniMK2 {
  private output: easymidi.Output;
  private input: easymidi.Input;

  constructor() {
    this.output = new easymidi.Output('APC mini mk2');
    this.input = new easymidi.Input('APC mini mk2');
  }

  // Pad control (0-63)
  setPad(pad: number, options: PadOptions): void {
    const channel = options.brightness ?? 6;
    this.output.send('noteon', {
      note: pad,
      velocity: options.color,
      channel
    });
  }

  setPadOff(pad: number): void {
    this.output.send('noteon', { note: pad, velocity: 0, channel: 0 });
  }

  // Set pad by coordinate (1-indexed)
  setPadAt(row: number, col: number, options: PadOptions): void {
    const note = (row - 1) * 8 + (col - 1);
    this.setPad(note, options);
  }

  // Custom RGB via SysEx
  setPadRGB(pad: number, r: number, g: number, b: number): void {
    const encode = (v: number) => [(v >> 7) & 0x01, v & 0x7F];
    const [rMSB, rLSB] = encode(r);
    const [gMSB, gLSB] = encode(g);
    const [bMSB, bLSB] = encode(b);

    this.output.send('sysex', [
      0xF0, 0x47, 0x7F, 0x4F, 0x24,
      0x00, 0x08,
      pad, pad,
      rMSB, rLSB, gMSB, gLSB, bMSB, bLSB,
      0xF7
    ]);
  }

  // Track buttons (100-107)
  setTrackButton(track: number, state: 'off' | 'on' | 'blink'): void {
    const note = 99 + track; // track 1 = note 100
    const velocity = state === 'off' ? 0 : state === 'on' ? 1 : 2;
    this.output.send('noteon', { note, velocity, channel: 0 });
  }

  // Scene buttons (112-119)
  setSceneButton(scene: number, state: 'off' | 'on' | 'blink'): void {
    const note = 111 + scene; // scene 1 = note 112
    const velocity = state === 'off' ? 0 : state === 'on' ? 1 : 2;
    this.output.send('noteon', { note, velocity, channel: 0 });
  }

  // Clear all LEDs
  clearAll(): void {
    // Clear pads
    for (let i = 0; i < 64; i++) {
      this.setPadOff(i);
    }
    // Clear track buttons
    for (let i = 1; i <= 8; i++) {
      this.setTrackButton(i, 'off');
    }
    // Clear scene buttons
    for (let i = 1; i <= 8; i++) {
      this.setSceneButton(i, 'off');
    }
  }

  // Event handlers
  onPadPress(callback: (pad: number, velocity: number) => void): void {
    this.input.on('noteon', (msg) => {
      if (msg.note < 64 && msg.velocity > 0) {
        callback(msg.note, msg.velocity);
      }
    });
  }

  onPadRelease(callback: (pad: number) => void): void {
    this.input.on('noteoff', (msg) => {
      if (msg.note < 64) {
        callback(msg.note);
      }
    });
  }

  onTrackButton(callback: (track: number, pressed: boolean) => void): void {
    this.input.on('noteon', (msg) => {
      if (msg.note >= 100 && msg.note <= 107) {
        callback(msg.note - 99, msg.velocity > 0);
      }
    });
  }

  onSceneButton(callback: (scene: number, pressed: boolean) => void): void {
    this.input.on('noteon', (msg) => {
      if (msg.note >= 112 && msg.note <= 119) {
        callback(msg.note - 111, msg.velocity > 0);
      }
    });
  }

  onFader(callback: (fader: number, value: number) => void): void {
    this.input.on('cc', (msg) => {
      if (msg.controller >= 48 && msg.controller <= 56) {
        callback(msg.controller - 47, msg.value);
      }
    });
  }

  close(): void {
    this.input.close();
    this.output.close();
  }
}

export { APCMiniMK2, PadOptions };

Usage Examples

Basic Pad Lighting

const apc = new APCMiniMK2();

// Light pad 0 red
apc.setPad(0, { color: 5 });

// Light pad at row 3, column 4 green
apc.setPadAt(3, 4, { color: 21 });

// Light pad with pulse animation
apc.setPad(10, { color: 45, brightness: 9 }); // Blue, pulse 1/4

// Custom RGB
apc.setPadRGB(20, 255, 128, 0); // Orange

Rainbow Pattern

const apc = new APCMiniMK2();

const rainbowColors = [5, 9, 13, 21, 33, 45, 49, 53];

function showRainbow() {
  for (let row = 0; row < 8; row++) {
    for (let col = 0; col < 8; col++) {
      const note = row * 8 + col;
      const color = rainbowColors[(row + col) % 8];
      apc.setPad(note, { color });
    }
  }
}

showRainbow();

Interactive Button Handler

const apc = new APCMiniMK2();
const padStates = new Map<number, boolean>();

apc.onPadPress((pad, velocity) => {
  const isOn = padStates.get(pad) ?? false;

  if (isOn) {
    apc.setPadOff(pad);
    padStates.set(pad, false);
  } else {
    apc.setPad(pad, { color: 5 }); // Red
    padStates.set(pad, true);
  }
});

// Scene buttons control rows
apc.onSceneButton((scene, pressed) => {
  if (pressed) {
    const row = 9 - scene; // Scene 1 = Row 8
    for (let col = 0; col < 8; col++) {
      const note = (row - 1) * 8 + col;
      apc.setPad(note, { color: 21 }); // Green
    }
  }
});

// Fader controls brightness
let currentBrightness = 6;
apc.onFader((fader, value) => {
  if (fader === 9) { // Master fader
    currentBrightness = Math.floor(value / 127 * 6);
    console.log('Brightness:', currentBrightness);
  }
});

Sequencer-Style Animation

const apc = new APCMiniMK2();

let currentStep = 0;
const BPM = 120;
const stepTime = (60 / BPM) * 1000 / 4; // 16th notes

function animate() {
  // Clear previous step
  const prevStep = (currentStep - 1 + 8) % 8;
  for (let row = 0; row < 8; row++) {
    apc.setPadOff(row * 8 + prevStep);
  }

  // Light current step
  for (let row = 0; row < 8; row++) {
    apc.setPad(row * 8 + currentStep, { color: 5 }); // Red
  }

  currentStep = (currentStep + 1) % 8;
}

setInterval(animate, stepTime);

// Cleanup on exit
process.on('SIGINT', () => {
  apc.clearAll();
  apc.close();
  process.exit();
});

Gradient Fill

function fillGradient(apc: APCMiniMK2, startR: number, startG: number, startB: number,
                      endR: number, endG: number, endB: number) {
  for (let i = 0; i < 64; i++) {
    const t = i / 63;
    const r = Math.round(startR + (endR - startR) * t);
    const g = Math.round(startG + (endG - startG) * t);
    const b = Math.round(startB + (endB - startB) * t);
    apc.setPadRGB(i, r, g, b);
  }
}

// Red to Blue gradient
fillGradient(apc, 255, 0, 0, 0, 0, 255);

Error Handling

import easymidi from 'easymidi';

function connectToAPC(): { input: easymidi.Input; output: easymidi.Output } | null {
  const inputs = easymidi.getInputs();
  const outputs = easymidi.getOutputs();

  const inputName = inputs.find(n => n.includes('APC mini'));
  const outputName = outputs.find(n => n.includes('APC mini'));

  if (!inputName || !outputName) {
    console.error('APC Mini MK2 not found');
    console.log('Available inputs:', inputs);
    console.log('Available outputs:', outputs);
    return null;
  }

  try {
    const input = new easymidi.Input(inputName);
    const output = new easymidi.Output(outputName);
    console.log('Connected to APC Mini MK2');
    return { input, output };
  } catch (error) {
    console.error('Failed to connect:', error);
    return null;
  }
}

TypeScript Interfaces

// Color definitions
const Colors = {
  OFF: 0,
  WHITE: 3,
  RED: 5,
  ORANGE: 9,
  YELLOW: 13,
  LIME: 17,
  GREEN: 21,
  MINT: 29,
  CYAN: 33,
  SKY: 37,
  BLUE: 45,
  PURPLE: 49,
  MAGENTA: 53,
  PINK: 57,
} as const;

type ColorName = keyof typeof Colors;

// Brightness/Animation channels
const Channels = {
  DIM_10: 0,
  DIM_25: 1,
  DIM_40: 2,
  DIM_55: 3,
  DIM_70: 4,
  DIM_85: 5,
  FULL: 6,
  PULSE_16: 7,
  PULSE_8: 8,
  PULSE_4: 9,
  PULSE_2: 10,
  BLINK_24: 11,
  BLINK_16: 12,
  BLINK_8: 13,
  BLINK_4: 14,
  BLINK_2: 15,
} as const;

type ChannelName = keyof typeof Channels;

// Button states
type ButtonState = 'off' | 'on' | 'blink';

// Note mappings
const Notes = {
  PAD_MIN: 0,
  PAD_MAX: 63,
  TRACK_MIN: 100,
  TRACK_MAX: 107,
  SCENE_MIN: 112,
  SCENE_MAX: 119,
  SHIFT: 122,
} as const;

export { Colors, Channels, Notes, ColorName, ChannelName, ButtonState };