Skip to content

Commit

Permalink
wav metadata update
Browse files Browse the repository at this point in the history
added wave metadata options
FIXED RMIDI FOR REAL
  • Loading branch information
spessasus committed Aug 7, 2024
1 parent 30e89b5 commit 4243af6
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 225 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SpessaSynth",
"version": "3.12.3",
"version": "3.13.0",
"type": "module",
"scripts": {
"start": "node src/website/server/server.js",
Expand Down
379 changes: 215 additions & 164 deletions src/spessasynth_lib/midi_parser/rmidi_writer.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/spessasynth_lib/synthetizer/worklet_processor.min.js

Large diffs are not rendered by default.

65 changes: 58 additions & 7 deletions src/spessasynth_lib/utils/buffer_to_wav.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
/**
* @typedef {Object} WaveMetadata
* @property {string} title - the song's title
* @property {string} album - the song's album
* @property {string} genre - the song's genre
* @property {string|undefined} title - the song's title
* @property {string|undefined} artist - the song's artist
* @property {string|undefined} album - the song's album
* @property {string|undefined} genre - the song's genre
*/

import { combineArrays } from './indexed_array.js'
import { getStringBytes } from './byte_functions/string.js'
import { writeRIFFOddSize } from '../soundfont/read/riff_chunk.js'

/**
*
* @param audioBuffer {AudioBuffer}
* @param normalizeAudio {boolean} find the max sample point and set it to 1, and scale others with it
* @param channelOffset {number} channel offset and channel offset + 1 get saved
* @param metadata {WaveMetadata}
* @returns {Blob}
*/
export function audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffset = 0)
export function audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffset = 0, metadata = {})
{
// this code currently doesn't add any metadata
const channel1Data = audioBuffer.getChannelData(channelOffset);
Expand Down Expand Up @@ -57,11 +63,52 @@ export function audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffs
// data chunk length
header.set(new Uint8Array([dataSize & 0xff, (dataSize >> 8) & 0xff, (dataSize >> 16) & 0xff, (dataSize >> 24) & 0xff]), 40);

const wavData = new Uint8Array(headerSize + dataSize);
wavData.set(header, 0);
// Interleave audio data (combine channels)
let wavData;
let offset = headerSize;
let infoChunk = undefined;
// INFO chunk
if(Object.keys(metadata).length > 0)
{
const encoder = new TextEncoder();
const infoChunks = [
getStringBytes("INFO"),
writeRIFFOddSize("ICMT", encoder.encode("Created with SpessaSynth"))
];
if(metadata.artist)
{
infoChunks.push(
writeRIFFOddSize("IART", encoder.encode(metadata.artist))
);
}
if(metadata.album)
{
infoChunks.push(
writeRIFFOddSize("IPRD", encoder.encode(metadata.album))
);
}
if(metadata.genre)
{
infoChunks.push(
writeRIFFOddSize("IGNR", encoder.encode(metadata.genre))
);
}
if(metadata.title)
{
infoChunks.push(
writeRIFFOddSize("INAM", encoder.encode(metadata.title))
);
}
infoChunk = writeRIFFOddSize("LIST", combineArrays(infoChunks));
wavData = new Uint8Array(headerSize + dataSize + infoChunk.length);
}
else
{
wavData = new Uint8Array(headerSize + dataSize);

}
wavData.set(header, 0);

// Interleave audio data (combine channels)
let multiplier;
if(normalizeAudio)
{
Expand Down Expand Up @@ -103,6 +150,10 @@ export function audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffs
wavData[offset++] = (sample2 >> 8) & 0xff;
}

if(infoChunk)
{
wavData.set(infoChunk, offset);
}

return new Blob([wavData.buffer], { type: 'audio/wav' });
}
67 changes: 64 additions & 3 deletions src/website/manager/export_audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ const RENDER_AUDIO_TIME_INTERVAL = 1000;
* @param normalizeAudio {boolean}
* @param additionalTime {number}
* @param separateChannels {boolean}
* @param meta {WaveMetadata}
* @returns {Promise<void>}
* @private
*/
export async function _doExportAudioData(normalizeAudio = true, additionalTime = 2, separateChannels = false)
export async function _doExportAudioData(normalizeAudio = true, additionalTime = 2, separateChannels = false, meta = {})
{
this.isExporting = true;
if(!this.seq)
Expand Down Expand Up @@ -115,7 +116,7 @@ export async function _doExportAudioData(normalizeAudio = true, additionalTime =
await new Promise(r => setTimeout(r, ANIMATION_REFLOW_TIME));
if(!separateChannels)
{
this.saveBlob(audioBufferToWav(buf, normalizeAudio), `${this.seqUI.currentSongTitle || 'unnamed_song'}.wav`);
this.saveBlob(audioBufferToWav(buf, normalizeAudio, 0, meta), `${this.seqUI.currentSongTitle || 'unnamed_song'}.wav`,);
}
else
{
Expand Down Expand Up @@ -182,6 +183,16 @@ export async function _exportAudioData()
return;
}
const wavPath = `locale.exportAudio.formats.formats.wav.options.`;
const metadataPath = "locale.exportAudio.formats.metadata.";
const verifyDecode = (type, def, decoder) => {
return this.seq.midiData.RMIDInfo?.[type] === undefined ? def : decoder.decode(this.seq.midiData.RMIDInfo?.[type])
}
const encoding = verifyDecode("IENC", "ascii", new TextDecoder());
const decoder = new TextDecoder(encoding);

const startAlbum = verifyDecode("IPRD", "", decoder);
const startArtist = verifyDecode("IART", "", decoder);
const startGenre = verifyDecode("IGNR", "", decoder);
/**
* @type {NotificationContent[]}
*/
Expand Down Expand Up @@ -209,6 +220,42 @@ export async function _exportAudioData()
"separate-channels-toggle": "1"
}
},
{
type: "input",
translatePathTitle: metadataPath + "songTitle",
attributes: {
"name": "song_title",
"type": "text",
"value": this.seqUI.currentSongTitle
}
},
{
type: "input",
translatePathTitle: metadataPath + "album",
attributes: {
"value": startAlbum,
"name": "album",
"type": "text"
}
},
{
type: "input",
translatePathTitle: metadataPath + "artist",
attributes: {
"value": startArtist,
"name": "artist",
"type": "text"
}
},
{
type: "input",
translatePathTitle: metadataPath + "genre",
attributes: {
"value": startGenre,
"name": "genre",
"type": "text"
}
},
{
type: "button",
textContent: this.localeManager.getLocaleString(wavPath + "confirm"),
Expand All @@ -217,7 +264,21 @@ export async function _exportAudioData()
const normalizeVolume = n.div.querySelector("input[normalize-volume-toggle]").checked;
const additionalTime = n.div.querySelector("input[type='number']").value;
const separateChannels = n.div.querySelector("input[separate-channels-toggle]").checked;
this._doExportAudioData(normalizeVolume, parseInt(additionalTime), separateChannels);
const artist = n.div.querySelector("input[name='artist']").value;
const album = n.div.querySelector("input[name='album']").value;
const title = n.div.querySelector("input[name='song_title']").value;
const genre = n.div.querySelector("input[name='genre']").value;
/**
* @type {WaveMetadata}
*/
const metadata = {
artist: artist.length > 0 ? artist : undefined,
album: album.length > 0 ? album : undefined,
title: title.length > 0 ? title : undefined,
genre: genre.length > 0 ? genre : undefined,
}

this._doExportAudioData(normalizeVolume, parseInt(additionalTime), separateChannels, metadata);
}
}
];
Expand Down
46 changes: 23 additions & 23 deletions src/website/minified/demo_main.min.js

Large diffs are not rendered by default.

44 changes: 22 additions & 22 deletions src/website/minified/local_main.min.js

Large diffs are not rendered by default.

0 comments on commit 4243af6

Please sign in to comment.