@@ -90,7 +93,13 @@ include("$root/analytics/matomo.php");
-
+
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/public/larynx/scripts/g-spectro.js b/public/larynx/scripts/g-spectro.js
new file mode 100644
index 0000000..245b3c3
--- /dev/null
+++ b/public/larynx/scripts/g-spectro.js
@@ -0,0 +1,261 @@
+var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
+context = new AudioContext();
+
+// Assumes context is an AudioContext defined outside of this class.
+
+Polymer('g-spectrogram', {
+ // Show the controls UI.
+ controls: false,
+ // Log mode.
+ log: false,
+ // Show axis labels, and how many ticks.
+ labels: false,
+ ticks: 5,
+ speed: 2,
+ // FFT bin size,
+ fftsize: 2048,
+ oscillator: false,
+ color: false,
+
+ attachedCallback: function() {
+ this.tempCanvas = document.createElement('canvas'),
+ console.log('Created spectrogram');
+ // Get input from the microphone.
+ if (navigator.mozGetUserMedia) {
+ navigator.mozGetUserMedia({audio: true},
+ this.onStream.bind(this),
+ this.onStreamError.bind(this));
+ } else if (navigator.webkitGetUserMedia) {
+ navigator.webkitGetUserMedia({audio: true},
+ this.onStream.bind(this),
+ this.onStreamError.bind(this));
+ }
+ this.ctx = this.$.canvas.getContext('2d');
+ },
+
+ render: function() {
+ //console.log('Render');
+ this.width = window.innerWidth;
+ this.height = window.innerHeight;
+
+ var didResize = false;
+ // Ensure dimensions are accurate.
+ if (this.$.canvas.width != this.width) {
+ this.$.canvas.width = this.width;
+ this.$.labels.width = this.width;
+ didResize = true;
+ }
+ if (this.$.canvas.height != this.height) {
+ this.$.canvas.height = this.height;
+ this.$.labels.height = this.height;
+ didResize = true;
+ }
+
+ //this.renderTimeDomain();
+ this.renderFreqDomain();
+
+ if (this.labels && didResize) {
+ this.renderAxesLabels();
+ }
+
+ requestAnimationFrame(this.render.bind(this));
+
+ var now = new Date();
+ if (this.lastRenderTime_) {
+ this.instantaneousFPS = now - this.lastRenderTime_;
+ }
+ this.lastRenderTime_ = now;
+ },
+
+ renderTimeDomain: function() {
+ var times = new Uint8Array(this.analyser.frequencyBinCount);
+ this.analyser.getByteTimeDomainData(times);
+
+ for (var i = 0; i < times.length; i++) {
+ var value = times[i];
+ var percent = value / 256;
+ var barHeight = this.height * percent;
+ var offset = this.height - barHeight - 1;
+ var barWidth = this.width/times.length;
+ this.ctx.fillStyle = 'black';
+ this.ctx.fillRect(i * barWidth, offset, 1, 1);
+ }
+ },
+
+ renderFreqDomain: function() {
+ var freq = new Uint8Array(this.analyser.frequencyBinCount);
+ this.analyser.getByteFrequencyData(freq);
+
+ var ctx = this.ctx;
+ // Copy the current canvas onto the temp canvas.
+ this.tempCanvas.width = this.width;
+ this.tempCanvas.height = this.height;
+ //console.log(this.$.canvas.height, this.tempCanvas.height);
+ var tempCtx = this.tempCanvas.getContext('2d');
+ tempCtx.drawImage(this.$.canvas, 0, 0, this.width, this.height);
+
+ // Iterate over the frequencies.
+ for (var i = 0; i < freq.length; i++) {
+ var value;
+ // Draw each pixel with the specific color.
+ if (this.log) {
+ logIndex = this.logScale(i, freq.length);
+ value = freq[logIndex];
+ } else {
+ value = freq[i];
+ }
+
+ ctx.fillStyle = (this.color ? this.getFullColor(value) : this.getGrayColor(value));
+
+ var percent = i / freq.length;
+ var y = Math.round(percent * this.height);
+
+ // draw the line at the right side of the canvas
+ ctx.fillRect(this.width - this.speed, this.height - y,
+ this.speed, this.speed);
+ }
+
+ // Translate the canvas.
+ ctx.translate(-this.speed, 0);
+ // Draw the copied image.
+ ctx.drawImage(this.tempCanvas, 0, 0, this.width, this.height,
+ 0, 0, this.width, this.height);
+
+ // Reset the transformation matrix.
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ },
+
+ /**
+ * Given an index and the total number of entries, return the
+ * log-scaled value.
+ */
+ logScale: function(index, total, opt_base) {
+ var base = opt_base || 2;
+ var logmax = this.logBase(total + 1, base);
+ var exp = logmax * index / total;
+ return Math.round(Math.pow(base, exp) - 1);
+ },
+
+ logBase: function(val, base) {
+ return Math.log(val) / Math.log(base);
+ },
+
+ renderAxesLabels: function() {
+ var canvas = this.$.labels;
+ canvas.width = this.width;
+ canvas.height = this.height;
+ var ctx = canvas.getContext('2d');
+ var startFreq = 440;
+ var nyquist = context.sampleRate/2;
+ var endFreq = nyquist - startFreq;
+ var step = (endFreq - startFreq) / this.ticks;
+ var yLabelOffset = 5;
+ // Render the vertical frequency axis.
+ for (var i = 0; i <= this.ticks; i++) {
+ var freq = startFreq + (step * i);
+ // Get the y coordinate from the current label.
+ var index = this.freqToIndex(freq);
+ var percent = index / this.getFFTBinCount();
+ var y = (1-percent) * this.height;
+ var x = this.width - 60;
+ // Get the value for the current y coordinate.
+ var label;
+ if (this.log) {
+ // Handle a logarithmic scale.
+ var logIndex = this.logScale(index, this.getFFTBinCount());
+ // Never show 0 Hz.
+ freq = Math.max(1, this.indexToFreq(logIndex));
+ }
+ var label = this.formatFreq(freq);
+ var units = this.formatUnits(freq);
+ ctx.font = '16px Inconsolata';
+ // Draw the value.
+ ctx.textAlign = 'right';
+ ctx.fillText(label, x, y + yLabelOffset);
+ // Draw the units.
+ ctx.textAlign = 'left';
+ ctx.fillText(units, x + 10, y + yLabelOffset);
+ // Draw a tick mark.
+ ctx.fillRect(x + 40, y, 30, 2);
+ }
+ },
+
+ clearAxesLabels: function() {
+ var canvas = this.$.labels;
+ var ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, this.width, this.height);
+ },
+
+ formatFreq: function(freq) {
+ return (freq >= 1000 ? (freq/1000).toFixed(1) : Math.round(freq));
+ },
+
+ formatUnits: function(freq) {
+ return (freq >= 1000 ? 'KHz' : 'Hz');
+ },
+
+ indexToFreq: function(index) {
+ var nyquist = context.sampleRate/2;
+ return nyquist/this.getFFTBinCount() * index;
+ },
+
+ freqToIndex: function(frequency) {
+ var nyquist = context.sampleRate/2;
+ return Math.round(frequency/nyquist * this.getFFTBinCount());
+ },
+
+ getFFTBinCount: function() {
+ return this.fftsize / 2;
+ },
+
+ onStream: function(stream) {
+ var input = context.createMediaStreamSource(stream);
+ var analyser = context.createAnalyser();
+ analyser.smoothingTimeConstant = 0;
+ analyser.fftSize = this.fftsize;
+
+ // Connect graph.
+ input.connect(analyser);
+
+ this.analyser = analyser;
+ // Setup a timer to visualize some stuff.
+ this.render();
+ },
+
+ onStreamError: function(e) {
+ console.error(e);
+ },
+
+ getGrayColor: function(value) {
+ return 'rgb(V, V, V)'.replace(/V/g, 255 - value);
+ },
+
+ getFullColor: function(value) {
+ var fromH = 62;
+ var toH = 0;
+ var percent = value / 255;
+ var delta = percent * (toH - fromH);
+ var hue = fromH + delta;
+ return 'hsl(H, 100%, 50%)'.replace(/H/g, hue);
+ },
+
+ logChanged: function() {
+ if (this.labels) {
+ this.renderAxesLabels();
+ }
+ },
+
+ ticksChanged: function() {
+ if (this.labels) {
+ this.renderAxesLabels();
+ }
+ },
+
+ labelsChanged: function() {
+ if (this.labels) {
+ this.renderAxesLabels();
+ } else {
+ this.clearAxesLabels();
+ }
+ }
+ });
\ No newline at end of file
diff --git a/public/larynx/v/2.0/index.php b/public/larynx/v/2.0/index.php
new file mode 100644
index 0000000..3d09435
--- /dev/null
+++ b/public/larynx/v/2.0/index.php
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
Larynx | Chiro - Canto
+
+
+
+
+
+
+
+ Larynx
+ A web tool for bat sound analysis
+
+
+
+
+
+
Larynx v=$version?>
+ PDO::ERRMODE_EXCEPTION
+ ));
+ } catch (Exception $e) {
+ die("Error : ".$e->getMessage());
+ }
+ if (isset($_GET['record'])) {
+ $req = $db->prepare('SELECT id, recordist_name, file_name, license, species, sound_type, date, time FROM `records` WHERE id=:id');
+ $req->execute(array(
+ "id"=>$_GET['record']
+ ));
+ } else {
+ $req = $db->prepare('SELECT id, recordist_name, file_name, license, species, sound_type, date, time FROM `records` ORDER BY date DESC, time DESC LIMIT 1');
+ $req->execute();
+ }
+ if ($data = $req->fetch()) {
+ // print_r($data);
+ ?>
+
+
+
+
+
+
+
+
=$data['license']?> =$data['recordist_name']?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/larynx/v/2.0/scripts/script.js b/public/larynx/v/2.0/scripts/script.js
new file mode 100644
index 0000000..41f7924
--- /dev/null
+++ b/public/larynx/v/2.0/scripts/script.js
@@ -0,0 +1,263 @@
+let analyser;
+let context;
+let fftsize = 2048;
+let oscillator = true;
+let log;
+let percent;
+let times;
+let speed = 0.1;
+let bufferLength;
+let labels;
+let color = true;
+let width = window.innerwidth;
+let height = window.innerheight;
+
+
+let canvas = document.getElementById('spectrogram-canvas');
+let ctx = canvas.getContext('2d');
+
+let lastRenderTime = new Date();
+let instantaneousFPS = 0;
+
+let audio = document.getElementById('audio');
+
+document.getElementById("launch-larynx").addEventListener('click', function () {
+ loadSound(audio.src)
+ startLarynx();
+});
+
+function loadSound(url) {
+ let AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
+ context = new AudioContext();
+ let source = context.createMediaElementSource(audio);
+}
+
+function startLarynx() {
+ analyser = context.createAnalyser();
+ render();
+}
+
+
+function render() {
+ let didResize = false;
+
+ analyser = context.createAnalyser();
+
+ // Ensure dimensions are accurate.
+ if (canvas.width != width) {
+ canvas.width = width;
+ didResize = true;
+ }
+ if (canvas.height != height) {
+ canvas.height = height;
+ didResize = true;
+ }
+
+ renderTimeDomain();
+ renderFreqDomain();
+
+ if (labels && didResize) {
+ renderAxesLabels();
+ }
+
+ let now = new Date();
+ if (lastRenderTime) {
+ instantaneousFPS = now - lastRenderTime;
+ }
+ lastRenderTime = now;
+}
+
+function draw() {
+ drawVisual = requestAnimationFrame(draw);
+ analyser.getByteTimeDomainData(times);
+
+ ctx.fillStyle = 'rgb(200, 200, 200)';
+ ctx.fillRect(0, 0, width, height);
+
+ ctx.linewidth = 2;
+ ctx.strokeStyle = 'rgb(0, 0, 0)';
+
+ ctx.beginPath();
+
+ var slicewidth = width * 1.0 / bufferLength;
+ var x = 0;
+
+ for (var i = 0; i < bufferLength; i++) {
+
+ var v = times[i] / 128.0;
+ var y = v * height / 2;
+
+ if (i === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ ctx.lineTo(x, y);
+ }
+
+ x += slicewidth;
+ }
+
+ ctx.lineTo(canvas.width, canvas.height / 2);
+ ctx.stroke();
+}
+
+function renderTimeDomain() {
+ bufferLength = analyser.frequencyBinCount;
+ times = new Uint8Array(bufferLength);
+ draw();
+}
+
+function renderFreqDomain() {
+ let freq = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteFrequencyData(freq);
+
+ for (let i = 0; i < freq.length; i++) {
+ let value;
+ // Draw each pixel with the specific color.
+ if (log) {
+ logIndex = logScale(i, freq.length);
+ value = freq[logIndex];
+ } else {
+ value = freq[i];
+ }
+ ctx.fillStyle = (color ? getFullColor(value) : getGrayColor(value));
+
+ let perceint = i / freq.length;
+ let y = Math.round(percent * height);
+
+ // Draw the line at the right side of the canvas.
+ ctx.fillRect(width - speed, height - y, speed, speed);
+ }
+ ctx.translate(-speed, 0);
+ // Reset the transformation matrix
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+}
+
+function logScale(index, total, opt_base) {
+ let base = opt_base || 2;
+ let logmax = logBase(total + 1, base);
+ let exp = logmax * index / total;
+ return Math.round(Math.pow(base, exp) - 1);
+}
+
+function logBase(val, base) {
+ return Math.log(val) / Math.log(base);
+}
+
+
+function renderAxesLabels() {
+ canvas.width = width;
+ canvas.height = height;
+ let ctx = canvas.getContext('2d');
+ let startFreq = 440;
+ let nyquist = context.sampleRate / 2;
+ let endFreq = nyquist - startFreq;
+ let step = (endFreq - startFreq) / ticks;
+ let yLabelOffset = 5;
+ // Render the vertical frequency axis.
+ for (let i = 0; i <= ticks; i++) {
+ let freq = startFreq + (step * i);
+ // Get the y coordinate from the current label.
+ let index = freqToIndex(freq);
+ let percent = index / getFFTBinCount();
+ let y = (1 - percent) * height;
+ let x = width - 60;
+ // Get the value for the current y coordinate.
+ let label;
+ if (log) {
+ // Handle a logarithmic scale.
+ let logIndex = logScale(index, getFFTBinCount());
+ // Never show 0 Hz.
+ freq = Math.max(1, indexToFreq(logIndex));
+ }
+ label = formatFreq(freq);
+ let units = formatUnits(freq);
+ ctx.font = '16px Inconsolata';
+ // Draw the value.
+ ctx.textAlign = 'right';
+ ctx.fillText(label, x, y + yLabelOffset);
+ // Draw the units.
+ ctx.textAlign = 'left';
+ ctx.fillText(units, x + 10, y + yLabelOffset);
+ // Draw a tick mark.
+ ctx.fillRect(x + 40, y, 30, 2);
+ }
+}
+
+function clearAxesLabels() {
+ let canvas = labels;
+ let ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, width, height);
+}
+
+function formatFreq(freq) {
+ return (freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq));
+}
+
+function formatUnits(freq) {
+ return (freq >= 1000 ? 'KHz' : 'Hz');
+}
+
+function indexToFreq(index) {
+ let nyquist = context.sampleRate / 2;
+ return nyquist / getFFTBinCount() * index;
+}
+
+function freqToIndex(frequency) {
+ let nyquist = context.sampleRate / 2;
+ return Math.round(frequency / nyquist * getFFTBinCount());
+}
+
+function getFFTBinCount() {
+ return fftsize / 2;
+}
+
+// function onStream(stream) {
+// let input = context.createMediaStreamSource(stream);
+// let analyser = context.createAnalyser();
+// analyser.smoothingTimeConstant = 0;
+// analyser.fftSize = fftsize;
+
+// // Connect graph.
+// input.connect(analyser);
+
+// // Setup a timer to visualize some stuff.
+// render();
+// }
+
+function onStreamError(e) {
+ console.error(e);
+}
+
+function getGrayColor(value) {
+ return 'rgb(V, V, V)'.replace(/V/g, 255 - value);
+}
+
+function getFullColor(value) {
+ let fromH = 62;
+ let toH = 0;
+ let percent = value / 255;
+ let delta = percent * (toH - fromH);
+ let hue = fromH + delta;
+ return 'hsl(H, 100%, 50%)'.replace(/H/g, hue);
+}
+
+function logChanged() {
+ if (labels) {
+ renderAxesLabels();
+ }
+}
+
+function ticksChanged() {
+ if (labels) {
+ renderAxesLabels();
+ }
+}
+
+function labelsChanged() {
+ if (labels) {
+ renderAxesLabels();
+ } else {
+ clearAxesLabels();
+ }
+}
+
diff --git a/public/larynx/v/2.0/scripts/spectro.js b/public/larynx/v/2.0/scripts/spectro.js
new file mode 100644
index 0000000..cff6d41
--- /dev/null
+++ b/public/larynx/v/2.0/scripts/spectro.js
@@ -0,0 +1,67 @@
+var audio = document.getElementById('audio');
+
+let button = document.getElementById('launch-larynx').addEventListener('click', function(){
+ loadSound(audio.src);
+ render();
+});
+
+let audioCtx;
+let source;
+let analyser;
+let canvasCtx = document.getElementById('spectrogram-canvas');
+
+let WIDTH = canvasCtx.width;
+let HEIGHT = canvasCtx.height;
+
+function loadSound(url) {
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ source = audioCtx.createMediaElementSource(audio);
+}
+
+function render() {
+ analyser = audioCtx.createAnalyser();
+
+ analyser.fftSize = 2048;
+ var bufferLength = analyser.frequencyBinCount;
+ var dataArray = new Uint8Array(bufferLength);
+ analyser.getByteTimeDomainData(dataArray);
+
+ // draw an oscilloscope of the current audio source
+
+ function draw() {
+
+ drawVisual = requestAnimationFrame(draw);
+
+ analyser.getByteTimeDomainData(dataArray);
+
+ canvasCtx.fillStyle = 'rgb(200, 200, 200)';
+ canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
+
+ canvasCtx.lineWidth = 2;
+ canvasCtx.strokeStyle = 'rgb(0, 0, 0)';
+
+ canvasCtx.beginPath();
+
+ var sliceWidth = WIDTH * 1.0 / bufferLength;
+ var x = 0;
+
+ for (var i = 0; i < bufferLength; i++) {
+
+ var v = dataArray[i] / 128.0;
+ var y = v * HEIGHT / 2;
+
+ if (i === 0) {
+ canvasCtx.moveTo(x, y);
+ } else {
+ canvasCtx.lineTo(x, y);
+ }
+
+ x += sliceWidth;
+ }
+
+ canvasCtx.lineTo(canvas.width, canvas.height / 2);
+ canvasCtx.stroke();
+ };
+
+ draw();
+}
\ No newline at end of file
diff --git a/public/menu.php b/public/menu.php
index 59205e6..f330c92 100755
--- a/public/menu.php
+++ b/public/menu.php
@@ -45,9 +45,16 @@
larynx
+
+ API
+
forum
+
+ messages
+
+
mysteries
diff --git a/public/search/searchbar.php b/public/search/searchbar.php
index 1269808..e69de29 100755
--- a/public/search/searchbar.php
+++ b/public/search/searchbar.php
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/public/styles/style.css b/public/styles/style.css
index fb5632f..6a3176a 100755
--- a/public/styles/style.css
+++ b/public/styles/style.css
@@ -1,5 +1,6 @@
* {
align-self: baseline;
+ overflow: auto;
}
body {
@@ -278,9 +279,8 @@ table a:hover {
}
#searchbar {
- display: flex;
+ display: inline-flex;
flex-direction: row;
- align-self: right;
}
ul.breadcrumb {
@@ -631,3 +631,25 @@ i, .fa {
background-color: lightgreen;
border-radius: 8px 8px 0px 8px;
}
+
+.notification {
+ text-decoration: none;
+ position: relative;
+ display: inline-block;
+}
+
+.notification span {
+ padding: 1em;
+}
+
+.notification .badge {
+ position: absolute;
+ font-size: 0.5em;
+ font-weight: bold;
+ top: -0.5em;
+ right: -0.5em;
+ padding: 5px;
+ border-radius: 50%;
+ background: red;
+ color: white;
+}