Added API doc

This commit is contained in:
Samuel Ortion 2021-04-18 16:59:01 +02:00
parent 02c9b8bc07
commit ca1959a672
19 changed files with 1062 additions and 106 deletions

5
public/api/index.php Normal file
View File

@ -0,0 +1,5 @@
<?php
$version = "v1";
header("Location: $version");

View File

@ -1,5 +1,10 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
include("$root/vendor/erusev/parsedown/Parsedown.php");
$Parsedown = new Parsedown();
?>
<!DOCTYPE html>
<html lang="en">
@ -21,6 +26,27 @@ $root = realpath($_SERVER["DOCUMENT_ROOT"]);
?>
<section>
<h2>Documentation API v1</h2>
<?php
if (isset($_GET['page']) and file_exists("md/".$_GET['page'].".md")) {
$page = $_GET['page'].".md";
$md = file_get_contents("$root/api/v1/doc/md/$page", "r");
print_r($Parsedown->text($md));
} else {
$doc_paths = glob("$root/api/v1/doc/md/*.md");
?>
<ul>
<?php
foreach ($doc_paths as $path) {
$file = explode('.', basename($path))[0];
?>
<li><a href="?page=<?=$file?>"><?=$file?></a></li>
<?php
}
?>
</ul>
<?php
}
?>
</section>
<?php
include("$root/footer.php");

30
public/api/v1/index.php Executable file → Normal file
View File

@ -0,0 +1,30 @@
<?php
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API v1 Documentation | Chiro - Canto</title>
<link rel="stylesheet" href="/styles/style.css">
</head>
<?php
include("$root/analytics/matomo.php");
include("$root/analytics/owa.php");
?>
<body>
<?php
include("$root/menu.php");
include("$root/header.php");
?>
<section>
<h2>API v1</h2>
<a href="doc">Documentation</a>
</section>
<?php
include("$root/footer.php");
?>
</body>
</html>

View File

@ -0,0 +1,88 @@
<?php
session_start();
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
} catch (Exception $e) {
die("Error : ".$e->getMessage());
}
$req = $db->prepare('SELECT id FROM `authors` WHERE username=:username');
$req->execute(array(
"username"=>$_SESSION['username']
));
$data = $req->fetch();
$user_id = $data['id'];
$req = $db->prepare('SELECT message_by, COUNT(message_by) AS n_msg FROM `messages` WHERE message_to=:user_id AND message_read=0');
$req->execute(array(
"user_id"=>$user_id
));
$result = $req->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Discussion | Chiro - Canto</title>
<link rel="stylesheet" type="text/css" href="/styles/style.css">
</head>
<?php
include("$root/analytics/owa.php");
include("$root/analytics/matomo.php");
?>
<body>
<?php include("$root/menu.php");?>
<?php include("$root/header.php");?>
<section>
<h2>Discussion</h2>
<h3>New messages</h3>
<ul>
<?php
$no_msg = true;
foreach($result as $sender) {
$req = $db->prepare('SELECT username FROM `authors` WHERE id=:id');
$req->execute(array(
"id"=>$sender['message_by']
));
if($data = $req->fetch()) {
$username = $data['username'];
?>
<li>
<a href="messages/?author=<?=$sender['message_by']?>" class="notification">
<span><?=$username?></span>
<span class="badge"><?=$sender['n_msg']?></span>
</a>
</li>
<?php
$no_msg = false;
}
}
if ($no_msg) {
echo "No message received.\n";
}
?>
</ul>
<h3>Send a message</h3>
<form action="messages" method="get">
<label for="author">Addressee</label>
<input type="text" name="author" id="author">
<input type="submit" value="Send">
</form>
</section>
<?php include("$root/footer.php");?>
</body>
<script src="/scripts/script.js"></script>
</html>

View File

