diff --git a/README.md b/README.md index 67423f8..6100633 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,10 @@ process.stdin.pipe(speaker); `require('speaker')` directly returns the `Speaker` constructor. It is the only interface exported by `node-speaker`. -### new Speaker([ format ]) -> Speaker instance +### new Speaker([ options ]) -> Speaker instance Creates a new `Speaker` instance, which is a writable stream that you can pipe -PCM audio data to. The optional `format` object may contain any of the `Writable` +PCM audio data to. The optional `options` object may contain any of the `Writable` base class options, as well as any of these PCM formatting options: * `channels` - The number of audio channels. PCM data must be interleaved. Defaults to `2`. @@ -61,12 +61,21 @@ base class options, as well as any of these PCM formatting options: * `signed` - Boolean specifying if the samples are signed or unsigned. Defaults to `true` when bit depth is 8-bit, `false` otherwise. * `float` - Boolean specifying if the samples are floating-point values. Defaults to `false`. * `samplesPerFrame` - The number of samples to send to the audio backend at a time. You likely don't need to mess with this value. Defaults to `1024`. +* `device` - The name of the playback device. E.g. `'hw:0,0'` for first device of first sound card or `'hw:1,0'` for first device of second sound card. Defaults to `null` which will pick the default device. #### "open" event Fired when the backend `open()` call has completed. This happens once the first `write()` call happens on the speaker instance. +#### "progress" event + +Fired after each data write to the speaker instance. The parameter contains progress info: +* `numwr` - the cumulative number of writes (this is related to the number of frames) +* `wrlen` - the number of bytes written this time +* `wrtotal` - the total number of bytes written +* `buflen` - the number of bytes currently remaining in the buffer + #### "flush" event Fired after the speaker instance has had `end()` called, and after the audio data diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..ff786f2 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,55 @@ +import { Writable, WritableOptions } from 'stream'; + +export interface Options extends WritableOptions { + readonly channels?: number; + readonly bitDepth?: number; + readonly sampleRate?: number; + readonly lowWaterMark?: number; + readonly highWaterMark?: number; +} + +export interface Format { + readonly float?: boolean; + readonly signed?: boolean; + readonly bitDepth?: number; + readonly channels?: number; + readonly sampleRate?: number; + readonly samplesPerFrame?: number; +} + +/** + * The `Speaker` class accepts raw PCM data written to it, and then sends that data + * to the default output device of the OS. + * + * @param opts options. + */ +export default class Speaker extends Writable { + constructor(opts?: Options); + + /** + * Closes the audio backend. Normally this function will be called automatically + * after the audio backend has finished playing the audio buffer through the + * speakers. + * + * @param flush Defaults to `true`. + */ + public close(flush: boolean): string; + + /** + * Returns the `MPG123_ENC_*` constant that corresponds to the given "format" + * object, or `null` if the format is invalid. + * + * @param format format object with `channels`, `sampleRate`, `bitDepth`, etc. + * @return MPG123_ENC_* constant, or `null` + */ + public getFormat(format: Format): number | null; + + /** + * Returns whether or not "format" is playable via the "output module" + * that was selected during compilation. + * + * @param format MPG123_ENC_* format constant + * @return whether or not is playable + */ + public isSupported(format: number): boolean; +} diff --git a/index.js b/index.js index 25d15d6..5a38f63 100644 --- a/index.js +++ b/index.js @@ -81,6 +81,10 @@ class Speaker extends Writable { debug('setting default %o: %o', 'signed', this.bitDepth !== 8) this.signed = this.bitDepth !== 8 } + if (this.device == null) { + debug('setting default %o: %o', 'device', null) + this.device = null + } const format = Speaker.getFormat(this) if (format == null) { @@ -97,7 +101,7 @@ class Speaker extends Writable { // initialize the audio handle // TODO: open async? this.audio_handle = bufferAlloc(binding.sizeof_audio_output_t) - const r = binding.open(this.audio_handle, this.channels, this.sampleRate, format) + const r = binding.open(this.audio_handle, this.channels, this.sampleRate, format, this.device) if (r !== 0) { throw new Error(`open() failed: ${r}`) } @@ -141,6 +145,10 @@ class Speaker extends Writable { debug('setting %o: %o', 'samplesPerFrame', opts.samplesPerFrame) this.samplesPerFrame = opts.samplesPerFrame } + if (opts.device != null) { + debug('setting %o: %o', 'device', opts.device) + this.device = opts.device + } if (opts.endianness == null || endianness === opts.endianness) { // no "endianness" specified or explicit native endianness this.endianness = endianness @@ -196,8 +204,13 @@ class Speaker extends Writable { binding.write(handle, b, b.length, onwrite) } +// var THIS = this; //preserve "this" for onwrite call-back; arrow functions don't have a "this" const onwrite = (r) => { - debug('wrote %o bytes', r) + debug('wrote %o bytes', r); +// removed -dj 12/15/18; too much latency (and too unpredictable due to node event loop) +// if (isNaN(++THIS.numwr)) THIS.numwr = 1; //track #writes; is this == #frames? +// if (isNaN(THIS.wrtotal += r)) THIS.wrtotal = r; //track total data written +// THIS.emit('progress', {numwr: THIS.numwr, wrlen: r, wrtotal: THIS.wrtotal, buflen: (left || []).length}); //give caller some progress info if (r !== b.length) { done(new Error(`write() failed: ${r}`)) } else if (left) { @@ -212,6 +225,61 @@ class Speaker extends Writable { write() } + + /** + * get current playback progress. -dj 12/15/18 + * Last couple of writes can be used to estimate current playback status. + * There will always be latency and hence uncertainty, but try to keep to minimum. + * Async event emit would introduce more (variable) latency due to uv event loop, + * so just let caller ask for progress data synchronously. + * + * @api public + */ + progress () { + debug('status()') + if (this._closed) return debug('already closed...') + if (!this.audio_handle) return debug('no handle, closed?'); + debug('invoking progess() native binding') + const retval = binding.progess(this.audio_handle); + debug('got back %o from progess()', retval) + return retval + } + + + /** + * get current playback volume. -dj 12/15/18 + * Not sure which value to use (base, actual, rva_db), so get them all. + * + * @api public + */ + get_vol () { + debug('get_vol()') + if (this._closed) return debug('already closed...') + if (!this.audio_handle) return debug('no handle, closed?'); + debug('invoking get_vol() native binding') + const retval = binding.volume_get(this.audio_handle); + debug('got back %o from volume_get()', retval) + return retval + } + + + /** + * set current playback volume. -dj 12/17/18 + * + * @api public + * @param {new_vol} float + */ + set_vol (new_vol) { + debug('set_vol() %o new volume', new_vol) + if (this._closed) return debug('already closed...') + if (!this.audio_handle) return debug('no handle, closed?'); + debug('invoking set_vol() native binding') + const retval = binding.volume_set(this.audio_handle, new_vol); + debug('got back %o from volume_set()', retval) + return retval + } + + /** * Called when this stream is pipe()d to from another readable stream. * If the "sampleRate", "channels", "bitDepth", and "signed" properties are diff --git a/package.json b/package.json index 1bfbff0..bf3909f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "speaker", - "version": "0.4.0", + "version": "0.4.2", "license": "MIT", "description": "Output PCM audio data to the speakers", "author": "Nathan Rajlich (http://tootallnate.net)", "repository": "TooTallNate/node-speaker", + "main": "index.js", + "types": "index.d.ts", "scripts": { "test": "standard && node-gyp rebuild --mpg123-backend=dummy && mocha --reporter spec" }, @@ -16,6 +18,7 @@ "readable-stream": "^2.3.3" }, "devDependencies": { + "@types/node": "^10.11.4", "mocha": "^3.5.0", "standard": "^10.0.3" }, diff --git a/src/binding.cc b/src/binding.cc index edc068b..173298a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,6 +1,9 @@ #include #include #include +#include //now(), duration<> +#include //std::map<> +#include //tan() #include "node_pointer.h" #include "output.h" @@ -12,6 +15,58 @@ extern mpg123_module_t mpg123_output_module_info; namespace { + +#define WANT_VOLUME + +//helper macros: +//#define TOSTR_NESTED(str) #str //kludge: need nested macro for this to work +//#define TOSTR(str) TOSTR_NESTED(str) +#define HERE(n) printf("here# " #n " @%d\n", __LINE__) + + +//using Now = std::chrono::high_resolution_clock::now(); +auto now() { return std::chrono::high_resolution_clock::now(); } //use high res clock for more accurate progress -dj 12/15/18 + +constexpr double PI() { return std::atan(1) * 4; } //https://stackoverflow.com/questions/1727881/how-to-use-the-pi-constant-in-c + +//add state info to audio_output struct: -dj 12/15/18 +typedef struct audio_output_MODIFIED_struct +{ + int numwr; //#writes queued + long wrtotal; //total bytes written +// long timestamp; //theoretical presentation time based on data samples + decltype(now()) enque, start, finish, epoch; //= Now(); //system time of last write (enqueue, start, finish), current time + float volume, multiplier; //volume control + audio_output_t original; //original untouched struct; put at end in case caller extends it +//methods: + double elapsed_usec(decltype(now())& when) + { +//examples at https://stackoverflow.com/questions/14391327/how-to-get-duration-as-int-millis-and-float-seconds-from-chrono + /*std::chrono::duration*/ auto diff = when - epoch; + return std::chrono::duration_cast(diff).count(); + } +// int sample_size; + static int SampleSize(int fmt) + { + static const std::map size_map = //map sample type => size + { + {MPG123_ENC_FLOAT_32, 32}, + {MPG123_ENC_FLOAT_64, 64}, + {MPG123_ENC_SIGNED_8, 8}, + {MPG123_ENC_UNSIGNED_8, 8}, + {MPG123_ENC_SIGNED_16, 16}, + {MPG123_ENC_UNSIGNED_16, 16}, + {MPG123_ENC_SIGNED_24, 24}, + {MPG123_ENC_UNSIGNED_24, 24}, + {MPG123_ENC_SIGNED_32, 32}, + {MPG123_ENC_UNSIGNED_32, 32}, + }; + return size_map.count(fmt)? size_map.find(fmt)->second / 8: 0; + } +} audio_output_MODIFIED_t; +#define audio_output_t audio_output_MODIFIED_t //use wedge to minimize source code changes + + struct write_req { uv_work_t req; audio_output_t *ao; @@ -21,21 +76,32 @@ struct write_req { Nan::Callback *callback; }; + NAN_METHOD(Open) { Nan::EscapableHandleScope scope; int r; audio_output_t *ao = UnwrapPointer(info[0]); memset(ao, 0, sizeof(audio_output_t)); - ao->channels = info[1]->Int32Value(); /* channels */ - ao->rate = info[2]->Int32Value(); /* sample rate */ - ao->format = info[3]->Int32Value(); /* MPG123_ENC_* format */ + ao->original.channels = info[1]->Int32Value(); /* channels */ + ao->original.rate = info[2]->Int32Value(); /* sample rate */ + ao->original.format = info[3]->Int32Value(); /* MPG123_ENC_* format */ + ao->epoch = now(); //remember init time -dj 12/15/18 + ao->volume = 1.0; + ao->multiplier = 1.0; + + if (info[4]->IsString()) { + v8::Local deviceString = info[4]->ToString(); + ao->original.device = new char[deviceString->Length() + 1]; + deviceString->WriteOneByte(reinterpret_cast(ao->original.device)); + } /* init_output() */ - r = mpg123_output_module_info.init_output(ao); + if (!ao->SampleSize(ao->original.format)) r = -12345; //throw "Unknown sample size"; //check if sample fmt is handled -dj 12/15/18 + else r = mpg123_output_module_info.init_output(&ao->original); if (r == 0) { /* open() */ - r = ao->open(ao); + r = ao->original.open(&ao->original); } info.GetReturnValue().Set(scope.Escape(Nan::New(r))); @@ -56,22 +122,99 @@ NAN_METHOD(Write) { req->len = len; req->written = 0; req->callback = new Nan::Callback(info[3].As()); + ao->enque = now(); //remember when write was queued (trying to measure latency) -dj 12/15/18 +//apply volume control here: +//CAUTION: alters data in-place; caller might not like it +//logic taken and generalized from https://github.com/reneraab/pcm-volume/ +//combined in here to reduce overhead (RPi doesn't have a lot of CPU power) +// info.GetReturnValue().Set(Nan::New(ao->volume)); +//#ifdef WANT_VOLUME +//printf("write: vol %f, mult %f\n", ao->volume, ao->multiplier); + switch (ao->original.format) + { +//logic taken from https://github.com/reneraab/pcm-volume/ + case MPG123_ENC_SIGNED_16: + if (ao->volume == 1.0) break; + for (int i = req->len / 2; i > 0; --i) + { + int32_t adjusted = ao->multiplier * *(int16_t*)buffer; //multiply Int16 by volume multiplier and round down +//clamp result to signed 16-bit value: + if (adjusted > 0x7FFF) adjusted = 0x7FFF; + if (adjusted < -0x7FFF) adjusted = -0x7FFF; + *(int16_t*)buffer++ = adjusted; + } + break; +//other cases generalized from above: + case MPG123_ENC_UNSIGNED_16: + if (ao->volume == 1.0) break; + for (int i = req->len / 2; i > 0; --i) + { + int32_t adjusted = ao->multiplier * *(uint16_t*)buffer; //multiply Int16 by volume multiplier and round down +//clamp result to unsigned 16-bit value: +//this seems incorrect for uint16; changed to 0..65535 +// if (adjusted > 32767) adjusted = 32767; +// if (adjusted < -32767) adjusted = -32767; + if (adjusted > 0xFFFF) adjusted = 0xFFFF; + if (adjusted < 0) adjusted = 0; + *(uint16_t*)buffer++ = adjusted; + } + break; + case MPG123_ENC_SIGNED_8: + if (ao->volume == 1.0) break; + for (int i = req->len; i > 0; --i) + { + int32_t adjusted = ao->multiplier * *(int8_t*)buffer; +//clamp result to signed 8-bit value: + if (adjusted > 0x7F) adjusted = 0x7F; + if (adjusted < -0x7F) adjusted = -0x7F; + *(int8_t*)buffer++ = adjusted; + } + break; + case MPG123_ENC_UNSIGNED_8: + if (ao->volume == 1.0) break; + for (int i = req->len; i > 0; --i) + { + int32_t adjusted = ao->multiplier * *(uint8_t*)buffer; +//clamp result to unsigned 8-bit value: + if (adjusted > 0xFF) adjusted = 0xFF; + if (adjusted < 0) adjusted = 0; + *(uint8_t*)buffer++ = adjusted; + } + break; +//TODO: other cases as needed + case MPG123_ENC_FLOAT_32: + case MPG123_ENC_FLOAT_64: + case MPG123_ENC_SIGNED_24: + case MPG123_ENC_UNSIGNED_24: + case MPG123_ENC_SIGNED_32: + case MPG123_ENC_UNSIGNED_32: + default: //just leave as-is + if (ao->volume == 1.0) break; +// info.GetReturnValue().SetUndefined(); //retval to indicate if vol control applied or not + break; + } +//#endif req->req.data = req; - uv_queue_work(uv_default_loop(), &req->req, write_async, (uv_after_work_cb)write_after); + uv_queue_work(Nan::GetCurrentEventLoop(), &req->req, write_async, (uv_after_work_cb)write_after); info.GetReturnValue().SetUndefined(); } void write_async (uv_work_t *req) { write_req *wreq = reinterpret_cast(req->data); - wreq->written = wreq->ao->write(wreq->ao, wreq->buffer, wreq->len); + wreq->written = wreq->ao->original.write(&wreq->ao->original, wreq->buffer, wreq->len); +//update progress data: -dj 12/15/18 + ++wreq->ao->numwr; //#writes queued + wreq->ao->wrtotal += wreq->len; //total bytes written + wreq->ao->start = now(); //system time of last write started } void write_after (uv_work_t *req) { Nan::HandleScope scope; write_req *wreq = reinterpret_cast(req->data); + wreq->ao->finish = now(); //system time of last write completed -dj 12/15/18 Local argv[] = { Nan::New(wreq->written) @@ -82,22 +225,100 @@ void write_after (uv_work_t *req) { delete wreq->callback; } + +//added method to get playback status -dj 12/15/18 +//caller can call this at precise intervals rather than receiving inprecise emitting async events +//NOTE: info is based on queued writes, but there will be latency anyway; works okay if consistent/predictable +NAN_METHOD(Progress) { + Nan::EscapableHandleScope scope; //Nan::HandleScope scope; //Escapable needed when returning a new object + audio_output_t *ao = UnwrapPointer(info[0]); + v8::Local retval = Nan::New(); +// retval->Set(Nan::New("numwr").ToLocalChecked(), info[0]->ToString()); +// HERE(2); +// printf("&ao %p, &numwr %p @%d\n", ao, &ao->numwr, __LINE__); +// printf("numwr %d, wrtotal %d @%d\n", ao->numwr, ao->wrtotal, __LINE__); + Nan::ForceSet(retval, Nan::New("numwr").ToLocalChecked(), Nan::New(ao->numwr)); //, static_cast(ReadOnly|DontDelete)); + Nan::ForceSet(retval, Nan::New("wrtotal_bytes").ToLocalChecked(), Nan::New(ao->wrtotal)); //, static_cast(ReadOnly|DontDelete)); +//get theoretical presentation time based on data samples: + long timestamp_msec = 1000L * ao->wrtotal / ao->original.channels / ao->original.rate / ao->SampleSize(ao->original.format); + Nan::ForceSet(retval, Nan::New("timestamp_msec").ToLocalChecked(), Nan::New(timestamp_msec)); //, static_cast(ReadOnly|DontDelete)); +// Nan::ForceSet(retval, Nan::New("now").ToLocalChecked(), Nan::New(ao->now)); //, static_cast(ReadOnly|DontDelete)); +//caller might have a different concept of system time, so return relative times: +//for disambiguation suggestions, see https://github.com/nodejs/nan/issues/233 + Nan::ForceSet(retval, Nan::New("enque_usec").ToLocalChecked(), Nan::New(ao->elapsed_usec(ao->enque))); + Nan::ForceSet(retval, Nan::New("start_usec").ToLocalChecked(), Nan::New(ao->elapsed_usec(ao->start))); + Nan::ForceSet(retval, Nan::New("finish_usec").ToLocalChecked(), Nan::New(ao->elapsed_usec(ao->finish))); + auto latest = now(); + Nan::ForceSet(retval, Nan::New("now_usec").ToLocalChecked(), Nan::New(ao->elapsed_usec(latest))); + info.GetReturnValue().Set(scope.Escape(retval)); + ao->epoch = latest; +} + + +//#ifdef WANT_VOLUME +//added methods to get/set volume -dj 12/17/18 +//not sure if stream needs to be paused to call this +//api docs at https://www.mpg123.de/api/group__mpg123__voleq.shtml +NAN_METHOD(VolumeGet) { +// Nan::EscapableHandleScope scope; //Nan::HandleScope scope; //Escapable needed when returning a new object + Isolate * isolate = info.GetIsolate(); //https://github.com/freezer333/nodecpp-demo/tree/master/conversions + audio_output_t *ao = UnwrapPointer(info[0]); +// double base, really, rva_db; +// int r = mpg123_get_volume(&ao->original, &base, &multipler, &rva_db); +// if (r) { info.GetReturnValue().Set(scope.Escape(Nan::New(r))); return; } //error +// v8::Local retval = Nan::New(); +// Nan::ForceSet(retval, Nan::New("base").ToLocalChecked(), Nan::New(base)); +// Nan::ForceSet(retval, Nan::New("actual").ToLocalChecked(), Nan::New(really)); +// Nan::ForceSet(retval, Nan::New("rva_db").ToLocalChecked(), Nan::New(rva_db)); +// info.GetReturnValue().Set(retval); + v8::Local retval = v8::Integer::New(isolate, ao->volume); +// v8::Local retval = v8::Number::New(isolate, value); +// info.GetReturnValue().Set(scope.Escape(Nan::New(r))); + info.GetReturnValue().Set(retval); +// info.GetReturnValue().Set(/*scope.Escape*/(Nan::Newvolume))); +} + + +NAN_METHOD(VolumeSet) { +// Nan::EscapableHandleScope scope; //Nan::HandleScope scope; //Escapable needed when returning a new object + audio_output_t *ao = UnwrapPointer(info[0]); +//#if 1 +// double vol = info[1]->DoubleValue(); +// int r = mpg123_volume(&ao->original, vol); +//#else +// double change = info[1]->DoubleValue(); +// int r = mpg123_volume_change(&ao->original, change); +//#endif +// info.GetReturnValue().Set(r); + ao->volume = info[1]->NumberValue(); +//see notes about volume control from https://github.com/reneraab/pcm-volume +//see also c.f. https://dsp.stackexchange.com/questions/2990/how-to-change-volume-of-a-pcm-16-bit-signed-audio/2996#2996 +//this.multiplier = Math.pow(10, (-48 + 54*this.volume)/20); +//see also c.f. http://www.ypass.net/blog/2010/01/pcm-audio-part-3-basic-audio-effects-volume-control/ + ao->multiplier = tan(ao->volume * PI() / 4); //vol 1 => mult 1.0 +//printf("setvol: vol %f, mult %f\n", ao->volume, ao->multiplier); + info.GetReturnValue().Set(/*scope.Escape*/(Nan::New(ao->volume))); +} +//#endif + + NAN_METHOD(Flush) { Nan::HandleScope scope; audio_output_t *ao = UnwrapPointer(info[0]); /* TODO: async */ - ao->flush(ao); + ao->original.flush(&ao->original); info.GetReturnValue().SetUndefined(); } NAN_METHOD(Close) { Nan::EscapableHandleScope scope; audio_output_t *ao = UnwrapPointer(info[0]); - ao->close(ao); + ao->original.close(&ao->original); int r = 0; - if (ao->deinit) { - r = ao->deinit(ao); + if (ao->original.deinit) { + r = ao->original.deinit(&ao->original); } + delete ao->original.device; info.GetReturnValue().Set(scope.Escape(Nan::New(r))); } @@ -118,13 +339,15 @@ void Initialize(Handle target) { audio_output_t ao; memset(&ao, 0, sizeof(audio_output_t)); - mpg123_output_module_info.init_output(&ao); - ao.channels = 2; - ao.rate = 44100; - ao.format = MPG123_ENC_SIGNED_16; - ao.open(&ao); - Nan::ForceSet(target, Nan::New("formats").ToLocalChecked(), Nan::New(ao.get_formats(&ao))); - ao.close(&ao); + mpg123_output_module_info.init_output(&ao.original); + ao.original.channels = 2; + ao.original.rate = 44100; + ao.original.format = MPG123_ENC_SIGNED_16; + ao.original.open(&ao.original); + Nan::ForceSet(target, Nan::New("formats").ToLocalChecked(), Nan::New(ao.original.get_formats(&ao.original))); + ao.original.close(&ao.original); + ao.epoch = now(); //remember init time -dj 12/15/18 + ao.multiplier = 1.0; target->Set(Nan::New("sizeof_audio_output_t").ToLocalChecked(), Nan::New(static_cast(sizeof(audio_output_t)))); @@ -148,6 +371,12 @@ void Initialize(Handle target) { Nan::SetMethod(target, "write", Write); Nan::SetMethod(target, "flush", Flush); Nan::SetMethod(target, "close", Close); +//-dj 12/15/18 added: + Nan::SetMethod(target, "progess", Progress); +#ifdef WANT_VOLUME + Nan::SetMethod(target, "volume_get", VolumeGet); + Nan::SetMethod(target, "volume_set", VolumeSet); +#endif } } // anonymous namespace diff --git a/test/test.js b/test/test.js index 6e7a94f..1154886 100644 --- a/test/test.js +++ b/test/test.js @@ -95,6 +95,15 @@ describe('Speaker', function () { done() }) + it('should accept a device option', function (done) { + const s = new Speaker({ device: 'test' }) + + assert.equal(s.device, 'test') + + s.on('close', done) + s.end(bufferAlloc(0)) + }) + it('should not throw an Error if native "endianness" is specified', function () { assert.doesNotThrow(function () { // eslint-disable-next-line no-new diff --git a/topi.sh b/topi.sh new file mode 100755 index 0000000..e8d1f21 --- /dev/null +++ b/topi.sh @@ -0,0 +1,46 @@ +#!/bin/bash +#send files to RPi +#set -x + +#HERE="$(dirname "$(readlink -fm "$0")")" #https://stackoverflow.com/questions/20196034/retrieve-parent-directory-of-script +#MY_TOP=`git rev-parse --show-toplevel` #from https://unix.stackexchange.com/questions/6463/find-searching-in-parent-directories-instead-of-subdirectories +#source "$MY_TOP"/scripts/colors.sh +#source "$HERE"/colors.sh +#from http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux +RED='\e[1;31m' #too dark: '\e[0;31m' #`tput setaf 1` +GREEN='\e[1;32m' #`tput setaf 2` +YELLOW='\e[1;33m' #`tput setaf 3` +BLUE='\e[1;34m' #`tput setaf 4` +PINK='\e[1;35m' #`tput setaf 5` +CYAN='\e[1;36m' #`tput setaf 6` +GRAY='\e[0;37m' +NORMAL='\e[0m' #`tput sgr0` + +DEST="$1/node_modules/speaker" +SRC="." + +set -x +cp "${SRC}/build/Release/binding.node" "${DEST}/build/Release/" +cp "${SRC}/index.js" "${DEST}/" + +exit 1 +#echo "i am at $HERE" +#echo use script "$HERE"/getcfg.js +#echo -e "${BLUE}running $HERE/getcfg.js${NORMAL}" +echo -e "${BLUE}setting vars${NORMAL}" +"$HERE"/getcfg.js +eval $("$HERE"/getcfg.js) + +if [ $# -lt 1 ]; then + echo -e "${RED}no files to transfer?${NORMAL}" +else + for file in "$@" + do + echo -e "${BLUE}xfr ${CYAN}$file ${BLUE}at `date`${NORMAL}" +# echo sshpass -p $RPi_pass scp "$file" $RPi_user@$RPi_addr:$RPi_folder + sshpass -p $RPi_pass scp "$file" $RPi_user@$RPi_addr:$RPi_folder + done + echo -e "${GREEN}$# files xfred.${NORMAL}" +fi + +#eof#