Skip to content

Cassiopeia chart embed

The complete sample chart.json references the supplied original-tone.wav. Keep both files together when downloading. The page’s original diagram uses its example rendererAdapter; the public game-theme path is shown below.

Mount a Cassiopeia chart player inside an ordinary website. The default player uses the existing Vue player, Three renderer, Our Notes plugin and Web audio/input host. A custom renderer can use authored artwork with the kernel and Web host. Each mount owns its DOM, media, input, renderer and resource loader.

Save the complete watch HTML as index.html. Save chart.json and original-tone.wav beside it. The chart is a complete eight-second original ChartEmbedDocument; the WAV is a quiet original test tone.

my-chart/
index.html
chart.json
original-tone.wav

Open a terminal in my-chart and run python3 -m http.server 8000 (Windows: py -m http.server 8000). Open http://localhost:8000/. See quickstart for Python installation and editor HTTP-preview alternatives. Stop the server with Ctrl+C when finished.

Click Load my chart.json, wait for Ready, then click Play. You should see the sample notes move in the default game presentation, while the original tone provides the media clock. Pause stops it; the position slider seeks in seconds. Close player stops media and removes the player; either load button can start a fresh instance afterward. An error appears above the stage so you can fix the file or connection and retry.

The hosted module supplies the default player and its layout styles. The sample needs the internet for that module and the public presentation assets, while your chart and WAV are served from your own folder. The note sound effects are disabled in this first example; the supplied music remains enabled.

In the same HTML, keep song ID 100070 and difficulty expert, then click Load Haneoka song. After Ready, click Play. This source reads the current song, parses its returned score file and uses its returned music URL. You can change the ID to a song from the API starter; the chosen difficulty must exist for that song. Close the player before switching sources.

The source and theme are independent factories. The complete HTML uses their public browser entry:

import { mountChart, haneokaChartSource, haneokaChartTheme } from "https://haneoka.org/embed/cassiopeia.js";
const chart = mountChart(container, {
source: haneokaChartSource({ songId: "100070", difficulty: "expert" }),
theme: haneokaChartTheme(), server: "intl", locale: "en", mode: "watch",
});
await chart.ready;
// In your Play button: await chart.play().
// When your component closes: await chart.dispose().

That fragment shows the integration point; the full HTML above supplies container, buttons, loading state, cancellation, errors and cleanup. It uses lightweight asset quality 2. Server and text language remain separate, and no account or release selection is required.

In the complete HTML’s mountChart options, change volume: 0.3 to a value from 0 to 1 or rate: 1 to 0.8, then reload the page and load again. mode: "watch" plays automatically after your Play click; mode: "play" enables manual chart input. Add settings: { noteSpeed: 9 } for scroll speed. The complete option, skin, settings, source and handle tables below give the public defaults, ranges and limits. Keep chart.json in the standard format; another game’s raw score needs its parser/converter.

Package imports further down are reference for an existing bundled application; npm publication is separate. For the initial plain HTML, use the hosted ESM above. A bundler imports @haneoka/embed-cassiopeia/style.css separately; the official facade mounts its matching styles and removes them when the last instance is disposed.

A standard ChartDocument contains version, bpmChanges, signatureChanges, timeScaleChanges, notes, lines, timeline and durationMs, as exported by @haneoka/cassiopeia. Supply exactly one document or source. An author document can use one language and arbitrary Unicode resource names.

const handle = mountChart(element, {
document: { chart: myChartDocument, audio: "musique/été.wav", bgmOffsetMs: 250 },
assetsBase: "https://my.example/project/",
theme: haneokaChartTheme(), // optional original presentation
locale: "fr-CA",
labels: { player: "Partition", pause: "Pause", loading: "Chargement" },
});
await handle.ready;

The audio element is the timing source. seek(seconds) uses media/presentation seconds; chart time is media time minus bgmOffsetMs. Positive offset creates pre-roll. No audio source means a paused visual preview: provide playable audio to advance the internal transport.