@ -22,29 +22,38 @@ if (isset($_SESSION['username'])) {
"username"=>$_SESSION['username']
));
if ($data = $req->fetch()) {
$user_id = $data['id'];
$sender_id = $data['id'];
}
} else {
$_SESSION['error_msg'] = "You must be logged in to receive an send message.";
header('Location: /auth/login');
}
if (isset($_GET['author'])) {
$req = $db->prepare('SELECT * FROM `messages` WHERE message_by=:user_id AND message_to=:author_id OR message_by=:author_id AND message_to=:user_id ORDER BY message_datetime ASC');
if (!is_numeric($_GET['author'])) {
$req = $db->prepare('SELECT id FROM `authors` WHERE username=:username');
$req->execute(array(
"username"=>$_GET['author']
));
if ($data = $req->fetch()) {
$user_id = $data['id'];
} else {
$user_id = $_GET['author'];
}
$req = $db->prepare('SELECT username FROM `authors` WHERE id=:id');
$req->execute(array(
"id"=>$sender_id
));
if ($data = $req->fetch()) {
$addressee = $data['username'];
}
}
$req = $db->prepare('SELECT * FROM `messages` WHERE message_by=:sender_id AND message_to=:user_id OR message_by=:sender_id AND message_to=:user_id ORDER BY message_datetime ASC');
$req->execute(array(
"author_id"=>$user_id,
"user_id"=>$_GET['author']
"sender_id"=>$sender_id,
"user_id"=>$user_id
));
$result = $req->fetchAll();
}
$req = $db->prepare('SELECT username FROM `authors` WHERE id=:id');
$req->execute(array(
"id"=>$_GET['author']
));
if ($data = $req->fetch()) {
$destinator = $data['username'];
}
?>
<!DOCTYPE html>
<html lang="en">
@ -66,10 +75,15 @@ include("$root/analytics/matomo.php");
<h2>Discussion</h2>
<div class="messages">
<div class="author">
<?=$destinator?>
<?=$addressee?>
</div>
<?php
foreach($result as $message) {
$message_id = $message['id'];
$req = $db->prepare('UPDATE `messages` SET message_read=1 WHERE id=:id');
$req->execute(array(
"id"=>$message_id
));
if ($message['message_by'] == $_SESSION['username']) {
$class = "right";
} else {

View File

@ -0,0 +1,35 @@
<?php
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
} catch (Exception $e) {
die("Error : ".$e->getMessage());
}
if (isset($_SESSION['username'])) {
$req = $db->prepare('SELECT id FROM `authors` WHERE username=:username');
$req->execute(array(
"username"=>$_SESSION['username']
));
$data = $req->fetch();
$user_id = $data['id'];
$req = $db->prepare('SELECT COUNT(*) AS counter FROM `messages` WHERE message_to=:user_id AND message_read=0');
$req->execute(array(
"user_id"=>$user_id
));
$data = $req->fetch();
$new_msg_number = $data['counter'];
if ($new_msg_number != 0) {
?>
<span class="badge"><?=$new_msg_number?></span>
<?php
}
} else {
echo "";
}

View File

@ -4,6 +4,29 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GUANO | Chiro-Canto</title>
<link rel="stylesheet" type="text/css" href="/styles/style.css">
</head>
<?php
include("$root/analytics/owa.php");
include("$root/analytics/matomo.php");
?>
<body>
<?php include("$root/menu.php"); ?>
<?php include("$root/header.php"); ?>
<section>
<h2>GUANO v1.0</h2>
<?=isset($_SESSION['error_msg']) and $_SESSION['error_msg'] != "" ? '<div class="error">'.$_SESSION['error_msg'].'</div>' : "";?>
<a href="/articles">about GUANO</a>
<article id="guano">
<?php
require "$root/database/credentials.php";
// Connect the database
try {
@ -29,28 +52,7 @@ if ($data = $req->fetch()) {
} else {
$_SESSION['error_msg'] = "Can't fetch data.";
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GUANO | Chiro-Canto</title>
<link rel="stylesheet" type="text/css" href="/styles/style.css">
</head>
<?php
include("$root/analytics/owa.php");
include("$root/analytics/matomo.php");
?>
<body>
<?php include("$root/menu.php"); ?>
<?php include("$root/header.php"); ?>
<section>
<h2>GUANO v1.0</h2>
<?=isset($_SESSION['error_msg']) and $_SESSION['error_msg'] != "" ? '<div class="error">'.$_SESSION['error_msg'].'</div>' : "";?>
<a href="/articles">about GUANO</a>
<article id="guano">
?>
GUANO|Version: 1.0<br><br>
Original Filename: <?=$data['file_name']?><br>

View File

@ -4,27 +4,6 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
} catch (Exception $e) {
die("Error : ".$e->getMessage());
}
if (isset($_GET['id'])) {
$req = $db->prepare('SELECT * FROM `records` WHERE id=:id');
$req->execute(array(
"id"=>$_GET['id']
));
} else {
$req = $db->prepare('SELECT * FROM `records` ORDER BY date');
$req->execute();
}
?>
<!DOCTYPE html>
<html lang="en">
@ -45,6 +24,27 @@ include("$root/analytics/owa.php");
<section>
<h3>Record</h3>
<?php
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
} catch (Exception $e) {
die("Error : ".$e->getMessage());
}
if (isset($_GET['id'])) {
$req = $db->prepare('SELECT * FROM `records` WHERE id=:id');
$req->execute(array(
"id"=>$_GET['id']
));
} else {
$req = $db->prepare('SELECT * FROM `records` ORDER BY date');
$req->execute();
}
if ($data = $req->fetch()) {
?>
<div class="sound">

View File

@ -56,21 +56,21 @@ if ($_SESSION['error_msg'] == "") {
if ($and) {
$sql .= " AND ";
}
$sql .= ' species="'.$_SESSION['query']['species'].'"';
$sql .= ' species LIKE "%'.$_SESSION['query']['species'].'%"';
$and = True;
}
if (isset($_SESSION['query']['subspecies']) and $_SESSION['query']['subspecies'] != "") {
if ($and) {
$sql .= " AND ";
}
$sql .= ' subspecies="'.$_SESSION['query']['subspecies'].'"';
$sql .= ' subspecies LIKE "%'.$_SESSION['query']['subspecies'].'%"';
$and = True;
}
if (isset($_SESSION['query']['recordist']) and $_SESSION['query']['recordist'] != "") {
if ($and) {
$sql .= " AND ";
}
$sql .= ' recordist_name="'.$_SESSION['query']['recordist'].'"';
$sql .= ' recordist_name LIKE "%'.$_SESSION['query']['recordist'].'%"';
$and = True;
}
if (isset($_SESSION['query']['date-after']) and $_SESSION['query']['date-after'] != "") {
@ -96,10 +96,6 @@ if ($_SESSION['error_msg'] == "") {
$and = True;
}
}
// echo $sql;
$req = $db->prepare($sql);
$req->execute();
$result = $req->fetchAll();
} else {
$_SESSION['error_msg'] .= "You did not enter any query.\n";
header("Location: /explore/search");
@ -108,9 +104,7 @@ if ($_SESSION['error_msg'] == "") {
} else {
header("Location: /explore/search");
}
if (isset($result)) {
?>
?>
<!DOCTYPE html>
<html lang="en">
@ -128,6 +122,11 @@ if (isset($result)) {
<h3>Explore</h3>
<h4>Search Results</h4>
<?php
// echo $sql;
$req = $db->prepare($sql);
$req->execute();
$result = $req->fetchAll();
// print_r($sql);
if (empty($result)) {
echo "No result for this query, please try again.\n";
} else {
@ -163,6 +162,4 @@ if (isset($result)) {
<?php include("$root/footer.php");?>
</body>
<script src="/scripts/script.js"></script>
</html>
<?php
}
</html>

View File

@ -5,7 +5,6 @@
<h1>Chiro - Canto</h1>
<h2>Bat sound sharing tools</h2>
</a>
</div>
<?php include("$root/search/searchbar.php");?>
</div>
</div>
</header>

View File

@ -0,0 +1,34 @@
<link rel="import" href="./bower_components/polymer/polymer.html">
<link rel="import" href="./g-spectrogram-controls.html">
<link rel="import" href="./g-oscillator.html">
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<polymer-element name="g-spectrogram" attributes="controls log fftsize labels ticks oscillator speed color">
<template>
<style>
canvas {
position: absolute;
width: 100%;
height: 100%;
}
g-spectrogram-controls {
position: absolute;
}
</style>
<canvas id="canvas"></canvas>
<canvas id="labels"></canvas>
<template if="{{controls}}">
<g-spectrogram-controls
log="{{log}}"
labels="{{labels}}"
speed="{{speed}}"
ticks="{{ticks}}"
color="{{color}}">
</g-spectrogram-controls>
</template>
<template if="{{oscillator}}">
<g-oscillator log={{log}} speed={{speed}}></g-oscillator>
</template>
</template>
<script src="g-spectrogram.js"></script>
</polymer-element>

View File

@ -5,27 +5,6 @@ error_reporting(E_ALL);
session_start();
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
$version = "1.0";
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => 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();
}
$data = $req->fetch();
?>
<!DOCTYPE html>
<html lang="en">
@ -35,14 +14,15 @@ $data = $req->fetch();
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Larynx | Chiro - Canto</title>
<link rel="stylesheet" type="text/css" href="/styles/style.css">
<link rel="import" href="/scripts/bower_components/polymer/polymer.html">
<link rel="import" href="./g-spectrogram-controls.html">
<link rel="import" href="./g-oscillator.html">
</head>
<?php
include("$root/analytics/owa.php");
include("$root/analytics/matomo.php");
?>
<?php
include("$root/analytics/matomo.php");
?>
<body>
<?php include("$root/menu.php");?>
<?php include("$root/header.php");?>
@ -55,8 +35,31 @@ include("$root/analytics/matomo.php");
</section>
<div id="larynx">
<h2>Larynx v<?=$version?></h2>
<audio src="<?="/storage/records/".$data['file_name']?>" id="audio"></audio>
<?php
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => 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);
?>
<audio src="<?="/storage/records/".$data['file_name']?>" id="audio" controls></audio>
<div class="spectrogram">
<div id="waveform"></div>
<div id="wave-spectrogram">
@ -90,7 +93,13 @@ include("$root/analytics/matomo.php");
<script src="https://unpkg.com/wavesurfer.js/dist/plugin/wavesurfer.spectrogram.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/addons/p5.sound.js"></script>
<script src="scripts/spectro.js"></script>
<script src="scripts/g-spectro.js"></script>
<!-- <script src="scripts/piano.js"></script> -->
</html>
</html>
<?php
} else {
$_SESSION['error_msg'] = "Error: Can't retrieve audio from database in Larynx";
header('Location: /');
}
?>

View File

@ -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();
}
}
});

