demoscene-stuff/201410_-_Sine_City/player-small.js

336 lines
11 KiB
JavaScript

/* -*- mode: javascript; tab-width: 4; indent-tabs-mode: nil; -*-
*
* Copyright (c) 2011-2013 Marcus Geelnard
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
*/
"use strict";
var CPlayer = function() {
//--------------------------------------------------------------------------
// Private methods
//--------------------------------------------------------------------------
// Oscillators
var osc_sin = function (value) {
return Math.sin(value * 6.283184);
};
var osc_saw = function (value) {
return 2 * (value % 1) - 1;
};
var osc_square = function (value) {
return (value % 1) < 0.5 ? 1 : -1;
};
var osc_tri = function (value) {
var v2 = (value % 1) * 4;
if(v2 < 2) return v2 - 1;
return 3 - v2;
};
var getnotefreq = function (n) {
// 174.61.. / 44100 = 0.003959503758 (F3)
return 0.003959503758 * Math.pow(2, (n - 128) / 12);
};
var createNote = function (instr, n) {
var osc1 = mOscillators[instr.i[0]],
o1vol = instr.i[1],
o1xenv = instr.i[3],
osc2 = mOscillators[instr.i[4]],
o2vol = instr.i[5],
o2xenv = instr.i[8],
noiseVol = instr.i[9],
attack = instr.i[10] * instr.i[10] * 4,
sustain = instr.i[11] * instr.i[11] * 4,
release = instr.i[12] * instr.i[12] * 4,
releaseInv = 1 / release;
var noteBuf = new Int32Array(attack + sustain + release);
// Calculate note frequencies for the oscillators
var o1t = getnotefreq(n + instr.i[2] - 128);
var o2t = getnotefreq(n + instr.i[6] - 128) * (1 + 0.0008 * instr.i[7]);
// Re-trig oscillators
var c1 = 0, c2 = 0;
// Local variables.
var j, e, t, rsample;
// Generate one note (attack + sustain + release)
for (j = 0; j < attack + sustain + release; j++) {
// Envelope
e = 1;
if (j < attack) {
e = j / attack;
} else if (j >= attack + sustain) {
e -= (j - attack - sustain) * releaseInv;
}
// Oscillator 1
t = o1t;
if (o1xenv) {
t *= e * e;
}
c1 += t;
rsample = osc1(c1) * o1vol;
// Oscillator 2
t = o2t;
if (o2xenv) {
t *= e * e;
}
c2 += t;
rsample += osc2(c2) * o2vol;
// Noise oscillator
if (noiseVol) {
rsample += (2 * Math.random() - 1) * noiseVol;
}
// Add to (mono) channel buffer
noteBuf[j] = (80 * rsample * e) | 0;
}
return noteBuf;
};
//--------------------------------------------------------------------------
// Private members
//--------------------------------------------------------------------------
// Array of oscillator functions
var mOscillators = [
osc_sin,
osc_square,
osc_saw,
osc_tri
];
// Private variables set up by init()
var mSong, mLastRow, mCurrentCol, mNumWords, mMixBuf;
//--------------------------------------------------------------------------
// Initialization
//--------------------------------------------------------------------------
this.init = function (song) {
// Define the song
mSong = song;
// Init iteration state variables
mLastRow = song.endPattern - 2;
mCurrentCol = 0;
// Prepare song info
mNumWords = song.rowLen * song.patternLen * (mLastRow + 1) * 2;
// Create work buffer (initially cleared)
mMixBuf = new Int32Array(mNumWords);
};
//--------------------------------------------------------------------------
// Public methods
//--------------------------------------------------------------------------
// Generate audio data for a single track
this.generate = function () {
// Local variables
var i, j, b, p, row, col, n, cp,
k, t, lfor, e, x, rsample, rowStartSample, f, da;
// Put performance critical items in local variables
var chnBuf = new Int32Array(mNumWords),
instr = mSong.songData[mCurrentCol],
rowLen = mSong.rowLen,
patternLen = mSong.patternLen;
// Clear effect state
var low = 0, band = 0, high;
var lsample, filterActive = false;
// Clear note cache.
var noteCache = [];
// Patterns
for (p = 0; p <= mLastRow; ++p) {
cp = instr.p[p];
// Pattern rows
for (row = 0; row < patternLen; ++row) {
// Execute effect command.
var cmdNo = cp ? instr.c[cp - 1].f[row] : 0;
if (cmdNo) {
instr.i[cmdNo - 1] = instr.c[cp - 1].f[row + patternLen] || 0;
// Clear the note cache since the instrument has changed.
if (cmdNo < 14) {
noteCache = [];
}
}
// Put performance critical instrument properties in local variables
var oscLFO = mOscillators[instr.i[13]],
lfoAmt = instr.i[14] / 512,
lfoFreq = Math.pow(2, instr.i[15] - 9) / rowLen,
fxLFO = instr.i[16],
fxFilter = instr.i[17],
fxFreq = instr.i[18] * 43.23529 * 3.141592 / 44100,
q = 1 - instr.i[19] / 255,
dist = instr.i[20] * 1e-5,
drive = instr.i[21] / 32,
panAmt = instr.i[22] / 512,
panFreq = 6.283184 * Math.pow(2, instr.i[23] - 9) / rowLen,
dlyAmt = instr.i[24] / 255,
dly = instr.i[25] * rowLen;
// Calculate start sample number for this row in the pattern
rowStartSample = (p * patternLen + row) * rowLen;
// Generate notes for this pattern row
for (col = 0; col < 4; ++col) {
n = cp ? instr.c[cp - 1].n[row + col * patternLen] : 0;
if (n) {
if (!noteCache[n]) {
noteCache[n] = createNote(instr, n);
}
// Copy note from the note cache
var noteBuf = noteCache[n];
for (j = 0, i = rowStartSample * 2; j < noteBuf.length; j++, i += 2) {
chnBuf[i] += noteBuf[j];
}
}
}
// Perform effects for this pattern row
for (j = 0; j < rowLen; j++) {
// Dry mono-sample
k = (rowStartSample + j) * 2;
rsample = chnBuf[k];
// We only do effects if we have some sound input
if (rsample || filterActive) {
// State variable filter
f = fxFreq;
if (fxLFO) {
f *= oscLFO(lfoFreq * k) * lfoAmt + 0.5;
}
f = 1.5 * Math.sin(f);
low += f * band;
high = q * (rsample - band) - low;
band += f * high;
rsample = fxFilter == 3 ? band : fxFilter == 1 ? high : low;
// Distortion
if (dist) {
rsample *= dist;
rsample = rsample < 1 ? rsample > -1 ? osc_sin(rsample*.25) : -1 : 1;
rsample /= dist;
}
// Drive
rsample *= drive;
// Is the filter active (i.e. still audiable)?
filterActive = rsample * rsample > 1e-5;
// Panning
t = Math.sin(panFreq * k) * panAmt + 0.5;
lsample = rsample * (1 - t);
rsample *= t;
} else {
lsample = 0;
}
// Delay is always done, since it does not need sound input
if (k >= dly) {
// Left channel = left + right[-p] * t
lsample += chnBuf[k-dly+1] * dlyAmt;
// Right channel = right + left[-p] * t
rsample += chnBuf[k-dly] * dlyAmt;
}
// Store in stereo channel buffer (needed for the delay effect)
chnBuf[k] = lsample | 0;
chnBuf[k+1] = rsample | 0;
// ...and add to stereo mix buffer
mMixBuf[k] += lsample | 0;
mMixBuf[k+1] += rsample | 0;
}
}
}
// Next iteration. Return progress (1.0 == done!).
mCurrentCol++;
return mCurrentCol / 8;
};
// Create a WAVE formatted Uint8Array from the generated audio data
this.createWave = function() {
// Create WAVE header
var l1 = mNumWords * 2 - 8;
var l2 = l1 - 36;
var headerLen = 44;
var wave = new Uint8Array(headerLen + mNumWords * 2);
wave.set(
[82,73,70,70,
l1 & 255,(l1 >> 8) & 255,(l1 >> 16) & 255,(l1 >> 24) & 255,
87,65,86,69,102,109,116,32,16,0,0,0,1,0,2,0,
68,172,0,0,16,177,2,0,4,0,16,0,100,97,116,97,
l2 & 255,(l2 >> 8) & 255,(l2 >> 16) & 255,(l2 >> 24) & 255]
);
// Append actual wave data
for (var i = 0, idx = headerLen; i < mNumWords; ++i) {
// Note: We clamp here
var y = mMixBuf[i];
y = y < -32767 ? -32767 : (y > 32767 ? 32767 : y);
wave[idx++] = y & 255;
wave[idx++] = (y >> 8) & 255;
}
// Return the WAVE formatted typed array
return wave;
};
// Get n samples of wave data at time t [s]. Wave data in range [-2,2].
this.getData = function(t, n) {
var i = 2 * Math.floor(t * 44100);
var d = new Array(n);
for (var j = 0; j < 2*n; j += 1) {
var k = i + j;
d[j] = t > 0 && k < mMixBuf.length ? mMixBuf[k] / 32768 : 0;
}
return d;
};
};