Każda receptura to krótki skrypt w Pythonie z użyciem klienta Pythona. Wynikają jedna z drugiej, więc zmienne przechodzą z receptury do receptury, ale każdą część da się też uruchomić osobno, gdy masz id torów z omni.tracks.list(). Każde pokazane wywołanie zostało uruchomione na działającym Omni i wszystko można cofnąć przez omni.edit.undo().
Wskazówka. Wszystko, co robi asystent, można zrobić tymi samymi wywołaniami, a są to dokładnie narzędzia, które wystawia serwer MCP. Gdy zastanawiasz się, co zrobi narzędzie AI, najpierw wypróbuj wywołanie tutaj.
1. Pierwszy kontakt
from omnidaw import Omni
omni = Omni()
print(omni.session.info())
for track in omni.tracks.list():
print(track["index"], track["name"], track["kind"], track["id"])
session.info mówi o projekcie, częstotliwości próbkowania oraz liczbie torów i scen. tracks.list daje id każdego toru, które przekazujesz w dalszych wywołaniach.
2. Pętla: akordy, bas, melodia i perkusja
key = {"root": "F", "mode": "minor"}
prog = {"key": key, "chords": "i VI III VII"}
keys = omni.tracks.create(kind="midi", name="Keys", instrument="builtin://piano-synth")["id"]
bass = omni.tracks.create(kind="midi", name="Bass", instrument="builtin://omni-synth")["id"]
lead = omni.tracks.create(kind="midi", name="Lead", instrument="builtin://omni-synth")["id"]
drums = omni.tracks.create(kind="midi", name="Drums", instrument="builtin://tomic")["id"]
# Chords as a pad, the same progression as a bass line
omni.clips.write_progression(track=keys, slot=0,
progression={**prog, "perform": {"pattern": "pad"}})
omni.clips.write_progression(track=bass, slot=0,
progression={**prog, "perform": {"chords": False, "bass": True}})
# A melody that follows the chords of the Keys clip
omni.clips.generate_melody(track=lead, slot=0, style="synth_arp", key=key, bars=4,
register="high", chords_from={"track": keys, "slot": 0})
# Drums for the TOMNIC drum synth: x = hit, X = accent, . = rest, one character per 1/16
omni.clips.write_drums(track=drums, slot=0, lanes=[
{"channel": "kick", "steps": "x...x...x...x..."},
{"channel": "snare", "steps": "....X.......X..."},
{"channel": "hihat", "steps": "x.x.x.x.x.x.x.x.", "swing": 30, "velocity": 70},
])
omni.scenes.launch(scene=0) # plays scene row 0 of every track
theory.progression pokazuje, w co zamienia się tekst progresji, niczego nie zapisując, a theory.library i theory.vocabulary wymieniają 240 gotowych progresji oraz każdą nazwę stylu, wzorca i trybu, jaką przyjmują pozostałe wywołania. clips.generate_melody przyjmuje styl melodii z tej listy: stepwise_flow, minimal_cell, classical_arch, funk_groove, celtic_reel, bebop_line, arpeggio_loop, synth_arp, sequence_run, ambient_drift, tension_build, modal_motive. To samo ziarno (seed) zawsze daje tę samą melodię.
3. Od pętli do utworu
Daj pozostałym wierszom scen więcej do grania, a potem ułóż sceny jako sekcje:
# Scene row 1: a fuller version of each part
for track in (keys, bass, lead):
omni.clips.duplicate(track=track, slot=0, to_slot=1)
omni.clips.write_drums(track=drums, slot=1, lanes=[
{"channel": "kick", "steps": "x..x..x...x..x.."},
{"channel": "snare", "steps": "....X.......X..X"},
{"channel": "hihat", "steps": "xxxxxxxxxxxxxxxx", "velocity": 60},
])
song = omni.arrangement.build_song(sections=[
{"scene": 0, "bars": 4, "name": "Intro"},
{"scene": 1, "bars": 8, "name": "Drop"},
])
print(song["end_beat"], [s["name"] for s in song["sections"]])
omni.arrangement.play(from_beat=0)
build_song kopiuje klip każdego toru z danego wiersza sceny na oś czasu na zadaną liczbę taktów i dodaje marker na sekcję. Odmawia nadpisania osi czasu, na której już są klipy; przekaż clear_existing=True albo późniejszy start_beat, by budować obok istniejącego materiału. Potem arrangement.edit_clip, copy_clip i remove_clip kształtują pojedyncze klipy, na przykład żeby wyciszyć bas w ostatnich taktach dropu.
4. Ruch: automatyka
Znajdź klucz parametru, a potem zapisz punkty w beatach, z wartościami w jednostce samego parametru:
cutoff = omni.params.list(track=lead, filter="cutoff")["params"][0]
print(cutoff["key"], cutoff["name"], cutoff["min"], cutoff["max"])
lo, hi = cutoff["min"], cutoff["max"]
omni.automation.write(track=lead, param=cutoff["key"], curve="smooth", points=[
{"beat": 16, "value": lo + 0.2 * (hi - lo)}, # closed at the start of the drop
{"beat": 48, "value": lo + 0.9 * (hi - lo)}, # open at the end
])
# Volume is in dB and pan from -1 to 1; no key needed
omni.automation.write(track=lead, param="volume", points=[
{"beat": 0, "value": -30}, {"beat": 16, "value": -6},
])
print(omni.automation.list(track=lead))
Kluczem parametru jest instrument:<id> dla instrumentu i fx:<slot>:<index> dla insertu, dokładnie tak, jak zwraca go params.list. Działa dla instrumentów wbudowanych oraz wtyczek VST3 i CLAP.
5. Posłuchaj miksu i popraw go
analysis.capture nagrywa master (albo jeden tor) przez kilka sekund, gdy gra transport, i analizuje to w tle. Poczekaj na zadanie, potem odczytaj liczby:
omni.arrangement.play(from_beat=16)
analysis = omni.wait(omni.analysis.capture(source="master", seconds=8)["job"])
print("integrated", analysis["loudness"]["integrated_lufs"], "LUFS")
print("true peak ", analysis["level"]["true_peak_dbtp"], "dBTP")
print("balance ", analysis["tonal_balance"]) # sub, bass, low_mid, mid, high_mid, high, air
print("stereo ", analysis["stereo"])
Potem działaj na podstawie tego, co słyszysz. Kompresor na basie i trochę pogłosu na leadzie przez szynę return:
omni.devices.add(chain={"track": bass}, device="Compressor")
print([p["name"] for p in omni.devices.get(chain={"track": bass}, slot=0)["params"]])
omni.devices.set_params(chain={"track": bass}, slot=0,
values={"Ratio": 3.0, "Threshold": -18.0})
space = omni.mixer.add_return(name="Space")["returns"][-1]["index"]
omni.devices.add(chain={"return_bus": space}, device="Reverb")
omni.mixer.set_send(track=lead, bus=space, level_db=-12)
Urządzenia przyjmują parametry po nazwie, w prawdziwych jednostkach; devices.get wymienia je z wartością, zakresem i jednostką. Nagraj ponownie, żeby zobaczyć efekt. Aby znaleźć, gdzie dwa tory walczą o te same częstotliwości, nagraj każdy osobno (source={"track": id}) i wywołaj analysis.compare(a=job_a, b=job_b).
6. Mastering pod serwis streamingowy
Wybierz cel dostawy (master.targets je wymienia: spotify, apple_music, youtube, tidal, amazon_music, deezer, podcast, ebu_r128, atsc_a85, club), zapętl najgłośniejszą część i pozwól Omni zbudować pierwszy łańcuch z pomiaru. Potem domknij różnicę w pętli:
omni.arrangement.set_loop(start_beat=16, end_beat=48, enabled=True)
omni.arrangement.play(from_beat=16)
job = omni.analysis.capture(source="master", seconds=15)["job"]
omni.wait(job)
chain = omni.master.build_chain(job=job, target="spotify") # glue compressor + true-peak limiter
limiter = chain["limiter_slot"]
for _ in range(5):
job = omni.analysis.capture(source="master", seconds=15)["job"]
omni.wait(job)
verdict = omni.master.check(job=job, target="spotify")
print(verdict["integrated_lufs"], verdict["true_peak_dbtp"], verdict["notes"])
if verdict["passed"]:
break
gain = next(p for p in omni.devices.get(chain="master", slot=limiter)["params"]
if p["name"] == "Input Gain")
omni.devices.set_params(chain="master", slot=limiter, values={
"Input Gain": min(gain["max"], gain["value"] + verdict["gain_to_target_db"])})
result = omni.wait(omni.export.render(path="C:/Music/my-song.wav", target="spotify", fast=True)["job"])
print(result["path"], result["analysis"]["loudness"]["integrated_lufs"])
Pętla ma znaczenie: limiter nie przekazuje całego dodatkowego wzmocnienia wejścia na głośność, więc jedna korekta zwykle ląduje w połowie drogi. W teście na miksie drum and bass potrzeba było dwóch rund, by przejść z -16,3 do -14,7 LUFS przy true peaku -3 dBTP. Nagrywaj co najmniej 15 sekund, żeby pomiar głośności miał sens. build_chain odmawia, gdy master ma już Limiter; wtedy dostrój go przez devices.set_params.
export.render z fast=True renderuje offline, szybciej niż w czasie rzeczywistym, a wynik zadania zawiera analizę zapisanego pliku. Bez fast utwór jest odtwarzany przez urządzenie audio w czasie rzeczywistym. Oba zapisują tylko do bezwzględnych ścieżek .wav w istniejących folderach i nie nadpisują, dopóki nie przekażesz overwrite=True.
7. Stemy
result = omni.wait(omni.export.stems(folder="C:/Music/stems")["job"])
for f in result["files"]:
print(f["name"], f["path"])
Jeden WAV na tor, renderowany offline: każdy stem to jego tor solo, z jego returnami i bez łańcucha mastera, chyba że przekażesz through_master=True. Przekaż tracks=[...], by wyrenderować tylko niektóre.
8. Pokrój break i zagraj go z powrotem
Najpierw pliki audio muszą być dozwolone: użytkownik włącza Let clients read audio files w Ustawieniach Omni, AI Create. Sprawdź "files" in omni.capabilities.
drum_stem = next(f for f in result["files"] if f["name"] == "Drums")
chop = omni.tracks.create(kind="audio", name="Chop")["id"]
omni.audio.import_(track=chop, slot=0, path=drum_stem["path"])
# Cut into sixteenths at the project tempo (division = slices per beat)
sliced = omni.sampler.slice(track=chop, slot=0, mode="beats", division=4)
print(len(sliced["slices"]), "slices; the first:", sliced["slices"][0])
# Replay eight slices as a new pattern (start and duration in beats)
order = [0, 0, 6, 2, 4, 0, 6, 7]
omni.clips.write_slices(track=chop, slot=0, length_beats=2, steps=[
{"slice": 8 + s, "start": i * 0.25, "duration": 0.25, "velocity": 110 if i % 4 == 0 else 90}
for i, s in enumerate(order)
])
omni.clips.launch(track=chop, slot=0)
Pocięty klip gra przez swoje nuty, więc może też zagrać melodię z jednego slice'a: powtarzaj ten sam slice z innym pitch (półtony) w każdym kroku. sampler.slice tnie też mode="transients" (z sensitivity) i mode="equal" (z count); klip mieści najwyżej 92 slice'y, więc krój krótką pętlę, nie cały utwór. sampler.set zmienia tryb odtwarzania, nutę bazową, transpozycję i obwiednię.
9. Audio na nuty
bass_stem = next(f for f in result["files"] if f["name"] == "Bass")
notes = omni.wait(omni.audio.transcribe(path=bass_stem["path"], mode="bass")["job"])
print(notes["note_count"], "notes; the first:", notes["notes"][0])
mode to poly (akordy), melody (jedna linia) lub bass. Wynik zawiera nuty w beatach w tempie projektu, gotowe dla clips.write_midi. Aby transkrybować audio, które już jest w slocie sesji, przekaż track i slot zamiast path. Renderowanie stemów miksu i ich transkrypcja to szybki sposób, by zamienić nagranie w edytowalne MIDI.
10. Syntezator-wtyczka i jego presety
plugins = [i for i in omni.instruments.list() if i["group"] == "Plugin"]
print([(p["name"], p["format"]) for p in plugins][:8])
surge = omni.tracks.create(kind="midi", name="Surge", instrument="Surge XT")["id"]
found = omni.instruments.presets(track=surge, query="factory leads")
print(found["total"], "presets;", [p["name"] for p in found["presets"][:5]])
omni.instruments.load_preset(track=surge, path=found["presets"][0]["path"])
# Its parameters, read from the plugin itself (they appear once the plugin has loaded)
import time
for _ in range(20):
params = omni.params.list(track=surge, filter="cutoff")["params"]
if params:
break
time.sleep(0.5)
print(params[:2])
instruments.list pokazuje instrumenty wbudowane i każdą wtyczkę-instrument zeskanowaną przez Omni. Wtyczka ładuje się w tle, więc jej parametry pojawiają się chwilę po utworzeniu toru. Presety to pliki .fxp zainstalowane razem z wtyczką. Zewnętrzne efekty działają tak samo przez devices.types i devices.add.
11. Efekt MIDI zrenderowany do nut
Efekty i generatory MIDI (Arpeggiator, Markov, L-System i inne; midi_fx.types je wymienia) mogą działać na żywo albo być zagrane offline do klipu, żebyś mógł edytować nuty:
omni.midi_fx.add(track=keys, device="Arpeggiator")
omni.midi_fx.set_params(track=keys, slot=0, values={"Octaves": 2})
# The arpeggiator hears the chord in slot 0 and writes what it plays into slot 2
clip = omni.midi_fx.render(track=keys, slot=0, to_slot=2, bars=2, input_slot=0)
print(clip["note_count"], "notes")
Po renderze efekt jest omijany (bypass), żeby klip nie był przetwarzany dwa razy. Generator taki jak Markov nie potrzebuje nut wejściowych.
12. Mapa tempa i groove
omni.tempo.set_map(points=[
{"beat": 0, "bpm": 124},
{"beat": 32, "bpm": 132, "curve": "linear"}, # ramps up from beat 32
])
omni.groove.set(style="mpc", amount=0.55, humanize_ms=4)
omni.groove.set_track(track=bass, follow=False) # a tight bass under swung drums
Klipy na osi czasu zachowują swoje beaty przy zmianie mapy tempa. tempo.set_time_signatures zmienia metrum od wskazanego taktu.
13. Cofanie
# Take back everything the script did, and stop at the first step that is not an API step
step = omni.edit.undo()
print("undid", step["label"])
while (step["next_undo"] or "").startswith("API:"):
step = omni.edit.undo()
print("undid", step["label"])
Każda z powyższych edycji to jeden krok z etykietą API: .... edit.redo przywraca krok. Wynik edit.undo nazywa też następny krok (next_undo), więc skrypt może się zatrzymać, zanim dojdzie do czegoś, co użytkownik zrobił ręcznie. Okno Historii Cofania w Omni pokazuje te same kroki.
The Python client, and minimal working clients in plain Python, Node.js and Rust. Everything needed to drive Omni from your own code.
What the OmniDAW API exposes and what it protects, the file-access permission, how undo contains mistakes, and the MIT License of the example code.
