Skip to content

Instantly share code, notes, and snippets.

@HelloWorld017
Last active August 18, 2025 16:36
Show Gist options
  • Select an option

  • Save HelloWorld017/f93758d534b3115115189fe1a924374e to your computer and use it in GitHub Desktop.

Select an option

Save HelloWorld017/f93758d534b3115115189fe1a924374e to your computer and use it in GitHub Desktop.
Convert your Songsterr GuitarPro (*.gp) files suitable for Musescore Editing.
<!DOCTYPE html>
<html>
<head>
<title>Drumsterr</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,[email protected],100..900&display=swap" rel="stylesheet">
<style>
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
all: unset;
display: revert;
}
*, *::before, *::after {
box-sizing: border-box;
}
:root {
font-size: 12px;
}
a, button {
cursor: revert;
}
img {
max-inline-size: 100%;
max-block-size: 100%;
}
</style>
<style>
:root {
background: #202020;
}
#app {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100svh;
max-width: 70rem;
margin: 0 auto;
color: #ffffff;
padding: 4rem;
font-family: Inter, system-ui, sans-serif;
letter-spacing: -0.01em;
}
h1 {
font-size: 2.4rem;
font-weight: 700;
margin-bottom: 1.6rem;
letter-spacing: -0.02em;
}
h3 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 1.6rem;
letter-spacing: -0.02em;
}
section {
margin-bottom: 3.6rem;
}
blockquote {
border-left: 3px solid #707070;
padding-left: 2rem;
& p {
font-size: 1.5rem;
line-height: 2rem;
}
}
p {
color: #707070;
font-size: 1.4rem;
line-height: 1.8rem;
}
ol {
margin-left: 2rem;
list-style-type: decimal;
}
li {
font-size: 1.4rem;
line-height: 1.8rem;
margin: 0.4rem 0;
}
code {
background: #20243a;
color: #0080ff;
padding: 0.4rem 0.8rem;
border-radius: 0.8rem;
}
.Dropzone {
border: 1px solid #404040;
border-radius: 1.5rem;
padding: 6rem 3rem;
text-align: center;
transition: all .4s ease;
& i {
display: inline-flex;
font-size: 2.4rem;
padding: 1rem;
margin-bottom: 3rem;
border: 1px solid #404040;
border-radius: 1rem;
}
}
.Dropzone--active {
border-color: #0080ff;
background: #292929;
}
</style>
</head>
<body>
<div id="app">
<h1>Drumsterr</h1>
<section>
<blockquote>
<p>Convert your Songsterr GuitarPro (*.gp) files suitable for Musescore Editing.</p>
</blockquote>
</section>
<h3>Instructions</h3>
<section>
<ol>
<li>Export your score in Songsterr</li>
<li>Drag it to this page</li>
<li>Download converted file and open it in Musescore</li>
<li>Go to layout tab in your sidebar, click the config icon, and click <code>Replace instrument</code></li>
<li>Find <code>Drum kit (common)</code>.</li>
<li>
To remove accidentals (which are shown by the Musescore bug), follow these steps.
<ol>
<li>click an accidental, click <code>Select > Similar</code></li>
<li>Go to properties tab in your sidebar, uncheck the <code>visible</code> checkbox.</li>
</ol>
</li>
</ol>
</section>
<h3>Converter</h3>
<div id="input"></div>
</div>
<script type="module">
import { createRoot } from 'https://esm.sh/[email protected]/client';
import { html } from 'https://esm.sh/[email protected]/react';
import { useCallback } from 'https://esm.sh/[email protected]';
import { useDropzone } from 'https://esm.sh/[email protected][email protected]';
import JSZip from 'https://esm.sh/[email protected]';
import { XMLParser, XMLBuilder } from 'https://esm.sh/[email protected]';
// TODO add elements remapping
const DRUM_LOOKUP = {
Bass: 36,
Snare: 38,
SnareRim: 37,
Cowbell: 56,
CymbalsCrash1: 49,
CymbalsCrash2: 57,
CymbalsRide1: 51,
CymbalsRide2: 59,
CymbalsRideCup: 53,
CymbalsSplash: 55,
HiHatClosed: 42,
HiHatOpened: 46,
HiHatPedal: 44,
TomsLow: 45,
TomsMidLow: 47,
TomsMid: 48,
TomsHigh: 50,
TomsFloor: 43,
TomsFloorLow: 41,
};
const opts = {
attributeNamePrefix: '',
ignoreAttributes: false,
preserveOrder: true
};
const parser = new XMLParser(opts);
const builder = new XMLBuilder(opts);
const alterNode = (target, tagName, originalNode, transformedNode) => {
const index = target?.findIndex(item => Object.hasOwn(item, tagName) && item[tagName] === originalNode);
return target?.toSpliced(index, index < 0 ? 0 : 1, { [tagName]: transformedNode });
};
const findByTagName = (target, tagName) =>
target?.find(item => Object.hasOwn(item, tagName))?.[tagName];
const findByAttribute = (target, tagName, [attrName, attrValue]) =>
target?.find(item => Object.hasOwn(item, tagName) && item[':@']?.[attrName] === attrValue)?.[tagName];
const isDrumNote = properties => {
const pitch = findByTagName(findByAttribute(properties, 'Property', ['name', 'ConcertPitch']), 'Pitch');
const step = findByTagName(findByTagName(pitch, 'Step'), '#text');
const octave = findByTagName(findByTagName(pitch, 'Octave'), '#text');
return step === 'C' && octave === -1;
};
const addMIDIProperty = properties => {
const midiProperty = findByAttribute(properties, 'Property', ['name', 'Midi']);
if (midiProperty) return properties;
const fretProperty = findByAttribute(properties, 'Property', ['name', 'Fret']);
const fret = findByTagName(findByTagName(fretProperty, 'Fret'), '#text');
return [
...properties,
{
':@': { name: 'Midi' },
Property: [
{ Number: [{ '#text': fret }] }
]
}
];
};
const transform = root => {
const gpif = findByTagName(root, 'GPIF');
const notes = findByTagName(gpif, 'Notes');
const notesTransformed = notes.map(note => {
if (!Object.hasOwn(note, 'Note')) return note;
const properties = findByTagName(note.Note, 'Properties');
if (!isDrumNote(properties)) return note;
const propertiesTransformed = addMIDIProperty(properties);
const noteTransformed = alterNode(note.Note, 'Properties', properties, propertiesTransformed);
return { ...note, Note: noteTransformed };
});
const gpifTransformed = alterNode(gpif, 'Notes', notes, notesTransformed);
const rootTransformed = alterNode(root, 'GPIF', gpif, gpifTransformed);
return rootTransformed;
};
const download = (url, name) => {
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
};
const Input = () => {
const onDrop = useCallback(async ([file]) => {
const zip = await JSZip.loadAsync(file);
const content = await zip.file('Content/score.gpif').async('string');
const contentObject = parser.parse(content);
const outputBlob = await zip.file('Content/score.gpif', builder.build(transform(contentObject)))
.generateAsync({ type: 'blob' });
download(URL.createObjectURL(outputBlob), file.name.replace(/\.gp$/, '-drumsterr.gp'));
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return html`
<div ...${getRootProps()} class="${`Dropzone${ isDragActive ? ' Dropzone--active' : ''}`}">
<input ...${getInputProps()} />
<i>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m2 2 8 8"/>
<path d="m22 2-8 8"/>
<ellipse cx="12" cy="9" rx="10" ry="5"/>
<path d="M7 13.4v7.9"/>
<path d="M12 14v8"/>
<path d="M17 13.4v7.9"/>
<path d="M2 9v8a10 5 0 0 0 20 0V9"/>
</svg>
</i>
<h3>Click or Drag & Drop</h3>
<p>
Drop a Songsterr GuitarPro file here.<br />
The file will not be uploaded and processed in your device.
</p>
</div>
`;
};
createRoot(document.querySelector('#input')).render(html`<${Input} />`);
</script>
</body>
</html>
@HelloWorld017
Copy link
Author

HelloWorld017 commented Apr 2, 2025

Drumsterr

Convert your Songsterr GuitarPro (*.gp) files suitable for Musescore Editing.

Link

Drumsterr

Usage

  1. Export your score in Songsterr
  2. Drag it to the Drumsterr
  3. Download converted file and open it in Musescore
  4. Go to layout tab in your sidebar, click the config icon, and click Replace instrument
  5. Find Drum kit (common).
  6. To remove accidentals (which are shown by the Musescore bug), follow these steps.
    1. Click an accidental, right click and select Select > Similar
    2. Go to properties tab in your sidebar, uncheck the Visible checkbox.

@HelloWorld017
Copy link
Author

HelloWorld017 commented Apr 2, 2025

How it works

The Songsterr-exported *.gp files does not have MIDI mapping.
So directly replacing the instrument yields this unwanted result:
image

This application adds the missing MIDI mappings.

Video Tutorial for Replacing Instrument

OeOXUtEEnV.mp4

@delete-macports
Copy link

delete-macports commented Aug 8, 2025

It stops working, We are getting

Loading failed for the <script> with source “https://gistcdn.rawgit.net/cdn-cgi/zaraz/s.js?z=JTdCJTIyZXhlY3V0ZWQlMjIlM0ElNUIlNUQlMkMlMjJ0JTIyJTNBJTIyRHJ1bXN0ZXJyJTIyJTJDJTIyeCUyMiUzQTAuODA0MDUzNjU2MTM2MDQ5MiUyQyUyMnclMjIlM0ExOTIwJTJDJTIyaCUyMiUzQTEwODAlMkMlMjJqJTIyJTNBOTI3JTJDJTIyZSUyMiUzQTE2NzYlMkMlMjJsJTIyJTNBJTIyaHR0cHMlM0ElMkYlMkZnaXN0Y2RuLnJhd2dpdC5uZXQlMkZIZWxsb1dvcmxkMDE3JTJGZjkzNzU4ZDUzNGIzMTE1MTE1MTg5ZmUxYTkyNDM3NGUlMkZyYXclMkZmODhkZGIwOGM0YTQxYmQ0MjhmMDhhN2M2ZTJhNGM1Yzg3N2EyMzM1JTJGZHJ1bXN0ZXJyLmh0bWwlMjIlMkMlMjJyJTIyJTNBJTIyaHR0cHMlM0ElMkYlMkZnaXN0LmdpdGh1Yi5jb20lMkYlMjIlMkMlMjJrJTIyJTNBMjQlMkMlMjJuJTIyJTNBJTIyVVRGLTglMjIlMkMlMjJvJTIyJTNBLTEyMCUyQyUyMnElMjIlM0ElNUIlNUQlN0Q=”. [drumsterr.html:1:1](https://gistcdn.rawgit.net/HelloWorld017/f93758d534b3115115189fe1a924374e/raw/f88ddb08c4a41bd428f08a7c6e2a4c5c877a2335/drumsterr.html)
Uncaught TypeError: can't access property "useMemo", i.H is null
    useMemo React
    Ur index.js:470
    Input drumsterr.html:271
    React 8
    I scheduler.production.js:152

And no drop area

@HelloWorld017
Copy link
Author

HelloWorld017 commented Aug 18, 2025

I have fixed it and now you can use it with updated link :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment