OMNi

OmniDAW API · 7 min

Biblioteki klienckie

Klient Pythona oraz minimalne działające klienty w czystym Pythonie, Node.js i Ruście. Wszystko, czego trzeba, by sterować Omni z własnego kodu.

Do korzystania z OmniDAW API nie potrzebujesz biblioteki: to linie JSON przez gniazdo TCP. Ta strona daje gotowego klienta dla Pythona oraz trzy małe w czystym Pythonie, Node.js i Ruście, które możesz skopiować i dopasować. Każdy jest kompletny: uruchom go przy otwartym Omni, a wypisze Twoją sesję.

Pobierz wszystko: omnidaw-api-examples.zip albo pojedyncze pliki z linków poniżej.

Klient Pythona

omnidaw.py to pojedynczy plik bez zależności poza biblioteką standardową Pythona. Pobierz go i połóż obok swojego skryptu. Czyta plik endpointu, wita się, wczytuje katalog i zamienia każdą metodę działającego Omni w wywołanie Pythona:

python
from omnidaw import Omni

with Omni() as omni:
    print(omni.capabilities)       # ['jobs', 'analysis', 'render', 'plugins', 'audio']
    track = omni.tracks.create(kind="midi", name="Keys", instrument="builtin://piano-synth")
    omni.clips.write_progression(track=track["id"], slot=0, progression={
        "key": {"root": "F", "mode": "minor"},
        "chords": "i VI III VII",
        "perform": {"pattern": "pad"},
    })
    job = omni.analysis.capture(source="master", seconds=4)["job"]
    print(omni.wait(job)["loudness"])

Ścieżka atrybutów to nazwa metody: omni.clips.write_progression(...) wywołuje clips.write_progression. Parametry to argumenty nazwane i odpowiadają jeden do jednego referencji. Ponieważ metody pochodzą z katalogu działającego Omni, nowsze Omni przynosi nowe metody do Twojego skryptu bez aktualizacji klienta.

ElementRobi
Omni(client_name="omnidaw.py", timeout=600.0)Łączy się, wita, wczytuje katalog. Zgłasza FileNotFoundError lub ConnectionRefusedError, gdy Omni nie działa z włączonym API
omni.domena.metoda(**params)Wywołuje domena.metoda i zwraca wynik
omni.call("domena.metoda", {...})To samo z nazwą metody i słownikiem
omni.wait(job)Czeka na koniec zadania i zwraca jego wynik; zgłasza OmniError, jeśli zadanie się nie powiodło
omni.methodsKatalog: słownik od nazwy metody do jej summary, schematu params, example, effect
omni.capabilities, omni.api_versionZ uścisku dłoni
OmniErrorZgłaszany, gdy Omni odpowie błędem. Ma .code (kod JSON-RPC) i .data

Warto wiedzieć:

  • Metoda nazwana jak słowo kluczowe Pythona dostaje końcowy podkreślnik: omni.audio.import_(...) wywołuje audio.import.
  • omni.domena.metoda.__doc__ to opis metody z katalogu, więc help() i edytor go pokazują.
  • Na kablu jest naraz jedno żądanie, chronione blokadą, więc klienta można bezpiecznie wywoływać z wielu wątków.
  • Aby pominąć plik endpointu, ustaw OMNI_API_ENDPOINT=host:port i OMNI_API_TOKEN.
  • wait(job) wywołuje w pętli jobs.wait (9 sekund na rundę), aż zadanie się skończy.

Błędy czyta się naturalnie:

python
from omnidaw import Omni, OmniError

with Omni() as omni:
    try:
        omni.tracks.update(track=0, volumee=3)
    except OmniError as e:
        print(e.code, e)   # -32602 tracks.update: -32602 unknown field `volumee`, expected one of ...

Minimalny klient w czystym Pythonie

Jeśli wolisz nie kopiować pliku, oto całość w około 30 liniach (minimal_client.py). Znajduje Omni, wita się i wykonuje wywołania:

python
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Tomasz Głuc
"""Smallest useful OmniDAW API client: no dependencies, just sockets and JSON."""
import json, os, socket, sys
from pathlib import Path

if sys.platform == "win32":
    config = Path(os.environ["APPDATA"]) / "omni"
elif sys.platform == "darwin":
    config = Path.home() / "Library" / "Application Support" / "omni"
else:
    config = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "omni"

endpoint = json.loads((config / "api" / "endpoint.json").read_text())
sock = socket.create_connection((endpoint["host"], endpoint["port"]))
stream = sock.makefile("rw", encoding="utf-8", newline="\n")
next_id = 0


def call(method, params=None):
    global next_id
    next_id += 1
    stream.write(json.dumps({"jsonrpc": "2.0", "id": next_id, "method": method, "params": params or {}}) + "\n")
    stream.flush()
    reply = json.loads(stream.readline())
    if "error" in reply:
        raise RuntimeError(f"{method}: {reply['error']['code']} {reply['error']['message']}")
    return reply["result"]


call("api.hello", {"token": endpoint["token"], "api_version": "0.21.0",
                   "client": {"name": "raw-example", "version": "1.0"}})
print(call("session.info"))
print([t["name"] for t in call("tracks.list")])

Minimalny klient w Node.js

To samo w JavaScript, jako moduł ES (minimal_client.mjs, przetestowane na Node.js 24), tylko na wbudowanych modułach:

javascript
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Tomasz Głuc
// Smallest useful OmniDAW API client for Node.js: built-in modules only.
import fs from 'node:fs';
import net from 'node:net';
import os from 'node:os';
import path from 'node:path';
import readline from 'node:readline';

