Skip to content

Commit

Permalink
Merge pull request #2 from introlab/electron
Browse files Browse the repository at this point in the history
Merge for 0.2 release
  • Loading branch information
GodCed authored Mar 5, 2018
2 parents 7b3153c + cb4da83 commit 23ee685
Show file tree
Hide file tree
Showing 29 changed files with 6,235 additions and 361 deletions.
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

0 comments on commit 23ee685

Please sign in to comment.