poly-peach
Version:
A targeted pitch-detection library for node in the browser
691 lines (690 loc) • 41.5 kB
JavaScript
"use strict";
/* global Cookies openToast closeAlert ABCJS backPageWithTitle openPageWithTitle closeMenu createAudioMeter */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PolyPeachDetector = void 0;
var Tone = __importStar(require("tone"));
var chromagram_1 = require("./chromagram");
var DEBUG = true;
var MIN_VOLUME = 5;
// The number of harmonic frequencies to check for looking to see if a pitch exists.
var MIN_HARMONICS = 3; //some false positives caused by small harmonic overlap?
//const MAX_ERROR_THRESHOLD = 1.0; // @4 harmonics, excludes some notes and doesn't catch doublestops well, @3, too much error on doublestops
//const MAX_ERROR_THRESHOLD = 1.5; //
var MAX_ERROR_THRESHOLD = 2; //
//const MAX_ERROR_THRESHOLD = 3; //
//const MIN_HARMONICS = 4;
//const MAX_ERROR_THRESHOLD = 1.25;
var MAX_HARMONICS = 10;
// The minimum amount of max sound amplitude present before pitches will be checked.
var MIN_LEVEL_FOR_SOUND = 2;
var DEFAULT_HARMONICS = 2;
//const DEFAULT_OCTAVES = 4;
var DEFAULT_BINS = 2;
// No harmonics lower than this will be tracked. All signature data will be relative to this.
var DEFAULT_REFERENCE_NOTE = 'E2';
//const DEFAULT_REFERENCE_NOTE = 'A1';
var DEFAULT_NUMBER_OF_OCTAVES = 6;
var UPPER_CUTOFF_MIDI_NOTE = 91;
var NOTES_PER_OCTAVE = 12;
var PIXELS_PER_NOTE = 40; //10;
var SEND_AUDIO = true;
var MAX_SAMPLES_SENT = 0; //10;
var VIOLIN = 'violin';
var CLASSICAL_GUITAR = 'classical-guitar';
var DEFAULT_SIGNATURE_TYPE = CLASSICAL_GUITAR;
// To record new signatures:
// 1. cd into ./examples
// 2. run ./runserver
// 3. enter note to track
// 4. click "start/stop" to enable microphone, clicking "allow" if your browser asks for permission
// 5. click "Record Signature"
// 6. play the note on the instrument
// 7. copy and paste the array from the browser console.
var SIGNATURES = {}; // {instrument_type: {note: [normalized level of 1st harmonic, 2nd, etc.]}}
SIGNATURES[CLASSICAL_GUITAR] = {};
SIGNATURES[CLASSICAL_GUITAR]['B1'] = [0.008353722475263978, 0.08353722475263978, 0.24922334320976444, 0.21343707037889467, 0.08451587405792234, 0.17224138200379366, 0.033774901708252765, 0.06911056800073963, 0.05806738126941937, 0.027738532143309357];
SIGNATURES[CLASSICAL_GUITAR]['C2'] = [0.01714748510848196, 0.1714748510848196, 0.30549932824977016, 0.1593114605402403, 0.11062781202791976, 0.06184840797192317, 0.04204884018654787, 0.042482957620565455, 0.05339368161661821, 0.036165175593113594];
SIGNATURES[CLASSICAL_GUITAR]['D2'] = [0.025229232239432863, 0.25229232239432864, 0.14094727664008924, 0.1734900507321308, 0.10845957248258795, 0.10996644391858704, 0.010030969047985007, 0.1120414001657053, 0.05370098154720274, 0.01384175083195036];
// 6st string.
SIGNATURES[CLASSICAL_GUITAR]['E2'] = [0.122295850941197, 0.27716587569620493, 0.1046082802481929, 0.05761219339793058, 0.12444229293881741, 0.15180154548623329, 0.053207216628688006, 0.02185373479648957, 0.06224104688970442, 0.024771962976541564];
SIGNATURES[CLASSICAL_GUITAR]['F2'] = [0.15259686104111125, 0.28668328011189786, 0.11993617759324385, 0.08804660602158135, 0.15614909453650105, 0.08070953117753697, 0.013026241192724324, 0.0474708679595814, 0.022405788673492494, 0.03297555169232946];
SIGNATURES[CLASSICAL_GUITAR]['F#2'] = [0.19981994584211402, 0.24276112744798772, 0.13648929793835604, 0.14170221027382515, 0.1203684431613425, 0.06036032903502198, 0.023712159055240007, 0.03610047645095376, 0.02473857486053893, 0.013947435934619772];
SIGNATURES[CLASSICAL_GUITAR]['G2'] = [0.22618409995563174, 0.20155479389616904, 0.10257553556556541, 0.17439074032147298, 0.11695759614650089, 0.07192534944260795, 0.01741167753093079, 0.05359579223273711, 0.017342094669194272, 0.018062320239189782];
SIGNATURES[CLASSICAL_GUITAR]['G#2'] = [0.33466280713665697, 0.3043186039403842, 0.07984114631986007, 0.07504719822409595, 0.08339294470532709, 0.034448030796226596, 0.016805303295821954, 0.03214603322839447, 0.016106413595188846, 0.02323151875804414];
// 5th string.
SIGNATURES[CLASSICAL_GUITAR]['A2'] = [0.21305179051991668, 0.3236132470349405, 0.1368776078426075, 0.08019394247329312, 0.06972486852464517, 0.0625289093161398, 0.026538690377956985, 0.03750825592137052, 0.02829046185499598, 0.021672226134133825];
SIGNATURES[CLASSICAL_GUITAR]['A#2'] = [0.2756962345230539, 0.33860423891732877, 0.066235334238123, 0.06052939171600377, 0.15440572227975335, 0.02190650792742798, 0.02349425709613552, 0.02344385846113954, 0.017135039932272396, 0.01854941490876166];
SIGNATURES[CLASSICAL_GUITAR]['B2'] = [0.4363488985495379, 0.15176725793302612, 0.2068833578050863, 0.08769104213654381, 0.02641690692140292, 0.02070560523161303, 0.013690927827612422, 0.023548212061537538, 0.01098217927159482, 0.021965612262044923];
SIGNATURES[CLASSICAL_GUITAR]['C3'] = [0.37268696402710794, 0.25097732930212985, 0.17437340898603107, 0.12202075147504517, 0.02875668005157027, 0.016617394811492894, 0.010077927794351954, 0.007663981931853062, 0.010605381862906875, 0.006220179757510845];
SIGNATURES[CLASSICAL_GUITAR]['C#3'] = [0.38355481044917034, 0.29048633651878, 0.04404286832530228, 0.14825954168398223, 0.04614102248568601, 0.02563601483376563, 0.01846847022681665, 0.020433747981030707, 0.006748730873919201, 0.016228456621546886];
// 4th string.
SIGNATURES[CLASSICAL_GUITAR]['D3'] = [0.19052171971039922, 0.23573111621152357, 0.1110230492189988, 0.10667965932904586, 0.08593086337050101, 0.12243648655075469, 0.025705879617473015, 0.04881641269592747, 0.05448051604126656, 0.01867429725410989];
SIGNATURES[CLASSICAL_GUITAR]['D#3'] = [0.30326866301242844, 0.3547987449559368, 0.10964822024998035, 0.056454684519237755, 0.07005368551875059, 0.029817329381958996, 0.020358047500074385, 0.017265382025746737, 0.019627604697326377, 0.018707638138559513];
SIGNATURES[CLASSICAL_GUITAR]['E3'] = [0.31950839385316243, 0.31400405685970206, 0.12961389057380995, 0.0494357280400105, 0.08862879708918667, 0.029648356262953222, 0.01302980525786959, 0.02310527252053414, 0.018829161756370606, 0.014196537786400674];
SIGNATURES[CLASSICAL_GUITAR]['F3'] = [0.4227381755980699, 0.2504423221601583, 0.08568165991472737, 0.09200226597190139, 0.05182182288232276, 0.02415415578515949, 0.02576502249938607, 0.02386761976802234, 0.007556108329283733, 0.01597084709096853];
SIGNATURES[CLASSICAL_GUITAR]['F#3'] = [0.37328046462767045, 0.3278589163502336, 0.06379871695069021, 0.07906126070454116, 0.03660305804442305, 0.025974525817361253, 0.02483289883687511, 0.025017444009689527, 0.021320893016365933, 0.022251821642149675];
// 3rd string.
SIGNATURES[CLASSICAL_GUITAR]['G3'] = [0.3478124168956828, 0.2667452323382379, 0.15271960293094702, 0.02373241402731488, 0.02790539428432555, 0.106944445686604, 0.01977932481312516, 0.018209953940704866, 0.01749100412568327, 0.01866021095737449];
SIGNATURES[CLASSICAL_GUITAR]['G#3'] = [0.4599766355388572, 0.26320702950972713, 0.04322157569460409, 0.04072061851410703, 0.07674196277631087, 0.014509616643671595, 0.03049577738129965, 0.02664493809175701, 0.025683661518831023, 0.018798184330834345];
SIGNATURES[CLASSICAL_GUITAR]['A3'] = [0.5828021638313978, 0.13567284130713392, 0.030422531853270485, 0.04765921843151788, 0.07626200067141474, 0.017910681470522043, 0.03035879868206245, 0.03149735627867146, 0.02960604433652343, 0.01780836313748575];
SIGNATURES[CLASSICAL_GUITAR]['A#3'] = [0.5609431762836757, 0.11281271296528593, 0.035094436972956264, 0.030622746291425224, 0.10462088519913475, 0.03382399838070928, 0.03610938736690961, 0.014787856554261755, 0.03485905877721128, 0.03632574120843019];
// 2nd string.
//SIGNATURES[CLASSICAL_GUITAR]['B3'] = [0.45812360766117177, 0.22569654387239138, 0.08385472881295354, 0.03784660709035133, 0.067932984065355, 0.043595046248842105, 0.022832206170645086, 0.016670699901498897, 0.018432353879482378, 0.025015222297308768];
SIGNATURES[CLASSICAL_GUITAR]['B3'] = [0.20416023768959102, 0.3475973381378323, 0.1602396585574644, 0.0991694407643142, 0.051813370130704334, 0.03809460701686076, 0.023090131061721652, 0.03134701544176759, 0.024464470033069365, 0.02002373116667437];
SIGNATURES[CLASSICAL_GUITAR]['C4'] = [0.4265380262694899, 0.2014094294439763, 0.07040921204851498, 0.030215480742709747, 0.16398363158284152, 0.018920963069382898, 0.03498375462512141, 0.014281689796883143, 0.010947858865323937, 0.028309953555755996];
SIGNATURES[CLASSICAL_GUITAR]['C#4'] = [0.3947819111735955, 0.29613240846268435, 0.095024328495909, 0.029305839637546332, 0.04033909780064078, 0.04294226310553229, 0.021882364712289066, 0.04289871088835553, 0.015461724584054813, 0.021231351139392455];
SIGNATURES[CLASSICAL_GUITAR]['D4'] = [0.4613202657508963, 0.19895166585883253, 0.04533505806707816, 0.08074094582262469, 0.10304533421659158, 0.03077555756617126, 0.016365371942191967, 0.014581829048814566, 0.02434462605591637, 0.02453934567088261];
SIGNATURES[CLASSICAL_GUITAR]['D#4'] = [0.5854065741539815, 0.11778358901506492, 0.04995925536733159, 0.03518609309823251, 0.05215560594391319, 0.04590253722484267, 0.01809519281777682, 0.04347989801709912, 0.02047184510552993, 0.03155940925622779];
// 1st string.
SIGNATURES[CLASSICAL_GUITAR]['E4'] = [0.4227419009962166, 0.22302453943146514, 0.10955192220930718, 0.05993144832265702, 0.038007787319864726, 0.0420601828393388, 0.02887116223754167, 0.029881439579449, 0.0233191706005812, 0.02261044646357872];
SIGNATURES[CLASSICAL_GUITAR]['F4'] = [0.41951238537990204, 0.24807954900637944, 0.04892334719573636, 0.033681106554851535, 0.10457163283171468, 0.021505430170142018, 0.033429783515443214, 0.034443999892255994, 0.033464667966526876, 0.02238809748704805];
SIGNATURES[CLASSICAL_GUITAR]['F#4'] = [0.6200452653367138, 0.12277575043636056, 0.04308149913316804, 0.03575003289383122, 0.041491526656213196, 0.025467852783021116, 0.01998044187118477, 0.03824589387748832, 0.019784202962627048, 0.03337753404939184];
SIGNATURES[CLASSICAL_GUITAR]['G4'] = [0.6221927406360364, 0.07355226869109247, 0.04012794263149465, 0.0330287884520763, 0.06856591296473696, 0.02402285855911311, 0.042143343951339486, 0.043542079001139676, 0.022644997466246434, 0.030179067646724476];
SIGNATURES[CLASSICAL_GUITAR]['G#4'] = [0.5797332544376933, 0.148789841445366, 0.025548698252446125, 0.04935218631551829, 0.046885362108485665, 0.035757134577954444, 0.033886885397673386, 0.026961940285525777, 0.025745987282593603, 0.02733870989674342];
SIGNATURES[CLASSICAL_GUITAR]['A4'] = [0.47864366810706593, 0.20622321921895564, 0.04586652719995738, 0.06038346656512431, 0.07163933275326169, 0.03569482026277012, 0.027508795242085705, 0.027114940676047052, 0.027745659571490965, 0.01917957040324128];
SIGNATURES[CLASSICAL_GUITAR]['A#4'] = [0.5092562367964059, 0.18991306214034215, 0.045518715140658146, 0.03111248671727977, 0.037026727754982974, 0.0396601928212558, 0.04584257516006542, 0.01591565624357895, 0.038774180898246, 0.04698016632718479];
SIGNATURES[CLASSICAL_GUITAR]['B4'] = [0.5487119427918494, 0.1370791045592195, 0.08594769989772379, 0.0577913634983074, 0.029608941860511873, 0.02660689794326807, 0.023067836990903763, 0.02348441440907624, 0.022595474780219834, 0.04510632326892013];
SIGNATURES[CLASSICAL_GUITAR]['C5'] = [0.6149462161077834, 0.06016983735633915, 0.039621406350026984, 0.034689398761210265, 0.044057270627706896, 0.04368457903186212, 0.03164944297041697, 0.04205920430546308, 0.02762802287841264, 0.061494621610778344];
SIGNATURES[CLASSICAL_GUITAR]['C#5'] = [0.67102231811212, 0.04267008219169093, 0.037615793101930285, 0.03167059633501829, 0.03290609354432486, 0.036667542778211756, 0.022600801036773217, 0.0372883589525666, 0.020456182136152044, 0.067102231811212];
SIGNATURES[CLASSICAL_GUITAR]['D5'] = [0.5896884622375657, 0.0843839635034977, 0.03569883130414447, 0.04733343856634157, 0.03351175343854408, 0.036329786411323325, 0.025616191768596117, 0.022947786297612037, 0.0655209402486184, 0.05896884622375657];
SIGNATURES[CLASSICAL_GUITAR]['D#5'] = [0.3786013504365225, 0.15923227751721877, 0.12347215479184781, 0.06531736952535841, 0.06258435609909092, 0.06202700442390176, 0.026718679132954774, 0.04211985631428361, 0.04206681671516917, 0.03786013504365225];
SIGNATURES[CLASSICAL_GUITAR]['E5'] = [0.5769269488739688, 0.0694908710048988, 0.036725161033295636, 0.027811632839895694, 0.0521782038004998, 0.017797559085498647, 0.025158065545969874, 0.0721158686092461, 0.06410299431932985, 0.05769269488739688];
var RECORD_SIGNATURE_BUTTON_OFF = 'Record Signature';
var RECORD_SIGNATURE_BUTTON_ON = 'Recording... ';
function getHarmonicFrequencyFromNote(note, i) {
// Given a note, returns the Frequency() object associated with it's given ith harmonic.
// Note, i is 1-indexed.
// e.g. getHarmonicNote('E2', 1) == 'E2'
// e.g. getHarmonicNote('E2', 2) == 'E3'
var freqObj = Tone.Frequency(note);
var freq = freqObj.toFrequency();
var harmonicFreq = freq * i;
var harmonicFreqObj = Tone.Frequency(harmonicFreq);
return harmonicFreqObj;
}
function getHarmonicNoteFromNote(note, i) {
// Given a note, returns the note associated with it's given ith harmonic.
// Note, i is 1-indexed.
// e.g. getHarmonicNote('E2', 1) == 'E2'
// e.g. getHarmonicNote('E2', 2) == 'E3'
return getHarmonicFrequencyFromNote(note, i).toNote();
}
function sumArray(ary) {
// Returns the sum of all elements in the array.
return ary.reduce(function (accumulator, a) {
return accumulator + a;
}, 0);
}
function normalizeArray(ary) {
// Scales all array values so that their sum is 1.
var sum = sumArray(ary);
var new_ary = new Array(ary.length);
for (var i = 0; i < ary.length; i++) {
new_ary[i] = ary[i] / sum;
}
return new_ary;
}
function getIndexOfMaxArrayValue(ary) {
return ary.indexOf(Math.max.apply(Math, __spreadArray([], __read(ary))));
}
function scaleArray(ary, s) {
var new_ary = new Array(ary.length);
for (var i = 0; i < ary.length; i++) {
new_ary[i] = ary[i] * s;
}
return new_ary;
}
var PolyPeachDetector = /** @class */ (function () {
function PolyPeachDetector() {
this._pitchVisualizationCtx = null;
this._gradientA = null;
this.microphone_source = null;
this.script_processor_node = null;
this.media_stream = null;
this.histogramProcessor = null;
//private _referenceOctave = null;
this._referenceFrequency = null;
this._lastReferenceFrequency = null;
this._tracking_notes = {}; // {note: [harmonic frequencies]}
this._tracking_notes_index_to_note = {}; //{pitch_index: '<note_letter><octave_number>'}
this._max_level_for_signature = null;
this._max_signature_for_note = null; // {harmonic: level}
this._signature_error = {}; // {note: lowest signature match error seen since last refresh}
this._signature_type = DEFAULT_SIGNATURE_TYPE;
this._display_count = 0;
this._pitchVisualizationCtx = null;
this._gradientA = null;
this._pitchSum = 0;
this._pitchCanvasWidth = null; //40*12*numOctavesToShow;
this._draw = false;
this._running = false;
this._samples_sent = 0;
//this._audioCtx = new AudioContext();
this._audioCtx = new (AudioContext || window.webkitAudioContext)();
this.microphone_source = null;
this.script_processor_node = null;
this.media_stream = null;
this.script_processor_node = null;
this.histogramAudioCtx = null;
this.histogramSource = null;
this.histogramProcessor = null;
this._referenceFrequency = Tone.Frequency(DEFAULT_REFERENCE_NOTE);
this._num_harmonics = DEFAULT_HARMONICS;
this._num_octaves = DEFAULT_NUMBER_OF_OCTAVES;
this._num_bins = DEFAULT_BINS;
this._lastReferenceFrequency = null;
this._tracking_notes = {}; // {note: [harmonic frequencies]}
this._tracking_notes_harmonics = new Set(); // all the notes, including their harmonics, we're currently looking for
this._tracking_notes_harmonics_midi = new Set();
this._tracking_notes_harmonics_index = new Set();
this._tracking_notes_index_to_note = {}; //{pitch_index: '<note_letter><octave_number>'}
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
this._tracking_notes_found = new Set(); // {set of string notes (e.g. "A3") that have been found}
this._tracking_notes_found_midi = new Set(); // Like _tracking_notes_found, except stores midi numbers.
//this._currentChroma = new Float32Array(NOTES_PER_OCTAVE);
//this._currentPitches = new Float32Array(NOTES_PER_OCTAVE * DEFAULT_NUMBER_OF_OCTAVES);
this._currentPitches = new Array(NOTES_PER_OCTAVE * DEFAULT_NUMBER_OF_OCTAVES);
this._recording_signature = false;
this._max_level_for_signature = null;
this._max_signature_for_note = null; // {harmonic: level}
this._signature_error = {}; // {note: lowest signature match error seen since last refresh}
this.chromagram = new chromagram_1.Chromagram(1024, 44100);
this.chromagram.setParameters(this._referenceFrequency.toFrequency(), this._num_harmonics, this._num_octaves, this._num_bins);
}
PolyPeachDetector.prototype.clearNotesFound = function () {
this._tracking_notes_found = new Set();
this._tracking_notes_found_midi = new Set();
};
PolyPeachDetector.prototype.setDraw = function (v) {
this._draw = Boolean(v);
};
PolyPeachDetector.prototype.getDraw = function () {
return this._draw;
};
PolyPeachDetector.prototype.onDrawClick = function () {
this._draw = !this._draw;
};
PolyPeachDetector.prototype.bindUI = function () {
document.querySelector('#alert-histogram .start-stop-button').addEventListener('click', (this.onStartStopClick).bind(this));
document.querySelector('#alert-histogram .draw-button').addEventListener('click', (this.onDrawClick).bind(this));
document.querySelector('#alert-histogram .reference-note').addEventListener('change', (this.pushReferenceNote).bind(this));
document.querySelector('#alert-histogram .track-notes').addEventListener('change', (this.on_track_note_change).bind(this));
document.querySelector('#alert-histogram .record-signature-button').addEventListener('click', (this.onRecordSignatureClick).bind(this));
};
PolyPeachDetector.prototype.clearTrackingNotesFound = function () {
this._tracking_notes_found = new Set();
this._tracking_notes_found_midi = new Set();
this._signature_error = {};
this.showTrackingNotesFound();
};
PolyPeachDetector.prototype.on_track_note_change = function (event) {
this.set_tracking_notes(null);
};
PolyPeachDetector.prototype.onStartStopClick = function () {
console.log('startstop');
this.toggleAudioStream();
};
PolyPeachDetector.prototype.onRecordSignatureClick = function () {
console.log('record');
console.log('this._tracking_notes:');
console.log(this._tracking_notes);
var tracking_notes_length = Object.keys(this._tracking_notes).length;
console.log('tracking_notes_length:' + tracking_notes_length);
if (!tracking_notes_length) {
console.log('No tracking note specified.');
return;
}
if (tracking_notes_length > 1) {
console.log('You can only record a signature for one tracking note.');
return;
}
this._recording_signature = true;
this._max_level_for_signature = 0;
this._max_signature_for_note = {};
var record_button = document.querySelector('#alert-histogram .record-signature-button');
if (record_button) {
record_button.textContent = RECORD_SIGNATURE_BUTTON_ON;
}
setTimeout((this.onRecordSignatureComplete).bind(this), 5000);
};
PolyPeachDetector.prototype.onRecordSignatureComplete = function () {
var tracking_note = Object.keys(this._tracking_notes)[0].toUpperCase();
var record_button = document.querySelector('#alert-histogram .record-signature-button');
if (record_button) {
record_button.textContent = RECORD_SIGNATURE_BUTTON_OFF;
}
console.log('record done');
this._recording_signature = false;
console.log('signature:');
console.log(this._max_signature_for_note);
var textEl = document.querySelector('#alert-histogram .note-signatures');
if (textEl) {
var norm = new Array(MAX_HARMONICS);
for (var i = 0; i < MAX_HARMONICS; i++) {
var harmonicNote = getHarmonicNoteFromNote(tracking_note, i + 1);
norm[i] = this._max_signature_for_note[harmonicNote];
}
norm = normalizeArray(norm);
console.log('signature.norm:');
console.log(norm);
textEl['value'] += '[\'' + tracking_note + '\'] = ' + JSON.stringify(norm).replaceAll('"', '\'') + ';\n';
}
};
//onWorkerMessage(ev) {
//// When the worker initializes, it'll send us a message saying it's ready.
//// When we get that message, we'll send it a message telling it what reference note to use as its lowest note.
//if (!this._worker_ready) {
//this._worker_ready = ev.data.ready;
//if (this._worker_ready) {
//this.pushReferenceNote();
//}
//return;
//}
//this._pitchCanvasWidth = PIXELS_PER_NOTE * NOTES_PER_OCTAVE * ev.data.numOctaves;
//if (ev.data.referenceFrequency != this._lastReferenceFrequency) {
//this._referenceOctave = ev.data.referenceOctave;
//this._referenceFrequency = Tone.Frequency(ev.data.referenceFrequency);
//this.set_tracking_notes(null);
//this._lastReferenceFrequency = ev.data.referenceFrequency;
//}
//this._currentPitches = ev.data.currentPitches;
//}
PolyPeachDetector.prototype.getPitchVisualizationCtx = function () {
if (this._pitchCanvasWidth != null) {
var pitchEl = document.querySelector('#alert-histogram .pitches-visualizer');
if (pitchEl) {
pitchEl.innerHTML = '<canvas width="' + this._pitchCanvasWidth + '" height="256" style="width:100%;">';
this._pitchVisualizationCtx = document.querySelector('#alert-histogram .pitches-visualizer canvas')['getContext']('2d'); //TODO:fix?
this._gradientA = this._pitchVisualizationCtx.createLinearGradient(0, 0, 0, 512);
this._gradientA.addColorStop(1, '#000000');
this._gradientA.addColorStop(0.75, '#2ecc71');
this._gradientA.addColorStop(0.25, '#f1c40f');
this._gradientA.addColorStop(0, '#e74c3c');
this._pitchVisualizationCtx.font = '' + PIXELS_PER_NOTE + 'px Georgia';
}
else {
console.log('no pitch el found');
// The modal containing the canvas has been destroyed, so there's nothing to show.
this._pitchVisualizationCtx = null;
return;
}
}
return this._pitchVisualizationCtx;
};
PolyPeachDetector.prototype.getGradient = function () {
return this._gradientA;
};
PolyPeachDetector.prototype.pushReferenceNote = function () {
console.log('pushReferenceNote');
var note = DEFAULT_REFERENCE_NOTE;
var referenceNoteEl = document.querySelector('#alert-histogram .reference-note');
if (referenceNoteEl) {
note = (referenceNoteEl['value'] || '').trim();
}
if (!note) {
return;
}
var freq_obj = Tone.Frequency(note);
if (freq_obj.toMidi() >= UPPER_CUTOFF_MIDI_NOTE) {
// TODO:Fix? The browser tends to crash when we go over g6 or a6?
console.log('Note too high.');
return;
}
console.log('pushing reference note');
this._referenceFrequency = freq_obj;
this.chromagram.setParameters(this._referenceFrequency.toFrequency(), this._num_harmonics, this._num_octaves, this._num_bins);
};
PolyPeachDetector.prototype.set_tracking_notes_from_midi_pitches = function (midi_pitches) {
var letter_notes = [];
for (var i = 0; i < midi_pitches.length; i++) {
letter_notes.push(Tone.Frequency(midi_pitches[i].pitch, 'midi').toNote());
}
console.log('letter_notes:');
console.log(letter_notes);
this.set_tracking_notes(letter_notes);
};
PolyPeachDetector.prototype.set_tracking_notes = function (notes) {
/*
* Arguments:
*
* notes := An optional space-delimited string of letter notes, or an array of letter notes.
* */
//TODO:call this after reference note changes?
if (this._referenceFrequency == null) {
console.log('No reference frequency set.');
return;
}
console.log('updateTrackingNotes');
var notes_list;
if (notes == null) {
// If no notes given, attempt to pull from modal.
var trackNotesEl = document.querySelector('#alert-histogram .track-notes');
if (trackNotesEl) {
notes = trackNotesEl['value'];
}
notes_list = (notes || '').replace(/\s+/g, ' ').trim().split(' ');
}
else if (typeof (notes) == 'string') {
notes_list = notes.replace(/\s+/g, ' ').trim().split(' ');
}
else if (typeof (notes) == 'object') {
notes_list = notes;
}
console.log('notes_list:' + notes_list);
this._tracking_notes = {}; //{note:[pitch indexes for each harmonic]}
this._tracking_notes_found = new Set();
this._tracking_notes_found_midi = new Set();
this._tracking_notes_harmonics = new Set();
this._tracking_notes_harmonics_midi = new Set();
this._tracking_notes_harmonics_index = new Set();
this._tracking_notes_index_to_note = {};
var referenceMidi = this._referenceFrequency.toMidi();
for (var i in notes_list) {
var note = notes_list[i].toUpperCase().trim();
if (!note) {
continue;
}
var freqObj = Tone.Frequency(note);
var freq = freqObj.toFrequency();
this._tracking_notes[note] = [];
for (var j = 1; j <= MAX_HARMONICS; j++) {
var harmonic_freq = Tone.Frequency(freq * j);
var harmonic_midi = harmonic_freq.toMidi();
var pitch_index = harmonic_midi - referenceMidi;
if (j <= MIN_HARMONICS) {
// Only actively look for the first N harmonics.
this._tracking_notes[note].push(pitch_index);
this._tracking_notes_harmonics.add(harmonic_freq.toNote());
this._tracking_notes_harmonics_midi.add(harmonic_midi);
this._tracking_notes_harmonics_index.add(pitch_index);
}
// But store more information for additional harmonics to help us in recording signatures.
this._tracking_notes_index_to_note[pitch_index] = harmonic_freq.toNote();
}
}
var ary = Array.prototype.slice.call(Array.from(this._tracking_notes_harmonics)).sort();
if (this._draw) {
var noteAndHarmonicsEl = document.querySelector('#alert-histogram .track-notes-and-harmonics');
if (noteAndHarmonicsEl) {
noteAndHarmonicsEl.textContent = ary.join(' ');
}
}
};
PolyPeachDetector.prototype.showTrackingNotesFound = function () {
var ary = Array.prototype.slice.call(Array.from(this._tracking_notes_found)).sort();
var notesFoundEl = document.querySelector('#alert-histogram .notes-found');
if (notesFoundEl) {
notesFoundEl.textContent = ary.join(' ');
}
};
PolyPeachDetector.prototype.handleHistogramAudioStream = function (stream) {
this.media_stream = stream;
this.histogramAudioCtx = new AudioContext();
this.histogramSource = this.histogramAudioCtx.createMediaStreamSource(stream);
this.histogramProcessor = this.histogramAudioCtx.createScriptProcessor(1024, 1, 1);
this.histogramSource.connect(this.histogramProcessor);
this.histogramProcessor.onaudioprocess = (this.onAudioProcess).bind(this);
this.histogramProcessor.connect(this.histogramAudioCtx.destination);
this.updateHistogramVisualization();
};
PolyPeachDetector.prototype.onAudioProcess = function (event) {
if (!this._running) {
return;
}
var audioData = event.inputBuffer.getChannelData(0);
this.chromagram.processAudioFrame(audioData);
this._currentPitches = this.chromagram.getPitches();
if (!this._pitchCanvasWidth) {
this._pitchCanvasWidth = PIXELS_PER_NOTE * NOTES_PER_OCTAVE * this.chromagram.getNumOctaves();
this._referenceFrequency = Tone.Frequency(this.chromagram.getReferenceFrequency());
this.set_tracking_notes(null);
}
};
PolyPeachDetector.prototype.startMicrophone = function () {
if (this._running) {
console.log('Microphone already started.');
return;
}
console.log('Starting microphone.');
this._running = true;
navigator.mediaDevices.getUserMedia({
audio: {
// Minimizes 'smart' features on mobile browsers that cause audio distortions,
// making pitch detection virtually impossible.
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
},
video: false
}).then((this.handleHistogramAudioStream).bind(this));
console.log('Started microphone.');
};
PolyPeachDetector.prototype.stopMicrophone = function () {
if (!this._running) {
console.log('Microphone already stopped.');
return;
}
console.log('Stopping microphone...');
this._running = false;
this.media_stream.getTracks().forEach(function (track) {
if (track.readyState == 'live' && track.kind === 'audio') {
track.stop();
}
});
this.histogramSource.disconnect();
this.histogramProcessor.disconnect();
if (this.media_stream && this.media_stream.stop) {
this.media_stream.stop();
}
this.histogramProcessor.onaudioprocess = null;
console.log('Microphone stopped.');
};
PolyPeachDetector.prototype.toggleAudioStream = function () {
if (this._running) {
this.stopMicrophone();
}
else {
this.startMicrophone();
}
};
PolyPeachDetector.prototype.getNotesFound = function () {
return new Set(this._tracking_notes_found);
};
PolyPeachDetector.prototype.getMidiNotesFound = function () {
return new Set(this._tracking_notes_found_midi);
};
PolyPeachDetector.prototype.get_pitch = function () {
return 123; //TODO
};
PolyPeachDetector.prototype.updateHistogramVisualization = function () {
if (this._running) {
requestAnimationFrame((this.updateHistogramVisualization).bind(this));
}
//console.log('_draw:'+this._draw);
// Show the strength of each specific pitch with octave.
var pitchVisualizationCtx = this.getPitchVisualizationCtx();
var gradientA = this.getGradient();
var draw = this._draw && pitchVisualizationCtx;
//console.log('pitchVisualizationCtx:');
//console.log(pitchVisualizationCtx);
//console.log('draw:'+draw);
//console.log('this._pitchCanvasWidth:'+this._pitchCanvasWidth);
if (draw) {
pitchVisualizationCtx.clearRect(0, 0, this._pitchCanvasWidth, 250);
}
//console.log('this._referenceFrequency:'+this._referenceFrequency);
if (this._currentPitches && this._referenceFrequency) {
if (this._recording_signature) {
//console.log('recording...')
var current_max_level = Math.max.apply(Math, __spreadArray([], __read(this._currentPitches)));
if (current_max_level > this._max_level_for_signature) {
this._max_level_for_signature = current_max_level;
//console.log('current_max_level:'+current_max_level);
//console.log('this._max_level_for_signature:'+this._max_level_for_signature);
var first = true;
var first_harmonic = void 0;
var harmonic_count = 0;
for (var pi in this._tracking_notes_index_to_note) {
harmonic_count += 1;
var harmonic_note = this._tracking_notes_index_to_note[pi];
if (first) {
first_harmonic = this._currentPitches[pi];
first = !first;
}
var harmonic_level = this._currentPitches[pi];
// If we aren't tracking the high octave harmonics, then estimate it based on rough formula using the first harmonic.
if (!harmonic_level || isNaN(harmonic_level)) {
harmonic_level = first_harmonic / harmonic_count;
}
this._max_signature_for_note[harmonic_note] = harmonic_level;
//console.log('note:' + harmonic_note + '=' + harmonic_level);
}
}
}
var referenceMidi = this._referenceFrequency.toMidi();
var level_sum = 0;
var level_max = 0;
var pitches_by_index = []; // [[pitch_index, pitch_value]]
//console.log('this._currentPitches.length:'+this._currentPitches.length);
for (var i = 0; i < this._currentPitches.length; i++) {
var value = this._currentPitches[i];
//console.log('value['+i+']'+value);
pitches_by_index.push([i, value]);
level_sum += value;
level_max = Math.max(level_max, value);
if (draw) {
// Draw level gradient proportional to pitch strength.
// The max value we've seen in a chroma is >50
pitchVisualizationCtx.fillStyle = gradientA;
pitchVisualizationCtx.fillRect(i * PIXELS_PER_NOTE, 250, PIXELS_PER_NOTE - 1, value * -5); // x, y, width, height
// Draw note name at bottom of bar.
pitchVisualizationCtx.fillStyle = 'black';
var freq = Tone.Frequency(referenceMidi + i, 'midi');
pitchVisualizationCtx.font = '' + PIXELS_PER_NOTE / 2 + 'px Georgia';
pitchVisualizationCtx.fillText(freq.toNote(), (i * PIXELS_PER_NOTE) + 3, 250); // x,y
}
}
var mean_level = level_sum / this._currentPitches.length;
if (draw) {
var meanLevelEl = document.querySelector('#alert-histogram .mean-level');
if (meanLevelEl) {
meanLevelEl.textContent = String(mean_level);
}
var maxLevelEl = document.querySelector('#alert-histogram .max-level');
if (maxLevelEl) {
maxLevelEl.textContent = String(level_max);
}
}
// Compare normalized pitches to signature.
var signature_error = [];
for (var note in this._tracking_notes) {
var signature = SIGNATURES[this._signature_type][note];
if (!signature) {
break;
}
// Re-normalize the truncated signature.
var signature_sample = void 0;
signature_sample = signature.slice(0, MIN_HARMONICS); // normalized levels of harmonic notes
// Extract the signature from current pitches.
var harmonic_sample = new Array(MIN_HARMONICS); //Array(...this._currentPitches);
var harmonic_max = 0;
var overall_max = Math.max.apply(Math, __spreadArray([], __read(this._currentPitches)));
for (var i = 0; i < MIN_HARMONICS; i++) {
var harmonicFreqObj = getHarmonicFrequencyFromNote(note, i + 1);
var harmonicIndex = harmonicFreqObj.toMidi() - this._referenceFrequency.toMidi();
harmonic_sample[i] = this._currentPitches[harmonicIndex];
harmonic_max = Math.max(harmonic_max, harmonic_sample[i]);
}
var too_quiet = overall_max < MIN_VOLUME;
// Scale signature to match range of harmonic sample.
var max_harmonic_level = Math.max.apply(Math, __spreadArray([], __read(this._currentPitches)));
max_harmonic_level = Math.max(max_harmonic_level, MIN_VOLUME);
var max_signature_level = Math.max.apply(Math, __spreadArray([], __read(signature_sample)));
var scaling_ratio = max_harmonic_level / max_signature_level;
var scaled_signature_sample = scaleArray(signature_sample, scaling_ratio);
// Calculate difference.
var error = 0; // mean absolute error.
for (var i = 0; i < scaled_signature_sample.length; i++) {
error += Math.abs(scaled_signature_sample[i] - harmonic_sample[i]);
}
error /= signature_sample.length;
var show = false;
this._signature_error[note] = Math.min(this._signature_error[note] || 999, Number(error.toFixed(2)));
this._signature_error[note + 'V'] = Math.max(this._signature_error[note + 'V'] || 0, Number(overall_max.toFixed(2)));
if (error < MAX_ERROR_THRESHOLD && !too_quiet) {
console.log('Found note:' + note);
this._tracking_notes_found.add(note);
this._tracking_notes_found_midi.add(Tone.Frequency(note).toMidi());
show = true;
}
if (DEBUG && draw && !this._display_count) {
console.log('note:' + note);
console.log('harmonic_max:' + harmonic_max);
console.log('signature_sample:');
console.log(signature_sample);
console.log('scaled_signature_sample:');
console.log(scaled_signature_sample);
console.log('harmonic_sample:');
console.log(harmonic_sample);
console.log('error:' + error);
}
}
if (draw) {
if (DEBUG) {
var signatureErrorEl = document.querySelector('#alert-histogram .signature-error');
if (signatureErrorEl) {
var sig_err = Object.assign({}, this._signature_error);
//sig_err['VOL'] = overall_max;
signatureErrorEl.textContent = JSON.stringify(sig_err);
}
}
this.showTrackingNotesFound();
}
this._display_count += 1;
this._display_count %= 50;
}
};
return PolyPeachDetector;
}());
exports.PolyPeachDetector = PolyPeachDetector;
window['PolyPeachDetector'] = PolyPeachDetector;