function configDir() {
  if (process.platform === 'win32') return path.join(process.env.APPDATA, 'omni');
  if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'omni');
  return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config'), 'omni');
}

const endpoint = JSON.parse(fs.readFileSync(path.join(configDir(), 'api', 'endpoint.json'), 'utf8'));
const socket = net.createConnection(endpoint.port, endpoint.host);
const pending = new Map();
let nextId = 0;

readline.createInterface({ input: socket }).on('line', (line) => {
  const reply = JSON.parse(line);
  const { resolve, reject } = pending.get(reply.id);
  pending.delete(reply.id);
  if (reply.error) reject(new Error(`${reply.error.code} ${reply.error.message}`));
  else resolve(reply.result);
});

const call = (method, params = {}) =>
  new Promise((resolve, reject) => {
    const id = ++nextId;
    pending.set(id, { resolve, reject });
    socket.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
  });

await call('api.hello', {
  token: endpoint.token,
  api_version: '0.21.0',
  client: { name: 'node-example', version: '1.0' },
});
console.log(await call('session.info'));
console.log((await call('tracks.list')).map((t) => t.name));
socket.end();

Odpowiedzi przychodzą w kolejności żądań, ale ten klient dopasowuje je po id, co pozwala też mieć kilka wywołań naraz.

Minimalny klient w Ruście

Ten klient potrzebuje tylko crate'a serde_json (cargo add serde_json). To ten sam plik co session_info.rs w paczce do pobrania:

rust
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Tomasz Głuc
//
// A complete OmniDAW API client in Rust. It needs only `serde_json`
// (`cargo add serde_json`): find Omni, one TCP connection, one JSON line per
// call. Omni must be running with the API on (Settings, AI Create).

use serde_json::{json, Value};
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::path::PathBuf;

/// The API version this client was written against.
const API_VERSION: &str = "0.21.0";

/// Omni's settings folder.
fn config_dir() -> Option<PathBuf> {
    let var = |name: &str| std::env::var_os(name).map(PathBuf::from);
    if cfg!(windows) {
        var("APPDATA").map(|p| p.join("omni"))
    } else if cfg!(target_os = "macos") {
        var("HOME").map(|h| h.join("Library/Application Support/omni"))
    } else {
        var("XDG_CONFIG_HOME")
            .or_else(|| var("HOME").map(|h| h.join(".config")))
            .map(|p| p.join("omni"))
    }
}

struct Omni {
    reader: BufReader<TcpStream>,
    writer: TcpStream,
    next_id: u64,
}

impl Omni {
    /// Find Omni, connect and shake hands; returns the connection and Omni's welcome.
    fn connect() -> Result<(Self, Value), Box<dyn Error>> {
        let file = config_dir()
            .ok_or("cannot find Omni's settings folder")?
            .join("api")
            .join("endpoint.json");
        let text = std::fs::read_to_string(&file).map_err(|e| {
            format!(
                "Omni is not running with the API on ({}: {e})",
                file.display()
            )
        })?;
        let endpoint: Value = serde_json::from_str(&text)?;
        let host = endpoint["host"]
            .as_str()
            .ok_or("endpoint.json has no host")?;
        let port = endpoint["port"]
            .as_u64()
            .ok_or("endpoint.json has no port")?;

        let stream = TcpStream::connect((host, u16::try_from(port)?))?;
        let mut omni = Omni {
            writer: stream.try_clone()?,
            reader: BufReader::new(stream),
            next_id: 0,
        };
        let welcome = omni.call(
            "api.hello",
            json!({
                "token": endpoint["token"],
                "api_version": API_VERSION,
                "client": { "name": "session_info example", "version": "1.0" },
            }),
        )?;
        Ok((omni, welcome))
    }

    /// Send one request and read its response.
    fn call(&mut self, method: &str, params: Value) -> Result<Value, Box<dyn Error>> {
        self.next_id += 1;
        let request =
            json!({ "jsonrpc": "2.0", "id": self.next_id, "method": method, "params": params });
        self.writer.write_all(format!("{request}\n").as_bytes())?;
        let mut line = String::new();
        if self.reader.read_line(&mut line)? == 0 {
            return Err("Omni closed the connection".into());
        }
        let mut reply: Value = serde_json::from_str(&line)?;
        if let Some(error) = reply.get("error") {
            return Err(format!("{method}: {} {}", error["code"], error["message"]).into());
        }
        Ok(reply["result"].take())
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let (mut omni, welcome) = Omni::connect()?;
    println!(
        "Omni speaks OmniDAW API {} ({} methods, capabilities {})",
        welcome["api_version"], welcome["method_count"], welcome["capabilities"]
    );
    let info = omni.call("session.info", json!({}))?;
    println!("{}", serde_json::to_string_pretty(&info)?);
    Ok(())
}

Wstaw go do src/main.rs nowego projektu Cargo i uruchom przez cargo run.

Dowolny inny język

Przepis jest wszędzie ten sam:

  1. Ustal folder ustawień (tabela na stronie o protokole) i przeczytaj api/endpoint.json.
  2. Otwórz połączenie TCP z host:port.
  3. Wyślij api.hello z tokenem jako pierwszą linię.
  4. Przy każdym wywołaniu zapisz jedną linię JSON i odczytaj jedną linię JSON.
  5. Odpowiedź z error traktuj jako błąd, a pola wyniku, których nie znasz, ignoruj.

Jeśli chcesz typów dla swojego języka, wygeneruj je z katalogu: api.methods (albo omni-mcp --catalog, gdy Omni nie działa) daje schemat JSON parametrów każdej metody.