Use an existing OurNotesAssetManifest as document.assets to supply your own compatible theme resources, or a theme({loader, signal, skin}) factory. createOurNotesAssetManifest from the original plugin builds this manifest from its public media/resolver contract. URL fields, HUD image maps, sound strings/layers and animation clip maps are resolved by the mount; names and inline atlas metadata are preserved.

For HTTP JSON and local files use the existing sources from @haneoka/embed-core:

import { httpDataSource, fileDataSource, fileResourceResolver } from "@haneoka/embed-core";
const source = httpDataSource({
url: "https://my.example/project/chart.json",
decode: (value) => value, // validate/convert your envelope here
});
const fromHttp = mountChart(element, { source, theme: haneokaChartTheme() });
// source.assetsBase defaults to the JSON URL's directory.
const local = mountChart(otherElement, {
source: fileDataSource(jsonFile),
resolveResource: fileResourceResolver(new Map([["musique/été.wav", audioFile]])),
theme: myLocalTheme,
});

fileResourceResolver resolves literal keys; a default original theme also requests its public API/assets and needs a resolver that passes those absolute URLs through. Blob URLs created by the loader are revoked after the player is disposed. Consumers own URLs they create themselves.

This is an extension point for your own graphics. You supply drawMyChart; the complete watch HTML above uses the default renderer and needs no such function.

Pass rendererAdapter to load the kernel and Web host without loading the default Vue/Three/Our Notes visual composition. The adapter returns render, resize, dispose and optionally laneAtClientPoint. Resolve artwork through loader.resourceUrl or resourceBytes using the provided signal. Release owned graphics in dispose.

const handle = mountChart(element, {
document: { chart: myChartDocument, audio: "audio.ogg" },
assetsBase: "https://my.example/project/",
rendererAdapter: {
async create({ element, chart, loader, signal }) {
const canvas = document.createElement("canvas");
element.append(canvas);
const context = canvas.getContext("2d");
const artwork = await loader.resourceUrl("notes.svg", { signal });
// Load your artwork with the same signal and render your own chart geometry.
return {
render(snapshot, options) {
drawMyChart(context, chart, snapshot, options, artwork);
},
resize(width, height, ratio) {
canvas.width = width * ratio;
canvas.height = height * ratio;
},
dispose() {
canvas.remove();
},
};
},
},
});

The embed uses the existing MediaClock, MusicTimeAnchor and session update/reset methods. A renderer with laneAtClientPoint(x,y) can receive play-mode input through the existing host; return continuous native lanes 0..23, or -1 outside. Held judgement is advanced by the session, with real pointer movement forwarded once. createSession(chart, options) lets an authored renderer supply different kernel rules; choose that port for a source game whose rules differ. Without it the current Cassiopeia session rules apply. Custom renderers own their presentation and sound effects; noteSoundEnabled, noteSoundVolume and original skin selections belong to the default player.

mountChart() returns immediately. Await handle.ready for initial visual readiness. The initial audio cue preparation reports failures through events; the existing player retries failed cues when playback unlocks. Readiness does not imply a completed media download. Call play() from a user gesture after readiness to authorize audio. pause() and seek() are synchronous; play(), setOptions(), setSkin() and dispose() return promises.

await handle.setOptions({ rate: 0.8, volume: 0.6, noteSoundVolume: 0.4, settings: { noteSpeed: 9 } });
await handle.setSkin({ noteSkin: "skin003", noteEffectSkin: "effect001Simple", noteSeGroup: 4 });
// Changing the default theme rebuilds the player paused at its current media time.
handle.seek(12);
await handle.play();
await handle.dispose();

setSkin requires a default-player theme factory. Original skin IDs are skin001 (Our Notes), skin002 (Garupa), skin003 (Honeycomb); effect IDs are effect001 and effect001Simple. Note audio group IDs are 1–4 (Our Notes, Solid, Wood, Typing). The actual plugin’s localized naming and audio loop metadata remain authoritative.

