Skip to content

Commit

Permalink
sfx
Browse files Browse the repository at this point in the history
  • Loading branch information
csecskedics committed Sep 3, 2018
1 parent 351fd99 commit f41f717
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 11 deletions.
5 changes: 4 additions & 1 deletion dist/index.html
Expand Up @@ -3,7 +3,7 @@
<head>
<title>Offliner</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link href="style.css" rel="stylesheet">
Expand All @@ -16,6 +16,9 @@ <h2>NEPTUNE</h2>
<li style="background-image:radial-gradient(circle at 50% 100%, #133, #255 50%, #466 60%, #133 61%, #000 63%)">
<h2>URANUS</h2>
</li>
<li style="background-image:radial-gradient(circle at 50% 100%, #650, #973 50%, #ca6 60%, #650 61%, #000 63%, #000 73%, #666 75%, #666 88%, #000 90%)">
<h2>SATURN</h2>
</li>
<li style="background-image:radial-gradient(circle at 50% 100%, #320, #540 60%, #650 70%, #320 71%, #000 75%)">
<h2>JUPITER</h2>
</li>
Expand Down
2 changes: 1 addition & 1 deletion dist/script.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/Game/Enemy.ts
Expand Up @@ -28,8 +28,9 @@ namespace Game {

intersect(hero: Hero) {
if (this.active && this.collider.intersect(hero.collider)) {
SFX.play('exp');
if (hero.speedTime) {
hero.tokens += 25;
hero.tokens += 10;
this.explode = 7;
return;
}
Expand Down
18 changes: 15 additions & 3 deletions src/Game/Hero.ts
Expand Up @@ -46,31 +46,43 @@ namespace Game {
left() {
if (this.x >= 0) {
this.x--;
SFX.play('move');
}
}

right() {
if (this.x <= 0) {
this.x++;
SFX.play('move');
}
}

jump() {
if (this.collide) {
this.acc = .065;
SFX.play('jump');
}
}

boost() {
this.speedTime = 75;
SFX.play('move');
}

magnetize() {
this.tokens += 5;
this.magnetTime = 450;
SFX.play('power');
}

dash() {
this.scaleTime = 40;
SFX.play('move');
}

coin() {
this.tokens += 1;
SFX.play('coin');
}

cancel() {
Expand All @@ -81,13 +93,13 @@ namespace Game {
let pos = this.transform.translate,
scale = this.scale,
rotate = this.transform.rotate,
speed = (this.speedTime ? .12 : .08) + Math.min(this.distance / 10000, .04);
speed = (this.speedTime ? .12 : .08) + Math.min(this.distance / 50000, .04);
this.speed.z += ((this.active ? speed : 0) - this.speed.z) / 20;
this.speedTime -= this.speedTime > 0 ? 1 : 0;
this.color = this.speedTime ? COLOR.GREY : COLOR.WHITE;
this.color = this.magnetTime > 100 || this.magnetTime % 20 > 10
? (this.speedTime ? COLOR.PURPLE : COLOR.PINK)
: (this.speedTime ? COLOR.GREY : COLOR.WHITE);
? (this.speedTime ? COLOR.PINK : COLOR.PURPLE)
: (this.speedTime ? COLOR.WHITE : COLOR.GREY);
this.scale += ((this.scaleTime ? .4 : .7) - this.scale) / 5;
this.scaleTime -= this.scaleTime > 0 ? 1 : 0;
this.magnetTime -= this.magnetTime > 0 ? 1 : 0;
Expand Down
1 change: 0 additions & 1 deletion src/Game/Scene.ts
Expand Up @@ -51,7 +51,6 @@ namespace Game {
next() {
if (this.planet > 0) {
this.planets.item(this.planet--).className = 'hide';
console.log('next');
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Game/Token.ts
Expand Up @@ -16,7 +16,7 @@ namespace Game {
}

score() {
return this.big ? 10 : 1;
return this.big ? 5 : 1;
}

update() {
Expand All @@ -37,9 +37,10 @@ namespace Game {
let pos = this.collider.getTranslate();
if (pos.distance(hero.transform.translate) < .5) {
this.active = false;
hero.tokens += this.score();
if (this.big) {
hero.magnetize();
} else {
hero.coin();
}
return;
}
Expand Down
5 changes: 4 additions & 1 deletion src/index.html
Expand Up @@ -3,7 +3,7 @@
<head>
<title>Offliner</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link href="style.css" rel="stylesheet">
Expand All @@ -16,6 +16,9 @@ <h2>NEPTUNE</h2>
<li style="background-image:radial-gradient(circle at 50% 100%, #133, #255 50%, #466 60%, #133 61%, #000 63%)">
<h2>URANUS</h2>
</li>
<li style="background-image:radial-gradient(circle at 50% 100%, #650, #973 50%, #ca6 60%, #650 61%, #000 63%, #000 73%, #666 75%, #666 88%, #000 90%)">
<h2>SATURN</h2>
</li>
<li style="background-image:radial-gradient(circle at 50% 100%, #320, #540 60%, #650 70%, #320 71%, #000 75%)">
<h2>JUPITER</h2>
</li>
Expand Down
38 changes: 37 additions & 1 deletion src/script.ts
Expand Up @@ -45,6 +45,7 @@ namespace Game {
}

let canvas: HTMLCanvasElement = <HTMLCanvasElement>$('#game'),
music: AudioBufferSourceNode,
menu: Menu = new Menu(),
time: number = new Date().getTime(),
gl: WebGLRenderingContext = canvas.getContext('webgl'),
Expand Down Expand Up @@ -112,7 +113,14 @@ namespace Game {
platform.fence = fence;
platform.enemy = enemy;
return platform.add(block).add(token).add(fence).add(enemy);
}, new Map('4111|311110003115|211135012111|301521513510|205120052051|311119973111|5111111d|3111|5713|551111dd', 5, 200));
}, new Map(
'4111|311110003115|211135012111|'+
'3111|311737173711|311119973111|'+
'301531513510|401510004510|5111111d|'+
'305130053051|2111|5713|551111dd',
6,
150
));

function resize() {
canvas.width = canvas.clientWidth;
Expand Down Expand Up @@ -173,6 +181,7 @@ namespace Game {
if (e.keyCode == 32) {
menu.hide();
scene.init();
play();
}
return;
}
Expand All @@ -181,10 +190,20 @@ namespace Game {
on($('#play'), 'click', () => {
menu.hide();
scene.init();
play();
});
on(window, 'resize', resize);
}

function play() {
if (!music) {
SFX.mixer('music').gain.value = .3;
SFX.play('music', true, 'music').then(buffer => {
music = buffer;
});
}
}

function render(item: T3D.Item, stroke: number = 0) {
item.childs.forEach(child => {
render(child, stroke);
Expand Down Expand Up @@ -228,10 +247,27 @@ namespace Game {
if (scene.ended()) {
menu.score(scene.score());
menu.show();
if (music) {
music.stop();
music = null;
}
}
}

on(window, 'load', () => {
Promise.all([
new SFX.Sound('custom', [5, 1, 0], 1).render('exp', [220,110], .5),
new SFX.Sound('square', [.5, .1, 0], 1).render('power', [440,880,440,880,440,880,440,880], .3),
new SFX.Sound('triangle', [.5, .1, 0], 1).render('jump', [220,880], .3),
new SFX.Sound('square', [.2, .1, 0], .2).render('coin', [1760,1760], .2),
new SFX.Sound('custom', [.1, .5, 0], .3).render('move', [1760,440], .3),
SFX.render('music', [
new SFX.Channel(new SFX.Sound('sawtooth', [1, .3], .1), '8a2,8a2,8b2,8c3|8|8g2,8g2,8a2,8b2|8|8e2,8e2,8f2,8g2|4|8g2,8g2,8a2,8b2|4|', 1),
new SFX.Channel( new SFX.Sound('sawtooth', [.5, .3], 1), '2a3,2a3e4,2a3d4,2a3e4|2|2g3,2g3d4,2g3c4,2g3d4|2|2e3,2e3a3,2e3b3,2e3a3,1g3b3,1g3c4|', 1)
])
]).then(() => {
//play();
});
camera.position.set(0, .5, 5);
camera.rotate.x = -.7;
gl.clearColor(0, 0, 0, 0);
Expand Down
178 changes: 178 additions & 0 deletions src/sfx.ts
@@ -0,0 +1,178 @@
namespace SFX {

const bitrate = 44100;
const keys = { c: 0, db: 1, d: 2, eb: 3, e: 4, f: 5, gb: 6, g: 7, ab: 8, a: 9, bb: 10, b: 11 };
const freq = [];
const out = new AudioContext();
const ctx = new OfflineAudioContext(1, bitrate * 2, bitrate);
const noise = ctx.createBuffer(1, bitrate * 2, bitrate);
const output = noise.getChannelData(0);
const buffers : { [id: string]: AudioBuffer } = {};
const gains: { [id: string]: GainNode } = {};

let a = Math.pow(2, 1 / 12);
for (let n = -57; n < 50; n++) {
freq.push(Math.round(Math.pow(a, n) * 44000) / 100);
}

for (let i = 0; i < bitrate * 2; i++) {
output[i] = Math.random() * 2 - 1;
}

export class Sound {

type: OscillatorType;
curve: Float32Array;
length: number;

constructor(type: OscillatorType, curve: number[], length: number) {
this.type = type;
this.curve = Float32Array.from(curve);
this.length = length;
}

time(max): number {
return max < this.length ? max - .01 : this.length;
}

async render(id: string, freq: number[], time: number): Promise<void> {
let ctx = new OfflineAudioContext(1, bitrate * time, bitrate),
vol = ctx.createGain(),
curve = Float32Array.from(freq);
vol.gain.value = 1;
vol.connect(ctx.destination);
if (this.curve) {
vol.gain.setValueCurveAtTime(this.curve, 0, this.time(time));
}
if (this.type == 'custom') {
let src = ctx.createBufferSource(),
filter = ctx.createBiquadFilter();
filter.connect(vol);
filter.detune.setValueCurveAtTime(curve, 0, time);
src.buffer = noise;
src.loop = true;
src.connect(filter);
src.start();
} else {
let osc = ctx.createOscillator();
osc.type = this.type;
osc.frequency.setValueCurveAtTime(curve, 0, time);
osc.connect(vol);
osc.start();
osc.stop(time);
}
buffers[id] = await ctx.startRendering();
}

}

export class Channel {

inst: Sound;
data: number[][];
size: number;
length: number;

constructor(inst: Sound, notes: string, tempo: number) {
this.inst = inst;
this.data = [];
this.size = 0;
this.length = 0;
let sheet = notes.split('|');
if (sheet.length > 1) {
notes = '';
for (let i = 0; i < sheet.length; i++) {
notes += i % 2
? (',' + sheet[i-1]).repeat(parseInt(sheet[i]) - 1)
: (notes ? ',' : '') + sheet[i];
}
}
notes.split(',').forEach((code) => {
let div = code.match(/^(\d+)/),
freqs = code.match(/([a-z]+\d+)/g);
if (div) {
let time = tempo / parseInt(div[1]),
row = [time];
this.length += time;
if (freqs) {
if (freqs.length > this.size) {
this.size = freqs.length;
}
for (let i = 0; i < freqs.length; i++) {
let note = freqs[i].match(/([a-z]+)(\d+)/);
if (note) {
row.push(freq[parseInt(note[2]) * 12 + keys[note[1]]]);
}
}
}
this.data.push(row);
}
});
}

play(ctx: OfflineAudioContext) {
let length = 0,
inst = this.inst,
vol = ctx.createGain(),
osc = [];
vol.gain.value = 1;
vol.connect(ctx.destination);
for (let i = 0; i < this.size; i++) {
osc[i] = ctx.createOscillator();
osc[i].type = inst.type;
osc[i].connect(vol);
}
this.data.forEach(note => {
if (inst.curve) {
vol.gain.setValueCurveAtTime(inst.curve, length, inst.time(note[0]));
}
osc.forEach((o, i) => {
o.frequency.setValueAtTime(note[i + 1] || 0, length);
});
length += note[0];
});
osc.forEach(o => {
o.start();
o.stop(length);
});
}

}

export async function render(id: string, channels: Channel[]): Promise<void> {
let length = 0;
channels.forEach(channel => {
if (channel.length > length) {
length = channel.length;
}
});
const ctx = new OfflineAudioContext(1, bitrate * length, bitrate);
channels.forEach((channel, i) => {
channel.play(ctx);
});
buffers[id] = await ctx.startRendering();
}

export function mixer(id: string): GainNode {
if (!(id in gains)) {
gains[id] = out.createGain();
gains[id].connect(out.destination);
}
return gains[id];
}

export async function play(id: string, loop: boolean = false, mixerId: string = 'master'): Promise<AudioBufferSourceNode> {
await out.resume();
if (id in buffers) {
let src = out.createBufferSource();
if (loop) {
src.loop = true;
}
src.buffer = buffers[id];
src.connect(mixer(mixerId));
src.start();
return src;
}
return null;
}
}

0 comments on commit f41f717

Please sign in to comment.