Full background - santismo/Logic-Pro-11-Chord-time-detect-Automation GitHub Wiki

Chord logger

Logic scripter chord/time detect:

// Enable timing info var NeedsTimingInfo = true;

var chordNotes = []; var chordStartBeat = 0; var progression = []; // holds only chord names var TICKS_WAIT = 60; // detection wait ticks var PPQ = 960; // pulses per quarter note var OFFSET_TICKS = 942; // calibrate to shift reported position back to actual onset var ticksSinceLastNote = 0;

var PluginParameters = [ { name: "Detected Chord", type: "menu", defaultValue: 0, valueStrings: ["No chord"] }, { name: "Show Full Progression", type: "momentary" }, { name: "Reset Progression", type: "momentary" } ];

// Build chord name list var roots = ["C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"]; var extensions = ["","m","7","maj7","m7","dim7","m7b5","aug","6","m6","9","m9", "maj9","maj13","6/9","m6/9","sus2","sus4","7(sus4)","7(#5)","7(b5)", "7(b9)","7(#9)","7(#9 13)","maj add 9","m11","m11(b9)","13","13(#9)", "13(#11)","maj9(#11)","maj9(#11#5)","maj9(sus4)","maj7(#5)","7(b5 b9)","m(maj7#5)" ]; var knownChords = ["No chord"]; roots.forEach(r => extensions.forEach(e => knownChords.push(r + e))); PluginParameters[0].valueStrings = knownChords;

function updateUI(name) { var idx = knownChords.indexOf(name); SetParameter(0, idx !== -1 ? idx : 0); }