setOptions passes mode, partial render settings, music volume/rate/loop and note audio enable/volume to the existing player. Mode changes use its existing reset behavior. Call cancel() to abort the mount and stop playback; this handle is terminal. Call dispose() to remove its DOM and release the loader. Disposal is idempotent and works during loading, including a factory that returns late. An external signal cancels the mount. loadTimeoutMs defaults to 30000 and applies to construction, not to the playable lifetime. Dispose before mounting again in the same container.

handle.snapshot exposes phase, playing, time and duration. Subscribe at construction to observe load progress/errors and current state:

const handle = mountChart(element, {
document: myDocument,
rendererAdapter: myRenderer,
onEvent(event) {
if (event.type === "load") showResourceProgress(event.event);
if (event.type === "state") updateTransport(event.snapshot);
if (event.type === "error") showError(event.error);
if (event.type === "player" && event.name === "judgement") showJudgement(event.args[0]);
},
});
// const unsubscribe = handle.subscribe(listener);

assetsBase is an absolute directory URL. Relative keys resolve under it; root-relative paths resolve on its origin; absolute URLs keep their origin. resolveResource(key, context) can asynchronously return a URL, string or Blob. The theme’s apiBase selects its API root and publicBase selects the public asset origin; these are separate from authored document resources. Returned paths use latest public resources and may change as the server updates.

fetcher(request), headers, credentials (default omit) and maxBytes come from embed-core. They apply to document/resource reads made through the loader. The default player downloads media/textures/SE with its own existing loaders and anonymous CORS; those requests do not use the injected fetcher or its private headers. Supply public CORS URLs or Blob resources for private media. An include credential policy only affects loader requests, whose server must explicitly allow credentials and the embedding origin. CSP must permit the script/module, asset and media origins plus owned blob: URLs where used.

Bundlers emit lazy chunks for the default player and its dependencies. For a plain <script type="module">, host the emitted ESM files and chunks together, or provide an import map for every bare dependency and exported subpath. Pin a compatible version of Vue/Three and all Cassiopeia packages in that map. Keep style.css linked separately. Importing the package itself does not create DOM, fetch data or initialize Three.

A small-screen page should reserve height, use touch-sized transport controls outside the stage, and reflect loading/error state with the page’s existing Material components. Renderer performance and media formats depend on the browser/device; validate the final consumer on the devices it targets.

The host displays the original Haneoka icon with a transparent background and55% SVG opacity, floating at the top-left of the full-size player. It follows the player through fullscreen/theme changes and is removed on dispose. Authored content keeps its own license.

Load an original eight-second chart first. Its renderer is an authored Canvas adapter driven by Cassiopeia’s actual session and MediaClock, with a generated quiet WAV Blob; it demonstrates integration, not the game’s visual skin. HTTP loads this chart JSON, and Local JSON loads your selected File. Select Haneoka data to use the actual song source and native theme. Edit options before loading, then use Play/Pause/Seek/setOptions; setSkin is available for the default theme.

1. Choose a chart

Start with the complete original file. Load it, then press Play to read the first line.

Open complete JSON · Download JSON

Preparing example…

Advanced parameters and complete input — reload to apply
Content language
Load log and error details
View this example's code

Only a click imports the SDK/creates the player. Another active example is disposed first. input, publicSource, nativeTheme and url are application fields that create the actual source/theme/renderer callbacks. JSON cannot encode a function; the executable adapter is in the linked example source. Five language controls retain an additional author-locale field.

import { mountChart, haneokaChartSource, haneokaChartTheme } from "https://haneoka.org/embed/r-22bfe102f7deb73f/cassiopeia.js";
const chart = mountChart(element, {
source: haneokaChartSource({ songId: "100070", difficulty: "expert" }),
theme: haneokaChartTheme(), server: "intl", locale: "en", mode: "watch",
});
await chart.ready;
await chart.play(); // call from a user's button
await chart.dispose();