View File

@ -0,0 +1,101 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
session_start();
$root = realpath($_SERVER["DOCUMENT_ROOT"]);
$version = "2.0";
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Larynx | Chiro - Canto</title>
<link rel="stylesheet" type="text/css" href="/styles/style.css">
</head>
<?php
include("$root/analytics/owa.php");
include("$root/analytics/matomo.php");
?>
<body>
<?php include("$root/menu.php");?>
<?php include("$root/header.php");?>
<section>
<h2>Larynx</h2>
<p>A web tool for bat sound analysis</p>
<a href="#larynx" title="Launch Larynx v<?=$version?>" id="launch-larynx">
<div class="arrow bounce"></div>
</a>
</section>
<div id="larynx">
<h2>Larynx v<?=$version?></h2>
<?php
require "$root/database/credentials.php";
// Connect the database
try {
$db = new PDO("mysql:host=$host;dbname=$database;charset=utf8",
$user,
$password,
array(PDO::ATTR_ERRMODE => 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);
?>
<audio src="<?="/storage/records/".$data['file_name']?>" id="audio" controls></audio>
<div class="spectrogram">
<div id="waveform"></div>
<div id="wave-spectrogram">
<canvas id="spectrogram-canvas"></canvas>
</div>
<div class="container row">
<div class="measures">
T:
<span id="cursor-time"></span>
s F:
<span id="cursor-frequency"></span>
kHz
</div>
<div class="controls">
<input type="button" name="play" value="play" id="play">
<input type="button" value="pause" id="pause">
<input type="button" value="restart" id="restart">
</div>
</div>
<p class="license"><?=$data['license']?> <?=$data['recordist_name']?></p>
<div class="settings">
<input type="checkbox" name="exp_x10" id="exp_x10">
<label for="exp_x10">exp x10</label>
</div>
</div>
</div>
<?php include("$root/footer.php");?>
</body>
<script src="/scripts/script.js"></script>
<!-- <script src="https://unpkg.com/wavesurfer.js"></script> -->
<!-- <script src="https://unpkg.com/wavesurfer.js/dist/plugin/wavesurfer.spectrogram.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/addons/p5.sound.js"></script> -->
<script src="scripts/spectro.js"></script>
<!-- <script src="scripts/piano.js"></script> -->
</html>
<?php
} else {
$_SESSION['error_msg'] = "Error: Can't retrieve audio from database in Larynx";
header('Location: /');
}
?>

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -45,9 +45,16 @@
<a href="/larynx">
<li>larynx</li>
</a>
<a href="/api">
<li>API</li>
</a>
<a href="/forum">
<li>forum</li>
</a>
<a href="/discussion" class="notification">
<li>messages</li>
<?php include("$root/discussion/notification.php")?>
</a>
<a href="/explore/mysteries">
<li>mysteries</li>
</a>

View File

@ -1,4 +0,0 @@
<form id="searchbar" action="/search/index.php" method="post">
<input type="text" name="query" placeholder="Search recordings.." required>
<input type="submit" name="search" value="Search">
</form>

View File

@ -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;
}