UNPKG

bitwig-nks-preview-generator

Version:

Streaming convert NKSF files to preview audio with using Bitwig Studio.

132 lines (124 loc) 3.56 kB
const fs = require('fs'), { promisify } = require('util'), fsOpen = promisify(fs.open), fsClose = promisify(fs.close), fsRead = promisify(fs.read) /** * validate & read .wav file * @function * @param {String} file - .wav file path * @return {Promise} - resolve Buffer */ async function validateRead(file) { const wav = await _readWav(file, true, true) // rewrite RIFF content size wav.header.buffer.writeUInt32LE(wav.fmt.length + wav.data.length + 4, 4) return Buffer.concat([ wav.header.buffer, wav.fmt.buffer, wav.data.buffer ]) } /** * read & parse fmt chunk & data size * @function * @param {String} file - .wav file path * @return {Promise} - resolve {Object} wav format */ async function readFormat(file) { const wav = await _readWav(file, true, false) return { audioFormat: wav.fmt.buffer.readUInt16LE(8), numChannels: wav.fmt.buffer.readUInt16LE(10), sampleRate: wav.fmt.buffer.readUInt32LE(12), byteRate: wav.fmt.buffer.readUInt32LE(16), blockAlign: wav.fmt.buffer.readUInt16LE(20), bitsPerSample: wav.fmt.buffer.readUInt16LE(22), dataSize: wav.data.size } } async function _readWav(file, readFmtContent, readDataContent) { const fd = await fsOpen(file) try { const header = await _header(fd, 'WAVE'), fileSize = header.size + 8, subscribes = [] if (readFmtContent) subscribes.push('fmt ') if (readDataContent) subscribes.push('data') var position = 12, fmtChunk, dataChunk while (position < fileSize) { const chunk = await _chunk(fd, position, subscribes) if (chunk.id === 'fmt ') fmtChunk = chunk if (chunk.id === 'data') dataChunk = chunk position += chunk.length } if (!fmtChunk) { throw new Error('[fmt ] chunk not included.') } if (!dataChunk) { throw new Error('[data] chunk not included') } return { header: header, fmt: fmtChunk, data: dataChunk } } finally { await fsClose(fd) } } async function _header(fd, expectFormType) { const buffer = await _read(fd, 12, 0), id = buffer.toString('ascii', 0, 4) if (id !== 'RIFF') { throw new Error('unknown magic id:' + id) } // formType 4 bytes // 'fmt ' (minimum 8 bytes) // 'data' (minumum 8 bytes) const size = buffer.readUInt32LE(4) if (size < 20) { throw new Error('header size:' + size) } const formType = buffer.toString('ascii', 8, 12) if (formType !== expectFormType) { throw new Error('unknown header formType:' + formType) } return { id: id, size: size, formType: formType, buffer: buffer } } async function _chunk(fd, position, subscribeIds) { const buffer = await _read(fd, 8, position), id = buffer.toString('ascii', 0, 4), size = buffer.readUInt32LE(4), contentLength = (size + 1) & ~1, ret = { id: id, size: size, length: contentLength + 8 // id + size + content } if (subscribeIds && subscribeIds.includes(id)) { ret.buffer = Buffer.concat([ buffer, await _read(fd, contentLength, position + 8) ]) } return ret } async function _read(fd, length, position) { const buffer = Buffer.alloc(length) const ret = await fsRead(fd, buffer, 0, length, position) if (ret.bytesRead !== length) { throw new Error(`wrong bytesRead. expect lengt:${length} bytesRead:${ret.bytesRead}`) } return buffer } module.exports = { validateRead: validateRead, readFormat: readFormat }