The live example uses the stable official alias. The hosted facade supplies the package stylesheet on mount and releases it after the last instance is disposed. Package/bundler consumers import style.css separately. r22 has no nonce option; a later nonce API must be taken from that release’s actual receipt/interface. Generated CDN files are deployment build outputs, with the source maintained in the package repositories.

To author editable notes with local music, use the chart creator guide. Its Project JSON and portable audio backup are separate from the playback ChartDocument below.

Required positional container is a browser HTMLElement with ownerDocument/defaultView and stable nonzero size. Nonbrowser TypeError, repeated mount before disposal Error. Exactly one source/document is required at runtime (TypeError for both/neither). Default composition also requires theme or document.assets; authoredRenderer provides its own artwork.

FieldType/defaultRole, combinations and errors
documentChartEmbedDocument; required alternativeIn-memory envelope below. notes/lines must be arrays and durationMs finite, otherwise ready TypeError.
sourceDataSource<ChartEmbedDocument>; required alternativememory/http/file/custom source. Load/decode errors reject ready.
serveroptional string;undefined, Haneoka intlRegistry independent from locale.
localeoptional string;enAuthored/UI language; any author locale.
assetsBaseoptional absolute HTTP(S) directory;source baseResource directory override; no query/fragment, TypeError for invalid base.
resolveResourceoptional (key,DataContext)→string/URL/Blob/PromiseLiteral author keys, URL/Blob mapping; default key. Missing paths reject.
fetcheroptional (Request)→Promise<Response>;fetchCore data/native-resource bytes. Public Haneoka assets/runtime/embed requests use no-referrer; original author/API requests retain policy. Music uses native anonymous CORS.
credentialsomit/same-origin/include;omitLoader transport, service origin/credential permission required.
headersHeadersInit;noneCore headers; music requests do not inherit private headers.
maxBytespositive safe integer;67108864Per decoded response budget, invalid RangeError; URL-only/audio-element download not a loader read.
onListenerErroroptional (unknown)→voidObserver-error sink, isolated.
themeoptional (ChartThemeContext)→OurNotesAssetManifest/PromiseDefault visual/audio factory; wins over document.assets. Errors reject construct; needed for setSkin.
skinoptional ChartSkin;{}Four optional fields below. Authored renderer does not consume native skin.
rendererAdapteroptional ChartRendererAdapterSelects kernel/Web host with authored presentation instead of Vue/Three native visual composition. It owns artwork/SE.
createSessionoptional (chart,playbackOptions)→CassiopeiaSessionPortCustom rules for authored renderer. Otherwise ChartSession; this field is not forwarded to the default Vue driver.
signaloptional AbortSignalAborts/terminates the handle, including after ready.
loadTimeoutMspositive safe integer;30000Applies to document loading and each construction stage; not the player’s usable lifetime. Illegal value RangeError, deadline TimeoutError.
labelsoptional {player?,pause?,loading?}string labels, defaults Chart player/Pause/Loading; default Vue composition only.
onEventoptional (ChartEmbedEvent)→voidRegistered before loading.
modechart/watch/play;watchSession mode: chart preview/watch auto/play input. Changing mode follows driver reset/pause semantics.
settingsoptional Partial<RenderSettings> plus judgementOffsetMs;{}Full settings below. setOptions merges settings rather than replacing unspecified fields.
volumenumber;.8Music volume0–1. Initial native media rejects out-of-range values; updates clamp. Use finite values.
ratenumber;1Positive playback rate normalized to .25–4; invalid/nonpositive falls back.
loopboolean;falseMusic loop.
noteSoundEnabledboolean;trueDefault player’s note SE; authored renderer owns its own sound behavior.
noteSoundVolumenumber;.7Default note SE gain; use finite0–1.

Common resource/callback details are in core. No autoplay field: await readiness and call play from a user gesture. The construction timeout covers its stages rather than promising that ready includes a fully downloaded song.

