← Back to Melovas Apps

Melovas external apps

Developer Guide

Build iframe-ready tools that run on apps.melovas.com, talk to the Melovas editor through postMessage, and request only the permissions they need.

1. How external apps work

A Melovas external app is a static web app opened inside the main editor in a sandboxed iframe. The main editor controls which app is opened, which origin is trusted, and which capabilities are granted. The external app sends structured messages to the parent editor when it wants to apply notes, control playback, use microphone-led workflows, or play Melovas instrument sounds.

Static app code should not contain secrets. Any private, expensive, or AI-backed work should go through a Melovas backend API.

2. Registry / package entry

The Melovas app registry tells the editor which apps exist and where their hosted iframe entry point lives. For now this can be served by the static host or by the API later.

{
  "id": "keyboard-player",
  "displayName": "Keyboard Player",
  "version": "1.6.0",
  "description": "Play Melovas keyboard and synth sounds with your computer keyboard.",
  "entryUrl": "https://apps.melovas.com/keyboard-player/1.6.0/index.html",
  "focusMode": "exclusive",
  "icon": "⌨",
  "capabilities": [
    "commit-notes",
    "request-timeline-playback",
    "keyboard-note-preview",
    "host-instrument-playback",
    "isolated-external-runtime"
  ],
  "messageNamespace": "melovas.external-app"
}

The host will still apply its own app-id based allowlist and capability grants. Declaring a capability in the app metadata does not automatically grant it.

3. Capabilities / permissions

CapabilityUse it when your app needs to...Main messages/features
commit-notesSend generated or recorded notes into the Melovas timeline.melovas.external-app.commit-notes
timeline-applyApply timeline changes. This is broader than note import and should be granted carefully.commit-notes, project commands where granted
commit-project-commandsSend structured project-edit commands such as creating tracks or adding blocks.melovas.external-app.commit-project-commands
request-timeline-playbackStart, stop, toggle, or restart host timeline playback from inside the iframe.melovas.external-app.timeline-playback
host-instrument-playbackUse the main app's real instrument sounds for previews/live performance.Instrument catalog, note-on/off, instrument actions
microphone-recordingRecord audio from the user's microphone inside the iframe.Browser microphone APIs inside the app
voice-to-midi-analysisAnalyse recorded audio and create notes.App-specific analysis + commit-notes
computer-keyboard-performanceCapture computer keyboard input for playable pads.Usually pairs with exclusive focus mode
virtual-keyboard-performanceOffer clickable/touchable virtual keys or pads.Usually pairs with host-instrument-playback
custom-user-layoutsLet users customise layouts inside the external app.Local app state; no special host message
pad-action-editingLet users map pads to notes/chords/instruments.Local app state + instrument catalog
isolated-external-runtimeRun as a hosted iframe app rather than as part of the main editor bundle.Required for hosted external apps

4. Messaging rules

Every app-to-host message should include the namespace melovas.external-app and a payload appId matching the app currently open in the iframe. The parent Melovas app validates the iframe window, origin, app identity, and effective capabilities before doing anything.

Melovas appends the parent origin to the iframe URL as melovasHostOrigin and hostOrigin. Your app should read this value and use it as the targetOrigin for window.parent.postMessage.

const params = new URLSearchParams(window.location.search);
const hostOrigin = params.get('melovasHostOrigin') || params.get('hostOrigin') || '*';

function postHostMessage(type, payload) {
  window.parent.postMessage({
    namespace: 'melovas.external-app',
    type,
    payload
  }, hostOrigin === '*' ? '*' : new URL(hostOrigin).origin);
}

5. Commit notes to the timeline

Use this when your app has note-like results: hum-to-MIDI output, keyboard-player recordings, melody ideas, etc. Notes are imported as editable Melovas blocks. Keyboard/piano imports become one-shot blocks; synth imports keep their duration.

postHostMessage('melovas.external-app.commit-notes', {
  appId: 'my-app-id',
  title: 'My App Recording',
  notes: [
    {
      id: 'note-1',
      midi: 60,
      midiNotes: [60, 64, 67],
      startSeconds: 0,
      durationSeconds: 1.2,
      velocity: 0.9,
      selected: true,
      instrument: 'keyboard',
      label: 'C'
    }
  ]
});

Supported import instruments today are keyboard and synth. Omitted or unknown values default to keyboard/piano.

6. Request host instrument sounds

Apps with host-instrument-playback should request the host instrument catalog first. The catalog is the contract for what Melovas can play. Do not guess note names, drum IDs, guitar strings, frets, chord names, or action payloads.

postHostMessage('melovas.external-app.request-instrument-catalog', {
  appId: 'my-app-id'
});

The host replies to the iframe with entries for keyboard, synth, drums, and guitar:

