Skip to content
This repository
Browse code

Merge branch 'triggerCallback'

* triggerCallback:
  Adds demo and documentation for flock.ugen.triggerCallback.
  Adds unit tests for flock.ugen.triggerCallback.
  Adds first draft of new triggerCallback ugen. Still needs unit tests and a demo.
  • Loading branch information...
commit 2e091ce87030aa8bba78c4c649409f05ba1f392f 2 parents eeb43e1 + f27c897
Colin Clark authored April 16, 2014
185  demos/interactive/css/shake.css
... ...
@@ -0,0 +1,185 @@
  1
+/* Based on code from http://elrumordelaluz.github.io/csshake/ */
  2
+
  3
+.shake {
  4
+    -webkit-animation-name: shake-slow;
  5
+    -ms-animation-name: shake-slow;
  6
+    animation-name: shake-slow;
  7
+
  8
+    -webkit-animation-duration: 5s;
  9
+    -ms-animation-duration: 5s;
  10
+    animation-duration: 5s;
  11
+
  12
+    -webkit-animation-iteration-count: infinite;
  13
+    -ms-animation-iteration-count: infinite;
  14
+    animation-iteration-count: infinite;
  15
+
  16
+    -webkit-animation-timing-function: ease-in-out;
  17
+    -ms-animation-timing-function: ease-in-out;
  18
+    animation-timing-function: ease-in-out;
  19
+
  20
+    -webkit-animation-delay: 0s;
  21
+    -ms-animation-delay: 0s;
  22
+    animation-delay: 0s;
  23
+    -webkit-animation-play-state: running;
  24
+    -ms-animation-play-state: running;
  25
+    animation-play-state: running;
  26
+}
  27
+
  28
+@-webkit-keyframes shake-slow {
  29
+  0% { -webkit-transform: translate(0px, 0px) rotate(0deg); }
  30
+  2% { -webkit-transform: translate(-1px, 3px) rotate(-1.5deg); }
  31
+  4% { -webkit-transform: translate(-4px, 5px) rotate(-1.5deg); }
  32
+  6% { -webkit-transform: translate(-1px, 6px) rotate(-0.5deg); }
  33
+  8% { -webkit-transform: translate(5px, -4px) rotate(-3.5deg); }
  34
+  10% { -webkit-transform: translate(-7px, -3px) rotate(-3.5deg); }
  35
+  12% { -webkit-transform: translate(-1px, 8px) rotate(2.5deg); }
  36
+  14% { -webkit-transform: translate(3px, -5px) rotate(-1.5deg); }
  37
+  16% { -webkit-transform: translate(1px, 0px) rotate(2.5deg); }
  38
+  18% { -webkit-transform: translate(-6px, -10px) rotate(-0.5deg); }
  39
+  20% { -webkit-transform: translate(3px, -2px) rotate(1.5deg); }
  40
+  22% { -webkit-transform: translate(0px, 0px) rotate(-2.5deg); }
  41
+  24% { -webkit-transform: translate(-5px, -4px) rotate(1.5deg); }
  42
+  26% { -webkit-transform: translate(-1px, 3px) rotate(-3.5deg); }
  43
+  28% { -webkit-transform: translate(1px, 1px) rotate(-3.5deg); }
  44
+  30% { -webkit-transform: translate(-4px, 8px) rotate(1.5deg); }
  45
+  32% { -webkit-transform: translate(-9px, 7px) rotate(-3.5deg); }
  46
+  34% { -webkit-transform: translate(4px, -9px) rotate(-2.5deg); }
  47
+  36% { -webkit-transform: translate(1px, -6px) rotate(-2.5deg); }
  48
+  38% { -webkit-transform: translate(-4px, 0px) rotate(-2.5deg); }
  49
+  40% { -webkit-transform: translate(3px, -7px) rotate(0.5deg); }
  50
+  42% { -webkit-transform: translate(4px, 4px) rotate(-0.5deg); }
  51
+  44% { -webkit-transform: translate(8px, -4px) rotate(-2.5deg); }
  52
+  46% { -webkit-transform: translate(9px, 9px) rotate(-3.5deg); }
  53
+  48% { -webkit-transform: translate(6px, -8px) rotate(-0.5deg); }
  54
+  50% { -webkit-transform: translate(-1px, 4px) rotate(-3.5deg); }
  55
+  52% { -webkit-transform: translate(4px, 6px) rotate(-1.5deg); }
  56
+  54% { -webkit-transform: translate(9px, -3px) rotate(2.5deg); }
  57
+  56% { -webkit-transform: translate(8px, -2px) rotate(-3.5deg); }
  58
+  58% { -webkit-transform: translate(-2px, -9px) rotate(-0.5deg); }
  59
+  60% { -webkit-transform: translate(-1px, -5px) rotate(2.5deg); }
  60
+  62% { -webkit-transform: translate(-8px, 3px) rotate(2.5deg); }
  61
+  64% { -webkit-transform: translate(6px, -2px) rotate(-3.5deg); }
  62
+  66% { -webkit-transform: translate(-5px, 9px) rotate(-1.5deg); }
  63
+  68% { -webkit-transform: translate(3px, 1px) rotate(-0.5deg); }
  64
+  70% { -webkit-transform: translate(6px, 4px) rotate(-1.5deg); }
  65
+  72% { -webkit-transform: translate(-6px, -5px) rotate(1.5deg); }
  66
+  74% { -webkit-transform: translate(-8px, 0px) rotate(-0.5deg); }
  67
+  76% { -webkit-transform: translate(-5px, -8px) rotate(1.5deg); }
  68
+  78% { -webkit-transform: translate(5px, -3px) rotate(-1.5deg); }
  69
+  80% { -webkit-transform: translate(-6px, -3px) rotate(-1.5deg); }
  70
+  82% { -webkit-transform: translate(7px, 8px) rotate(-1.5deg); }
  71
+  84% { -webkit-transform: translate(-6px, 9px) rotate(0.5deg); }
  72
+  86% { -webkit-transform: translate(1px, 8px) rotate(-3.5deg); }
  73
+  88% { -webkit-transform: translate(-9px, -2px) rotate(1.5deg); }
  74
+  90% { -webkit-transform: translate(4px, -6px) rotate(-1.5deg); }
  75
+  92% { -webkit-transform: translate(0px, -1px) rotate(0.5deg); }
  76
+  94% { -webkit-transform: translate(2px, -9px) rotate(2.5deg); }
  77
+  96% { -webkit-transform: translate(-9px, 1px) rotate(-2.5deg); }
  78
+  98% { -webkit-transform: translate(-9px, -5px) rotate(-3.5deg); }
  79
+}
  80
