Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
but non-responsive is still the default.
* Update for changes in <https://www.capeirish.com/ittl/tunefolders/> ({pull}`104`).
* Add Paul Hardy to sources ({mod}`pyabc2.sources.hardy`; {pull}`105`).
* Update abcjs version from 6.4.4 to 6.6.2 (late Feb 2026; {pull}`106`).
Integrate the new chord grid option into the widget.

## v0.1.2 (2026-02-03)

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
html_js_files = [
# TODO: just the pages that need it (example notebooks) instead of every page, using `app.add_js_file`
(
"https://cdn.jsdelivr.net/npm/abcjs@6.4.4/dist/abcjs-basic-min.js",
"https://cdn.jsdelivr.net/npm/abcjs@6.6.2/dist/abcjs-basic-min.js",
{
"crossorigin": "anonymous",
# We need it to load before the widget instances
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/widget.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@
"metadata": {},
"outputs": [],
"source": [
"abc = \"K: C\\nT: The best scale\\n\" + \"CDEF GABc | \" * 4\n",
"abc = \"K: C\\nM: 4/4\\nT: The best scale\\n\" + '\"C\"' + \"CDEF GABc | \" * 4\n",
"\n",
"w = interactive(abc, foreground=\"#808080\", staff_width=600)\n",
"w"
Expand Down
4 changes: 2 additions & 2 deletions pyabc2/abcjs/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"abcjs-render": "./cli.cjs"
},
"dependencies": {
"abcjs": "6.4.4",
"jsdom": "^26.0.0"
"abcjs": "6.6.2",
"jsdom": "^29.0.0"
},
"author": "zmoon",
"license": "ISC"
Expand Down
2 changes: 1 addition & 1 deletion pyabc2/abcjs/htmlgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<head>
<meta charset="utf-8" />
<title>{title:s}</title>
<script src="https://cdn.jsdelivr.net/npm/abcjs@6.4.4/dist/abcjs-basic-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/abcjs@6.6.2/dist/abcjs-basic-min.js"></script>
<style>
div#notation {{
white-space: pre-wrap;
Expand Down
43 changes: 43 additions & 0 deletions pyabc2/abcjs/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ class ABCJSWidget(anywidget.AnyWidget):
0,
help="Visual transpose in half steps.",
).tag(sync=True)
chord_grid = traitlets.Unicode(
None,
allow_none=True,
help=(
"Chord grid. "
"``'withMusic'`` to print above the staff, or "
"``'noMusic'`` to print without staff. "
"``None`` to disable (default). "
"Somewhat experimental, currently only works in certain situations."
),
).tag(sync=True)

_CHORD_GRID_OPTIONS = ("withMusic", "noMusic")

@traitlets.validate("chord_grid")
def _validate_chord_grid(self, proposal):
v = proposal["value"]
if v is not None and v not in self._CHORD_GRID_OPTIONS:
raise traitlets.TraitError(
f"chord_grid must be one of {self._CHORD_GRID_OPTIONS} or None, got {v!r}"
)
return v


def interactive(abc: str = "", **kwargs) -> "ipywidgets.Widget": # pragma: no cover
Expand Down Expand Up @@ -149,6 +171,18 @@ def interactive(abc: str = "", **kwargs) -> "ipywidgets.Widget": # pragma: no c
description="Transpose (half steps)",
**slider_kws,
)
chord_grid_radio = ipw.RadioButtons(
options=["off", "with music", "no music"],
value="off",
description="Chord grid",
orientation="horizontal",
)
chord_grid_radio_to_widget = {
"off": None,
"with music": "withMusic",
"no music": "noMusic",
}
chord_grid_widget_to_radio = {v: k for k, v in chord_grid_radio_to_widget.items()}
foreground_picker = ipw.ColorPicker(
concise=False,
description="Foreground color",
Expand All @@ -169,6 +203,14 @@ def interactive(abc: str = "", **kwargs) -> "ipywidgets.Widget": # pragma: no c
ipw.link((w, "line_thickness_increase"), (line_thickness_slider, "value"))
ipw.link((w, "transpose"), (transpose_slider, "value"))
ipw.link((w, "foreground"), (foreground_picker, "value"))
ipw.link(
(w, "chord_grid"),
(chord_grid_radio, "value"),
transform=(
chord_grid_widget_to_radio.__getitem__, # widget -> radio
chord_grid_radio_to_widget.__getitem__, # radio -> widget
),
)
# ipw.link((w, "logo"), (logo_cbox, "value"))
ipw.link((w, "debug_box"), (debug_box_cbox, "value"))
ipw.link((w, "debug_grid"), (debug_grid_cbox, "value"))
Expand Down Expand Up @@ -214,6 +256,7 @@ def save(_):
line_thickness_slider,
transpose_slider,
foreground_picker,
chord_grid_radio,
# logo_cbox,
w,
ipw.HBox(
Expand Down
4 changes: 3 additions & 1 deletion pyabc2/abcjs/widget/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ABCJS_URL = 'https://cdn.jsdelivr.net/npm/abcjs@6.4.4/dist/abcjs-basic-min.js';
const ABCJS_URL = 'https://cdn.jsdelivr.net/npm/abcjs@6.6.2/dist/abcjs-basic-min.js';
const ABCJS_LOGO_URL = 'https://raw.githubusercontent.com/paulrosen/abcjs/' +
'refs/heads/main/docs/' +
'.vuepress/public/img/abcjs_comp_extended_08.svg';
Expand Down Expand Up @@ -55,6 +55,7 @@ function render({ model, el }) {
let staffwidth = () => model.get('staff_width');
let doResize = () => model.get('responsive');
let visualTranspose = () => model.get('transpose');
let chordGrid = () => model.get('chord_grid');

let active_music_ids = model.get("_active_music_ids");
let first_load = model.get("_first_load");
Expand Down Expand Up @@ -146,6 +147,7 @@ function render({ model, el }) {
showDebug: showDebug,
staffwidth: staffwidth(),
visualTranspose: visualTranspose(),
chordGrid: chordGrid(),
},
);
if (tunes.length === 0) {
Expand Down
18 changes: 18 additions & 0 deletions tests/test_abcjs_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
import traitlets

from pyabc2.abcjs.widget import ABCJSWidget


def test_chord_grid_validation():
with pytest.raises(traitlets.TraitError, match="chord_grid must be one of"):
_ = ABCJSWidget(chord_grid="asdf")

w = ABCJSWidget()
assert w.chord_grid is None
with pytest.raises(traitlets.TraitError, match="chord_grid must be one of"):
w.chord_grid = "asdf"
w.chord_grid = "withMusic"
assert w.chord_grid == "withMusic"
w.chord_grid = None
assert w.chord_grid is None