Skip to content

Commit

Permalink
Initial typescript migration.
Browse files Browse the repository at this point in the history
  • Loading branch information
Katharine committed Jan 19, 2018
1 parent 6a9dd8b commit 7e8f6bb
Show file tree
Hide file tree
Showing 28 changed files with 5,543 additions and 350 deletions.
3 changes: 0 additions & 3 deletions .babelrc

This file was deleted.

13 changes: 7 additions & 6 deletions assets/js/index.js → assets/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import Cookies from "js-cookie";
import * as Cookies from "js-cookie";
import {isCompatible} from "./lib/compat";

import css from "../css/index.css";
import "../css/index.css";
import ponytones from "../img/ponytones.png";

window.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('ponytone-image')) {
document.getElementById('ponytone-image').src = ponytones;
let img = <HTMLImageElement>document.getElementById('ponytone-image');
if (img) {
img.src = ponytones;
if (isCompatible()) {
document.getElementById('partybutton').onclick = () => createParty();
} else {
document.getElementById('partybutton').parentNode.innerHTML = `
(<HTMLElement>document.getElementById('partybutton').parentNode).innerHTML = `
<p>Unfortunately, your browser is not supported. Try <a href="https://chrome.google.com">Google Chrome</a>.
`
}
Expand All @@ -25,7 +26,7 @@ async function createParty() {
});
let text = await request.text();
document.getElementById('overlay').style.display = 'block';
let a = document.getElementById('targetlink');
let a = <HTMLAnchorElement>document.getElementById('targetlink');
a.href += text;
a.innerHTML += text;
document.getElementById('beginbutton').onclick = () => location.href = a.href;
Expand Down
71 changes: 50 additions & 21 deletions assets/js/lib/audio/live.js → assets/js/lib/audio/live.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import {getNoteFromBuffer} from "./pitch";
import {getNoteFromBuffer, Note} from "./pitch";
import {getAudioContext} from "../util/audio-context";
import EventEmitter from "events";
import {Song} from "../ultrastar/parser";
import {EventEmitter} from "events";


const SAMPLES_PER_BUFFER = 1024;
const BUFFERS_REQUIRED = 1; // I'm not convinced this works properly when > 1
const TOTAL_SAMPLES = SAMPLES_PER_BUFFER * BUFFERS_REQUIRED;

export class LiveAudio extends EventEmitter {
ready: boolean;
onnote: (note: Note) => void;
source: MediaStreamAudioSourceNode;

private context: AudioContext;
private analyser: ScriptProcessorNode;
private biquad: BiquadFilterNode;
private dummy: AnalyserNode;
private gain: GainNode;
private stream: MediaStream;
private _buffers: Float32Array[];

constructor() {
super();
this.ready = false;
Expand All @@ -27,23 +40,23 @@ export class LiveAudio extends EventEmitter {
this._getMedia();
}

async _getMedia() {
private async _getMedia(): Promise<void> {
try {
this.stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false,
googAutoGainControl: false,
mozAutoGainControl: false,
googNoiseSuppression: false,
mozNoiseSuppression: false,
// echoCancellation: false,
// noiseSuppression: false,
// autoGainControl: false,
// googAutoGainControl: false,
// mozAutoGainControl: false,
// googNoiseSuppression: false,
// mozNoiseSuppression: false,
}
});
} catch (e) {
this.ready = false;
console.error("Media failed.");
console.log(err);
console.log(e);
this.emit("failed");
}

Expand All @@ -55,7 +68,7 @@ export class LiveAudio extends EventEmitter {
this.ready = true;
}

_processAudio(e) {
_processAudio(e: AudioProcessingEvent): void {
let buffer = e.inputBuffer.getChannelData(0);
if (this._buffers.length >= BUFFERS_REQUIRED) {
this._buffers.shift();
Expand All @@ -78,7 +91,7 @@ export class LiveAudio extends EventEmitter {
}
}

static isAudioAvailable() {
static isAudioAvailable(): boolean {
if (!(window.AudioContext || window.webkitAudioContext)) {
return false;
}
Expand All @@ -88,38 +101,54 @@ export class LiveAudio extends EventEmitter {
return true;
}

static requestPermissions() {
static requestPermissions(): void {
navigator.mediaDevices.getUserMedia({audio: true});
}
}

export interface AudioTrack {
currentTime: number;
}

export interface SungNote {
time: number;
note: number;
}

export class Singing {
constructor(song, audio) {
song: Song;
audio: LiveAudio;
track: AudioTrack;
notes: SungNote[];
currentBeat: number;
samples: number;
average: number;

constructor(song: Song, audio: AudioTrack) {
this.song = song;
this.audio = new LiveAudio();
this.track = audio;
this.interval = null;
this.notes = [];
this.currentBeat = -1;
this.samples = 0;
this.average = 0;
}

start() {
start(): void {
this.currentBeat = -1;
this.samples = 0;
this.average = 0;
this.audio.onnote = (note) => this._addNote(note);
}

stop() {
stop(): void {
this.audio.onnote = null;
if (this.audio.source) {
this.audio.source.disconnect();
}
}

_addNote(note) {
_addNote(note: Note): void {
let beat = this.song.msToBeats((this.track.currentTime*1000)|0) - 2;
if (beat < 0 || beat <= this.currentBeat) {
return;
Expand All @@ -138,11 +167,11 @@ export class Singing {
}
}

notesInRange(start, end) {
notesInRange(start: number, end: number): SungNote[] {
return this.notes.filter((x) => start <= x.time && x.time < end);
}

get ready() {
return audio.ready;
return this.audio.ready;
}
}
25 changes: 16 additions & 9 deletions assets/js/lib/audio/pitch.js → assets/js/lib/audio/pitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
const MIN_RMS = 0.01;
const GOOD_ENOUGH_CORRELATION = 0.9; // this is the "bar" for how close a correlation needs to be

function autoCorrelate(buffer, sampleRate) {
export interface Note {
freq: number;
number: number;
name: string;
offset: number;
}

function autoCorrelate(buffer: Float32Array, sampleRate: number): number {
// Keep track of best period/correlation
let bestPeriod = 0;
let bestCorrelation = 0;
Expand Down Expand Up @@ -42,7 +49,7 @@ function autoCorrelate(buffer, sampleRate) {

// Abort if not enough signal
if (rms < MIN_RMS) {
return false;
return null;
}

/**
Expand Down Expand Up @@ -145,26 +152,26 @@ function autoCorrelate(buffer, sampleRate) {
}


let noteStrings = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const noteStrings = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];

function noteNumberFromPitch(frequency) {
function noteNumberFromPitch(frequency: number): number {
let noteNum = 12 * (Math.log(frequency / 440)/Math.log(2));
return Math.round(noteNum) + 69;
}

function noteNameFromNumber(number) {
function noteNameFromNumber(number: number): string {
return noteStrings[number % 12];
}

function frequencyFromNoteNumber( note ) {
function frequencyFromNoteNumber(note: number): number {
return 440 * Math.pow(2,(note-69)/12);
}

function centsOffFromPitch(frequency, note) {
function centsOffFromPitch(frequency: number, note: number): number {
return Math.floor(1200 * Math.log(frequency / frequencyFromNoteNumber(note))/Math.log(2));
}

export function getNoteFromBuffer(buffer, sampleRate) {
export function getNoteFromBuffer(buffer: Float32Array, sampleRate: number): Note {
let freq = autoCorrelate(buffer, sampleRate);
if (!freq) {
return {freq: null, number: null, name: null, offset: null};
Expand All @@ -176,4 +183,4 @@ export function getNoteFromBuffer(buffer, sampleRate) {
name: noteNameFromNumber(number),
offset: centsOffFromPitch(freq, number)
}
}
}
10 changes: 4 additions & 6 deletions assets/js/lib/compat.js → assets/js/lib/compat.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use strict";

import {getAudioContext} from "./util/audio-context";

export function isCompatible() {
export function isCompatible(): boolean {
try {
if (!getAudioContext()) {
console.error("No audio context.");
Expand All @@ -29,8 +27,8 @@ export function isCompatible() {
return false;
}
try {
let p = getAudioContext().decodeAudioData(1);
if (!p instanceof Promise) {
let p = getAudioContext().decodeAudioData(<any>1); // intentional type violation to test error handling
if (!(p instanceof Promise)) {
console.error("decodeAudioData doesn't return a Promise.");
return false;
}
Expand Down Expand Up @@ -58,4 +56,4 @@ export function isCompatible() {
return false;
}
return true;
}
}
Loading

0 comments on commit 7e8f6bb

Please sign in to comment.