function HandleMIDI(event) { if (event instanceof NoteOn) { // record chord onset beat on first note if (chordNotes.length === 0) { chordStartBeat = GetTimingInfo().blockStartBeat; } if (!chordNotes.includes(event.pitch)) chordNotes.push(event.pitch); ticksSinceLastNote = 0; } event.send(); }

function ProcessMIDI() { if (chordNotes.length > 0) { ticksSinceLastNote++; if (ticksSinceLastNote >= TICKS_WAIT) { chordNotes.sort((a, b) => a - b); var name = getChordName(chordNotes);

  // calculate onsetBeat directly
  var onsetBeat = chordStartBeat;
  var info = GetTimingInfo();
  var meter = info.meterNumerator;
  var measure = Math.floor(onsetBeat / meter) + 1;
  var fracBeat = onsetBeat % meter;
  var beatNum = Math.floor(fracBeat) + 1;
  var ticksRaw = Math.round((fracBeat - Math.floor(fracBeat)) * PPQ);
  // apply offset
  var ticksAdj = ticksRaw - OFFSET_TICKS;
  // wrap around if negative
  if (ticksAdj < 0) {
    ticksAdj += PPQ;
    beatNum -= 1;
    if (beatNum < 1) {
      measure -= 1;
      beatNum = meter;
    }
  }
  var projectPos = measure + ":" + beatNum + ":" + ticksAdj;

  Trace(projectPos + "," + name);
  progression.push(name);
  updateUI(name);

  chordNotes = [];
  ticksSinceLastNote = 0;
}

} }

function ParameterChanged(param, value) { if (param === 1) Trace("\nFull Progression:\n" + progression.join(", ")); if (param === 2) { progression = []; Trace("\nProgression reset."); } }

function getNoteName(pitch) { var names = ["C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"]; return names[pitch % 12]; }

// Extended chord library (unchanged) var chordLibrary = [ { name: "m(maj7#5)", intervals: [0,3,8,11] }, { name: "m11(b9)", intervals: [0,3,7,10,1,2,5] }, { name: "m11", intervals: [0,3,7,10,2,5] }, { name: "7(b5 b9)", intervals: [0,4,6,10,1] }, { name: "13(#9)", intervals: [0,4,7,10,3,9] }, { name: "7(#9 13)", intervals: [0,4,7,10,3,9] }, { name: "maj9(#11)", intervals: [0,4,7,11,2,6] }, { name: "maj7(#5)", intervals: [0,4,8,11] }, { name: "maj9(sus4)", intervals: [0,5,7,2,4] }, { name: "m9", intervals: [0,3,7,10,2] }, { name: "9", intervals: [0,4,7,10,2] }, { name: "m7", intervals: [0,3,7,10] }, { name: "7", intervals: [0,4,7,10] }, { name: "maj7", intervals: [0,4,7,11] }, { name: "m7b5", intervals: [0,3,6,10] }, { name: "dim7", intervals: [0,3,6,9] }, { name: "7(#5)", intervals: [0,4,8,10] }, { name: "m6/9", intervals: [0,3,7,9,2] }, { name: "6/9", intervals: [0,4,7,9,2] }, { name: "m", intervals: [0,3,7] }, { name: "", intervals: [0,4,7] }, { name: "sus4", intervals: [0,5,7] }, { name: "sus2", intervals: [0,2,7] }, { name: "7(sus4)", intervals: [0,5,10] } ];

function getChordName(pitches) { if (!pitches.length) return "No chord"; var best = { name: "?", root: pitches[0], match: 0, weight: 0 }; for (var i = 0; i < pitches.length; i++) { var root = pitches[i]; var intervals = pitches.map(p => (p - root + 12) % 12); var unique = Array.from(new Set(intervals)); chordLibrary.forEach(function(ch) { var matchCount = ch.intervals.filter(iv => unique.includes(iv)).length; var ratio = matchCount / ch.intervals.length; var isDomForm = ch.name.includes("7") && !ch.name.includes("maj"); if (ratio >= 0.75 && matchCount > best.match) { best = { name: ch.name, root: root, match: matchCount, weight: ratio + (isDomForm && unique.includes(4) && unique.includes(10) ? 0.1 : 0) }; } }); } var rootName = getNoteName(best.root); var bass = getNoteName(pitches[0]); var chordName = rootName + best.name; if (best.root % 12 !== pitches[0] % 12) chordName += " / " + bass; return chordName; }

Apple Shortcut, automate marker positions and chord name input:

Command option control space —> chord marker place

on run -- 1) Read all lines from the clipboard set lineList to paragraphs of (the clipboard)

tell application "System Events"
	repeat with i from 1 to count of lineList
		set thisLine to item i of lineList
		if thisLine is not "" then
			-- 2) Split into Bars:Beats:Ticks and chord name
			set AppleScript's text item delimiters to ","
			set {posString, chordNameRaw} to text items of thisLine
			set AppleScript's text item delimiters to ""
			
			-- 3) Clean up chord name
			set cleanedChord to my trimText(chordNameRaw)
			
			-- A) Go to Position
			keystroke "z"
			delay 0.15
			-- Paste exact time string
			set the clipboard to posString
			delay 0.1
			keystroke "v" using {command down}
			delay 0.15
			key code 36 -- Return
			delay 0.15
			
			-- B) Create Marker (Control+Option+M)
			key code 46 using {control down, option down}
			delay 0.15
			
			-- C) Rename Marker with '=' key
			key code 24 -- '='
			delay 0.1
			-- Type the cleaned chord name
			keystroke cleanedChord
			delay 0.15
			key code 36 -- Return
			delay 0.15
		end if
	end repeat
end tell

end run

-- Helper: trim whitespace on trimText(txt) set txt to txt as string set txt to do shell script "echo " & quoted form of txt & " | sed 's/^ *//;s/ *$//'" return txt end trimText

