diff --git a/.gitignore b/.gitignore index 3aadc51..3868748 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ out gen node_modules/ +npm-debug.log diff --git a/README.md b/README.md index e69de29..ed60445 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,21 @@ +# Node wave +Render audio waveform like SoundCloud on backend + +## Requirement + This module base on `web-audio-api` and `canvas` modules. +## Attribute + +| Option | Desciption | Default | +| ------------- | ------------- | ------------- | +| width | Image width | 600px | +| height | Image height | 80px | +| barWidth | bar height | 3px | +| barGrap | space between 2 bar | 0.2 | +| waveColor | bar color | gray | +| waveAlpha | bar opacity | 1 | +| backgroundColor | Image background color | #fff | +| backgroundImage | setup image background | | +| baseline | center of waveform | 40 | +| padding | Top and bottom padding | 10 | +| baselineWidth | baseline width | 1 | +| baselineColor | baseline color | white | diff --git a/lib/render.js b/lib/render.js index 597d65e..73e8df6 100644 --- a/lib/render.js +++ b/lib/render.js @@ -1,58 +1,46 @@ -var AudioContext = require('web-audio-api').AudioContext; -var fs = require('fs'); +var AudioContext = require('web-audio-api').AudioContext +var fs = require('fs') var Canvas = require('canvas') -Array.prototype.max = function () { - return Math.max.apply(null, this); -}; - var defaultSetting = { width: 600, height: 80, barWidth: 3, barGap: 0.2, - waveColor: "blue", + waveColor: 'gray', waveAlpha: 1, backgroundColor: '#fff', - baseline: 60, -} - -function bufferMeasure(position, length, data) { - var sum = 0.0 - for (var i = position; i <= (position + length) - 1; i++) { - sum += Math.pow(data[i], 2) - } - return Math.sqrt(sum / data.length) + backgroundImage: '', + baseline: 40, + padding: 10, + baselineWidth: 0, + baselineColor: 'white' } -module.exports = function render(url, options, cb) { - var audioContext = new AudioContext - if (typeof options === "function") { +module.exports = function render (url, options, cb) { + var audioContext = new AudioContext() + if (typeof options === 'function') { cb = options options = {} } options = Object.assign({}, defaultSetting, options) if (options.baseline < 0 && options.height < 0 && options.width < 0) { - return cb( new Error('Size must be greater than 0')) + return cb(new Error('Size must be greater than 0')) } if (options.baseline > options.height) { - return cb( new Error('Baseline must be smaller than waveform height')) + return cb(new Error('Baseline must be smaller than waveform height')) } var canvas = new Canvas(options.width, options.height) var canvasContext = canvas.getContext('2d') - - fs.readFile("Canon.mp3", function (err, buffer) { - - if (err) { - return cb(err) - } + fs.readFile(url, function (err, buffer) { + if (err) return cb(err) audioContext.decodeAudioData(buffer, function (result) { var data = result.getChannelData(0) - var step = Math.floor(result.length / options.width) + var step = Math.floor(data.length / options.width) var ratio = options.baseline / options.height var vals = [] @@ -62,31 +50,55 @@ module.exports = function render(url, options, cb) { canvasContext.fillStyle = options.waveColor for (var i = 0; i < options.width; i += options.barWidth) { - vals.push(bufferMeasure(i * step, step, data) * 10000); + var position = i * step + var sum = 0.0 + for (var j = position; j <= (position + step) - 1; j++) { + sum += Math.pow(data[j], 2) + } + vals.push(Math.sqrt(sum / data.length) * 10000) } - for (var j = 0; j < options.width; j += options.barWidth) { - var scale = options.height / vals.max(); - var val = bufferMeasure(j * step, step, data) * 10000; - val *= scale; - val += 1; - var w = options.barWidth; + var maxValue = Math.max.apply(null, vals) + + vals.forEach(function (val, index) { + var scale = options.height / maxValue + val *= scale + var w = options.barWidth if (options.barGap !== 0) { - w *= Math.abs(1 - options.barGap); + w *= Math.abs(1 - options.barGap) + } + var x = index * options.barWidth + (w / 2) + + var lowerHeight = val * ratio + + if (lowerHeight < options.padding) { + lowerHeight = 1 + } else { + lowerHeight -= options.padding } - var x = j + (w / 2); - var lowerHeight = val * ratio; - var upperHeight = val * (1 - ratio); + var upperHeight = val * (1 - ratio) - if(options.waveAlpha < 1) { - canvasContext.clearRect(x, options.baseline, w, upperHeight); - canvasContext.clearRect(x, options.baseline, w, -lowerHeight); + if (upperHeight < options.padding) { + upperHeight = 1 + } else { + upperHeight -= options.padding } - canvasContext.fillRect(x, options.baseline, w, upperHeight); - canvasContext.fillRect(x, options.baseline, w, -lowerHeight); + + if (options.waveAlpha < 1) { + canvasContext.clearRect(x, options.baseline, w, upperHeight) + canvasContext.clearRect(x, options.baseline, w, -lowerHeight) + canvasContext.globalAlpha = options.waveAlpha + } + canvasContext.fillRect(x, options.baseline, w, upperHeight) + canvasContext.fillRect(x, options.baseline, w, -lowerHeight) + }) + + if (options.baselineWidth >= 1) { + canvasContext.fillStyle = options.baselineColor + canvasContext.fillRect(0, options.baseline - (options.baselineWidth / 2), options.width, options.baselineWidth) } cb(null, canvas.toBuffer()) }) - }); + }) } diff --git a/package.json b/package.json index 9001065..50f75f4 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "node-wave", - "version": "0.0.1", - "description": "", + "version": "0.0.2", + "description": "Render waveform image like SoundCloud", "main": "index.js", "scripts": { - "test": "standard && node index.js" + "lint": "standard" }, "author": "Tran Quoc Cuong ", "license": "MIT", @@ -13,6 +13,7 @@ "web-audio-api": "^0.2.2" }, "devDependencies": { + "mocha": "^3.2.0", "standard": "^8.6.0" } } diff --git a/test/out.png b/test/out.png index 33091f0..015b09e 100644 Binary files a/test/out.png and b/test/out.png differ diff --git a/test/test.js b/test/render.js similarity index 64% rename from test/test.js rename to test/render.js index b25462a..2e38c82 100644 --- a/test/test.js +++ b/test/render.js @@ -2,5 +2,6 @@ var render = require('../lib/render') var fs = require('fs') render('Canon.mp3', function (err, buffer) { - fs.writeFileSync('out.png', buffer); + if (err) return err + fs.writeFileSync('out.png', buffer) })