Is there an easier way to transpose than this?
benlieb opened this issue · comments
First of all thanks for the amazing work on this!
I wanted to have an interface where users can transpose. This is in a React context fwiw. You can see this live [here](https://music.benlieb.dev/abc/show?abc=X%3A%201%0AT%3A%20The%20New%20Land%0AR%3A%20waltz%0AM%3A%203%2F4%0AL%3A%201%2F8%0AK%3A%20Fmaj%0ACDE%7C%3AF4FG%7CAc3d2%7Cc3AG2%7CF3G(3AGF%7CB3AGA%7CBd3f2%7C%0Ad3BG2%7CG3G(3AGF%7CA3FAF%7CA2c2f2%7Ca3gf2%7Cd3efg%7C%0Aa3gf2%7Cge3c2%7Cd4de%7C1%20d3cAG%3A%7C2%20d3efg%7C%7C%0A%7C%3Aa3gab%7Ca2g2f2%7CB4Bc%7CB2d2f2%7Cg3fga%7Cg2f2e2%7CA4AB%7CA2c2e2%7C%0Af3efg%7Cf2e2d2%7CG4GA%7CG2F2D2%7CcA3F2%7CGE3C2%7CD4DE%7CD6%3A%7C%0A).
It took me quite a bit of fiddling with this to get it to work. The main issues that I needed were to save the original visualObj
from the first render, in order to make transpositions work. My understanding of that is from these line from the transposition demo:
var output = ABCJS.strTranspose(abc, visualObj, steps)
outputEl.innerText = output
var newVisualObj = ABCJS.renderAbc("paper2", output, renderParams)
To be honest I'm not sure why the visualObj
is needed in the ABCJS.strTranspose
call, but it seems to be. Perhaps there's a more direct way?
Here's my full components:
import React, { useEffect, useRef, useState } from 'react'
import abcjs from 'abcjs';
import AbcAudioPlayer from './AbcAudioPlayer';
export default function ShowAbcApp(props) {
const queryParameters = new URLSearchParams(window.location.search)
const paramsAbc = queryParameters.get('abc')
const defaultInput = paramsAbc || "X:1\nK:D\nD4|\n"
const [abcInput, setAbcInput] = useState(defaultInput)
const [transpose, setTranspose] = useState(0)
const [origVisualObj, setOrigVisualObj] = useState(null)
const [visualObj, setVisualObj] = useState(null)
const renderEl = useRef(null)
const getLink = () => {
return `?abc=${encodeURIComponent(abcInput)}`
}
useEffect(() => {
if ( !visualObj ) {
// initial render
const visualObj = abcjs.renderAbc( renderEl.current, abcInput)
setOrigVisualObj(visualObj)
setVisualObj(visualObj)
} else {
var transposedAbcString = abcjs.strTranspose(abcInput, origVisualObj, transpose)
var transposedVisualObj = abcjs.renderAbc(renderEl.current, transposedAbcString)
setVisualObj(transposedVisualObj)
}
}, [renderEl.current, abcInput, transpose])
return (
<>
<textarea
rows={10}
cols={80}
value={abcInput}
onChange={(e) => setAbcInput(e.target.value)} />
<br/>
<input type="number" value={transpose} onChange={(e) => setTranspose(e.target.value)} />
<a href={getLink()}>Link to this notation</a>
<br/>
<br/>
<div id="show-abc-app">
<AbcAudioPlayer visualObj={visualObj} />
<div className="rendered-abc-container" ref={renderEl} />
</div>
</>
)
}
import React, { useEffect, useRef, useState } from 'react'
import abcjs, { synth } from 'abcjs';
export default function AbcAudioPlayer({ visualObj, midiTranspose } ) {
const renderEl = useRef(null)
const initSyntControl = () => {
const synthControl = new abcjs.synth.SynthController();
synthControl.load(
renderEl.current,
{}, //cursorControl
{
displayLoop: true,
displayRestart: true,
displayPlay: true,
displayProgress: true,
displayWarp: true
}
);
return synthControl;
}
const initAudio = () => {
var myContext = new AudioContext();
const synthControl = initSyntControl();
const synth = new abcjs.synth.CreateSynth();
synth.init({
audioContext: myContext,
millisecondsPerMeasure: 500,
visualObj: visualObj[0],
options: {
midiTranspose: 2,
}
}).then((results) => {
synthControl.setTune(visualObj[0], false, {})
}).catch(function (reason) {
console.log(reason)
});
}
useEffect(() => {
if (!visualObj || !renderEl.current) return
initAudio()
}, [renderEl.current, visualObj, midiTranspose])
return (
<>
<div className="audio-player-container" ref={renderEl} />
</>
)
}
The reason the visualObj is needed is that it is the same processing that is needed for transposing. I could do it inside strTranspose but for common uses it is likely that it is already on the page. Note that you can use ABCJS.renderAbc("*", ...) to create that object if needed without displaying it.