Instructions:

  1. Open a software instrument track and select Scripter in the MIDI FX slot.

  2. Replace the existing script with the provided code.

  3. Enable timing info by setting var NeedsTimingInfo = true and ensuring the plugin settings have "Receive Timing" enabled.

  4. Save the preset as "Chord Logger" or similar. For the Apple Shortcut setup:

  5. Use the clipboard to copy the plugin’s console output of detected chord data and run the shortcut. Detailed setup instructions for Logic Pro and Apple Shortcut

  6. In Logic Pro, map the following key commands:

    • "z" to "Go to Position" (Transport > Go to Position).
    • Control + Option + M to "Create Marker".
    • "=" to "Rename Marker".
  7. In Logic's Key Commands, cancel the default assignment for Control + Option + Command + Space to allow the Apple Shortcut to trigger.

  8. In the Shortcuts app, create a new shortcut named "Chord logger".

    • Add the "Run AppleScript" action and paste the script inside.
    • Set Command + Option + Control + Space as the shortcut trigger. Thought for 12 seconds Here’s a step-by-step guide to get everything running smoothly. We’ll cover:
  9. Logic Pro key-command setup

  10. Installing the Scripter plugin

  11. Assigning the shortcut in macOS Shortcuts

  12. Disabling the macOS “⌃⌥⌘Space” conflict

  13. Workflow: logging → copying → marker creation

  14. Logic Pro key-command setup

  15. Open Logic Pro → Key Commands → Edit…

  16. Map “Go to Position…” to z

    • Search for Transport → Go to Position…
    • Click Learn by Key Label, press z, click Assign.
  17. Map “Create Marker” to Control + Option + M

    • Search for Global Tracks → Marker → Create Marker
    • Click Learn by Key Label, press ⌃⌥M, click Assign.
  18. Map “Edit Marker Text” to =

    • Search for Global Tracks → Marker → Edit Marker Text
    • Click Learn by Key Label, press =, click Assign.
  19. Unassign any Logic command using ⌃⌥⌘Space

    • In the same window, search by pressing Learn by Key Label, then hit ⌃⌥⌘Space
    • If it finds a command, select it and click Clear.
  20. Click Save and give your key-commands set a name (e.g. “ChordLogger”).

  21. Install & configure the Scripter plugin

  22. In your project, create (or select) a Software Instrument track.

  23. In the MIDI FX slot, choose Scripter → Open Editor.

  24. Delete all default code and paste in the full “Logic scripter chord/time detect” script you provided.

  25. At the top of the plugin window make sure Enable is on.

  26. Click the Preset dropdown → Save As… → name it Chord/Time Detect.

  27. Close the editor. Tip: you don’t need to touch the plugin’s UI—your script’s NeedsTimingInfo = true already grabs beat positions.

  28. Create the macOS Shortcut

  29. Open Shortcuts.app.

  30. Click + to make a new shortcut, name it Create Chord Markers.

  31. Add action Run AppleScript, paste your Apple Shortcut code block.

  32. Click the ⚙️ (settings) button in the upper right.

    • Toggle Use as Quick Action if you like.
    • In Keyboard Shortcut, click none then press ⌃⌥⌘Space.
  33. Close the settings, then File → Save.

  34. Disable the macOS “⌃⌥⌘Space” conflict By default, macOS uses Control + Command + Space (with or without Option) to open the Emoji & Symbols panel. To free it:

  35. Open System Settings → Keyboard → Keyboard Shortcuts.

  36. Go to Input Sources (or “Show Emoji & Symbols”).

  37. Uncheck the shortcut for Show Emoji & Symbols. Now ⌃⌥⌘Space will reliably fire your “Create Chord Markers” shortcut instead.

  38. Full workflow

  39. Play or record your MIDI track with the Scripter plugin active.

  40. Open the Scripter Console (click the little “Trace” icon in the plugin).

  41. When you’re done, click inside that console window, ⌘A (Select All) → ⌘C (Copy).

  42. Switch back to the Arrange window.

  43. Press ⌃⌥⌘Space.

    • The shortcut will parse each “bar:beat:tick,chordName” line,
    • Jump to that position (z → paste → Return),
    • Create a marker (⌃⌥M),
    • Press = to rename,
    • Type the chord name and hit Return. And you’re done—Logic Pro will place global markers at every detected chord, named accordingly.