skin keyAllowed/default in Haneoka themeMeaning
noteSkinskin001/skin002/skin003;skin001Our Notes/Garupa/Honeycomb.
noteEffectSkineffect001/effect001Simple;effect001Native effect selection.
noteSeGroup1/2/3/4;1Our Notes/Solid/Wood/Typing.
currentQuality0/1/2;2High/Middle/Low asset profile; independent from graphicsQuality setting.

Skin fields are typed unions, resolved by the selected theme. Invalid/missing underlying assets reject construction rather than creating a invented fallback skin. labels.player/labels.pause/labels.loading are all optional strings; none changes data locale.

haneokaChartSource({songId,difficulty,apiBase?}): songId and difficulty required; difficulty is easy/normal/hard/expert/special/master and must exist in the song DTO. apiBase defaults https://haneoka.org/api/v1/. Source validates song structure, reads its returned score file, parses/builds the standard chart, and uses returned musicUrl. Missing difficulty rejects Error; data/budget/parser errors retain their cause. server belongs to mount context.

haneokaChartTheme({apiBase?,publicBase?,server?}={}): API directory defaults https://haneoka.org/api/v1/; publicBase defaults that origin root; server override defaults loader.server then intl. URL fields are resolved separately from author’s assetsBase. It discovers actual source/output descriptors; missing/ambiguous required outputs reject Error.

ChartThemeContext has exactly loader:EmbedLoader<ChartEmbedDocument>, signal:AbortSignal, skin:ChartSkin. A custom theme can call loader.resourceBytes with that signal and return the public manifest. No extra context cache or renderer is injected.

Defaults below are the public Our Notes plugin’s RenderSettings defaults; the mount itself passes an empty partial object. A pinned older SDK retains its own plugin revision. Fields not explicitly validated by the mount are forwarded; use finite values and the documented render range, not a claim of per-field SDK validation.

KeyType/defaultRendering/normalization
noteSpeednumber/5Scroll speed; authored/native visual interpretation.
noteSpeedCurvenumber/1.30999994Note-display offset curve constant.
mirrorboolean/falseMirror original24-lane coordinates.
effectsboolean/trueNote effects.
noteSizenumber/1Clamped .1–2 in frame builder.
longAlphanumber/.6Long-line opacity, clamped .1–1.
guideAlphanumber/.6Guide opacity, clamped .1–1.
graphicsQualitynumber/1Quality preference; native0/1/2 high/middle/low.
guidelineCountnumber/6Lane subdivision display.
guidelineOpacitynumber/.25Guideline alpha, use0–1.
laneOpacitynumber/.8Lane alpha, use0–1.
showJudgementLineboolean/falseJudgement line.
showSimultaneousLineboolean/trueSimultaneous-note connector.
showEaseNoteboolean/falseEase/critical mark emphasis.
backgroundBrightnessnumber/.7Background brightness, use0–1.
judgeResultPositioncenter/lane/none;centerJudgement display placement.
noteOverlapLaneBuffernumber/0Judgement-lane overlap buffer.
showFastSlowboolean/trueTiming label.
showPerfectFastSlowboolean/trueInclude perfect timing label.
showJudgeOffsetMsboolean/falseMillisecond offset text.
alwaysShowFastSlowboolean/falsePersistent timing label preference.
scoreRankScoresoptional readonly number[];noneSource C/B/A/S/SS score thresholds.
judgementOffsetMsnumber/0Session timing offset in milliseconds; authored adapter updates it through setOffset.

Complete document envelope and renderer adapter

Section titled “Complete document envelope and renderer adapter”

ChartEmbedDocument.chart is required. audio?:string/background?:string are resource keys or URLs, default absent. bgmOffsetMs?:number defaults0; chart time equals media milliseconds minus offset. assets?:OurNotesAssetManifest supplies default artwork when theme is absent; rendererAdapter can omit it. No audio leaves a paused visual preview.

Standard chart root fields, all required: version:number; bpmChanges:BpmChange; signatureChanges:ChartSignatureChange; timeScaleChanges:{timeMs,scale}[]; notes:ChartNote; lines:ChartLine; timeline:{skills,fever,callChanges}; durationMs:number. BPM rows contain tick/beat/timeMs/bpm; signatures tick/beat/timeMs/numerator/denominator. Skill rows index/tick/timeMs; fever rows index/startTick/endTick/startTimeMs/endTimeMs; callChanges index/tick/timeMs/rhythms:number. Lines contain id,kind:long|guide,critical:boolean,noteIds:number.

Each note has id,tick,timeMs,beat,pos,size,laneX,width; operateType,judgementType,judgementAreaOffsetType,direction; critical,judged,visible:boolean; lineIds:number; slideAlong:boolean; indexInLine:number|null; easeL/easeR:ease-enum|null. Use the public converter/enums rather than guessing source-game note flags. The original authored example includes every required field.

The native asset manifest has id; source{game,noteSkin,laneSkin,noteEffectSkin,authoredProfile}; liveQuality{quality,effectRenderingScale,backgroundCameraFps,live2dFps}; noteAtlas; optional arrowGradient; lane; particles; slideLineStyle; hud; optional tmpSdfFont; noteSeGroup; noteSounds; palette; tiltThresholds[{distance,value}]. These are provider DTOs, not a second list of mount options. Use the published manifest schema for sprite/particle/audio descriptors and supply their declared resources through the loader.

rendererAdapter.create({element,chart,loader,signal}) returns {render,resize,dispose,laneAtClientPoint?} synchronously or by Promise. render(snapshot,options) consumes the reused SessionSnapshot during that call; do not hold it as an immutable future state. resize(width,height,pixelRatio) updates your graphics; dispose() releases them. laneAtClientPoint(clientX,clientY) maps to continuous0–23 lanes, or-1 outside. The authored adapter uses MediaClock/input/ChartSession rather than an invented timer. createSession(chart,options) can return an alternate public session port; observe its reset/update/input/finish contract.

MemberSignature/resultConstraints and lifecycle
readyPromise<void>Initial visual readiness; required SE failure can reject, media may still buffer.
snapshotreadonly {phase,playing,time,duration}phase:loading/ready/cancelled/error/disposed; media seconds.
play()=>Promise<void>Requires ready; browser audio rejection is returned.
pause()=>voidRequires ready, emits paused state.
seek(seconds:number)=>voidFinite nonnegative, otherwise RangeError; clamps to known duration, requires ready.
setOptions(ChartPlaybackOptions)=>Promise<void>Requires ready, merges partial settings. Volume/rate/loop/mode/SE are forwarded to the chosen driver.
setSkin(ChartSkin)=>Promise<void>Requires ready/default theme factory; authored renderer or no factory Error. Rebuilds at media position, paused.
subscribelistener=>unsubscribeFuture events, disposed Error.
cancel()=>voidTerminal loading or ready handle; stops player/input/resource calls.
disposeshared Promise<void>Idempotent, including loading; removes DOM/fullscreen hook, stops media/renderer, revokes owned URLs; cleanup failures AggregateError.

Events: load{event:EmbedEvent}, state{snapshot}, player{name,args:readonly unknown[]}, error{error}. Forwarded default names are playing,media-playing,timeupdate,duration,judgement,skill,fever,callchange; typed payloads are defined by the underlying player. timeupdate/duration use seconds, judgement includes note/judgement/timing/diffMs/judgedAtMs/combo/maxCombo/score/scoreDelta/life. Observer failures use onListenerError. Source/transport/theme/decoder failures keep their Error objects; the embed adds no new error-code envelope. Destroy before remounting the same container.

Current source uses a transparent attribution background,55% icon opacity and a48px corner touch target over the full viewport. Stable aliases adopt this behavior when the matching build is deployed. The immutable r22 player retains its earlier reserved-row layout; use its documented version semantics when explicitly pinning it.