Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
__pycache__/
*.pyc
node_modules/
63 changes: 38 additions & 25 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,53 @@
## Inheritance

feedBack's core plugin contract governs everything in this repo (manifest,
plugin context: `get_dlc_dir`, `get_sloppak_cache_dir`, asset serving, the
`slopsmithViz_*` visualization factory contract, splitscreen mounting). This
constitution lists Tab View's own non-negotiables.
the `slopsmithViz_*` visualization factory contract, splitscreen mounting).
This constitution lists Tab View's own non-negotiables.

## Core Principles

### I. alphaTab is the renderer; we're the bridge
Tab View MUST NOT render notation glyphs itself. alphaTab is the source of
truth for all musical glyphs, beam grouping, stems, and bar layout. Our job
is to translate arrangement XML → Guitar Pro 5 (`rs2gp.py`) and to drive
alphaTab's cursor (`tickPosition`) from `audio.currentTime` using beat
timing data the highway already exposes.
is to translate the renderer bundle (notes/chords/tuning/stringCount/beats)
into an alphaTab `Score` (`src/chart-quantize.js` + `src/score-builder.js`)
and to drive our own boundsLookup-driven marker from `window.highway.getTime()`
(single-player) or `bundle.currentTime` (splitscreen), using beat timing data
the highway exposes.

### II. Multi-instance by construction (slopsmith#36)
Per-instance state lives in factory closures returned from `createFactory()`.
Module-level scope is reserved for genuine singletons:
- The CDN script load promise (one `<script>` per page).
- `_tvFilename` captured from `window.playSong` and `arrangement:changed`
(one global player → one filename, even when multiple panels render
different arrangements of the same song).
- `_nextInstanceId` for unique DOM ids.

### III. Pin the alphaTab CDN version
`ALPHATAB_VERSION = '1.8.2'` MUST be an explicit constant. New jsDelivr
cache invalidations or upstream breaking changes cannot land silently in
`ALPHATAB_VERSION = '1.8.2'` MUST be an explicit constant, kept in sync with
package.json's `@coderline/alphatab` devDependency (used to test
score-builder.js against the real model classes). New jsDelivr cache
invalidations or upstream breaking changes cannot land silently in
production. Bumps require local QA against cursor-sync and tab-highlight
behaviour.

### IV. Path-traversal guard on the GP5 endpoint
`GET /api/plugins/tabview/gp5/{filename:path}` MUST resolve `filename`
under the configured DLC dir and reject anything that escapes (`..`, absolute
paths). The endpoint is publicly mounted; the guard is the single defence.
### IV. One AlphaTabApi instance per activation
Tab View MUST NOT destroy and recreate its `AlphaTabApi` instance on every
chart rebuild — `renderScore()` on a live instance is alphaTab's own
documented way to switch content; recreating redoes font/layout setup and
DOM teardown for no reason. The instance is destroyed only in
`_teardown()`. Each render registers its own `scoreLoaded`/
`renderFinished`/`error` closures; the previous render's listeners MUST be
unregistered first via the unregister functions `.on()` returns, or they
accumulate for the life of the instance.

### V. Sloppak path is loaded lazily
Older feedBack cores ship without `lib/sloppak.py`. A top-level
`import sloppak` here would disable Tab View entirely on those installs
(including for archive songs). The sloppak branch MUST `import sloppak`
inside the function and surface a `501 Not Implemented` when missing.
### V. The tab is built from the bundle, not a converted file
Tab View MUST NOT fetch or convert a separate file server-side.
`buildScoreFromBundle` (`src/score-builder.js`) builds the alphaTab `Score`
directly from `bundle.notes`/`.chords`/`.tuning`/`.stringCount`/`.beats` —
the same bundle passed to any custom renderer's `init`/`draw`. Since
`highway.js` stages any registered chart-transform provider's output into
that bundle first, this is also the only path by which a transform reaches
the tab. This also drops GP5's hard 7-string cap — alphaTab's own model
has no string-count ceiling.

