diff --git a/package.json b/package.json index 8317deca..33b730f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "supercolliderjs", "description": "Tools for working with the SuperCollider music language environment", - "version": "0.11.2", + "version": "0.11.3", "author": "Chris Sattinger ", "contributors": [ { @@ -27,12 +27,12 @@ "devDependencies": { "babel": "^6.2.4", "babel-cli": "^6.10.1", - "babel-jest": "^12.1.0", + "babel-jest": "^13.0.0", "babel-preset-es2015": "^6.9.0", "baconjs": "^0.7.84", - "eslint": "^2.13.1", - "jest-cli": "^12.1.1", - "jscs": "^3.0.5" + "eslint": "^3.0.1", + "jest-cli": "^13.1.0", + "jscs": "^3.0.6" }, "license": "MIT", "keywords": [ @@ -93,7 +93,8 @@ "node_modules/rx", "node_modules/baconjs" ], - "collectCoverage": false + "collectCoverage": true, + "automock": true }, "jshintConfig": { "esnext": true diff --git a/src/dryads/SCSynthDef.js b/src/dryads/SCSynthDef.js index d81148e5..491750cd 100644 --- a/src/dryads/SCSynthDef.js +++ b/src/dryads/SCSynthDef.js @@ -11,6 +11,17 @@ const StateKeys = { /** * Compile a SynthDef from sclang source code + * or load a precompiled .scsyndef + * + * If compilation is required then it will insert SCLang as a parent if necessary. + * + * properties: + * - source - sclang source code to compile + * - compileFrom - path of .scd file to compile + * - watch (Boolean) - watch compileFrom file and recompile on changes + * - saveToDir - path to save compiled .scsyndef to after compiling + * - loadFrom - path of previously compiled .scsyndef file to load to server + * This can be used to load SynthDefs without needing sclang running at all. * * `synthDef` is set in the context for children Dryads to access. * It is an object: @@ -18,22 +29,13 @@ const StateKeys = { * - .bytes * - .synthDesc object with descriptive meta data * - * `synthDefName` is set in context for children Dryads + * `synthDefName` is also set in context for children Dryads + * + * The synthDefName is not known until after the source code is compiled. * - * options: - * - source - sclang source code to compile - * - compileFrom - path of .scd file to compile - * - watch - watch compileFrom file and recompile on changes - * - saveToDir - path to save compiled .scsyndef to after compiling - * - loadFrom - path of .scsyndef file to load to server */ export default class SCSynthDef extends Dryad { - // saveToDir, watch=false, - // constructor(source, compileFrom, loadFrom, children=[]) { - // super({source, compileFrom, loadFrom}, children); - // } - /** * If there is no SCLang in the parent context, * then this will wrap itself in an SCLang (language interpreter). @@ -78,6 +80,7 @@ export default class SCSynthDef extends Dryad { } _sendSynthDef(context, result) { + // ! alters context // name bytes // synthDefName should be set for child context this.putSynthDef(context, result.name, result.synthDesc); diff --git a/src/dryads/Synth.js b/src/dryads/Synth.js index efc69688..57c73ca3 100644 --- a/src/dryads/Synth.js +++ b/src/dryads/Synth.js @@ -8,6 +8,9 @@ import * as _ from 'underscore'; /** * Creates a synth on the server. * + * Properties: + * - def + * - args */ export default class Synth extends Dryad { @@ -20,6 +23,16 @@ export default class Synth extends Dryad { } subgraph() { + let def = this.properties.def; + if (def.isDryad) { + // wrap self as a child of SCSynthDef + let d = def.clone(); + let m = this.clone(); + m.properties.def = null; // will get synthDefName from context + d.children = [m]; + return d; + } + var sg = []; // clone and tag each one so they can be looked up during .add _.each(this.properties.args, (v, k) => { @@ -32,15 +45,6 @@ export default class Synth extends Dryad { sg.push(this); - // wrap self in SynthDef - let def = this.properties.def; - if (def.isDryad) { - let d = def.clone(); - d.tag = 'def'; - d.children = d.children.concat(sg); - return d; - } - return new Dryad({}, sg); } @@ -54,6 +58,7 @@ export default class Synth extends Dryad { } synthDefName(context) { + // The parent SCSynthDef publishes both .synthDef (object) and .synthDefName to context let name = _.isString(this.properties.def) ? this.properties.def : context.synthDef.name; if (!name) { throw new Error('No synthDefName supplied to Synth', context); diff --git a/src/dryads/__tests__/index-spec.js b/src/dryads/__tests__/index-spec.js index cbc69be6..d70bfd8e 100644 --- a/src/dryads/__tests__/index-spec.js +++ b/src/dryads/__tests__/index-spec.js @@ -4,7 +4,7 @@ jest.dontMock('../SCServer'); jest.dontMock('../Group'); var dryads = require('../index'); - +var SCServer = require('../SCServer').default; describe('SCServer', function() { @@ -13,7 +13,7 @@ describe('SCServer', function() { }); it('should set default options of SCServer', function() { - let s = new dryads.SCServer(); + let s = new SCServer(); expect(s.properties).toEqual({options: {debug: false}}); }); diff --git a/src/lang/internals/__tests__/fixtures/routine-postln.txt b/src/lang/internals/__tests__/fixtures/routine-postln.txt new file mode 100644 index 00000000..0fd24785 --- /dev/null +++ b/src/lang/internals/__tests__/fixtures/routine-postln.txt @@ -0,0 +1,11 @@ +SUPERCOLLIDERJS:c1c4f120-4346-11e6-9df0-edccf25b99df:CAPTURE:START + + +SUPERCOLLIDERJS:c1c4f120-4346-11e6-9df0-edccf25b99df:CAPTURE:END + +SUPERCOLLIDERJS:c1c4f120-4346-11e6-9df0-edccf25b99df:START:Result +SUPERCOLLIDERJS:c1c4f120-4346-11e6-9df0-edccf25b99df:CHUNK:"a Routine" +SUPERCOLLIDERJS:c1c4f120-4346-11e6-9df0-edccf25b99df:END:Result +SUPERCOLLIDERJS.interpreted +-> +hi diff --git a/src/lang/internals/__tests__/sclang-io-test.js b/src/lang/internals/__tests__/sclang-io-test.js index ee11534e..d9887f54 100644 --- a/src/lang/internals/__tests__/sclang-io-test.js +++ b/src/lang/internals/__tests__/sclang-io-test.js @@ -171,4 +171,18 @@ describe('sclang-io', function() { }); }); + describe('capture', function() { + it('should capture any immediate postln from end of CAPTURE', function() { + var io = new SclangIO(); + io.setState(STATES.READY); + let output = []; + io.on('stdout', (o) => output.push(o)); + + feedIt('routine-postln.txt', io); + // should have emited stdout with 'hi' + expect(output.length > 0).toBeTruthy(); + expect(output[0].match(/hi/)).toBeTruthy(); + }); + }); + }); diff --git a/src/lang/internals/sclang-io.js b/src/lang/internals/sclang-io.js index cc84cead..376c1832 100644 --- a/src/lang/internals/sclang-io.js +++ b/src/lang/internals/sclang-io.js @@ -59,6 +59,7 @@ class SclangIO extends EventEmitter { if (stf.fn(match, input) === true) { echo = false; } + // break if its not a /g regex with multiple results if (!stf.re.global) { break; @@ -71,6 +72,15 @@ class SclangIO extends EventEmitter { this.emit('stdout', input); } + // anything left over should be emitted to stdout ? + // This might result in some content being emitted twice. + // Currently if there is anything after SUPERCOLLIDERJS.interpret + // it is emitted. + // if (last < input.length && (startState === this.state)) { + // console.log('leftovers:', input.substr(last)); + // // this.parse(input.substr(last)); + // } + // state has changed and there is still text to parse if (last < input.length && (startState !== this.state)) { // parse remainder with new state @@ -174,6 +184,10 @@ class SclangIO extends EventEmitter { // REPL is now active ready: [ { + // There may be multiple SUPERCOLLIDERJS matches in a block of text. + // ie. this is a multi-line global regex + // This fn is called for each of them with a different match each time + // but the same text body. re: /^SUPERCOLLIDERJS\:([0-9a-f\-]+)\:([A-Za-z]+)\:(.*)$/mg, fn: (match, text) => { var @@ -187,75 +201,86 @@ class SclangIO extends EventEmitter { started = false, stopped = false; - if (type === 'CAPTURE') { - if (body === 'START') { - this.capturing[guid] = []; - } - if (body === 'START') { - lines = []; - // yuck - _.each(text.split('\n'), (l) => { - if (l.match(/SUPERCOLLIDERJS\:([0-9a-f\-]+)\:CAPTURE:START/)) { - started = true; - } else if (l.match(/SUPERCOLLIDERJS\:([0-9a-f\-]+)\:CAPTURE:END/)) { - stopped = true; + switch (type) { + case 'CAPTURE': + if (body === 'START') { + this.capturing[guid] = []; + lines = []; + // yuck + _.each(text.split('\n'), (l) => { + if (l.match(/SUPERCOLLIDERJS\:([0-9a-f\-]+)\:CAPTURE:START/)) { + started = true; + } else if (l.match(/SUPERCOLLIDERJS\:([0-9a-f\-]+)\:CAPTURE:END/)) { + stopped = true; + } else { + if (started && (!stopped)) { + lines.push(l); + } + } + }); + this.capturing[guid].push(lines.join('\n')); + } + return true; + + case 'START': + this.responseCollectors[guid] = { + type: body, + chunks: [] + }; + return true; + + case 'CHUNK': + this.responseCollectors[guid].chunks.push(body); + return true; + + case 'END': + response = this.responseCollectors[guid]; + stdout = response.chunks.join(''); + obj = JSON.parse(stdout); + + if (guid in this.calls) { + if (response.type === 'Result') { + // anything posted during CAPTURE should be forwarded + // to stdout + stdout = this.capturing[guid].join('\n'); + delete this.capturing[guid]; + if (stdout) { + this.emit('stdout', stdout); + } + this.calls[guid].resolve(obj); } else { - if (started && (!stopped)) { - lines.push(l); + if (response.type === 'SyntaxError') { + stdout = this.capturing[guid].join('\n'); + obj = this.parseSyntaxErrors(stdout); + delete this.capturing[guid]; } + this.calls[guid].reject({type: response.type, error: obj}); } - }); - this.capturing[guid].push(lines.join('\n')); - } - return true; - } - if (type === 'START') { - this.responseCollectors[guid] = { - type: body, - chunks: [] - }; - return true; - } - if (type === 'CHUNK') { - this.responseCollectors[guid].chunks.push(body); - return true; - } - if (type === 'END') { - response = this.responseCollectors[guid]; - stdout = response.chunks.join(''); - obj = JSON.parse(stdout); - - if (guid in this.calls) { - if (response.type === 'Result') { - // anything posted during CAPTURE should be forwarded - // to stdout - stdout = this.capturing[guid].join('\n'); - delete this.capturing[guid]; - if (stdout) { - this.emit('stdout', stdout); - } - this.calls[guid].resolve(obj); + delete this.calls[guid]; } else { - if (response.type === 'SyntaxError') { - stdout = this.capturing[guid].join('\n'); - obj = this.parseSyntaxErrors(stdout); - delete this.capturing[guid]; + // I hope sc doesn't post multiple streams at the same time + if (guid === '0') { + // out of band error + this.emit('error', {type: response.type, error: obj}); } - this.calls[guid].reject({type: response.type, error: obj}); - } - delete this.calls[guid]; - } else { - // I hope sc doesn't post multiple streams at the same time - if (guid === '0') { - // out of band error - this.emit('error', {type: response.type, error: obj}); } - } - delete this.responseCollectors[guid]; - return true; + delete this.responseCollectors[guid]; + return true; + + default: } } }, + { + re: /^SUPERCOLLIDERJS.interpreted$/mg, + fn: (match, text) => { + let rest = text.substr(match.index + 28); + // remove the prompt -> + rest = rest.replace(/-> \r?\n?/, ''); + this.emit('stdout', rest); + return true; + } + }, { // user compiled programmatically eg. with Quarks.gui button re: /^compiling class library/m, diff --git a/src/utils/logger.js b/src/utils/logger.js index 93fccc10..b640e89d 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -103,7 +103,7 @@ export default class Logger { case 'stdin': case 'sendosc': case 'rcvosc': - this.log.debug(clean); + this.log.info(clean); break; case 'stderr': case 'error': diff --git a/src/utils/resolveOptions.js b/src/utils/resolveOptions.js index ced7a058..c9e03b57 100644 --- a/src/utils/resolveOptions.js +++ b/src/utils/resolveOptions.js @@ -42,9 +42,8 @@ function defaultOptions() { opts.sclang_conf = join(defaultRoot, 'sclang_conf.yaml'); break; case 'darwin': - defaultRoot = '/Applications/SuperCollider/SuperCollider.app/Contents/MacOS'; - opts.sclang = join(defaultRoot, 'sclang'); - opts.scsynth = join(defaultRoot, 'scsynth'); + opts.sclang = '/Applications/SuperCollider/SuperCollider.app/Contents/MacOS/sclang'; + opts.scsynth = '/Applications/SuperCollider/SuperCollider.app/Contents/Resources/scsynth'; opts.sclang_conf = `${getUserHome()}/Library/Application Support/SuperCollider/sclang_conf.yaml`; break; default: