PlayPiano: pianoforte virtuale a 88 tasti con app desktop
App web a file singolo con pianoforte campionato, spartiti MIDI e MusicXML, pentagramma, cascata di note, modalità apprendimento, compositore di idee, generatore di accordi e idee con IA locale tramite Ollama. Include il contenitore Electron e il flusso GitHub Actions per i pacchetti macOS, Windows e Linux.
This commit is contained in:
commit
7a4ee5de28
44 files changed
+5401
No files matched your search
@@ -0,0 +1,44 @@
|
|||||||
|
# Costruisce i pacchetti desktop su macchine native di GitHub.
|
||||||
|
# Si avvia a mano (Actions > Pacchetti desktop > Run workflow) oppure pubblicando un tag "v*",
|
||||||
|
# nel qual caso i pacchetti vengono anche allegati a una Release.
|
||||||
|
name: Pacchetti desktop
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: windows-latest, name: windows, args: "--win nsis portable --x64" }
|
||||||
|
- { os: ubuntu-latest, name: linux, args: "--linux AppImage --x64" }
|
||||||
|
- { os: macos-latest, name: macos, args: "--mac" }
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
- run: npm ci
|
||||||
|
- run: npx electron-builder ${{ matrix.args }} --publish never
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PlayPiano-${{ matrix.name }}
|
||||||
|
if-no-files-found: error
|
||||||
|
path: |
|
||||||
|
dist/*.exe
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.AppImage
|
||||||
|
- name: Allega alla Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/*.exe
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.AppImage
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Avvia PlayPiano su http://localhost:8765 (necessario perché Ollama accetti le richieste della pagina)
|
||||||
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
PORT=8765
|
||||||
|
if lsof -nP -iTCP:$PORT -sTCP:LISTEN >/dev/null 2>&1; then
|
||||||
|
echo "PlayPiano è già in esecuzione: apro il browser."
|
||||||
|
open "http://localhost:$PORT/"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "PlayPiano in esecuzione su http://localhost:$PORT/"
|
||||||
|
echo "Lascia aperta questa finestra mentre usi l'app. Chiudila (o premi Ctrl+C) per terminare."
|
||||||
|
( sleep 1; open "http://localhost:$PORT/" ) &
|
||||||
|
exec python3 -m http.server "$PORT" --bind 127.0.0.1
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# PlayPiano
|
||||||
|
|
||||||
|
Pianoforte virtuale a 88 tasti con spartiti (MIDI e MusicXML), pentagramma, modalità apprendimento,
|
||||||
|
compositore di idee e generazione con IA locale tramite Ollama.
|
||||||
|
|
||||||
|
## Usarlo nel browser
|
||||||
|
Doppio clic su `Avvia PlayPiano.command` (macOS). Apre l'app su http://localhost:8765.
|
||||||
|
|
||||||
|
## App desktop (macOS, Windows, Linux)
|
||||||
|
Serve Node.js 20 o superiore.
|
||||||
|
|
||||||
|
npm install # solo la prima volta
|
||||||
|
npm start # avvia l'app desktop in prova
|
||||||
|
|
||||||
|
### Creare i pacchetti
|
||||||
|
npm run dist:mac # dist/PlayPiano-<versione>-arm64.dmg (Apple Silicon) e PlayPiano-<versione>.dmg (Intel)
|
||||||
|
npx electron-builder --win zip --x64 # versione portatile per Windows
|
||||||
|
npx electron-builder --linux tar.gz --x64 # versione portatile per Linux
|
||||||
|
|
||||||
|
L'AppImage per Linux richiede strumenti per processori Intel. Su un Mac con chip Apple si costruisce con Docker:
|
||||||
|
|
||||||
|
docker run --rm --platform linux/amd64 -v "$PWD":/project -w /project \
|
||||||
|
electronuserland/builder:wine /bin/bash -lc "npx electron-builder --linux AppImage --x64"
|
||||||
|
|
||||||
|
L'installer Windows (.exe) non si può produrre da un Mac con chip Apple. Si costruisce su un PC Windows con
|
||||||
|
`npm install` e `npm run dist:win`, oppure su GitHub con il flusso `.github/workflows/build.yml`.
|
||||||
|
|
||||||
|
I pacchetti finiscono nella cartella `dist/`.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
- I pacchetti non sono firmati con un certificato a pagamento. Su macOS, al primo avvio: clic destro sull'app e poi "Apri".
|
||||||
|
Su Windows: "Ulteriori informazioni" e poi "Esegui comunque".
|
||||||
|
- Le idee con IA richiedono Ollama in esecuzione sullo stesso computer (http://localhost:11434).
|
||||||
|
- Campioni di pianoforte: Salamander Grand Piano di Alexander Holm, licenza CC-BY 3.0 (vedi `samples/LEGGIMI.txt`).
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
// PlayPiano desktop: finestra Electron che carica l'app da un piccolo server locale.
|
||||||
|
// Il server serve a due cose: i campioni del pianoforte si leggono via http, e Ollama
|
||||||
|
// accetta le richieste solo da pagine con origine http://localhost (non da file://).
|
||||||
|
const { app, BrowserWindow, session, shell, Menu } = require('electron');
|
||||||
|
const http = require('http'), fs = require('fs'), path = require('path');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.mp3': 'audio/mpeg', '.mid': 'audio/midi', '.musicxml': 'application/xml', '.mxl': 'application/zip', '.png': 'image/png', '.txt': 'text/plain; charset=utf-8', '.json': 'application/json' };
|
||||||
|
const ALLOWED = ['index.html', 'samples', 'esempi'];
|
||||||
|
|
||||||
|
function startServer() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
let rel; try { rel = decodeURIComponent(new URL(req.url, 'http://localhost').pathname).replace(/^\/+/, '') || 'index.html'; } catch (_) { res.writeHead(400); return res.end(); }
|
||||||
|
const file = path.normalize(path.join(ROOT, rel));
|
||||||
|
if (!file.startsWith(ROOT + path.sep) || !ALLOWED.includes(rel.split('/')[0])) { res.writeHead(404); return res.end('Not found'); }
|
||||||
|
fs.readFile(file, (err, data) => {
|
||||||
|
if (err) { res.writeHead(404); return res.end('Not found'); }
|
||||||
|
res.writeHead(200, { 'Content-Type': MIME[path.extname(file).toLowerCase()] || 'application/octet-stream', 'Cache-Control': 'no-cache' });
|
||||||
|
res.end(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.on('error', reject);
|
||||||
|
server.listen(0, '127.0.0.1', () => resolve(server.address().port));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let win = null;
|
||||||
|
async function createWindow() {
|
||||||
|
const port = await startServer();
|
||||||
|
win = new BrowserWindow({
|
||||||
|
width: 1440, height: 960, minWidth: 720, minHeight: 560, backgroundColor: '#14110f', title: 'PlayPiano', autoHideMenuBar: true,
|
||||||
|
icon: path.join(__dirname, 'icon.png'),
|
||||||
|
webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true, backgroundThrottling: false }
|
||||||
|
});
|
||||||
|
win.webContents.setWindowOpenHandler(({ url }) => { if (/^https?:/.test(url)) shell.openExternal(url); return { action: 'deny' }; });
|
||||||
|
win.on('closed', () => { win = null; });
|
||||||
|
await win.loadURL('http://localhost:' + port + '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock()) app.quit();
|
||||||
|
else {
|
||||||
|
app.on('second-instance', () => { if (win) { if (win.isMinimized()) win.restore(); win.focus(); } });
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
// tastiere MIDI: concedi il permesso senza chiedere
|
||||||
|
const ok = p => p === 'midi' || p === 'midiSysex' || p === 'keyboardLock';
|
||||||
|
session.defaultSession.setPermissionRequestHandler((wc, permission, cb) => cb(ok(permission)));
|
||||||
|
session.defaultSession.setPermissionCheckHandler((wc, permission) => ok(permission));
|
||||||
|
if (process.platform !== 'darwin') Menu.setApplicationMenu(null);
|
||||||
|
createWindow();
|
||||||
|
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
|
||||||
|
});
|
||||||
|
app.on('window-all-closed', () => app.quit());
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0"?><score-partwise version="3.1"><part-list><score-part id="P1"><part-name>Piano</part-name></score-part></part-list><part id="P1"><measure number="1"><attributes><divisions>4</divisions><key><fifths>2</fifths></key><time><beats>4</beats><beat-type>4</beat-type></time><staves>2</staves></attributes><direction><sound tempo="90"/></direction><note><pitch><step>D</step><octave>4</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>F</step><alter>1</alter><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>A</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>D</step><octave>5</octave></pitch><duration>6</duration><staff>1</staff></note><note><pitch><step>C</step><alter>1</alter><octave>5</octave></pitch><duration>2</duration><staff>1</staff></note><backup><duration>16</duration></backup><note><pitch><step>D</step><octave>3</octave></pitch><duration>8</duration><staff>2</staff></note><note><chord/><pitch><step>A</step><octave>3</octave></pitch><duration>8</duration><staff>2</staff></note><note><pitch><step>A</step><octave>2</octave></pitch><duration>8</duration><staff>2</staff></note></measure><measure number="2"><note><pitch><step>B</step><octave>4</octave></pitch><duration>1</duration><staff>1</staff></note><note><pitch><step>C</step><alter>1</alter><octave>5</octave></pitch><duration>1</duration><staff>1</staff></note><note><pitch><step>D</step><octave>5</octave></pitch><duration>1</duration><staff>1</staff></note><note><pitch><step>E</step><octave>5</octave></pitch><duration>1</duration><staff>1</staff></note><note><pitch><step>F</step><alter>0</alter><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>F</step><alter>1</alter><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>A</step><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><backup><duration>16</duration></backup><note><pitch><step>G</step><octave>2</octave></pitch><duration>16</duration><staff>2</staff></note><note><chord/><pitch><step>D</step><octave>3</octave></pitch><duration>16</duration><staff>2</staff></note><note><chord/><pitch><step>E</step><octave>3</octave></pitch><duration>16</duration><staff>2</staff></note></measure><measure number="3"><note><pitch><step>D</step><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>C</step><octave>6</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>B</step><alter>-1</alter><octave>3</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>A</step><octave>4</octave></pitch><duration>4</duration><staff>1</staff></note><backup><duration>16</duration></backup><note><pitch><step>D</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>F</step><alter>1</alter><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>A</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>D</step><octave>4</octave></pitch><duration>4</duration><staff>2</staff></note></measure><measure number="4"><note><pitch><step>D</step><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>C</step><octave>6</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>B</step><alter>-1</alter><octave>3</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>A</step><octave>4</octave></pitch><duration>4</duration><staff>1</staff></note><backup><duration>16</duration></backup><note><pitch><step>D</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>F</step><alter>1</alter><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>A</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>D</step><octave>4</octave></pitch><duration>4</duration><staff>2</staff></note></measure><measure number="5"><note><pitch><step>D</step><octave>5</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>C</step><octave>6</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>B</step><alter>-1</alter><octave>3</octave></pitch><duration>4</duration><staff>1</staff></note><note><pitch><step>A</step><octave>4</octave></pitch><duration>4</duration><staff>1</staff></note><backup><duration>16</duration></backup><note><pitch><step>D</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>F</step><alter>1</alter><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>A</step><octave>3</octave></pitch><duration>4</duration><staff>2</staff></note><note><pitch><step>D</step><octave>4</octave></pitch><duration>4</duration><staff>2</staff></noLine truncated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0"?><score-partwise version="3.1"><part-list><score-part id="P1"><part-name>Piano</part-name></score-part></part-list><part id="P1"><measure number="1"><attributes><divisions>2</divisions><staves>2</staves></attributes><direction><sound tempo="100"/></direction><note><pitch><step>C</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>D</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>E</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>C</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><backup><duration>8</duration></backup><note><pitch><step>C</step><octave>3</octave></pitch><duration>8</duration><tie type="start"/><staff>2</staff></note><note><chord/><pitch><step>G</step><octave>3</octave></pitch><duration>8</duration><staff>2</staff></note></measure><measure number="2"><note><pitch><step>E</step><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>F</step><alter>1</alter><octave>4</octave></pitch><duration>2</duration><staff>1</staff></note><note><pitch><step>G</step><octave>4</octave></pitch><duration>4</duration><staff>1</staff></note><backup><duration>8</duration></backup><note><pitch><step>C</step><octave>3</octave></pitch><duration>8</duration><tie type="stop"/><staff>2</staff></note></measure></part></score-partwise>
|
||||||
Binary file not shown.
+1559
File diff suppressed because it is too large.
Load diff
Generated
+3598
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"name": "playpiano",
|
||||||
|
"productName": "PlayPiano",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Pianoforte virtuale a 88 tasti con spartiti, modalità apprendimento e compositore",
|
||||||
|
"author": "PlayPiano",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"main": "desktop/main.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron .",
|
||||||
|
"dist:mac": "electron-builder --mac",
|
||||||
|
"dist:win": "electron-builder --win",
|
||||||
|
"dist:linux": "electron-builder --linux",
|
||||||
|
"dist": "electron-builder --mac --win --linux"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "it.playpiano.app",
|
||||||
|
"productName": "PlayPiano",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.html",
|
||||||
|
"samples/**",
|
||||||
|
"esempi/**",
|
||||||
|
"desktop/main.js",
|
||||||
|
"desktop/icon.png",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"icon": "desktop/icon.png",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.music",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "dmg",
|
||||||
|
"arch": [
|
||||||
|
"arm64",
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identity": "-",
|
||||||
|
"hardenedRuntime": false,
|
||||||
|
"gatekeeperAssess": false
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "nsis",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "portable",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"artifactName": "PlayPiano-Setup-${version}.${ext}"
|
||||||
|
},
|
||||||
|
"portable": {
|
||||||
|
"artifactName": "PlayPiano-Portable-${version}.${ext}"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "AppImage",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"category": "AudioVideo",
|
||||||
|
"artifactName": "PlayPiano-${version}-${arch}.${ext}"
|
||||||
|
},
|
||||||
|
"dmg": {
|
||||||
|
"sign": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^44.4.2",
|
||||||
|
"electron-builder": "^26.15.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
Campioni di pianoforte: Salamander Grand Piano (Yamaha C5) di Alexander Holm.
|
||||||
|
Licenza Creative Commons Attribution 3.0 (CC-BY 3.0).
|
||||||
|
File in formato mp3 scaricati da https://tonejs.github.io/audio/salamander/
|
||||||
Reference in new issue
Block a user