### VI. Visualization is opt-in (`matchesArrangement` deliberately absent)
Tab View does not advertise itself as the auto-select renderer for any
Expand All @@ -50,9 +59,13 @@ review.

## Governance

Amendments touching the GP5 conversion (`rs2gp.py`) must keep a back-compat
fall-through for older arrangement XML formats. Amendments touching the
factory contract must align with whatever the latest core
`slopsmithViz_*` interface requires.
Amendments touching score construction (`src/score-builder.js`,
`src/chart-quantize.js`) must keep `test/score-builder.test.mjs` and
`test/chart-quantize.test.mjs` passing — the former against the real
`@coderline/alphatab` package, the only verification available without a
live browser. New alphaTab-independent chart math belongs in
`chart-quantize.js`, keeping most conversion logic testable without
alphaTab. Amendments touching the factory contract must align with the
latest core `slopsmithViz_*` interface.

**Version**: 3.0.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-05-09
**Version**: 4.0.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-07-22
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ A [feedBack](https://github.com/got-feedback/feedback) plugin that renders custo

## Features

- Converts arrangement XML to Guitar Pro 5 format on the fly
- Builds an alphaTab score directly from the renderer bundle (notes, chords, tuning, string count, beats) — no server round trip
- Renders scrolling tablature notation via alphaTab in the browser
- Cursor syncs to the existing audio playback
- Supports guitar and bass arrangements
- Preserves techniques: bends, slides, hammer-ons, pull-offs, harmonics, palm mutes, tremolo picking
- Handles custom tunings and capo
- Supports guitar and bass arrangements, including extended-range instruments (no string-count ceiling)
- Preserves techniques: bends, slides, hammer-ons, pull-offs, harmonics, palm mutes, tremolo picking, accents
- Per-measure tempo changes
- Reflects any active chart-transform provider — since the tab is built from the same bundle every other renderer sees, a provider's remapped notes/strings/tuning show up automatically

## Installation

Expand All @@ -33,20 +33,24 @@ Restart feedBack. The plugin loads automatically.

## Dependencies

- **Server**: `pyguitarpro` (already included in feedBack's requirements)
- **Client**: alphaTab is loaded from CDN on first use
- **Dev/test**: `@coderline/alphatab` (npm, pinned to the same version as the CDN load) — used only to test `src/score-builder.js` against the real alphaTab model classes in Node; never shipped to the browser

## How it works

1. **routes.py** exposes `GET /api/plugins/tabview/gp5/{filename}?arrangement=N`
2. **rs2gp.py** converts the arrangement (notes, chords, beats, tuning, techniques) into a Guitar Pro 5 file using `pyguitarpro`
3. **screen.js** loads alphaTab from CDN, fetches the GP5 file, renders it, and syncs the cursor to `audio.currentTime` using the beat timing data from the highway
1. **screen.js** gathers the current chart bundle (notes/chords/tuning/stringCount/beats/songInfo — the same bundle any custom renderer's `init`/`draw` receives) and passes it to `buildScoreFromBundle`.
2. **src/chart-quantize.js** does the alphaTab-independent chart math: groups beats into measures, quantizes note onsets to a 32nd-note grid, decomposes gaps into duration/dots pairs, and builds the tuning table.
3. **src/score-builder.js** builds an `alphaTab.model.Score` from that: constructs the `Track`/`Staff`/`Bar`/`Voice`/`Beat`/`Note` graph and maps techniques (hammer-on/pull-off, slides, bends, harmonics, palm mute, tremolo, accents) onto alphaTab `Note`/`Beat` fields.
4. **screen.js** hands the built `Score` to `alphaTabApi.renderScore()` (reusing one `AlphaTabApi` instance across rebuilds) and positions its own boundsLookup-driven marker from `window.highway.getTime()` (single-player) or `bundle.currentTime` (splitscreen).

The tab rebuilds whenever `bundle.notes`' identity changes — on a song/arrangement switch, a mastery-slider move, or a chart-transform provider rerunning (a provider always restages a fresh notes array), so the tab always reflects the same effective chart the highway itself is drawing.

## Files

| File | Purpose |
|------|---------|
| `plugin.json` | Plugin manifest |
| `routes.py` | FastAPI endpoint serving GP5 files |
| `rs2gp.py` | arrangement → Guitar Pro 5 converter |
| `screen.js` | Frontend: alphaTab integration, cursor sync, UI |
| `src/chart-quantize.js` | Pure chart math: measures, quantization, tuning table (no alphaTab dependency) |
| `src/score-builder.js` | bundle → alphaTab `Score` builder (techniques, track/staff/bar assembly) |
| `test/` | Node tests for `src/chart-quantize.js` and `src/score-builder.js` (`npm test`) |
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"private": true,
"type": "module",
"devDependencies": {
"@coderline/alphatab": "1.8.2"
},
"scripts": {
"test": "node --test test/*.test.mjs"
}
}
8 changes: 4 additions & 4 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"id": "tabview",
"name": "Tab View",
"version": "3.0.1",
"id": "tabview_dev",
"name": "Tab View (dev)",
"version": "4.0.0",
"type": "visualization",
"private": false,
"script": "screen.js",
"routes": "routes.py",
"scriptType": "module",
Comment on lines +4 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Any leftover Python / server-route artifacts from the removed GP5 route?
fd -e py -e pyi . || true
rg -n --hidden -g '!node_modules' -e 'routes' -e 'pyguitarpro' -e 'guitarpro' -e 'gp5' -i . || true
# Confirm the manifest keys the host is expected to read
fd -t f 'plugin.json' --exec cat {}

Repository: got-feedBack/feedBack-plugin-tabview

Length of output: 1346


🌐 Web query:

Grafana plugin manifest scriptType module scriptType classic visualization plugin manifest documentation

💡 Result:

In Grafana plugin development, the terms "module" and "classic" do not refer to a specific scriptType field in the plugin.json manifest. Instead, they relate to how plugins are loaded and structured [1][2]. The plugin.json file is the required metadata manifest for all Grafana plugins [3][4]. While it does not contain a scriptType field, it does define a loadingStrategy (found in internal plugin metadata) that dictates how frontend assets are loaded [5][1]. Key Concepts: 1. Loading Strategies: Grafana defines a PluginLoadingStrategy which can be set to either fetch or script [1]. This strategy signals to the underlying system (e.g., SystemJS) how to load the frontend JavaScript assets [1]. 2. Module vs. Classic (Context): - The term "module" typically refers to the modern standard for Grafana plugins, where module.ts (compiled to module.js) serves as the entry point [4][2]. This file exports the plugin implementation (such as a class extending PanelPlugin) [4][6]. - "Classic" often refers to older versions or legacy plugin architectures (e.g., those using PanelCtrl or older AngularJS-based approaches) [7]. Modern development relies on the current plugin SDK and standard ESM/module-based loading [4][2]. 3. Plugin Manifest (plugin.json): - This file is mandatory for all plugins [3]. It contains essential metadata such as the plugin id, name, and type (e.g., panel, datasource, app) [3][4]. It does not configure the loading script type explicitly via a scriptType manifest key [3][8]. For current development, you should follow the official Grafana Plugin Tools documentation, which focuses on modern React-based plugins using the standard module.ts entry point [4][2]. There is no requirement to configure a "script type" in the manifest, as the build process handles the necessary module format [4].

Citations:


🌐 Web query:

site:grafana.com developers plugin-tools reference plugin.json scriptType

💡 Result:

Based on the official Grafana Plugin Tools documentation, there is no field named scriptType in the plugin.json file [1][2][3]. The plugin.json file is a mandatory metadata file used to define plugin configuration, such as its id, type (e.g., "app", "datasource", "panel", "renderer"), name, and various dependencies or includes [1][2][3]. If you are looking for information on how to configure build scripts or tooling configurations, those are typically managed within package.json (for npm scripts) or via configuration files in the project root, such as webpack.config.ts or files within the.config/ directory [4][5]. If you are encountering a reference to scriptType, it may be from an outdated version of the Grafana plugin framework, a third-party tool, or a misunderstanding of a different configuration property. For current development, please refer to the official plugin.json reference guide provided by Grafana [1].

Citations:


🌐 Web query:

grafana visualization plugin script scriptType classic module plugin.json

💡 Result:

In Grafana plugin development, the plugin.json file serves as the mandatory metadata configuration for all plugins [1][2][3]. While the specific terms "classic" or "module" do not define distinct plugin types in the current Grafana plugin schema, they reflect the evolution of how plugins are structured, loaded, and built [2][4][5]. Plugin Structure and Loading The module field in plugin.json specifies the entry point for the plugin's frontend code, typically pointing to module.js (compiled from src/module.ts) [2][4][5]. Modern Grafana plugins use a build process that outputs this module file, which Grafana loads to initialize the plugin [2][6]. Evolution of Plugin Loading Grafana internally tracks how plugins are loaded via a loadingStrategy [4]. This strategy dictates how frontend assets are handled: 1. module: In modern development, plugins are built as modules that Grafana loads as a bundle [2][5]. 2. script: Historically, or in specific integration scenarios, plugins might have relied on different loading mechanisms, sometimes referred to as "classic" (often associated with older Angular-based plugins or manual script inclusion) [4][7]. Current Standards The term "classic" is generally deprecated in modern Grafana plugin documentation, which now emphasizes using the create-plugin tool to scaffold plugins [6][3]. This tool generates a standard configuration that expects a modern module-based structure [5][6]. If you are working with an older plugin, you may encounter references to "classic" loading styles, but current official documentation focuses on the standard module-based approach where the plugin.json defines the entry point and the build system handles the module creation [2][5]. For definitive schema information, refer to the official plugin.json schema, which defines the expected structure for current plugin development [1][2].

Citations:


🌐 Web query:

"scriptType: \"module\"" "plugin.json" "script" visualization grafana

💡 Result:

In the context of Grafana plugin development, there is no standardized or required scriptType: "module" field within the plugin.json file [1][2][3]. The plugin.json file is primarily used to store essential plugin metadata such as its unique id, type, name, version, and dependencies [1][4][5]. The frontend entry point for a Grafana plugin is defined by the module file, typically named module.ts (or module.js after compilation) [6][4][7]. Grafana automatically discovers and loads this file when the plugin is initialized [4][7]. While some internal Grafana structures, such as the PluginMeta interface found in @grafana/data, contain a loadingStrategy field with options like PluginLoadingStrategy.fetch or PluginLoadingStrategy.script [8], this configuration is generally handled by the Grafana plugin framework and build tools (such as @grafana/toolkit or the newer create-plugin tool) rather than manual entry in the plugin.json file [5][9]. If you are encountering a scriptType: "module" reference, it may be related to custom build configurations, specific SystemJS setups, or an experimental feature not included in the core plugin metadata schema [1][2]. For standard plugin development, follow the official Grafana Plugin Tools documentation to ensure your plugin.json and module.ts files are correctly structured [1][7][10].

Citations:


No orphaned server route artifacts remain.

The manifest’s added scriptType does not address the host-loader contract for screen.js; if the host ignores it, the static import will fail when injected as a classic script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin.json` around lines 4 - 8, Update the plugin manifest and screen.js
host-loading integration so screen.js is loaded according to the host’s
classic-script contract rather than relying on scriptType: "module". Remove or
avoid the static import in screen.js, and use a compatible loading pattern that
works when the host ignores the manifest’s module setting.

"category": "practice",
"description": "Guitar Pro-style tablature view.",
"icon": "assets/thumb.svg"
Expand Down
4 changes: 0 additions & 4 deletions requirements.txt

This file was deleted.

93 changes: 0 additions & 93 deletions routes.py

This file was deleted.

Loading