Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge for 0.2 release #2

Merged
merged 22 commits into from
Mar 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules
google-api-key.json
build
config
44 changes: 17 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,7 @@ tracked: {
* **SSS**
```
separated: {
fS = 44100;
hopSize = 512;
nBits = 16;

interface: {
type = "blackhole";
}
};

postfiltered: {
fS = 44100;
hopSize = 512;
nBits = 16;

interface: {
type = "socket";
ip = "<IP>";
port = 10000;
}
};
```
**OR**
```
separated: {
fS = 44100;
fS = <SAMPLE RATE>;
hopSize = 512;
nBits = 16;

Expand All @@ -86,12 +62,14 @@ separated: {
};

postfiltered: {
fS = 44100;
fS = <SAMPLE RATE>;
hopSize = 512;
nBits = 16;

interface: {
type = "blackhole";
type = "socket";
ip = "<IP>";
port = 10001;
}
};
```
Expand Down Expand Up @@ -120,8 +98,20 @@ Click the record button at the upper right of the Live Data window to open the R
![ODAS Studio Record page screenshot](/screenshots/record.png)

* **Workspace Path** : specifies a folder where audio files will be recorded. It must be set before enabling recording.
* **Show** : chooses to dispay either separated, postfiltered or all recordings.
* **Record** : when selected, separated audio sources will be recorded in distinct wav files.
* The file list displays recordings in the workspace and allows playback and deletion of files.
* The transcript displays the speech to text transcription of the hovered recording.

### Configure
Click the configure button at the upper right of the Live Data window to open the Configure window.

![ODAS Studio Configure page screenshot](/screenshots/configure.png)

* **Use Google Speech Voice Recognition** : sends recorded audio to Google Speech service to generate transcriptions.
* **Google API Keyfile** : path to your Google Speech API json key.
* **Transcription Language** : language in which the recorded audio is processed by Google.
* **Sample Rate** : sample rate of the audio streams produced by ODAS. Must match ODAS sink config file.

## License
ODAS Studio is free and open source. ODAS Studio is licensed under the [MIT License](/LICENSE).
172 changes: 172 additions & 0 deletions audio-recorder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
const streamToText = require('./stream-to-text.js');
const path = require('path');
const wav = require('wav')
const EventEmitter = require('events');
const appSettings = require('./settings.js').appSettings;

/*
* Audio parameters
*/

const bitNumber = 16

/*
End of parameters
*/

// Audio Recorder
exports.AudioRecorder = class AudioRecorder extends EventEmitter {

constructor(index, suffix) {
super();

this.active = false;
this.hold = false;
this.index = index;

this.recordingEnabled = false;
this.workspacePath = '';
this.suffix = suffix;

this.buffer = undefined;
this.writer = undefined;
this.path = undefined;

this.transcripter = new streamToText.Transcripter();

this.transcripter.on('data', data => {
this.emit('fuzzy-transcript', this.path, data);
console.log(data);
});

this.transcripter.on('error', err => {
console.error(err);
});
}

receive(data) {

if(this.active) {

if(this.hold) {

if(typeof(this.buffer) !== 'undefined') {

this.buffer = Buffer.concat([this.buffer, data]);
}

else {

this.buffer = data;
}
}

else {

try {

if(typeof(this.buffer) !== 'undefined') {

data = Buffer.concat([this.buffer, data]);
this.buffer = undefined;

console.log(`Wrote samples buffered in writer ${this.index}`);
}

if( !this.writer.write(data)) {

console.warn(`Write stream ${this.index} is full\nHolding samples...`);
this.hold = true;
}
}

catch(err) {

console.error(`Couldn't write to recorder ${this.index}`);
console.warn(err);

this.stopRecording();
}

}

this.transcripter.putData(data);

}
}

startRecording(id) {
if(this.recordingEnabled) {
if(typeof(this.writer) !== 'undefined') { // Verify that previous recording is cleared

setTimeout(() => {

this.startRecording(id);
console.log(`Recorder ${id} was defined. Retrying...`);
}, 100);

return;
}

console.log(`Recorder ${this.index} started`)
console.log(`Recorder ${this.index} was ${this.active} active`)

let filename = path.join(this.workspacePath, `ODAS_${id}_${new Date().toLocaleString()}_${this.suffix}.wav`)
this.path = filename

try {
this.writer = new wav.FileWriter(filename,{channels:1, sampleRate:appSettings.sampleRate, bitDepth:bitNumber});
this.emit('fuzzy-recording', filename);

this.writer.on('drain', () => { // Release hold when write stream is cleared
console.log(`Writer ${this.index} is empty.\nResuming...`);
this.hold = false;
});

this.active = true;
this.hold = false;
this.buffer = undefined;
this.transcripter.start();
}

catch(err) {
console.error(`Failed to start recorder ${this.index}`);
console.log(err);
this.writer = undefined;
}
}
}

stopRecording() {

if(this.active) {

this.active = false;
console.log(`Recorder ${this.index} ended`);
this.transcripter.stop();

console.log(`Registering header on recorder ${this.index}`);
this.writer.end();

this.writer.on('header',(header) => {

console.log(`Registered header on recorder ${this.index}`);
this.emit('add-recording', this.writer.path);

this.writer = undefined;
console.log(`Recorder ${this.index} undefined`);

});
}
}

enableRecording(workspacePath) {
this.recordingEnabled = true;
this.workspacePath = workspacePath;
}

disableRecording() {
this.recordingEnabled = false;
this.stopRecording();
}
}
22 changes: 22 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const packager = require('electron-packager');
const rebuild = require('electron-rebuild').rebuild;

packager({
dir: '.',
afterCopy: [
(buildPath, electronVersion, platform, arch, callback) => {
rebuild({ buildPath, electronVersion, arch })
.then(() => callback())
.catch((error) => callback(error));
}
],
arch: ['ia32','x64','armv7l'],
executableName: 'odasStudio',
icon: 'resources/images/introlab_icon',
name: 'ODAS Studio',
out: 'build',
overwrite: 'true',
platform: 'linux, win32, darwin'
})

.catch(err => console.error(err));
84 changes: 84 additions & 0 deletions configure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const electron = require('electron');
const path = require('path');
const url = require('url');
const appSettings = require('./settings.js').appSettings;

const BrowserWindow = electron.BrowserWindow;
const ipcMain = electron.ipcMain;

/*
* Manage configuration window
*/

let configureWindow = null;

function openConfigureWindow () {

if(configureWindow !== null) {
configureWindow.show();
return;
}

configureWindow = new BrowserWindow({width: 800, height: 600, show: false});

configureWindow.loadURL(url.format({
pathname: path.join(__dirname, 'views','configure.html'),
protocol: 'file:',
slashes: true
}));

configureWindow.once('ready-to-show', function () {
configureWindow.show();
});

configureWindow.on('closed', function () {
configureWindow = null;
});
}

ipcMain.on('open-configure-window', openConfigureWindow);

/*
* Access the app settings
*/

const getSettings = function() {

let settings = {
language: appSettings.language,
sampleRate: appSettings.sampleRate,
apiKeyfile: appSettings.apiKeyfile,
useSpeech: appSettings.useSpeech
};

return settings;
}

const setSettings = function(settings) {

if(settings.useSpeech) {
appSettings.language = settings.language;
appSettings.sampleRate = settings.sampleRate;
appSettings.apiKeyfile = settings.apiKeyfile;
appSettings.useSpeech = settings.useSpeech;
}

else {
appSettings.sampleRate = settings.sampleRate;
appSettings.useSpeech = settings.useSpeech;
}
}

/*
* Respond to settings window requests
*/

ipcMain.on('get-settings', event => {
let settings = getSettings();
event.sender.send('settings', settings);
});

ipcMain.on('set-settings', (event, settings) => {
setSettings(settings);
event.sender.send('settings-applied');
});
7 changes: 4 additions & 3 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function createWindow () {
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
odasStudio.mainWindow = null
record.quit()
app.quit()
})

Expand Down Expand Up @@ -78,10 +79,10 @@ app.on('activate', function () {
// code. You can also put them in separate files and require them here.

const sockets = require('./servers.js')
const recordings = require('./recordings.js')
const record = require('./record.js')
const share = require('./share.js')
const configure = require('./configure.js')
odasStudio.odas = require('./odas.js')

recordings.register(odasStudio)

sockets.startTrackingServer(odasStudio)
sockets.startPotentialServer(odasStudio)
Loading