+
  81
+@-ms-keyframes shake-slow {
  82
+  0% { -ms-transform: translate(0px, 0px) rotate(0deg); }
  83
+  2% { -ms-transform: translate(-10px, 5px) rotate(-2.5deg); }
  84
+  4% { -ms-transform: translate(7px, 7px) rotate(-3.5deg); }
  85
+  6% { -ms-transform: translate(8px, -7px) rotate(-2.5deg); }
  86
+  8% { -ms-transform: translate(-8px, 3px) rotate(-0.5deg); }
  87
+  10% { -ms-transform: translate(3px, -10px) rotate(-1.5deg); }
  88
+  12% { -ms-transform: translate(-9px, -6px) rotate(2.5deg); }
  89
+  14% { -ms-transform: translate(-2px, -6px) rotate(-0.5deg); }
  90
+  16% { -ms-transform: translate(6px, -1px) rotate(0.5deg); }
  91
+  18% { -ms-transform: translate(5px, -1px) rotate(0.5deg); }
  92
+  20% { -ms-transform: translate(7px, -5px) rotate(-0.5deg); }
  93
+  22% { -ms-transform: translate(-8px, 5px) rotate(2.5deg); }
  94
+  24% { -ms-transform: translate(0px, 4px) rotate(2.5deg); }
  95
+  26% { -ms-transform: translate(-1px, 2px) rotate(-1.5deg); }
  96
+  28% { -ms-transform: translate(-1px, -1px) rotate(1.5deg); }
  97
+  30% { -ms-transform: translate(-5px, -5px) rotate(2.5deg); }
  98
+  32% { -ms-transform: translate(0px, 7px) rotate(-0.5deg); }
  99
+  34% { -ms-transform: translate(-9px, 3px) rotate(-0.5deg); }
  100
+  36% { -ms-transform: translate(3px, -5px) rotate(-2.5deg); }
  101
+  38% { -ms-transform: translate(5px, 2px) rotate(-0.5deg); }
  102
+  40% { -ms-transform: translate(6px, -3px) rotate(0.5deg); }
  103
+  42% { -ms-transform: translate(-4px, -6px) rotate(-0.5deg); }
  104
+  44% { -ms-transform: translate(9px, 2px) rotate(-3.5deg); }
  105
+  46% { -ms-transform: translate(6px, -4px) rotate(1.5deg); }
  106
+  48% { -ms-transform: translate(6px, 5px) rotate(2.5deg); }
  107
+  50% { -ms-transform: translate(-9px, -2px) rotate(-2.5deg); }
  108
+  52% { -ms-transform: translate(-7px, 9px) rotate(-0.5deg); }
  109
+  54% { -ms-transform: translate(-5px, -5px) rotate(-3.5deg); }
  110
+  56% { -ms-transform: translate(-6px, -10px) rotate(1.5deg); }
  111
+  58% { -ms-transform: translate(-3px, 1px) rotate(-3.5deg); }
  112
+  60% { -ms-transform: translate(3px, 5px) rotate(2.5deg); }
  113
+  62% { -ms-transform: translate(-1px, -8px) rotate(2.5deg); }
  114
+  64% { -ms-transform: translate(6px, -7px) rotate(-0.5deg); }
  115
+  66% { -ms-transform: translate(-7px, -1px) rotate(0.5deg); }
  116
+  68% { -ms-transform: translate(-3px, -4px) rotate(-1.5deg); }
  117
+  70% { -ms-transform: translate(-10px, 9px) rotate(2.5deg); }
  118
+  72% { -ms-transform: translate(9px, 9px) rotate(2.5deg); }
  119
+  74% { -ms-transform: translate(-6px, 8px) rotate(-0.5deg); }
  120
+  76% { -ms-transform: translate(-5px, -10px) rotate(-2.5deg); }
  121
+  78% { -ms-transform: translate(-7px, -9px) rotate(-0.5deg); }
  122
+  80% { -ms-transform: translate(8px, -4px) rotate(2.5deg); }
  123
+  82% { -ms-transform: translate(9px, 4px) rotate(-0.5deg); }
  124
+  84% { -ms-transform: translate(-1px, -1px) rotate(2.5deg); }
  125
+  86% { -ms-transform: translate(-6px, -3px) rotate(0.5deg); }
  126
+  88% { -ms-transform: translate(-2px, -4px) rotate(0.5deg); }
  127
+  90% { -ms-transform: translate(5px, 1px) rotate(0.5deg); }
  128
+  92% { -ms-transform: translate(1px, 2px) rotate(-3.5deg); }
  129
+  94% { -ms-transform: translate(-5px, -10px) rotate(1.5deg); }
  130
+  96% { -ms-transform: translate(-6px, 3px) rotate(-3.5deg); }
  131
+  98% { -ms-transform: translate(-1px, -7px) rotate(-2.5deg); }
  132
+}
  133
+
  134