{
  namespace: 'melovas.external-app',
  type: 'melovas.external-app.instrument-catalog',
  payload: {
    instruments: [
      {
        instrumentId: 'keyboard',
        liveNoteMode: 'one-shot',
        noteNames: ['A0', 'As0', 'B0', 'C1', '...C8'],
        actions: [{ action: 'play-note', messageType: 'instrument-note-on' }]
      },
      {
        instrumentId: 'synth',
        liveNoteMode: 'held',
        noteNames: ['A0', 'As0', 'B0', 'C1', '...C8'],
        actions: [{ action: 'play-note', messageType: 'instrument-note-on' }]
      },
      {
        instrumentId: 'drums',
        liveNoteMode: 'one-shot',
        sounds: [{ id: 'Kick', label: 'Kick' }],
        actions: [{ action: 'hit', messageType: 'instrument-action' }]
      },
      {
        instrumentId: 'guitar',
        liveNoteMode: 'one-shot',
        stringIndexRange: [0, 5],
        fretRange: [0, 12],
        chordNames: ['C', 'Cm', 'C7', 'Cmaj7', '...'],
        strumDirections: ['down', 'up'],
        actions: [
          { action: 'pluck-string', messageType: 'instrument-action' },
          { action: 'strum-chord', messageType: 'instrument-action' },
          { action: 'strum-voicing', messageType: 'instrument-action' },
          { action: 'start-strum-pattern', messageType: 'instrument-action' },
          { action: 'stop-strum-pattern', messageType: 'instrument-action' }
        ]
      }
    ]
  }
}

The host currently uses Cs4, Ds4, etc. for sharps, not C#4. instruments comes back empty if your app does not have the host-sound capability.

7. Instrument actions

External apps report musical actions. Melovas owns instrument behaviour. For playable notes, send instrument-note-on and instrument-note-off; the host decides whether note-off matters.

Keyboard and synth

Use note lifecycle messages for both keyboard and synth. Keyboard is one-shot in the host, so note-off is ignored. Synth is held, so note-off releases the matching voiceId.

postHostMessage('melovas.external-app.instrument-note-on', {
  appId: 'my-app-id',
  voiceId: 'pad-a-c4',
  instrumentId: 'keyboard', // or 'synth'
  noteName: 'C4',
  velocity: 0.9
});

postHostMessage('melovas.external-app.instrument-note-off', {
  appId: 'my-app-id',
  voiceId: 'pad-a-c4',
  releaseSeconds: 0.08
});

Drums

Use instrument-action with a soundId from the catalog.

postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'drums',
  action: 'hit',
  soundId: 'Kick',
  velocity: 0.9
});

Guitar

Guitar strings use Melovas' sample order: 0 is the high/small E string and 5 is the low/thick E string. Frets are integers from 0 to 12. Direction down means low/thick to high/small; direction up means high/small to low/thick.

postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'guitar',
  action: 'pluck-string',
  stringIndex: 2,
  fret: 5,
  velocity: 0.9
});
postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'guitar',
  action: 'strum-chord',
  chordName: 'C',
  direction: 'down',
  velocity: 0.9,
  spreadSeconds: 0.015
});
postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'guitar',
  action: 'strum-voicing',
  strings: [0, 1, 0, 2, 3, 'x'],
  direction: 'up',
  velocity: 0.9,
  spreadSeconds: 0.015
});

A custom strings voicing must contain exactly six values in order [String0, String1, String2, String3, String4, String5]. Use 'x' to mute a string.

Gated strumming pattern

Use start-strum-pattern when a held key/pad should play a finite down/up pattern. The host schedules the relative strums, stops pending strums on stop-strum-pattern, and lets already-played strums ring naturally. If the key stays held, the pattern plays once and then goes silent.

postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'guitar',
  action: 'start-strum-pattern',
  voiceId: 'guitar-c-pattern-key-a',
  chordName: 'C',
  patternName: 'DDUUD',
  lengthBeats: 4,
  events: [
    { beatOffset: 0, direction: 'down' },
    { beatOffset: 1, direction: 'down' },
    { beatOffset: 1.5, direction: 'up' },
    { beatOffset: 2.5, direction: 'up' },
    { beatOffset: 3, direction: 'down' }
  ],
  velocity: 0.9,
  spreadSeconds: 0.015
});

postHostMessage('melovas.external-app.instrument-action', {
  appId: 'my-app-id',
  instrumentId: 'guitar',
  action: 'stop-strum-pattern',
  voiceId: 'guitar-c-pattern-key-a'
});

Pattern names should describe the direction sequence where possible, for example DU, DDUUD, or DDUUDU.

8. Timeline playback control

Apps with request-timeline-playback can ask the host transport to start, stop, toggle, or restart.

postHostMessage('melovas.external-app.timeline-playback', {
  appId: 'my-app-id',
  action: 'start',
  volume: 0.35
});

9. Safety and quality checklist

  • Serve the app from an origin trusted by Melovas, currently https://apps.melovas.com.
  • Declare only the capabilities the app genuinely needs.
  • Never store API keys or secrets in static app files.
  • Always request the instrument catalog before triggering host sounds.
  • For playable key/pad controls, send note-on and note-off; let Melovas decide one-shot vs sustain.
  • Use instrument-action for drums and guitar; keep trigger-instrument-note only for old/simple one-shot previews.
  • Send note-off for any voice IDs that are still active when the user stops playback, changes layout, or closes the app.
  • Keep message payloads bounded. Large imports may be rejected by the host.