essi
essi5mo ago

Play MIDI files through `mo.audio()`

Hi, I'd like to try out marimo to compose music, but I can't play midi files. For instance,
mo.audio(src="https://bitmidi.com/uploads/79828.mid")
mo.audio(src="https://bitmidi.com/uploads/79828.mid")
... will only display a music player with 0:00 / 0:00.
Solution:
Wow, magic!!! Thanks++! Edit: the code with mo.state. ```...
Jump to solution
9 Replies
Myles Scolnick
Myles Scolnick4mo ago
I can look into this today
Myles Scolnick
Myles Scolnick4mo ago
It looks like .mid is not an audio file (it can contain many audio files). You will likely need to bring in another library for this. quick search led me to https://pypi.org/project/midi2audio/
PyPI
midi2audio
Easy to use MIDI to audio or playback via FluidSynth
essi
essiOP4mo ago
Hi, MIDI format indeed contains musical instructions rather than sound, requiring a synthesizer or soundfont for playback. In Jupyter, music21 (a Python library for music representation) can display scores with my_stream.show(), using an installed software like MuseScore, or play MIDI via my_stream.show('midi'). I developed Djalgo, a Python package for music composition using Jupyter and music21. As local installation is complex, I’m considering using Pyodide and Marimo on marimo.app. However, I need to solve display issues for both scores and MIDI. I’m considering developing a JavaScript MIDI player and integrating score rendering via e.g. ABCjs to overcome these challenges. However, I couldn't find a way to run JavaScript in Marimo cells, e.g.
python
import marimo as mo

def create_abc_score(abc_notation):
return mo.Html(f"""
<div id="notation"></div>
<script src="https://paulrosen.github.io/abcjs/dist/abcjs-basic-min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {{
var abcNotation = `{abc_notation}`;
abcjs.renderAbc("notation", abcNotation);
}});
</script>
""")

_abc = """
X:1
T:Simple Scale
M:4/4
L:1/4
K:C
C D E F|G A B c|
"""

_score = create_abc_score(_abc)
_score
python
import marimo as mo

def create_abc_score(abc_notation):
return mo.Html(f"""
<div id="notation"></div>
<script src="https://paulrosen.github.io/abcjs/dist/abcjs-basic-min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {{
var abcNotation = `{abc_notation}`;
abcjs.renderAbc("notation", abcNotation);
}});
</script>
""")

_abc = """
X:1
T:Simple Scale
M:4/4
L:1/4
K:C
C D E F|G A B c|
"""

_score = create_abc_score(_abc)
_score
Myles Scolnick
Myles Scolnick4mo ago
if you need to run javascript, we recommend using Anywidget which is a plugin framework that we support: https://anywidget.dev/en/notebooks/counter/
anywidget
Build a Counter Widget | anywidget
Jupyter notebook with example
essi
essiOP4mo ago
Hi @Myles Scolnick, I played a bit with ABCjs, anywidget and Marimo, but without success. It's working in Jupyter, though. Have I done anything wrong?
# cell 1
import anywidget
import traitlets
import marimo as mo

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode("X:1\nT:Scale\nM:4/4\nL:1/4\nK:C\nCDEF|GABC'|").tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
"""

widget = mo.ui.anywidget(ABCjsWidget())
# widget = ABCjsWidget() # jupyter

# cell 2
widget.abc = """
X:1
T:Ode to Joy
M:4/4
L:1/4
K:C
EEFG|GFED|CCDE|E2D2|
"""

# cell 3
widget
# cell 1
import anywidget
import traitlets
import marimo as mo

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode("X:1\nT:Scale\nM:4/4\nL:1/4\nK:C\nCDEF|GABC'|").tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
"""

widget = mo.ui.anywidget(ABCjsWidget())
# widget = ABCjsWidget() # jupyter

# cell 2
widget.abc = """
X:1
T:Ode to Joy
M:4/4
L:1/4
K:C
EEFG|GFED|CCDE|E2D2|
"""

# cell 3
widget
`
Myles Scolnick
Myles Scolnick4mo ago
since you are technically mutating state- we may not be re-rendering this (which is a marimo requirement, no mutating state) Given this is an any widget, we can possibly handle this like we do with mo.state
essi
essiOP4mo ago
Thanks for the tip! But even without state mutation, nothing is displayed.
import marimo as mo
import anywidget
import traitlets

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode("""
T:C-major scale
X:1
M:4/4
L:1/4
K:C
CDEF|GABC
""").tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
"""

mo.ui.anywidget(ABCjsWidget())
import marimo as mo
import anywidget
import traitlets

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode("""
T:C-major scale
X:1
M:4/4
L:1/4
K:C
CDEF|GABC
""").tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
"""

mo.ui.anywidget(ABCjsWidget())
Maybe that's an issue displaying the svg outcome?
Myles Scolnick
Myles Scolnick4mo ago
You need to add export default { render }; at the bottom
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
export default { render };
"""
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
export default { render };
"""
this might actually be fixed in the latest marimo as well, actually export default { render }; seems required
Solution
essi
essi4mo ago
Wow, magic!!! Thanks++! Edit: the code with mo.state.
import marimo as mo
import anywidget
import traitlets

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode().tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
export default { render };
"""

abc_state = mo.state("T:C-major scale\nX:1\nM:4/4\nL:1/4\nK:C\nCDEF|GABC")
widget = mo.ui.anywidget(ABCjsWidget(abc=abc_state[0]._value))
widget
import marimo as mo
import anywidget
import traitlets

class ABCjsWidget(anywidget.AnyWidget):
abc = traitlets.Unicode().tag(sync=True)
_esm = """
import * as abcjs from "https://cdn.jsdelivr.net/npm/abcjs@6.2.2/+esm";
export function render({ model, el }) {
function updateABC() {
el.innerHTML = '';
abcjs.renderAbc(el, model.get('abc'));
}
model.on('change:abc', updateABC);
updateABC();
}
export default { render };
"""

abc_state = mo.state("T:C-major scale\nX:1\nM:4/4\nL:1/4\nK:C\nCDEF|GABC")
widget = mo.ui.anywidget(ABCjsWidget(abc=abc_state[0]._value))
widget