+@keyframes shake-slow {
  135
+  0% { transform: translate(0px, 0px) rotate(0deg); }
  136
+  2% { transform: translate(6px, -7px) rotate(2.5deg); }
  137
+  4% { transform: translate(8px, -8px) rotate(2.5deg); }
  138
+  6% { transform: translate(1px, -8px) rotate(-3.5deg); }
  139
+  8% { transform: translate(-3px, 4px) rotate(-0.5deg); }
  140
+  10% { transform: translate(0px, -3px) rotate(1.5deg); }
  141
+  12% { transform: translate(-1px, 2px) rotate(0.5deg); }
  142
+  14% { transform: translate(6px, 6px) rotate(-1.5deg); }
  143
+  16% { transform: translate(-7px, 4px) rotate(-0.5deg); }
  144
+  18% { transform: translate(7px, 8px) rotate(-3.5deg); }
  145
+  20% { transform: translate(-6px, 2px) rotate(1.5deg); }
  146
+  22% { transform: translate(9px, 5px) rotate(-1.5deg); }
  147
+  24% { transform: translate(7px, -2px) rotate(0.5deg); }
  148
+  26% { transform: translate(-7px, -10px) rotate(-0.5deg); }
  149
+  28% { transform: translate(-10px, -8px) rotate(-1.5deg); }
  150
+  30% { transform: translate(8px, 4px) rotate(0.5deg); }
  151
+  32% { transform: translate(0px, 4px) rotate(1.5deg); }
  152
+  34% { transform: translate(-8px, 6px) rotate(-0.5deg); }
  153
+  36% { transform: translate(-5px, 7px) rotate(1.5deg); }
  154
+  38% { transform: translate(-4px, -4px) rotate(-1.5deg); }
  155
+  40% { transform: translate(9px, 4px) rotate(-1.5deg); }
  156
+  42% { transform: translate(9px, -5px) rotate(2.5deg); }
  157
+  44% { transform: translate(-5px, -4px) rotate(-2.5deg); }
  158
+  46% { transform: translate(7px, -7px) rotate(1.5deg); }
  159
+  48% { transform: translate(-5px, 8px) rotate(0.5deg); }
  160
+  50% { transform: translate(9px, 1px) rotate(-1.5deg); }
  161
+  52% { transform: translate(-9px, -5px) rotate(-3.5deg); }
  162
+  54% { transform: translate(-2px, 9px) rotate(1.5deg); }
  163
+  56% { transform: translate(6px, -1px) rotate(1.5deg); }
  164
+  58% { transform: translate(-6px, 0px) rotate(-0.5deg); }
  165
+  60% { transform: translate(3px, 1px) rotate(1.5deg); }
  166
+  62% { transform: translate(5px, -7px) rotate(-0.5deg); }
  167
+  64% { transform: translate(9px, 2px) rotate(2.5deg); }
  168
+  66% { transform: translate(6px, 0px) rotate(-2.5deg); }
  169
+  68% { transform: translate(5px, -4px) rotate(-2.5deg); }
  170
+  70% { transform: translate(-8px, 5px) rotate(-2.5deg); }
  171
+  72% { transform: translate(-6px, -2px) rotate(0.5deg); }
  172
+  74% { transform: translate(-3px, 7px) rotate(-3.5deg); }
  173
+  76% { transform: translate(-7px, -8px) rotate(-3.5deg); }
  174
+  78% { transform: translate(-1px, -2px) rotate(2.5deg); }
  175
+  80% { transform: translate(8px, 6px) rotate(-2.5deg); }
  176
+  82% { transform: translate(-2px, -9px) rotate(2.5deg); }
  177
+  84% { transform: translate(8px, -10px) rotate(-0.5deg); }
  178
+  86% { transform: translate(-6px, 0px) rotate(2.5deg); }
  179
+  88% { transform: translate(-1px, 9px) rotate(-3.5deg); }
  180
+  90% { transform: translate(-7px, 8px) rotate(1.5deg); }
  181
+  92% { transform: translate(-10px, -8px) rotate(0.5deg); }
  182
+  94% { transform: translate(-8px, 6px) rotate(1.5deg); }
  183
+  96% { transform: translate(4px, -9px) rotate(2.5deg); }
  184
+  98% { transform: translate(-4px, 9px) rotate(0.5deg); }
  185
+}
25  demos/interactive/html/playground.html
@@ -11,6 +11,7 @@
11 11
         <link rel="stylesheet" type="text/css" href="../../../ui/editor/css/flockingcm.css" />
12 12
         <link rel="stylesheet" type="text/css" href="../../../ui/shared/css/flocking-icon-font.css" />
13 13
         <link rel="stylesheet" type="text/css" href="../css/playground.css" />
  14
+        <link rel="stylesheet" type="text/css" href="../css/shake.css" />
14 15
 
15 16
         <script src="../../../third-party/jquery/js/jquery-2.0.0.js"></script>
16 17
         <script src="../../../third-party/infusion/js/Fluid.js"></script>
@@ -102,6 +103,9 @@
102 103
 		                        <option value="line_mod">Mod SinOsc Freq</option>
103 104
 		                        <option value="line_phase">SinOsc Phase</option>
104 105
 		                    </optgroup>
  106
+                            <optgroup label="Triggers">
  107
+                                <option value="triggerCallback">Trigger a callback</option>
  108
+                            </optgroup>
105 109
 		                    <optgroup label="DOM UGens">
106 110
 		                        <option value="scope">Scope</option>
107 111
 		                        <option value="mouse_x">Mouse X</option>
@@ -235,6 +239,27 @@
235 239
 });
236 240
         </script>
237 241
 
  242
+        <script type="application/flocking" id="triggerCallback">
  243
+// Periodically trigger a function that causes the scope area to shake.
  244
+var synth = flock.synth({
  245
+    synthDef: {
  246
+        ugen: "flock.ugen.triggerCallback",
  247
+        trigger: {
  248
+            ugen: "flock.ugen.impulse",
  249
+            freq: 0.75,
  250
+            phase: 0.5
  251
+        },
  252
+        options: {
  253
+            callback: {
  254
+                func: function () {
  255
+                    $("#gfx").toggleClass("shake");
  256
+                }
  257
+            }
  258
+        }
  259
+    }
  260
+});
  261
+        </script>
  262
+
238 263
         <script type="application/flocking" id="simpleASR">
239 264
 // Sine tone shaped by a simple attack/sustain/release envelope and periodically triggered.
240 265
 var synth = flock.synth({
94  dist/flocking-all.js
@@ -23425,7 +23425,7 @@ var fluid = fluid || require("infusion"),
23425 23425
     });
23426 23426
 
23427 23427
 
