vocal_tract_length 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +24 -0
- data/Rakefile +2 -0
- data/config.ru +3 -0
- data/lib/public/frequency.png +0 -0
- data/lib/public/index.html +174 -0
- data/lib/public/length.png +0 -0
- data/lib/public/recorder.js +89 -0
- data/lib/public/recorderWorker.js +131 -0
- data/lib/vocal_tract_length/version.rb +3 -0
- data/lib/vocal_tract_length.rb +10 -0
- data/vocal_tract_length.gemspec +24 -0
- metadata +114 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 1807e5f276002fa0b457c2097660cc3e842856be
|
|
4
|
+
data.tar.gz: e065811735ab6fcaf704942a9e70e0be484d14e3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a6abf0acca943104437bd2d9ce179feed3f87eca7d6600c3070a8df70cd95199400acf6d084d5a8ca4e8f01ee0a8703f06ea333e4c649dc4d679220f37bfa692
|
|
7
|
+
data.tar.gz: 0df030b9089c01e570e5126bc6c19ce734f2c951e015fbce5f77d5f09afc4dea87f2c673929791f16336743a624870658ce06d82816ca6ad9aa6f20c28ae61a6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2014 Max Holder
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Vocal Tract Length
|
|
2
|
+
|
|
3
|
+
This small Sinatra application provides the frontend for users to record their
|
|
4
|
+
voice in order to calculate the length of their vocal tract.
|
|
5
|
+
|
|
6
|
+
See it in action at:
|
|
7
|
+
[http://maxwellholder.com/vocal_tract_length/index.html](http://maxwellholder.com/vocal_tract_length/index.html)
|
|
8
|
+
|
|
9
|
+
# Backend
|
|
10
|
+
|
|
11
|
+
To see the [Praat](http://praat.org) script and the Sinatra extension that provides the
|
|
12
|
+
`/extract_formant1` route, see the
|
|
13
|
+
[sinatra-praat](https://github.com/mxhold/sinatra-praat) project.
|
|
14
|
+
|
|
15
|
+
# How to Run
|
|
16
|
+
|
|
17
|
+
1. Install a recent version of [Ruby](https://ruby-lang.org)
|
|
18
|
+
2. Install [Praat](http://praat.org) and add it to your $PATH
|
|
19
|
+
3. Clone the project: `git clone https://github.com/mxhold/vocal_tract_length`
|
|
20
|
+
4. Run `bundle install`
|
|
21
|
+
5. Run `rackup`
|
|
22
|
+
6. It should be running at
|
|
23
|
+
[http://localhost:9292/vocal_tract_length/index.html](http://localhost:9292/vocal_tract_length/index.html)
|
|
24
|
+
|
data/Rakefile
ADDED
data/config.ru
ADDED
|
Binary file
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Calculate Your Vocal Tract Length</title>
|
|
4
|
+
<script>
|
|
5
|
+
var audio_context;
|
|
6
|
+
var recorder;
|
|
7
|
+
|
|
8
|
+
window.onload = function init() {
|
|
9
|
+
try {
|
|
10
|
+
// webkit shim
|
|
11
|
+
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
12
|
+
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
|
|
13
|
+
window.URL = window.URL || window.webkitURL;
|
|
14
|
+
|
|
15
|
+
audio_context = new AudioContext;
|
|
16
|
+
navigator.getUserMedia({audio: true}, startUserMedia, function(e) {
|
|
17
|
+
console.log('No live audio input: ' + e);
|
|
18
|
+
});
|
|
19
|
+
} catch (e) {
|
|
20
|
+
alert('No web audio support in this browser!');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function startUserMedia(stream) {
|
|
26
|
+
var input = audio_context.createMediaStreamSource(stream);
|
|
27
|
+
recorder = new Recorder(input);
|
|
28
|
+
document.querySelector("[data-id=allowHint]").style.display = 'none';
|
|
29
|
+
startRecordingButton = document.querySelector("[data-action=startRecording]");
|
|
30
|
+
startRecordingButton.disabled = false;
|
|
31
|
+
startRecordingButton.addEventListener("click", startRecording, false);
|
|
32
|
+
console.log("bound");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function startRecording() {
|
|
36
|
+
this.innerHTML = "Recording... "
|
|
37
|
+
this.disabled = true;
|
|
38
|
+
recorder && recorder.record();
|
|
39
|
+
|
|
40
|
+
setTimeout(stopRecording, 2000, this);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stopRecording(button) {
|
|
44
|
+
button.innerHTML = "Record"
|
|
45
|
+
button.disabled = false;
|
|
46
|
+
recorder && recorder.stop();
|
|
47
|
+
|
|
48
|
+
recorder.exportWAV(sendWAV);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function calculateVocalTractLength(formant1) {
|
|
52
|
+
return 34400 / (4.0 * formant1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function roundTo(number, places) {
|
|
56
|
+
return Math.round(number * Math.pow(10, places))/Math.pow(10, places);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setFormant1Result(formant1Text) {
|
|
60
|
+
formant1 = parseFloat(formant1Text);
|
|
61
|
+
document.querySelector("[data-id=formant1]").innerHTML = roundTo(formant1, 0);
|
|
62
|
+
document.querySelector("[data-id=vocalTractLength]").innerHTML = roundTo(calculateVocalTractLength(formant1), 1);
|
|
63
|
+
document.querySelector("[data-id=results]").style.display = 'initial';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sendWAV(blob) {
|
|
67
|
+
var formData = new FormData();
|
|
68
|
+
formData.append('data', blob);
|
|
69
|
+
xhr = new XMLHttpRequest();
|
|
70
|
+
xhr.open("POST", "extract_formant1", true);
|
|
71
|
+
xhr.onload = function() {
|
|
72
|
+
setFormant1Result(xhr.responseText);
|
|
73
|
+
}
|
|
74
|
+
xhr.send(formData);
|
|
75
|
+
|
|
76
|
+
recorder.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
</script>
|
|
81
|
+
<script src="recorder.js"></script>
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
<nav>
|
|
85
|
+
<a href="/">Home</a>
|
|
86
|
+
<a href="https://github.com/mxhold/vocal_tract_length">Source code</a>
|
|
87
|
+
</nav>
|
|
88
|
+
<section>
|
|
89
|
+
<h1>Calculate Your Vocal Tract Length</h1>
|
|
90
|
+
<p>
|
|
91
|
+
Click <em>Record</em> and produce an unobstructed vowel (the last sound in
|
|
92
|
+
the English word <em>comma</em>) continuously for 2 seconds.
|
|
93
|
+
</p>
|
|
94
|
+
<p data-id="allowHint">
|
|
95
|
+
<em>First, click "Allow" in your browser to let this page to record your
|
|
96
|
+
voice.</em>
|
|
97
|
+
</p>
|
|
98
|
+
<p>
|
|
99
|
+
<button data-action="startRecording" disabled>Record</button>
|
|
100
|
+
</p>
|
|
101
|
+
</section>
|
|
102
|
+
<section data-id="results" style="display: none;">
|
|
103
|
+
<h1>Results</h1>
|
|
104
|
+
<p>
|
|
105
|
+
F<sub>1</sub> = <span data-id="formant1">error</span> Hz
|
|
106
|
+
</p>
|
|
107
|
+
<p>
|
|
108
|
+
Estimated vocal tract length:
|
|
109
|
+
<span data-id="vocalTractLength">error</span> cm
|
|
110
|
+
</p>
|
|
111
|
+
<p>
|
|
112
|
+
Average vocal tract length (female): 14.1 cm<br>
|
|
113
|
+
Average vocal tract length (male): 17 cm<br>
|
|
114
|
+
</p>
|
|
115
|
+
</section>
|
|
116
|
+
<section>
|
|
117
|
+
<h1>About</h1>
|
|
118
|
+
<p>
|
|
119
|
+
If we simplify things significantly, the human vocal tract is really just
|
|
120
|
+
a tube that is closed on one end (where your vocal folds vibrate) and open
|
|
121
|
+
on another end (your lips).
|
|
122
|
+
</p>
|
|
123
|
+
<p>
|
|
124
|
+
Given this model, there is a formula that expresses the relationship
|
|
125
|
+
between resonance frequencies (<em>f</em>, which we is what we are
|
|
126
|
+
measuring here), the speed of sound (<em>v</em>, which we know), and the
|
|
127
|
+
length of the tube (<em>L</em>, which is what we are trying to figure
|
|
128
|
+
out):
|
|
129
|
+
</p>
|
|
130
|
+
<img src="frequency.png" alt="f = nv/2L" height="50">
|
|
131
|
+
<p>
|
|
132
|
+
The <em>n</em> here refers to which resonance frequency we're talking
|
|
133
|
+
about. Vowels will usually have four or more distinguishable resonance
|
|
134
|
+
frequencies, but we're just dealing with the first (F<sub>1</sub>) here,
|
|
135
|
+
so we can replace this with 1. We can also replace <em>v</em> with 34400
|
|
136
|
+
cm/s since we know that's the speed of sound at sea level at room
|
|
137
|
+
temperature (20°C). And since we know the frequency but want to
|
|
138
|
+
determine the length, we can solve for L to get this equation:
|
|
139
|
+
</p>
|
|
140
|
+
<img src="length.png" alt="L = 34400/4f" height="50">
|
|
141
|
+
<h2>How are you measuring F<sub>1</sub>?</h2>
|
|
142
|
+
<p>
|
|
143
|
+
When you hit record, we are using a Javascript library that wraps the
|
|
144
|
+
browser's audio recording API to record a 2 second WAV audio clip.
|
|
145
|
+
Thanks to
|
|
146
|
+
<a href="http://matt-diamond.com/">Matt Diamond's</a>
|
|
147
|
+
<a href="https://github.com/mattdiamond/Recorderjs">Recorderjs project</a>
|
|
148
|
+
for making that library.
|
|
149
|
+
</p>
|
|
150
|
+
<p>
|
|
151
|
+
This data is then sent to the server where it is processed using <a
|
|
152
|
+
href="http://praat.org">Praat</a>, a seriously cool program that can do
|
|
153
|
+
all sorts of phonetic analysis. This gives us the first formant which we
|
|
154
|
+
then plug into the above equation.
|
|
155
|
+
</p>
|
|
156
|
+
<p>
|
|
157
|
+
You can see the source code for this page and the server side Praat script
|
|
158
|
+
on <a href="https://github.com/mxhold/vocal_tract_length">GitHub</a>.
|
|
159
|
+
</p>
|
|
160
|
+
<h2>Is this accurate? Why is the estimate so inconsistent?</h2>
|
|
161
|
+
<p>
|
|
162
|
+
It is pretty hard to get people to produce an unobstructed vowel
|
|
163
|
+
consistently so that is probably the biggest limitation with this method.
|
|
164
|
+
We're also using a simplified model of the vocal tract, likely using a
|
|
165
|
+
less than perfect microphone, and only taking a 2 second sample.
|
|
166
|
+
</p>
|
|
167
|
+
<p>
|
|
168
|
+
The point of this is not to actually give you an accurate measure, but
|
|
169
|
+
rather to show it's possible to calculate the length of the vocal tract
|
|
170
|
+
from a recording, which I think is pretty cool.
|
|
171
|
+
</p>
|
|
172
|
+
</section>
|
|
173
|
+
</body>
|
|
174
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
(function(window){
|
|
2
|
+
|
|
3
|
+
var WORKER_PATH = 'recorderWorker.js';
|
|
4
|
+
|
|
5
|
+
var Recorder = function(source, cfg){
|
|
6
|
+
var config = cfg || {};
|
|
7
|
+
var bufferLen = config.bufferLen || 4096;
|
|
8
|
+
this.context = source.context;
|
|
9
|
+
this.node = (this.context.createScriptProcessor ||
|
|
10
|
+
this.context.createJavaScriptNode).call(this.context,
|
|
11
|
+
bufferLen, 2, 2);
|
|
12
|
+
var worker = new Worker(config.workerPath || WORKER_PATH);
|
|
13
|
+
worker.postMessage({
|
|
14
|
+
command: 'init',
|
|
15
|
+
config: {
|
|
16
|
+
sampleRate: this.context.sampleRate
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
var recording = false,
|
|
20
|
+
currCallback;
|
|
21
|
+
|
|
22
|
+
this.node.onaudioprocess = function(e){
|
|
23
|
+
if (!recording) return;
|
|
24
|
+
worker.postMessage({
|
|
25
|
+
command: 'record',
|
|
26
|
+
buffer: [
|
|
27
|
+
e.inputBuffer.getChannelData(0),
|
|
28
|
+
e.inputBuffer.getChannelData(1)
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.configure = function(cfg){
|
|
34
|
+
for (var prop in cfg){
|
|
35
|
+
if (cfg.hasOwnProperty(prop)){
|
|
36
|
+
config[prop] = cfg[prop];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.record = function(){
|
|
42
|
+
recording = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.stop = function(){
|
|
46
|
+
recording = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.clear = function(){
|
|
50
|
+
worker.postMessage({ command: 'clear' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.getBuffer = function(cb) {
|
|
54
|
+
currCallback = cb || config.callback;
|
|
55
|
+
worker.postMessage({ command: 'getBuffer' })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.exportWAV = function(cb, type){
|
|
59
|
+
currCallback = cb || config.callback;
|
|
60
|
+
type = type || config.type || 'audio/wav';
|
|
61
|
+
if (!currCallback) throw new Error('Callback not set');
|
|
62
|
+
worker.postMessage({
|
|
63
|
+
command: 'exportWAV',
|
|
64
|
+
type: type
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
worker.onmessage = function(e){
|
|
69
|
+
var blob = e.data;
|
|
70
|
+
currCallback(blob);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
source.connect(this.node);
|
|
74
|
+
this.node.connect(this.context.destination); //this should not be necessary
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Recorder.forceDownload = function(blob, filename){
|
|
78
|
+
var url = (window.URL || window.webkitURL).createObjectURL(blob);
|
|
79
|
+
var link = window.document.createElement('a');
|
|
80
|
+
link.href = url;
|
|
81
|
+
link.download = filename || 'output.wav';
|
|
82
|
+
var click = document.createEvent("Event");
|
|
83
|
+
click.initEvent("click", true, true);
|
|
84
|
+
link.dispatchEvent(click);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
window.Recorder = Recorder;
|
|
88
|
+
|
|
89
|
+
})(window);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
var recLength = 0,
|
|
2
|
+
recBuffersL = [],
|
|
3
|
+
recBuffersR = [],
|
|
4
|
+
sampleRate;
|
|
5
|
+
|
|
6
|
+
this.onmessage = function(e){
|
|
7
|
+
switch(e.data.command){
|
|
8
|
+
case 'init':
|
|
9
|
+
init(e.data.config);
|
|
10
|
+
break;
|
|
11
|
+
case 'record':
|
|
12
|
+
record(e.data.buffer);
|
|
13
|
+
break;
|
|
14
|
+
case 'exportWAV':
|
|
15
|
+
exportWAV(e.data.type);
|
|
16
|
+
break;
|
|
17
|
+
case 'getBuffer':
|
|
18
|
+
getBuffer();
|
|
19
|
+
break;
|
|
20
|
+
case 'clear':
|
|
21
|
+
clear();
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function init(config){
|
|
27
|
+
sampleRate = config.sampleRate;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function record(inputBuffer){
|
|
31
|
+
recBuffersL.push(inputBuffer[0]);
|
|
32
|
+
recBuffersR.push(inputBuffer[1]);
|
|
33
|
+
recLength += inputBuffer[0].length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function exportWAV(type){
|
|
37
|
+
var bufferL = mergeBuffers(recBuffersL, recLength);
|
|
38
|
+
var bufferR = mergeBuffers(recBuffersR, recLength);
|
|
39
|
+
var interleaved = interleave(bufferL, bufferR);
|
|
40
|
+
var dataview = encodeWAV(interleaved);
|
|
41
|
+
var audioBlob = new Blob([dataview], { type: type });
|
|
42
|
+
|
|
43
|
+
this.postMessage(audioBlob);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getBuffer() {
|
|
47
|
+
var buffers = [];
|
|
48
|
+
buffers.push( mergeBuffers(recBuffersL, recLength) );
|
|
49
|
+
buffers.push( mergeBuffers(recBuffersR, recLength) );
|
|
50
|
+
this.postMessage(buffers);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clear(){
|
|
54
|
+
recLength = 0;
|
|
55
|
+
recBuffersL = [];
|
|
56
|
+
recBuffersR = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mergeBuffers(recBuffers, recLength){
|
|
60
|
+
var result = new Float32Array(recLength);
|
|
61
|
+
var offset = 0;
|
|
62
|
+
for (var i = 0; i < recBuffers.length; i++){
|
|
63
|
+
result.set(recBuffers[i], offset);
|
|
64
|
+
offset += recBuffers[i].length;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function interleave(inputL, inputR){
|
|
70
|
+
var length = inputL.length + inputR.length;
|
|
71
|
+
var result = new Float32Array(length);
|
|
72
|
+
|
|
73
|
+
var index = 0,
|
|
74
|
+
inputIndex = 0;
|
|
75
|
+
|
|
76
|
+
while (index < length){
|
|
77
|
+
result[index++] = inputL[inputIndex];
|
|
78
|
+
result[index++] = inputR[inputIndex];
|
|
79
|
+
inputIndex++;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function floatTo16BitPCM(output, offset, input){
|
|
85
|
+
for (var i = 0; i < input.length; i++, offset+=2){
|
|
86
|
+
var s = Math.max(-1, Math.min(1, input[i]));
|
|
87
|
+
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeString(view, offset, string){
|
|
92
|
+
for (var i = 0; i < string.length; i++){
|
|
93
|
+
view.setUint8(offset + i, string.charCodeAt(i));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function encodeWAV(samples){
|
|
98
|
+
var buffer = new ArrayBuffer(44 + samples.length * 2);
|
|
99
|
+
var view = new DataView(buffer);
|
|
100
|
+
|
|
101
|
+
/* RIFF identifier */
|
|
102
|
+
writeString(view, 0, 'RIFF');
|
|
103
|
+
/* RIFF chunk length */
|
|
104
|
+
view.setUint32(4, 36 + samples.length * 2, true);
|
|
105
|
+
/* RIFF type */
|
|
106
|
+
writeString(view, 8, 'WAVE');
|
|
107
|
+
/* format chunk identifier */
|
|
108
|
+
writeString(view, 12, 'fmt ');
|
|
109
|
+
/* format chunk length */
|
|
110
|
+
view.setUint32(16, 16, true);
|
|
111
|
+
/* sample format (raw) */
|
|
112
|
+
view.setUint16(20, 1, true);
|
|
113
|
+
/* channel count */
|
|
114
|
+
view.setUint16(22, 2, true);
|
|
115
|
+
/* sample rate */
|
|
116
|
+
view.setUint32(24, sampleRate, true);
|
|
117
|
+
/* byte rate (sample rate * block align) */
|
|
118
|
+
view.setUint32(28, sampleRate * 4, true);
|
|
119
|
+
/* block align (channel count * bytes per sample) */
|
|
120
|
+
view.setUint16(32, 4, true);
|
|
121
|
+
/* bits per sample */
|
|
122
|
+
view.setUint16(34, 16, true);
|
|
123
|
+
/* data chunk identifier */
|
|
124
|
+
writeString(view, 36, 'data');
|
|
125
|
+
/* data chunk length */
|
|
126
|
+
view.setUint32(40, samples.length * 2, true);
|
|
127
|
+
|
|
128
|
+
floatTo16BitPCM(view, 44, samples);
|
|
129
|
+
|
|
130
|
+
return view;
|
|
131
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'vocal_tract_length/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "vocal_tract_length"
|
|
8
|
+
spec.version = VocalTractLength::VERSION
|
|
9
|
+
spec.authors = ["Max Holder"]
|
|
10
|
+
spec.email = ["mxhold@gmail.com"]
|
|
11
|
+
spec.summary = %q{Calculate your vocal tract length by recording your voice}
|
|
12
|
+
spec.homepage = ""
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
22
|
+
spec.add_dependency 'sinatra'
|
|
23
|
+
spec.add_dependency 'sinatra-praat'
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: vocal_tract_length
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Max Holder
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-12-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: sinatra
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sinatra-praat
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description:
|
|
70
|
+
email:
|
|
71
|
+
- mxhold@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- Gemfile
|
|
78
|
+
- LICENSE.txt
|
|
79
|
+
- README.md
|
|
80
|
+
- Rakefile
|
|
81
|
+
- config.ru
|
|
82
|
+
- lib/public/frequency.png
|
|
83
|
+
- lib/public/index.html
|
|
84
|
+
- lib/public/length.png
|
|
85
|
+
- lib/public/recorder.js
|
|
86
|
+
- lib/public/recorderWorker.js
|
|
87
|
+
- lib/vocal_tract_length.rb
|
|
88
|
+
- lib/vocal_tract_length/version.rb
|
|
89
|
+
- vocal_tract_length.gemspec
|
|
90
|
+
homepage: ''
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata: {}
|
|
94
|
+
post_install_message:
|
|
95
|
+
rdoc_options: []
|
|
96
|
+
require_paths:
|
|
97
|
+
- lib
|
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
requirements: []
|
|
109
|
+
rubyforge_project:
|
|
110
|
+
rubygems_version: 2.2.2
|
|
111
|
+
signing_key:
|
|
112
|
+
specification_version: 4
|
|
113
|
+
summary: Calculate your vocal tract length by recording your voice
|
|
114
|
+
test_files: []
|