diff --git a/README.md b/README.md index 7d6accba..9db9e56a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ SoundFont2 based realtime synthetizer and MIDI player written in JavaScript usin - Modular design allows easy integrations into other projects - Written in pure JavaScript using WebAudio API (Every modern browser supports it) - No dependencies (Node.js is only required for the app, the core synth and sequencer library needs no dependencies) -- Comes bundled with a small [GeneralUser GS](https://schristiancollins.com/generaluser.php) soundFont to get you started +- Comes bundled with a compressed [SGM](https://musical-artifacts.com/artifacts/855) SoundFont to get you started ### Limitations - It might not sound as good as other synthetizers (e.g. FluidSynth or BASSMIDI) diff --git a/index.html b/index.html index bb21a4bc..7c146df4 100644 --- a/index.html +++ b/index.html @@ -41,7 +41,7 @@

SpessaSynth: Online Demo

@@ -60,218 +60,6 @@

SpessaSynth: Online Demo

- + \ No newline at end of file diff --git a/soundfonts/.gitignore b/soundfonts/.gitignore index e41ed791..d190587a 100644 --- a/soundfonts/.gitignore +++ b/soundfonts/.gitignore @@ -1,3 +1,3 @@ * !.gitignore -!GeneralUser_GS.sf3 \ No newline at end of file +!SGM.sf3 \ No newline at end of file diff --git a/soundfonts/GeneralUser_GS.sf3 b/soundfonts/GeneralUser_GS.sf3 deleted file mode 100644 index 3d881d09..00000000 Binary files a/soundfonts/GeneralUser_GS.sf3 and /dev/null differ diff --git a/soundfonts/SGM.sf3 b/soundfonts/SGM.sf3 new file mode 100644 index 00000000..0e38e7a7 Binary files /dev/null and b/soundfonts/SGM.sf3 differ diff --git a/src/website/demo_main.js b/src/website/demo_main.js new file mode 100644 index 00000000..3a3cc8eb --- /dev/null +++ b/src/website/demo_main.js @@ -0,0 +1,210 @@ +"use strict" + +import { Manager } from './manager.js' +import { MIDI } from '../spessasynth_lib/midi_parser/midi_loader.js' + +import { SoundFont2 } from '../spessasynth_lib/soundfont/soundfont_parser.js' +import { ShiftableByteArray } from '../spessasynth_lib/utils/shiftable_array.js' +import { formatTitle } from '../spessasynth_lib/utils/other.js' + + +const SF_NAME = "SGM.sf3"; +const TITLE = "SpessaSynth: SoundFont2 Javascript Synthetizer Online Demo"; + +/** + * @type {HTMLHeadingElement} + */ +let titleMessage = document.getElementById("title"); + +/** + * @type {HTMLInputElement} + */ +let fileInput = document.getElementById("midi_file_input"); +fileInput.onclick = e => { + e.preventDefault(); + titleMessage.innerText = "You need to upload a SoundFont first"; +} + + +let sfInput = document.getElementById("sf_file_input"); +// remove the old files +fileInput.value = ""; +fileInput.focus(); + +async function fetchFont(url, callback) +{ + let response = await fetch(url); + if(!response.ok) + { + titleMessage.innerText = "Error downloading soundfont!"; + throw response; + } + let size = response.headers.get("content-length"); + let reader = await (await response.body).getReader(); + let done = false; + let dataArray = new ShiftableByteArray(parseInt(size)); + let offset = 0; + do{ + let readData = await reader.read(); + if(readData.value) { + dataArray.set(readData.value, offset); + offset += readData.value.length; + } + done = readData.done; + let percent = Math.round((offset / size) * 100); + callback(percent); + }while(!done); + return dataArray; +} + +document.getElementById("bundled_sf").onclick = () => { + titleMessage.innerText = "Downloading SoundFont..."; + fetchFont(`soundfonts/${SF_NAME}`, percent => titleMessage.innerText = `Loading SF3: ${percent}%`).then(arr => { + try { + window.soundFontParser = new SoundFont2(arr); + document.getElementById("sf_upload").innerText = SF_NAME; + } + catch (e) + { + titleMessage.innerHTML = `Error parsing soundfont:
${e}
`; + console.log(e); + return; + } + prepareUI(); + }); +} + + +/** + * @param midiFile {File} + * @returns {Promise} + */ +async function parseMidi(midiFile) +{ + const buffer = await midiFile.arrayBuffer(); + const arr = new ShiftableByteArray(buffer); + try { + return new MIDI(arr, midiFile.name); + } + catch (e) { + titleMessage.innerHTML = `Error parsing MIDI:
${e}
`; + throw e; + } +} + +/** + * @param midiFiles {FileList} + */ +async function startMidi(midiFiles) +{ + let fName; + if(midiFiles[0].name.length > 20) + { + fName = midiFiles[0].name.substring(0, 21) + "..."; + } + else + { + fName = midiFiles[0].name; + } + if(midiFiles.length > 1) + { + fName += ` and ${midiFiles.length - 1} others`; + } + document.getElementById("file_upload").innerText = fName; + /** + * @type {MIDI[]} + */ + const parsed = []; + + /** + * @type {string[]} + */ + const titles = []; + for (let i = 0; i < midiFiles.length; i++) { + titleMessage.innerText = `Parsing ${midiFiles[i].name}`; + parsed.push(await parseMidi(midiFiles[i])); + + let title; + if(parsed[i].midiName.trim().length > 0) + { + title = parsed[i].midiName.trim(); + } + else + { + title = formatTitle(midiFiles[i].name); + } + titles.push(title); + } + titleMessage.style.fontStyle = "italic"; + document.title = titles[0]; + titleMessage.innerText = titles[0] + + if(manager.seq) + { + manager.seq.loadNewSongList(parsed); + + } + else { + manager.play(parsed); + } + manager.seqUI.setSongTitles(titles); +} + +/** + * @param e {{target: HTMLInputElement}} + * @return {Promise} + */ +sfInput.onchange = async e => { + if(!e.target.files[0]) + { + return; + } + /** + * @type {File} + */ + const file = e.target.files[0]; + + document.getElementById("sf_upload").innerText = file.name; + titleMessage.innerText = "Parsing SoundFont..."; + + const arr = await file.arrayBuffer(); + try { + window.soundFontParser = new SoundFont2(new ShiftableByteArray(arr)); + } + catch (e) + { + titleMessage.innerHTML = `Error parsing SoundFont:
${e}
`; + console.log(e); + return; + } + prepareUI(); +} + +function prepareUI() +{ + titleMessage.innerText = TITLE; + document.getElementById("bundled_sf").style.display = "none"; + document.getElementById("bundled_sf").onclick = undefined; + + window.audioContextMain = new AudioContext({sampleRate: 44100}); + + // prepare midi interface + window.manager = new Manager(audioContextMain, soundFontParser); + + sfInput.onchange = undefined; + if(fileInput.files[0]) + { + startMidi(fileInput.files); + } + else + { + fileInput.onclick = undefined; + fileInput.onchange = e => { + if(e.target.files[0]) + { + startMidi(fileInput.files); + } + } + } +} +