23428  
-    flock.ugen.inputTrigger = function (inputs, output, options) {
  23428
+    flock.ugen.inputChangeTrigger = function (inputs, output, options) {
23429 23429
         var that = flock.ugen(inputs, output, options);
23430 23430
 
23431 23431
         that.gen = function (numSamps) {
@@ -23471,7 +23471,8 @@ var fluid = fluid || require("infusion"),
23471 23471
         that.calculateStrides();
23472 23472
         return that;
23473 23473
     };
23474  
-    fluid.defaults("flock.ugen.inputTrigger", {
  23474
+
  23475
+    fluid.defaults("flock.ugen.inputChangeTrigger", {
23475 23476
         rate: "control",
23476 23477
 
23477 23478
         inputs: {
@@ -23489,6 +23490,95 @@ var fluid = fluid || require("infusion"),
23489 23490
         }
23490 23491
     });
23491 23492
 
  23493
+    flock.ugen.triggerCallback = function (inputs, output, options) {
  23494
+        var that = flock.ugen(inputs, output, options);
  23495
+
  23496
+        that.gen = function (numSamps) {
  23497
+            var m = that.model,
  23498
+                o = that.options,
  23499
+                out = that.output,
  23500
+                inputs = that.inputs,
  23501
+                triggerInc = m.strides.trigger,
  23502
+                sourceInc = m.strides.source,
  23503
+                trig = inputs.trigger.output,
  23504
+                source = inputs.source.output,
  23505
+                cbSpec = o.callback,
  23506
+                fn = cbSpec.func,
  23507
+                args = cbSpec.args,
  23508
+                cbThis = cbSpec.this,
  23509
+                lastArgIdx = m.lastArgIdx,
  23510
+                prevTrig = m.prevTrig,
  23511
+                i,
  23512
+                j,
  23513
+                k,
  23514
+                currTrig,
  23515
+                sourceVal;
  23516
+
  23517
+            for (i = j = k = 0; i < numSamps; i++, j += triggerInc, k += sourceInc) {
  23518
+                currTrig = trig[j];
  23519
+                sourceVal = source[k];
  23520
+
  23521
+                if (currTrig > 0.0 && prevTrig <= 0.0 && fn) {
  23522
+                    // Insert the current source value into the arguments list
  23523
+                    // and then invoke the specified callback function.
  23524
+                    args[lastArgIdx] = sourceVal;
  23525
+                    fn.apply(cbThis, args);
  23526
+                }
  23527
+
  23528
+                out[i] = sourceVal;
  23529
+                prevTrig = currTrig;
  23530
+            }
  23531
+
  23532
+            m.prevTrig = prevTrig;
  23533
+        };
  23534
+
  23535
+        that.onInputChanged = function () {
  23536
+            var o = that.options,
  23537
+                m = that.model,
  23538
+                cbSpec = o.callback,
  23539
+                funcName = cbSpec.funcName;
  23540
+
  23541
+            if (funcName) {
  23542
+                cbSpec.func = fluid.getGlobalValue(funcName);
  23543
+            } else if (cbSpec.this && cbSpec.method) {
  23544
+                if (typeof cbSpec.this !== "string") {
  23545
+                    throw new Error("flock.ugen.triggerCallback doesn't support raw 'this' objects." +
  23546
+                        "Use a global key path instead.");
  23547
+                }
  23548
+                cbSpec.this = typeof cbSpec.this === "string" ?
  23549
+                    fluid.getGlobalValue(cbSpec.this) : cbSpec.this;
  23550
+                cbSpec.func = fluid.get(cbSpec.this, cbSpec.method);
  23551
+            }
  23552
+
  23553
+            m.lastArgIdx = cbSpec.args.length;
  23554
+            that.calculateStrides();
  23555
+        };
  23556
+
  23557
+        that.onInputChanged();
  23558
+        return that;
  23559
+    };
  23560
+
  23561
+    fluid.defaults("flock.ugen.triggerCallback", {
  23562
+        rate: "audio",
  23563
+        inputs: {
  23564
+            source: 0,
  23565
+            trigger: 0
  23566
+        },
  23567
+        ugenOptions: {
  23568
+            model: {
  23569
+                funcName: undefined,
  23570
+                lastArgIdx: 0
  23571
+            },
  23572
+            callback: {
  23573
+                "this": undefined,
  23574
+                method: undefined,
  23575
+                func: undefined,
  23576
+                args: []
  23577
+            },
  23578
+            strideInputs: ["source", "trigger"]
  23579
+        }
  23580
+    });
  23581
+
23492 23582
     flock.ugen.math = function (inputs, output, options) {
23493 23583
         var that = flock.ugen(inputs, output, options);
23494 23584
         that.expandedSource = new Float32Array(that.options.audioSettings.blockSize);
4  dist/flocking-all.min.js
2 additions, 2 deletions not shown
2  docs/scheduling.md
Source Rendered
@@ -66,7 +66,7 @@ Here's an example of using the Infusion style to create a very simple drum machi
66 66
                             },
67 67
                             trigger: {
68 68
                                 id: "trig",
69  
-                                ugen: "flock.ugen.inputTrigger",
  69
+                                ugen: "flock.ugen.inputChangeTrigger",
70 70
                                 source: 0,
71 71
                                 duration: 0.01
72 72
                             }
188  docs/ugens/triggers.md
Source Rendered
... ...
@@ -0,0 +1,188 @@
  1
+# Triggers and Triggerables #
  2
+
  3
+Triggerable unit generators are unit generators that perform a particular action when they receive a trigger signal. A trigger occurs whenever a signal changes from non-positive to positive. For example, `flock.ugen.playBuffer` will play back an audio buffer whenever it receives a trigger value.
  4
+
  5
+Triggers are unit generators that emit periodic positive values, which can be used to trigger actions in other unit generators such as envelopes and granulators. For example. `flock.ugen.impulse` will emit a single positive value at the specified frequency.
  6
+
  7
+# Triggerables #
  8
+
  9
+## flock.ugen.triggerCallback ##
  10
+
  11
+`flock.ugen.triggerCallback` will cause a function to be invoked whenever its `trigger` input crosses from non-positive to positive. The value of this unit generator's `source` input will always be provided as the last argument to the callback. The callback is specified using a _callbackSpec_, which can contain any of the following properties:
  12
+
  13
+<table>
  14
+    <tr>
  15
+        <th>func</th>
  16
+        <td>A raw function pointer to invoke</td>
  17
+    </tr>
  18
+    <tr>
  19
+        <th>funcName</th>
  20
+        <td>A key path specifying a global function to invoke</td>
  21
+    </tr>
  22
+    <tr>
  23
+        <th>"this"</th>
  24
+        <td>A key path pointing to an object instance (e.g. "jQuery"). Must be accompanied by a `method` property.</td>
  25
+    </tr>
  26
+    <tr>
  27
+        <th>method</th>
  28
+        <td>A string specifying the method to invoke (e.g. "ajax")</td>
  29
+    </tr>
  30
+    <tr>
  31
+        <th>args</th>
  32
+        <td>An array of arguments to pass to each callback. The `source` input's current value will always be passed as the last argument.</td>
  33
+    </tr>
  34
+</table>
  35
+
  36
+
  37
+For example, this synth will print out the current value of a sine wave oscillator to the console twice per second:
  38
+
  39
+    {
  40
+        ugen: "flock.ugen.triggerCallback",
  41
+        source: {
  42
+            ugen: "flock.ugen.sin"
  43
+            freq: 440
  44
+        },
  45
+        trigger: {
  46
+            ugen: "flock.ugen.impulse",
  47
+            freq: 2
  48
+        },
  49
+        options: {
  50
+            callback: {
  51
+                "this": "console",
  52
+                method: "log",
  53
+                args: ["Sinewave value is"]
  54
+            }
  55
+        }
  56
+    }
  57
+
  58
+This unit generator will always pass through its `source` input unchanged as its output. `flock.ugen.triggerCallback` supports the following rates:
  59
+
  60
+`demand`, `scheduled`, `control`, `constant`
  61
+
  62
+
  63
+### Inputs ###
  64
+
  65
+#### source ####
  66
+<table>
  67
+    <tr>
  68
+        <th>description</th>
  69
+        <td>a source signal, which will be passed through as-is (and will be provided as the last argument to the callback).</td>
  70
+    </tr>
  71
+    <tr>
  72
+        <th>range</th>
  73
+        <td>`0`..`Infinity`</td>
  74
+    </tr>
  75
+    <tr>
  76
+        <th>rates</th>
  77
+        <td>`constant`, `control`, `audio`</td>
  78
+    </tr>
  79
+    <tr>
  80
+        <th>default</th>
  81
+        <td>`0` (constant)</td>
  82
+    </tr>
  83
+</table>
  84
+
  85
+#### trigger ####
  86
+<table>
  87
+    <tr>
  88
+        <th>description</th>
  89
+        <td>a trigger signal, which will cause the callback to fire whenever it crosses from positive to non-positive</td>
  90
+    </tr>
  91
+    <tr>
  92
+        <th>range</th>
  93
+        <td>`0`..`1.0`</td>
  94
+    </tr>
  95
+    <tr>
  96
+        <th>rates</th>
  97
+        <td>`constant`, `control`, `audio`</td>
  98
+    </tr>
  99
+    <tr>
  100
+        <th>default</th>
  101
+        <td>`0.0` (constant)</td>
  102
+    </tr>
  103
+</table>
  104
+
  105
+
  106
+### Options ###
  107
+
  108
+#### callback ####
  109
+<table>
  110
+    <tr>
  111
+        <th>description</th>
  112
+        <td>a callback spec</td>
  113
+    </tr>
  114
+    <tr>
  115
+        <th>range</th>
  116
+        <td>Object</td>
  117
+    </tr>
  118
+</table>
  119
+
  120
+
  121
+# Triggers #
  122
+
  123
+## flock.ugen.valueChangeTrigger ##
  124
+
  125
+`flock.ugen.valueChangeTrigger` will fire (i.e. cross from non-positive to positive) whenever its `source` input changes value. For example, this synthDef will cause a trigger to fire whenever the mouse is clicked:
  126
+
  127
+    {
  128
+        ugen: "flock.ugen.valueChangeTrigger",
  129
+        source: {
  130
+            ugen: "flock.ugen.mouse.click"
  131
+        }
  132
+    }
  133
+
  134
+This supports the following rates:
  135
+
  136
+`demand`, `scheduled`, `control`, `constant`
  137
+
  138
+### Inputs ###
  139
+
  140
+#### source ####
  141
+<table>
  142
+    <tr>
  143
+        <th>description</th>
  144
+        <td>a source signal that will cause the trigger to fire whenever its value changes</td>
  145
+    </tr>
  146
+    <tr>
  147
+        <th>range</th>
  148
+        <td>`0`..`Infinity`</td>
  149
+    </tr>
  150
+    <tr>
  151
+        <th>rates</th>
  152
+        <td>`constant`, `control`, `audio`</td>
  153
+    </tr>
  154
+    <tr>
  155
+        <th>default</th>
  156
+        <td>`0` (constant)</td>
  157
+    </tr>
  158
+</table>
  159
+
  160
+## flock.ugen.inputChangeTrigger ##
  161
+
  162
+`flock.ugen.inputChangeTrigger` will fire (i.e. cross from non-positive to positive) whenever its `source` input is changed to a different unit generator. This will only occur if the user changes the unit generator graph by making a call to `set`.
  163
+
  164
+This supports the following rates:
  165
+
  166
+`demand`, `scheduled`, `control`, `constant`
  167
+
  168
+### Inputs ###
  169
+
  170
+#### source ####
  171
+<table>
  172
+    <tr>
  173
+        <th>description</th>
  174
+        <td>a source signal that will cause the trigger to fire whenever it changes (with a call to `set`)</td>
  175
+    </tr>
  176
+    <tr>
  177
+        <th>range</th>
  178
+        <td>`0`..`Infinity`</td>
  179
+    </tr>
  180
+    <tr>
  181
+        <th>rates</th>
  182
+        <td>`constant`, `control`, `audio`</td>
  183
+    </tr>
  184
+    <tr>
  185
+        <th>default</th>
  186
+        <td>`0` (constant)</td>
  187
+    </tr>
  188
+</table>
94  flocking/flocking-ugens.js
@@ -451,7 +451,7 @@ var fluid = fluid || require("infusion"),
451 451
     });
452 452
 
453 453
 
454  
-    flock.ugen.inputTrigger = function (inputs, output, options) {
  454
+    flock.ugen.inputChangeTrigger = function (inputs, output, options) {
455 455
         var that = flock.ugen(inputs, output, options);
456 456
 
457 457
         that.gen = function (numSamps) {
@@ -497,7 +497,8 @@ var fluid = fluid || require("infusion"),
497 497
         that.calculateStrides();
498 498
         return that;
499 499
     };
500  
-    fluid.defaults("flock.ugen.inputTrigger", {
  500
+
  501
+    fluid.defaults("flock.ugen.inputChangeTrigger", {
501 502
         rate: "control",
502 503
 
503 504
         inputs: {
@@ -515,6 +516,95 @@ var fluid = fluid || require("infusion"),
515 516
         }
516 517
     });
517 518
 
  519
+    flock.ugen.triggerCallback = function (inputs, output, options) {
  520
+        var that = flock.ugen(inputs, output, options);
  521
+
  522
+        that.gen = function (numSamps) {
  523
+            var m = that.model,
  524
+                o = that.options,
  525
+                out = that.output,
  526
+                inputs = that.inputs,
  527
+                triggerInc = m.strides.trigger,
  528
+                sourceInc = m.strides.source,
  529
+                trig = inputs.trigger.output,
  530
+                source = inputs.source.output,
  531
+                cbSpec = o.callback,
  532
+                fn = cbSpec.func,
  533
+                args = cbSpec.args,
  534
+                cbThis = cbSpec.this,
  535
+                lastArgIdx = m.lastArgIdx,
  536
+                prevTrig = m.prevTrig,
  537
+                i,
  538
+                j,
  539
+                k,
  540
+                currTrig,
  541
+                sourceVal;
  542
+
  543
+            for (i = j = k = 0; i < numSamps; i++, j += triggerInc, k += sourceInc) {
  544
+                currTrig = trig[j];
  545
+                sourceVal = source[k];
  546
+
  547
+                if (currTrig > 0.0 && prevTrig <= 0.0 && fn) {
  548
+                    // Insert the current source value into the arguments list
  549
+                    // and then invoke the specified callback function.
  550
+                    args[lastArgIdx] = sourceVal;
  551
+                    fn.apply(cbThis, args);
  552
+                }
  553
+
  554
+                out[i] = sourceVal;
  555
+                prevTrig = currTrig;
  556
+            }
  557
+
  558
+            m.prevTrig = prevTrig;
  559
+        };
  560
+
  561
+        that.onInputChanged = function () {
  562
+            var o = that.options,
  563
+                m = that.model,
  564
+                cbSpec = o.callback,
  565
+                funcName = cbSpec.funcName;
  566
+
  567
+            if (funcName) {
  568
+                cbSpec.func = fluid.getGlobalValue(funcName);
  569
+            } else if (cbSpec.this && cbSpec.method) {
  570
+                if (typeof cbSpec.this !== "string") {
  571
+                    throw new Error("flock.ugen.triggerCallback doesn't support raw 'this' objects." +
  572
+                        "Use a global key path instead.");
  573
+                }
  574
+                cbSpec.this = typeof cbSpec.this === "string" ?
  575
+                    fluid.getGlobalValue(cbSpec.this) : cbSpec.this;
  576
+                cbSpec.func = fluid.get(cbSpec.this, cbSpec.method);
  577
+            }
  578
+
  579
+            m.lastArgIdx = cbSpec.args.length;
  580
+            that.calculateStrides();
  581
+        };
  582
+
  583
+        that.onInputChanged();
  584
+        return that;
  585
+    };
  586
+
  587
+    fluid.defaults("flock.ugen.triggerCallback", {
  588
+        rate: "audio",
  589
+        inputs: {
  590
+            source: 0,
  591
+            trigger: 0
  592
+        },
  593
+        ugenOptions: {
  594
+            model: {
  595
+                funcName: undefined,
  596
+                lastArgIdx: 0
  597
+            },
  598
+            callback: {
  599
+                "this": undefined,
  600
+                method: undefined,
  601
+                func: undefined,
  602
+                args: []
  603
+            },
  604
+            strideInputs: ["source", "trigger"]
  605
+        }
  606
+    });
  607
+
518 608
     flock.ugen.math = function (inputs, output, options) {
519 609
         var that = flock.ugen(inputs, output, options);
520 610
         that.expandedSource = new Float32Array(that.options.audioSettings.blockSize);
355  tests/flocking/js/ugen-tests.js
@@ -1411,117 +1411,119 @@ var fluid = fluid || require("infusion"),
1411 1411
     });
1412 1412
 
1413 1413
 
1414  
-    module("flock.ugen.filter tests");
  1414
+    (function () {
  1415
+        module("flock.ugen.filter tests");
1415 1416
 
1416  
-    var filterInputValues = [
1417  
-        {
1418  
-            freq: 440,
1419  
-            q: 1.0
1420  
-        },
1421  
-        {
1422  
-            freq: 880,
1423  
-            q: 0.5
1424  
-        },
1425  
-        {
1426  
-            freq: 22050,
1427  
-            q: 0.1
1428  
-        },
1429  
-        {
1430  
-            freq: 440,
1431  
-            q: 10
1432  
-        },
1433  
-        {
1434  
-            freq: 880,
1435  
-            q: 20
1436  
-        },
1437  
-        {
1438  
-            freq: 22050,
1439  
-            q: 100
1440  
-        }
1441  
-    ];
  1417
+        var filterInputValues = [
  1418
+            {
  1419
+                freq: 440,
  1420
+                q: 1.0
  1421
+            },
  1422
+            {
  1423
+                freq: 880,
  1424
+                q: 0.5
  1425
+            },
  1426
+            {
  1427
+                freq: 22050,
  1428
+                q: 0.1
  1429
+            },
  1430
+            {
  1431
+                freq: 440,
  1432
+                q: 10
  1433
+            },
  1434
+            {
  1435
+                freq: 880,
  1436
+                q: 20
  1437
+            },
  1438
+            {
  1439
+                freq: 22050,
  1440
+                q: 100
  1441
+            }
  1442
+        ];
1442 1443
 
1443  
-    var checkCoefficient = function (coefficient) {
1444  
-        ok(!isNaN(coefficient), "The coefficient should never be NaN");
1445  
-        ok(coefficient !== Infinity, "The coefficient should never be Infinity");
1446  
-        ok(coefficient !== Number.NEGATIVE_INFINITY, "The coefficient should never be negative Infinity");
1447  
-        //ok(coefficient >= -1.0 && coefficient <= 1.0, "The coefficient should be in the range of -1.0 to 1.0");
1448  
-    };
  1444
+        var checkCoefficient = function (coefficient) {
  1445
+            ok(!isNaN(coefficient), "The coefficient should never be NaN");
  1446
+            ok(coefficient !== Infinity, "The coefficient should never be Infinity");
  1447
+            ok(coefficient !== Number.NEGATIVE_INFINITY, "The coefficient should never be negative Infinity");
  1448
+            //ok(coefficient >= -1.0 && coefficient <= 1.0, "The coefficient should be in the range of -1.0 to 1.0");
  1449
+        };
1449 1450
 
1450  
-    var checkCoefficients = function (model) {
1451  
-        $.each(model.coeffs, function (i, coefficientArray) {
1452  
-            $.each(coefficientArray, function (i, coefficient) {
1453  
-                checkCoefficient(coefficient);
  1451
+        var checkCoefficients = function (model) {
  1452
+            $.each(model.coeffs, function (i, coefficientArray) {
  1453
+                $.each(coefficientArray, function (i, coefficient) {
  1454
+                    checkCoefficient(coefficient);
  1455
+                });
1454 1456
             });
1455  
-        });
1456  
-    };
  1457
+        };
1457 1458
 
1458  
-    var forEachFilterType = function (fn) {
1459  
-        $.each(flock.coefficients, function (recipeName, recipe) {
1460  
-            $.each(recipe, function (filterType, calculator) {
1461  
-                // TODO: This suggests that the payload for filter recipes isn't quite right.
1462  
-                if (filterType === "sizes") {
1463  
-                    return;
1464  
-                }
1465  
-                fn(recipeName, recipe, filterType, calculator);
  1459
+        var forEachFilterType = function (fn) {
  1460
+            $.each(flock.coefficients, function (recipeName, recipe) {
  1461
+                $.each(recipe, function (filterType, calculator) {
  1462
+                    // TODO: This suggests that the payload for filter recipes isn't quite right.
  1463
+                    if (filterType === "sizes") {
  1464
+                        return;
  1465
+                    }
  1466
+                    fn(recipeName, recipe, filterType, calculator);
  1467
+                });
1466 1468
             });
1467  
-        });
1468  
-    };
  1469
+        };
1469 1470
 
1470  
-    var testEachFilterInputValue = function (name, fn) {
1471  
-        test(name, function () {
1472  
-            $.each(filterInputValues, function (i, inputs) {
1473  
-                fn(inputs);
  1471
+        var testEachFilterInputValue = function (name, fn) {
  1472
+            test(name, function () {
  1473
+                $.each(filterInputValues, function (i, inputs) {
  1474
+                    fn(inputs);
  1475
+                });
1474 1476
             });
1475  
-        });
1476  
-    };
  1477
+        };
1477 1478
 
1478  
-    // Test all coefficient recipes.
1479  
-    forEachFilterType(function (recipeName, receipe, filterType, fn) {
1480  
-        var name = "flock.coefficients." + recipeName + "." + filterType;
  1479
+        // Test all coefficient recipes.
  1480
+        forEachFilterType(function (recipeName, receipe, filterType, fn) {
  1481
+            var name = "flock.coefficients." + recipeName + "." + filterType;
1481 1482
 
1482  
-        testEachFilterInputValue(name, function (inputs) {
1483  
-            var model = {
1484  
-                coeffs: {
1485  
-                    a: new Float32Array(2),
1486  
-                    b: new Float32Array(3)
1487  
-                },
1488  
-                sampleRate: sampleRate
1489  
-            };
  1483
+            testEachFilterInputValue(name, function (inputs) {
  1484
+                var model = {
  1485
+                    coeffs: {
  1486
+                        a: new Float32Array(2),
  1487
+                        b: new Float32Array(3)
  1488
+                    },
  1489
+                    sampleRate: sampleRate
  1490
+                };
1490 1491
 
1491  
-            fn(model, inputs.freq, inputs.q);
1492  
-            checkCoefficients(model);
  1492
+                fn(model, inputs.freq, inputs.q);
  1493
+                checkCoefficients(model);
  1494
+            });
1493 1495
         });
1494  
-    });
1495 1496
 
1496  
-    // Test the flock.ugen.filter unit generator with all filter types and a set of generic input values.
1497  
-    /*
1498  
-    forEachFilterType(function (recipeName, recipe, filterType) {
1499  
-        var name = "flock.ugen.filter() " + recipeName + "." + filterType;
1500  
-        testEachFilterInputValue(name, function (inputs) {
1501  
-            var ugen = {
1502  
-                id: "filter",
1503  
-                ugen: "flock.ugen.filter",
1504  
-                inputs: inputs,
1505  
-                options: {
1506  
-                    // TODO: API bug. I should just be able to specify a type (as a key path) without a recipe if I want.
1507  
-                    recipe: recipe,
1508  
-                    type: filterType
1509  
-                }
1510  
-            };
1511  
-            ugen.inputs.source = {
1512  
-                ugen: "flock.ugen.lfNoise",
1513  
-                inputs: {
1514  
-                    freq: 440,
1515  
-                    mul: 0.95
1516  
-                }
1517  
-            };
  1497
+        // Test the flock.ugen.filter unit generator with all filter types and a set of generic input values.
  1498
+        /*
  1499
+        forEachFilterType(function (recipeName, recipe, filterType) {
  1500
+            var name = "flock.ugen.filter() " + recipeName + "." + filterType;
  1501
+            testEachFilterInputValue(name, function (inputs) {
  1502
+                var ugen = {
  1503
+                    id: "filter",
  1504
+                    ugen: "flock.ugen.filter",
  1505
+                    inputs: inputs,
  1506
+                    options: {
  1507
+                        // TODO: API bug. I should just be able to specify a type (as a key path) without a recipe if I want.
  1508
+                        recipe: recipe,
  1509
+                        type: filterType
  1510
+                    }
  1511
+                };
  1512
+                ugen.inputs.source = {
  1513
+                    ugen: "flock.ugen.lfNoise",
  1514
+                    inputs: {
  1515
+                        freq: 440,
  1516
+                        mul: 0.95
  1517
+                    }
  1518
+                };
1518 1519
 
1519  
-            var filterSynth = flock.synth(ugen);
1520  
-            filterSynth.gen(64);
1521  
-            flock.test.arrayUnbrokenSignal(filterSynth.get("filter"), -1.0, 1.0);
  1520
+                var filterSynth = flock.synth(ugen);
  1521
+                filterSynth.gen(64);
  1522
+                flock.test.arrayUnbrokenSignal(filterSynth.get("filter"), -1.0, 1.0);
  1523
+            });
1522 1524
         });
1523  
-    });
1524  
-    */
  1525
+        */
  1526
+    }());
1525 1527
 
1526 1528
     test("flock.ugen.delay", function () {
1527 1529
         var sourceBuffer = flock.test.ascendingBuffer(64, 1),
@@ -2228,6 +2230,167 @@ var fluid = fluid || require("infusion"),
2228 2230
     });
2229 2231
 
2230 2232
     (function () {
  2233
+        module("flock.ugen.triggerCallback");
  2234
+        flock.test.CallbackCounter = function () {
  2235
+            this.callbackRecords = [];
  2236
+        };
  2237
+
  2238
+        flock.test.CallbackCounter.prototype.callback = function () {
  2239
+            this.callbackRecords.push(arguments);
  2240
+        };
  2241
+
  2242
+        flock.test.CallbackCounter.prototype.clear = function () {
  2243
+            this.callbackRecords = [];
  2244
+        };
  2245
+
  2246
+        var makeCallbackCounter = function () {
  2247
+            var counter = new flock.test.CallbackCounter();
  2248
+            counter.boundCallback = counter.callback.bind(counter);
  2249
+            flock.test.CallbackCounter.singleton = counter;
  2250
+            return counter;
  2251
+        };
  2252
+
  2253
+        fluid.defaults("flock.test.triggerCallbackSynth", {
  2254
+            gradeNames: ["flock.synth", "autoInit"],
  2255
+            synthDef: {
  2256
+                ugen: "flock.ugen.triggerCallback",
  2257
+                source: {
  2258
+                    ugen: "flock.mock.ugen",
  2259
+                    options: {
  2260
+                        buffer: flock.generate(64, function (i) {
  2261
+                            return i;
  2262
+                        })
  2263
+                    }
  2264
+                },
  2265
+                trigger: {
  2266
+                    ugen: "flock.mock.ugen",
  2267
+                    options: {
  2268
+                        buffer: flock.generate(64, function (i) {
  2269
+                            return i === 31 ? 1.0 : 0.0;
  2270
+                        })
  2271
+                    }
  2272
+                },
  2273
+                options: {}
  2274
+            }
  2275
+        });
  2276
+
  2277
+        var testTriggerCallback = function (testSpec) {
  2278
+            var counter = makeCallbackCounter();
  2279
+            var synthDefSpec = {
  2280
+                options: {
  2281
+                    callback: {}
  2282
+                }
  2283
+            };
  2284
+
  2285
+            if (testSpec.type === "func" || testSpec.type === "funcName") {
  2286
+                synthDefSpec.options.callback[testSpec.type] = counter.boundCallback;
  2287
+            }
  2288
+
  2289
+            var synth = flock.test.triggerCallbackSynth({
  2290
+                synthDef: $.extend(true, synthDefSpec, testSpec.synthDefOverrides)
  2291
+            });
  2292
+            synth.gen();
  2293
+
  2294
+            var expectedNumCalls = testSpec.expectedCallbackArgs.length;
  2295
+            equal(counter.callbackRecords.length, expectedNumCalls, "The callback should have been invoked " +
  2296
+                expectedNumCalls + " times.");
  2297
+
  2298
+            for (var i = 0; i < expectedNumCalls; i++) {
  2299
+                var expectedCallbackRecord = fluid.makeArray(testSpec.expectedCallbackArgs[i]);
  2300
+                var actualCallbackRecord = counter.callbackRecords[i];
  2301
+                equal(actualCallbackRecord.length, expectedCallbackRecord.length,
  2302
+                    expectedCallbackRecord.length + " arguments should have been passed to the callback.");
  2303
+                for (var j = 0; j < expectedCallbackRecord.length; j++) {
  2304
+                    equal(actualCallbackRecord[j], expectedCallbackRecord[j],
  2305
+                        "The expected argument at position " + j + " should have been passed to the callback.");
  2306
+                }
  2307
+            }
  2308
+        };
  2309
+
  2310
+        var runTriggerCallbackTests = function (testSpecs) {
  2311
+            fluid.each(testSpecs, function (testSpec) {
  2312
+                test(testSpec.name, function () {
  2313
+                    testTriggerCallback(testSpec);
  2314
+                });
  2315
+            });
  2316
+        };
  2317
+
  2318
+        var triggerCallbackTestSpecs = [
  2319
+            {
  2320
+                name: "Raw function",
  2321
+                type: "func",
  2322
+                expectedCallbackArgs: [
  2323
+                    [31]
  2324
+                ]
  2325
+            },
  2326
+            {
  2327
+                name: "Raw function, multiple triggers",
  2328
+                type: "func",
  2329
+                synthDefOverrides: {
  2330
+                    trigger: {
  2331
+                        options: {
  2332
+                            buffer: flock.generate(64, function (i) {
  2333
+                                return (i === 31 || i === 62) ? 1.0 : 0.0;
  2334
+                            })
  2335
+                        }
  2336
+                    }
  2337
+                },
  2338
+
  2339
+                expectedCallbackArgs: [
  2340
+                    [31],
  2341
+                    [62]
  2342
+                ]
  2343
+            },
  2344
+            {
  2345
+                name: "Raw function with arguments",
  2346
+                type: "func",
  2347
+                synthDefOverrides: {
  2348
+                    options: {
  2349
+                        callback: {
  2350
+                            args: ["cat"]
  2351
+                        }
  2352
+                    }
  2353
+                },
  2354
+                expectedCallbackArgs: [
  2355
+                    ["cat", 31]
  2356
+                ]
  2357
+            },
  2358
+            {
  2359
+                name: "Function EL path",
  2360
+                type: "funcName",
  2361
+                synthDefOverrides: {
  2362
+                    options: {
  2363
+                        callback: {
  2364
+                            funcName: "flock.test.CallbackCounter.singleton.boundCallback"
  2365
+                        }
  2366
+                    }
  2367
+                },
  2368
+                expectedCallbackArgs: [
  2369
+                    [31]
  2370
+                ]
  2371
+            },
  2372
+            {
  2373
+                name: "this/method pair",
  2374
+                synthDefOverrides: {
  2375
+                    options: {
  2376
+                        callback: {
  2377
+                            "this": "flock.test.CallbackCounter.singleton",
  2378
+                            method: "callback"
  2379
+                        }
  2380
+                    }
  2381
+                },
  2382
+                expectedCallbackArgs: [
  2383
+                    [31]
  2384
+                ]
  2385
+            }
  2386
+        ];
  2387
+
  2388
+        runTriggerCallbackTests(triggerCallbackTestSpecs);
  2389
+
  2390
+    }());
  2391
+
  2392
+
  2393
+    (function () {
2231 2394
         module("flock.ugen.pan2");
2232 2395
 
2233 2396
         var makePannerSynth = function (panVal) {

0 notes on commit 2e091ce

Please sign in to comment.
Something went wrong with that request. Please try again.