Razer WYVRN¶
⚠️ Not available in this release. The integration is complete and lives in the repository, but it is compiled out by default (
STREAMELEMENTS_ENABLE_WYVRN=OFF) while its behaviour is confirmed against real hardware. In a build without it, none of the calls on this page are registered andgetHostCapabilitiesis not registered either. This page documents what returns when the option is turned back on.
window.host
Chroma RGB lighting and Sensa HD haptics, driven by naming an event. What that event looks and feels like is decided by the WYVRN configurations installed on the viewer's machine, not by SE.Live.
The integration is optional and Windows-only. It needs Razer Synapse 4 and
the Chroma App, which most OBS users do not have. Every failure path ends in
"unavailable" — the calls below still answer normally, they simply report that
nothing is there. Check
getHostCapabilities
before assuming otherwise.
Initialization is asynchronous and takes about 3.4 seconds. It never blocks
OBS start, so for the first few seconds of a session the status is
initializing and setRazerWyvrnEvent returns false. Call
getHostCapabilities first, then subscribe to
hostRazerWyvrnStatusChanged — a
page that only subscribes will miss the event entirely if it loads after
initialization has already finished.
SE.Live ships no WYVRN configuration of its own. Anything that renders does so through a configuration some other application installed, which Synapse distributes automatically. This is by design, not a defect.
getAllRazerWyvrnEvents(ResultCallback<RazerWyvrnEventInfo[]>)¶
Available since API version 6.8
Every event declared by every WYVRN configuration installed on this machine.
Each entry carries its Chroma and haptic components along with session-signed URLs for their assets, so one call answers both "what can I fire" and "what would firing it do". There is no need for a second call to inspect an event.
Returns an empty array when the integration is unavailable — never an error.
Filtering. A machine with Synapse installed typically declares around 4,000 events, so an optional filter object is accepted:
window.host.getAllRazerWyvrnEvents(
{ source: '007 First Light', idPrefix: 'Aim_' },
function (events) { /* ... */ });
| Property | Type | Description |
|---|---|---|
| source | string | Match the containing configuration folder exactly, case-insensitively. Omit or leave empty for all. |
| idPrefix | string | Match the beginning of the event id, case-insensitively. Omit or leave empty for all. |
| components | bool | Include each event's components and asset URLs. Defaults to true. |
Cost¶
components is not a cosmetic flag. Including them costs one filesystem probe
and one URL signature per component, and the whole request runs inside the
process-wide API lock — so a slow call makes OBS unresponsive, not just this
caller.
Measured on a machine with Synapse installed (4,044 events, 24,698 components):
| Call | Time | Payload |
|---|---|---|
| Filtered by source, with components | ~50 ms | 0.13 MB |
Unfiltered, components: false |
~65 ms | 0.28 MB |
| Unfiltered, with components | ~2.1 s | 7.7 MB |
Filter, or turn components off. Asking for all 4,044 events with their components is supported and correct, but it is a two-second request returning nearly eight megabytes, and nothing else can call the host API while it runs. The intended shape is a cheap ids-only sweep followed by a filtered call for whatever the user actually selected.
Data structures: RazerWyvrnEventInfo
setRazerWyvrnEvent(RazerWyvrnEventInfo, ResultCallback<success>)¶
Available since API version 6.8
Fire an event.
Takes the whole RazerWyvrnEventInfo object
rather than a bare string, so an item obtained from getAllRazerWyvrnEvents can
be handed back unmodified; only id and fallback are read. A bare string is
also accepted.
id is matched case-insensitively, and the event is fired under the spelling
the configuration uses — so aim_on reaches the SDK as Aim_On.
Stopping playback¶
Three spellings mean the same thing, because a caller clearing an event should not have to remember which shape the API wanted:
window.host.setRazerWyvrnEvent(null, cb); // null
window.host.setRazerWyvrnEvent(cb); // no argument at all
window.host.setRazerWyvrnEvent({ id: '' }, cb); // an empty id
fallback¶
fallback names another event to try when this one is not declared by any
configuration on the machine. It takes the same shape and nests to arbitrary
depth:
window.host.setRazerWyvrnEvent({
id: 'Headshot',
fallback: { id: 'Hit',
fallback: { id: 'Generic_Impact' } }
}, cb);
The first entry in the chain that names a real event wins, and only that one is
fired. If nothing in the chain exists on this machine, nothing is sent and
the call returns false — the chain that was tried is written to the OBS log.
The chain is resolved against the scanned configurations, not against the SDK.
That is deliberate: CoreSetEventName accepts an event belonging to a different
application and reports success, so asking the SDK "did that work?" would always
answer yes and the fallback would never fire.
Nesting is bounded at 16 levels, since the whole call runs inside the process-wide API lock.
Return value¶
Returns false when the integration is not ready — including during the first
few seconds of a session, while initialization is still running — and when no
event in the chain exists on this machine.
Rate limiting is newest-wins. The SDK accepts at most 30 events per second. An event arriving inside that window is parked rather than dropped, and if another arrives before the parked one is sent, it replaces it. The backlog is one item by construction, and the name that survives is always the most recent — the last thing that happened on the stream is the thing worth rendering. A superseded event is logged to the OBS log so a name that never rendered is visible rather than silently absent.
Data structures: RazerWyvrnEventInfo
Enum values¶
Every closed vocabulary this API returns is camelCase, or plain lowercase
where the value is a single word. The configurations on disk use the vendor's
own capitalisation (Chest, VeryHigh, ChromaLink); it is normalised on the
way out, by lowercasing the first character and leaving the rest.
| Field | Values |
|---|---|
RazerWyvrnChromaComponent.device |
keyboard, keyboardExtended, keypad, mouse, mousepad, headset, chromaLink |
RazerWyvrnHapticComponent.mixing |
merge, override |
RazerWyvrnHapticComponent.priority |
veryLow, low, medium, high, veryHigh |
RazerWyvrnHapticTarget.target |
hand, head, chest, waist, leg, all, down, top |
RazerWyvrnHapticTarget.spatialization |
global, left, right |
RazerWyvrnEventInfo.kind |
exact, fallbackPattern |
RazerWyvrnStatus.status |
see RazerWyvrnStatus |
These are the values observed across every configuration installed on a machine with Synapse. Treat the lists as complete but not closed — a future configuration may introduce a value not listed here, and it will arrive normalised the same way rather than being dropped.
Normalisation folds case splits in the source data onto one value: both Waist
and waist occur in the wild and both arrive as waist. It does not map
unknown spellings onto known ones — the shipped data contains a Wasit typo,
which arrives as wasit rather than being silently corrected into waist,
because a caller cannot otherwise tell a real value from a repaired one.
Rendering previews¶
The asset URLs returned by getAllRazerWyvrnEvents point at the local file
server and are session-signed; fetching one yields the exact bytes on disk. Both
formats decode entirely in the browser.
A URL with a missing or altered signature is refused with
{ "success": false, "message": "Invalid Request Signature" }, and the file is
not served.
The .chroma format¶
A frame-by-frame colour animation. All integers are little-endian.
uint32 version
uint8 deviceType 0 = 1D strip, 1 = 2D grid
uint8 device
uint32 frameCount
repeat frameCount times:
float32 duration seconds
uint32 colors[ledCount]
Colours are COLORREF — 0x00BBGGRR, so the low byte is red, not blue.
ledCount is not in the file. It comes from the (deviceType, device)
pair, which is the one thing a decoder cannot discover from the asset itself:
| Device | deviceType |
device |
Colors per frame | Grid |
|---|---|---|---|---|
| ChromaLink | 0 | 0 | 5 | 5 LEDs |
| Headset | 0 | 1 | 5 | 5 LEDs |
| Mousepad | 0 | 2 | 15 | 15 LEDs |
| Keyboard | 1 | 0 | 132 | 6 × 22 |
| Keypad | 1 | 1 | 20 | 4 × 5 |
| Mouse | 1 | 2 | 63 | 9 × 7 |
| Keyboard (extended) | 1 | 3 | 192 | 8 × 24 |
device indexes within its type class, so the number alone is ambiguous — the
pair is the key.
Both keyboard geometries occur under the same _Keyboard filename, and only
the header separates them. Assuming the 192-LED grid parses the 132-LED files
without error and renders as noise, so verify the size rather than trusting the
table:
size === 10 + frameCount * (4 + ledCount * 4)
Refuse a mismatch.
The .haps format¶
JSON, in one of two schemas: a plain one (vibration.melodies[].notes[]) and an
m_-prefixed one (m_vibration.m_melodies[].m_notes[]). Both occur in the
shipped data.
They are not simply renamed. In the m_ schema, amplitude is normalised to
0..1 but frequency is absolute Hz, where the plain schema normalises both.
Treating one as the other yields frequencies three orders of magnitude wrong.
Transients (transients[] / m_transients[]) sit alongside melodies and carry
their own amplitude; a decoder that walks only the melodies reports zero
amplitude for a transient-only file.