├── .gitignore ├── COPYING ├── README.txt ├── SoundAsPureForm.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── include ├── DelayUGens.hpp ├── ErrorCodes.hpp ├── FilterUGens.hpp ├── Hash.hpp ├── MathFuns.hpp ├── MathOps.hpp ├── Midi.hpp ├── MultichannelExpansion.hpp ├── Object.hpp ├── Opcode.hpp ├── OscilUGens.hpp ├── Parser.hpp ├── Play.hpp ├── RCObj.hpp ├── SoundFiles.hpp ├── Spectrogram.hpp ├── Types.hpp ├── UGen.hpp ├── VM.hpp ├── clz.hpp ├── dsp.hpp ├── elapsedTime.hpp ├── makeImage.hpp ├── primes.hpp ├── rc_ptr.hpp ├── rgen.hpp └── symbol.hpp ├── libmanta ├── Manta.cpp ├── Manta.h ├── MantaClient.h ├── MantaExceptions.h ├── MantaMulti.cpp ├── MantaMulti.h ├── MantaServer.h ├── MantaUSB.cpp ├── MantaUSB.h ├── MantaVersion.h └── extern │ └── hidapi │ ├── README.txt │ ├── hidapi │ └── hidapi.h │ ├── libusb │ └── hid.c │ ├── m4 │ ├── ax_pthread.m4 │ └── pkg.m4 │ └── mac │ └── hid.c ├── sapf-bif-examples.txt ├── sapf-examples.txt ├── sapf-prelude.txt ├── src ├── CoreOps.cpp ├── DelayUGens.cpp ├── ErrorCodes.cpp ├── FilterUGens.cpp ├── MathFuns.cpp ├── MathOps.cpp ├── Midi.cpp ├── MultichannelExpansion.cpp ├── Object.cpp ├── Opcode.cpp ├── OscilUGens.cpp ├── Parser.cpp ├── Play.cpp ├── RCObj.cpp ├── RandomOps.cpp ├── SetOps.cpp ├── SoundFiles.cpp ├── Spectrogram.cpp ├── StreamOps.cpp ├── Types.cpp ├── UGen.cpp ├── VM.cpp ├── dsp.cpp ├── elapsedTime.cpp ├── main.cpp ├── makeImage.mm ├── primes.cpp └── symbol.cpp └── unit-tests.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Trashes 3 | *.swp 4 | *.lock 5 | *~.nib 6 | DerivedData/ 7 | build/ 8 | *.pbxuser 9 | *.mode1v3 10 | *.mode2v3 11 | *.perspectivev3 12 | !default.pbxuser 13 | !default.mode1v3 14 | !default.mode2v3 15 | !default.perspectivev3 16 | *.xccheckout 17 | xcuserdata/ 18 | xcshareddata/ 19 | *.xcworkspace/xcuserdata/ 20 | *.xcworkspace/xcshareddata/ 21 | *.hmap 22 | *.ipa 23 | *.xcuserstate 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | -------------------------------------------------------------------------------- /SoundAsPureForm.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SoundAsPureForm.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /include/DelayUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__DelayUGens__ 18 | #define __taggeddoubles__DelayUGens__ 19 | 20 | #include "Object.hpp" 21 | 22 | void AddDelayUGenOps(); 23 | 24 | #endif /* defined(__taggeddoubles__DelayUGens__) */ 25 | -------------------------------------------------------------------------------- /include/ErrorCodes.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __ErrorCodes_h__ 18 | #define __ErrorCodes_h__ 19 | 20 | const int errNone = 0; 21 | const int errHalt = -1000; 22 | const int errFailed = -1001; 23 | const int errIndefiniteOperation = -1002; 24 | const int errWrongType = -1003; 25 | const int errOutOfRange = -1004; 26 | const int errSyntax = -1005; 27 | const int errInternalError = -1006; 28 | const int errWrongState = -1007; 29 | const int errNotFound = -1008; 30 | const int errStackOverflow = -1009; 31 | const int errStackUnderflow = -1010; 32 | const int errInconsistentInheritance = -1011; 33 | const int errUndefinedOperation = -1012; 34 | const int errUserQuit = -1013; 35 | const int kNumErrors = 14; 36 | 37 | extern const char* errString[kNumErrors]; 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /include/FilterUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef taggeddoubles_FilterUGens_h 18 | #define taggeddoubles_FilterUGens_h 19 | 20 | #include "Object.hpp" 21 | 22 | void AddFilterUGenOps(); 23 | 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /include/Hash.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef _Hash_ 18 | #define _Hash_ 19 | 20 | #include 21 | 22 | 23 | // hash function for a string 24 | inline int32_t Hash(const char *inKey) 25 | { 26 | // the one-at-a-time hash. 27 | // a very good hash function. ref: a web page by Bob Jenkins. 28 | // http://www.burtleburtle.net/bob/hash/doobs.html 29 | int32_t hash = 0; 30 | while (*inKey) { 31 | hash += *inKey++; 32 | hash += hash << 10; 33 | hash ^= hash >> 6; 34 | } 35 | hash += hash << 3; 36 | hash ^= hash >> 11; 37 | hash += hash << 15; 38 | return hash; 39 | } 40 | 41 | // hash function for a string that also returns the length 42 | inline int32_t Hash(const char *inKey, size_t *outLength) 43 | { 44 | // the one-at-a-time hash. 45 | // a very good hash function. ref: a web page by Bob Jenkins. 46 | const char *origKey = inKey; 47 | int32_t hash = 0; 48 | while (*inKey) { 49 | hash += *inKey++; 50 | hash += hash << 10; 51 | hash ^= hash >> 6; 52 | } 53 | hash += hash << 3; 54 | hash ^= hash >> 11; 55 | hash += hash << 15; 56 | *outLength = inKey - origKey; 57 | return hash; 58 | } 59 | 60 | // hash function for an array of char 61 | inline int32_t Hash(const char *inKey, size_t inLength) 62 | { 63 | // the one-at-a-time hash. 64 | // a very good hash function. ref: a web page by Bob Jenkins. 65 | int32_t hash = 0; 66 | for (size_t i=0; i> 6; 70 | } 71 | hash += hash << 3; 72 | hash ^= hash >> 11; 73 | hash += hash << 15; 74 | return hash; 75 | } 76 | 77 | // hash function for integers 78 | inline int32_t Hash(int32_t inKey) 79 | { 80 | // Thomas Wang's integer hash. 81 | // http://www.concentric.net/~Ttwang/tech/inthash.htm 82 | // a faster hash for integers. also very good. 83 | uint32_t hash = (uint32_t)inKey; 84 | hash += ~(hash << 15); 85 | hash ^= hash >> 10; 86 | hash += hash << 3; 87 | hash ^= hash >> 6; 88 | hash += ~(hash << 11); 89 | hash ^= hash >> 16; 90 | return (int32_t)hash; 91 | } 92 | 93 | inline int64_t Hash64(int64_t inKey) 94 | { 95 | // Thomas Wang's 64 bit integer hash. 96 | uint64_t hash = (uint64_t)inKey; 97 | hash ^= ((~hash) >> 31); 98 | hash += (hash << 28); 99 | hash ^= (hash >> 21); 100 | hash += (hash << 3); 101 | hash ^= ((~hash) >> 5); 102 | hash += (hash << 13); 103 | hash ^= (hash >> 27); 104 | hash += (hash << 32); 105 | return (int64_t)hash; 106 | } 107 | 108 | inline int64_t Hash64bad(int64_t inKey) 109 | { 110 | // Thomas Wang's 64 bit integer hash. 111 | uint64_t hash = (uint64_t)inKey; 112 | hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1; 113 | hash ^= (hash >> 24); 114 | hash += (hash << 3) + (hash << 8); // hash * 265 115 | hash ^= (hash >> 14); 116 | hash += (hash << 2) + (hash << 4); // hash * 21 117 | hash ^= (hash >> 28); 118 | hash += (hash << 31); 119 | return (int64_t)hash; 120 | } 121 | 122 | inline int32_t Hash(const int32_t *inKey, int32_t inLength) 123 | { 124 | // one-at-a-time hashing of a string of int32_t's. 125 | // uses Thomas Wang's integer hash for the combining step. 126 | int32_t hash = 0; 127 | for (int i=0; i. 16 | 17 | #ifndef __MathOps_h__ 18 | #define __MathOps_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | struct UnaryOpGen : public Gen 23 | { 24 | VIn _a; 25 | UnaryOp* op; 26 | 27 | UnaryOpGen(Thread& th, UnaryOp* inOp, Arg a) : Gen(th, itemTypeV, a.isFinite()), op(inOp), _a(a) {} 28 | 29 | virtual const char* TypeName() const override { return "UnaryOpGen"; } 30 | 31 | virtual void pull(Thread& th) override 32 | { 33 | int framesToFill = mBlockSize; 34 | V* out = mOut->fulfill(framesToFill); 35 | while (framesToFill) { 36 | int n = framesToFill; 37 | int astride; 38 | V *a; 39 | if (_a(th, n,astride, a)) { 40 | setDone(); 41 | break; 42 | } else { 43 | op->loop(th, n, a, astride, out); 44 | _a.advance(n); 45 | framesToFill -= n; 46 | out += n; 47 | } 48 | } 49 | produce(framesToFill); 50 | } 51 | }; 52 | 53 | // element-wise 54 | struct BinaryOpGen : public Gen 55 | { 56 | VIn _a; 57 | VIn _b; 58 | BinaryOp* op; 59 | 60 | BinaryOpGen(Thread& th, BinaryOp* inOp, Arg a, Arg b) : Gen(th, itemTypeV, mostFinite(a, b)), op(inOp), _a(a), _b(b) {} 61 | 62 | virtual const char* TypeName() const override { return "BinaryOpGen"; } 63 | 64 | virtual void pull(Thread& th) override 65 | { 66 | int framesToFill = mBlockSize; 67 | V* out = mOut->fulfill(framesToFill); 68 | while (framesToFill) { 69 | int n = framesToFill; 70 | int astride, bstride; 71 | V *a, *b; 72 | if (_a(th, n,astride, a) || _b(th, n,bstride, b)) { 73 | setDone(); 74 | break; 75 | } else { 76 | op->loop(th, n, a, astride, b, bstride, out); 77 | _a.advance(n); 78 | _b.advance(n); 79 | framesToFill -= n; 80 | out += n; 81 | } 82 | } 83 | produce(framesToFill); 84 | } 85 | }; 86 | 87 | struct BinaryOpLinkGen : public Gen 88 | { 89 | VIn _a; 90 | VIn _b; 91 | BinaryOp* op; 92 | 93 | BinaryOpLinkGen(Thread& th, BinaryOp* inOp, Arg a, Arg b) : Gen(th, itemTypeV, mostFinite(a, b)), op(inOp), _a(a), _b(b) {} 94 | 95 | virtual const char* TypeName() const override { return "BinaryOpLinkGen"; } 96 | 97 | virtual void pull(Thread& th) override 98 | { 99 | int framesToFill = mBlockSize; 100 | V* out = mOut->fulfill(framesToFill); 101 | while (framesToFill) { 102 | int n = framesToFill; 103 | int astride, bstride; 104 | V *a, *b; 105 | if (_a(th, n,astride, a)) { 106 | produce(framesToFill); 107 | _b.link(th, mOut); 108 | setDone(); 109 | return; 110 | } else if (_b(th, n,bstride, b)) { 111 | produce(framesToFill); 112 | _a.link(th, mOut); 113 | setDone(); 114 | return; 115 | } else { 116 | op->loop(th, n, a, astride, b, bstride, out); 117 | _a.advance(n); 118 | _b.advance(n); 119 | framesToFill -= n; 120 | out += n; 121 | } 122 | } 123 | produce(framesToFill); 124 | } 125 | }; 126 | 127 | struct UnaryOpZGen : public Gen 128 | { 129 | ZIn _a; 130 | UnaryOp* op; 131 | 132 | UnaryOpZGen(Thread& th, UnaryOp* inOp, Arg a) : Gen(th, itemTypeZ, a.isFinite()), _a(a), op(inOp) {} 133 | 134 | virtual const char* TypeName() const override { return "UnaryOpZGen"; } 135 | 136 | virtual int numInputs() const { return 1; } 137 | 138 | virtual void pull(Thread& th) override; 139 | }; 140 | 141 | struct BinaryOpZGen : public Gen 142 | { 143 | ZIn _a; 144 | ZIn _b; 145 | BinaryOp* op; 146 | 147 | BinaryOpZGen(Thread& th, BinaryOp* _op, Arg a, Arg b) 148 | : Gen(th, itemTypeZ, mostFinite(a,b)), _a(a), _b(b), op(_op) {} 149 | 150 | virtual const char* TypeName() const override { return "BinaryOpZGen"; } 151 | 152 | virtual void pull(Thread& th) override; 153 | }; 154 | 155 | struct BinaryOpLinkZGen : public Gen 156 | { 157 | ZIn _a; 158 | ZIn _b; 159 | BinaryOp* op; 160 | 161 | BinaryOpLinkZGen(Thread& th, BinaryOp* _op, Arg a, Arg b) 162 | : Gen(th, itemTypeZ, mostFinite(a,b)), _a(a), _b(b), op(_op) {} 163 | 164 | virtual const char* TypeName() const override { return "BinaryOpLinkZGen"; } 165 | 166 | virtual void pull(Thread& th) override; 167 | }; 168 | 169 | 170 | struct ScanOpZGen : public Gen 171 | { 172 | ZIn _a; 173 | BinaryOp* op; 174 | Z z; 175 | bool once; 176 | 177 | ScanOpZGen(Thread& th, Arg a, BinaryOp* _op) 178 | : Gen(th, itemTypeZ, a.isFinite()), _a(a), op(_op) , once(true) {} 179 | 180 | virtual const char* TypeName() const override { return "ScanOpZGen"; } 181 | 182 | virtual void pull(Thread& th) override 183 | { 184 | int framesToFill = mBlockSize; 185 | Z* out = mOut->fulfillz(framesToFill); 186 | while (framesToFill) { 187 | int n = framesToFill; 188 | int astride; 189 | Z *a; 190 | if (_a(th, n,astride, a) ) { 191 | setDone(); 192 | break; 193 | } else { 194 | if (once) { 195 | once = false; 196 | out[0] = z = a[0]; 197 | op->scanz(n-1, z, a+1, astride, out+1); 198 | } else { 199 | op->scanz(n, z, a, astride, out); 200 | } 201 | _a.advance(n); 202 | framesToFill -= n; 203 | out += n; 204 | } 205 | } 206 | produce(framesToFill); 207 | } 208 | }; 209 | 210 | struct IScanOpZGen : public Gen 211 | { 212 | ZIn _a; 213 | Z z; 214 | BinaryOp* op; 215 | bool once; 216 | 217 | IScanOpZGen(Thread& th, Arg a, Z b, BinaryOp* _op) 218 | : Gen(th, itemTypeZ, a.isFinite()), _a(a), z(b), op(_op) , once(true) {} 219 | 220 | virtual const char* TypeName() const override { return "IScanOpZGen"; } 221 | 222 | virtual void pull(Thread& th) override 223 | { 224 | int framesToFill = mBlockSize; 225 | Z* out = mOut->fulfillz(framesToFill); 226 | while (framesToFill) { 227 | int n = framesToFill; 228 | int astride; 229 | Z *a; 230 | if (_a(th, n,astride, a) ) { 231 | setDone(); 232 | break; 233 | } else { 234 | if (once) { 235 | once = false; 236 | out[0] = z; 237 | op->scanz(n-1, z, a, astride, out+1); 238 | _a.advance(n-1); 239 | } else { 240 | op->scanz(n, z, a, astride, out); 241 | _a.advance(n); 242 | } 243 | framesToFill -= n; 244 | out += n; 245 | } 246 | } 247 | produce(framesToFill); 248 | } 249 | }; 250 | 251 | struct PairsOpZGen : public Gen 252 | { 253 | ZIn _a; 254 | BinaryOp* op; 255 | Z z; 256 | bool once; 257 | 258 | PairsOpZGen(Thread& th, Arg a, BinaryOp* _op) 259 | : Gen(th, itemTypeZ, a.isFinite()), _a(a), op(_op) , once(true) {} 260 | 261 | virtual const char* TypeName() const override { return "PairsOpZGen"; } 262 | 263 | virtual void pull(Thread& th) override { 264 | int framesToFill = mBlockSize; 265 | Z* out = mOut->fulfillz(framesToFill); 266 | while (framesToFill) { 267 | int n = framesToFill; 268 | int astride; 269 | Z *a; 270 | if (_a(th, n,astride, a) ) { 271 | setDone(); 272 | break; 273 | } else { 274 | if (once) { 275 | once = false; 276 | out[0] = z = a[0]; 277 | op->pairsz(n-1, z, a+1, astride, out+1); 278 | } else { 279 | op->pairsz(n, z, a, astride, out); 280 | } 281 | _a.advance(n); 282 | framesToFill -= n; 283 | out += n; 284 | } 285 | } 286 | produce(framesToFill); 287 | } 288 | }; 289 | 290 | 291 | struct IPairsOpZGen : public Gen 292 | { 293 | ZIn _a; 294 | Z z; 295 | BinaryOp* op; 296 | bool once; 297 | 298 | IPairsOpZGen(Thread& th, Arg a, Z b, BinaryOp* _op) 299 | : Gen(th, itemTypeZ, a.isFinite()), _a(a), z(b), op(_op) , once(true) {} 300 | 301 | virtual const char* TypeName() const override { return "IPairsOpZGen"; } 302 | 303 | virtual void pull(Thread& th) override { 304 | int framesToFill = mBlockSize; 305 | Z* out = mOut->fulfillz(framesToFill); 306 | while (framesToFill) { 307 | int n = framesToFill; 308 | int astride; 309 | Z *a; 310 | if (_a(th, n,astride, a) ) { 311 | setDone(); 312 | break; 313 | } else { 314 | if (once) { 315 | once = false; 316 | out[0] = z; 317 | z = a[0]; 318 | op->pairsz(n-1, z, a+1, astride, out+1); 319 | } else { 320 | op->pairsz(n, z, a, astride, out); 321 | } 322 | _a.advance(n); 323 | framesToFill -= n; 324 | out += n; 325 | } 326 | } 327 | produce(framesToFill); 328 | } 329 | }; 330 | 331 | 332 | struct ScanOpGen : public Gen 333 | { 334 | VIn _a; 335 | BinaryOp* op; 336 | V z; 337 | bool once; 338 | 339 | ScanOpGen(Thread& th, Arg a, BinaryOp* _op) 340 | : Gen(th, itemTypeV, a.isFinite()), _a(a), op(_op) , once(true) {} 341 | 342 | virtual const char* TypeName() const override { return "ScanOpGen"; } 343 | 344 | virtual void pull(Thread& th) override { 345 | int framesToFill = mBlockSize; 346 | V* out = mOut->fulfill(framesToFill); 347 | while (framesToFill) { 348 | int n = framesToFill; 349 | int astride; 350 | V *a; 351 | if (_a(th, n,astride, a) ) { 352 | setDone(); 353 | break; 354 | } else { 355 | if (once) { 356 | once = false; 357 | out[0] = z = a[0]; 358 | op->scan(th, n-1, z, a+1, astride, out+1); 359 | } else { 360 | op->scan(th, n, z, a, astride, out); 361 | } 362 | _a.advance(n); 363 | framesToFill -= n; 364 | out += n; 365 | } 366 | } 367 | produce(framesToFill); 368 | } 369 | }; 370 | 371 | struct IScanOpGen : public Gen 372 | { 373 | VIn _a; 374 | BinaryOp* op; 375 | V z; 376 | bool once; 377 | 378 | IScanOpGen(Thread& th, Arg a, Arg b, BinaryOp* _op) 379 | : Gen(th, itemTypeV, a.isFinite()), _a(a), z(b), op(_op) , once(true) {} 380 | 381 | virtual const char* TypeName() const override { return "IScanOpGen"; } 382 | 383 | virtual void pull(Thread& th) override { 384 | int framesToFill = mBlockSize; 385 | V* out = mOut->fulfill(framesToFill); 386 | while (framesToFill) { 387 | int n = framesToFill; 388 | int astride; 389 | V *a; 390 | if (_a(th, n,astride, a) ) { 391 | setDone(); 392 | break; 393 | } else { 394 | if (once) { 395 | once = false; 396 | out[0] = z; 397 | op->scan(th, n-1, z, a, astride, out+1); 398 | _a.advance(n-1); 399 | } else { 400 | op->scan(th, n, z, a, astride, out); 401 | _a.advance(n); 402 | } 403 | framesToFill -= n; 404 | out += n; 405 | } 406 | } 407 | produce(framesToFill); 408 | } 409 | }; 410 | 411 | struct PairsOpGen : public Gen 412 | { 413 | VIn _a; 414 | BinaryOp* op; 415 | V z; 416 | bool once; 417 | 418 | PairsOpGen(Thread& th, Arg a, BinaryOp* _op) 419 | : Gen(th, itemTypeV, a.isFinite()), _a(a), op(_op) , once(true) {} 420 | 421 | virtual const char* TypeName() const override { return "PairsOpGen"; } 422 | 423 | virtual void pull(Thread& th) override { 424 | int framesToFill = mBlockSize; 425 | V* out = mOut->fulfill(framesToFill); 426 | while (framesToFill) { 427 | int n = framesToFill; 428 | int astride; 429 | V *a; 430 | if (_a(th, n,astride, a) ) { 431 | setDone(); 432 | break; 433 | } else { 434 | if (once) { 435 | once = false; 436 | out[0] = z = a[0]; 437 | op->pairs(th, n-1, z, a+1, astride, out+1); 438 | } else { 439 | op->pairs(th, n, z, a, astride, out); 440 | } 441 | _a.advance(n); 442 | framesToFill -= n; 443 | out += n; 444 | } 445 | } 446 | produce(framesToFill); 447 | } 448 | }; 449 | 450 | struct IPairsOpGen : public Gen 451 | { 452 | VIn _a; 453 | V z; 454 | BinaryOp* op; 455 | bool once; 456 | 457 | IPairsOpGen(Thread& th, Arg a, Arg b, BinaryOp* _op) 458 | : Gen(th, itemTypeV, a.isFinite()), _a(a), z(b), op(_op) , once(true) {} 459 | 460 | virtual const char* TypeName() const override { return "IPairsOpGen"; } 461 | 462 | virtual void pull(Thread& th) override { 463 | int framesToFill = mBlockSize; 464 | V* out = mOut->fulfill(framesToFill); 465 | while (framesToFill) { 466 | int n = framesToFill; 467 | int astride; 468 | V *a; 469 | if (_a(th, n,astride, a) ) { 470 | setDone(); 471 | break; 472 | } else { 473 | if (once) { 474 | once = false; 475 | out[0] = z; 476 | z = a[0]; 477 | op->pairs(th, n-1, z, a+1, astride, out+1); 478 | } else { 479 | op->pairs(th, n, z, a, astride, out); 480 | } 481 | _a.advance(n); 482 | framesToFill -= n; 483 | out += n; 484 | } 485 | } 486 | produce(framesToFill); 487 | } 488 | }; 489 | 490 | 491 | #endif 492 | -------------------------------------------------------------------------------- /include/Midi.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__Midi__ 18 | #define __taggeddoubles__Midi__ 19 | 20 | #include 21 | 22 | void AddMidiOps(); 23 | 24 | #endif /* defined(__taggeddoubles__Midi__) */ 25 | -------------------------------------------------------------------------------- /include/MultichannelExpansion.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __MultichannelExpansion_h__ 18 | #define __MultichannelExpansion_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | Prim* mcx(int n, Arg f, const char* name, const char* help); 23 | Prim* automap(const char* mask, int n, Arg f, const char* inName, const char* inHelp); 24 | List* handleEachOps(Thread& th, int numArgs, Arg fun); 25 | void flop_(Thread& th, Prim* prim); 26 | void flops_(Thread& th, Prim* prim); 27 | void flop1_(Thread& th, Prim* prim); 28 | void lace_(Thread& th, Prim* prim); 29 | void sel_(Thread& th, Prim* prim); 30 | void sell_(Thread& th, Prim* prim); 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /include/Opcode.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __Opcode_h__ 18 | #define __Opcode_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | enum { 23 | BAD_OPCODE, 24 | opNone, 25 | opPushImmediate, 26 | opPushLocalVar, 27 | opPushFunVar, 28 | opPushWorkspaceVar, 29 | 30 | opPushFun, 31 | 32 | opCallImmediate, 33 | opCallLocalVar, 34 | opCallFunVar, 35 | opCallWorkspaceVar, 36 | 37 | 38 | opDot, 39 | opComma, 40 | opBindLocal, 41 | opBindLocalFromList, 42 | opBindWorkspaceVar, 43 | opBindWorkspaceVarFromList, 44 | 45 | opParens, 46 | opNewVList, 47 | opNewZList, 48 | opNewForm, 49 | opInherit, 50 | opEach, 51 | 52 | opReturn, 53 | 54 | kNumOpcodes 55 | }; 56 | 57 | extern const char* opcode_name[kNumOpcodes]; 58 | 59 | #endif 60 | 61 | -------------------------------------------------------------------------------- /include/OscilUGens.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__OscilUGens__ 18 | #define __taggeddoubles__OscilUGens__ 19 | 20 | #include "Object.hpp" 21 | 22 | void AddOscilUGenOps(); 23 | 24 | #endif /* defined(__taggeddoubles__OscilUGens__) */ 25 | -------------------------------------------------------------------------------- /include/Parser.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __Parser_h__ 18 | #define __Parser_h__ 19 | 20 | #include "VM.hpp" 21 | 22 | bool parseElems(Thread& th, P& code); 23 | 24 | ////////////////////////////////// 25 | 26 | #pragma mark PIPER SYNTAX 27 | 28 | struct AST : RCObj 29 | { 30 | virtual const char* TypeName() const { return "AST"; } 31 | virtual void dump(std::ostream& ost, int indent) = 0; 32 | virtual void codegen(P& code) = 0; 33 | }; 34 | 35 | typedef P ASTPtr; 36 | 37 | ASTPtr parseExpr(const char*& in); 38 | 39 | void printi(std::ostream& ost, int indent, const char* fmt, ...); 40 | void prints(std::ostream& ost, const char* fmt, ...); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/Play.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "VM.hpp" 18 | 19 | void playWithAudioUnit(Thread& th, V& v); 20 | void recordWithAudioUnit(Thread& th, V& v, Arg filename); 21 | 22 | void stopPlaying(); 23 | void stopPlayingIfDone(); 24 | 25 | -------------------------------------------------------------------------------- /include/RCObj.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __no_web2__RCObj__ 18 | #define __no_web2__RCObj__ 19 | 20 | #include 21 | #include 22 | #include "rc_ptr.hpp" 23 | 24 | class RCObj 25 | { 26 | public: 27 | mutable std::atomic refcount; 28 | 29 | public: 30 | RCObj(); 31 | RCObj(RCObj const&); 32 | virtual ~RCObj(); 33 | 34 | void retain() const; 35 | void release(); 36 | virtual void norefs(); 37 | 38 | int32_t getRefcount() const { return refcount; } 39 | 40 | void negrefcount(); 41 | void alreadyDead(); 42 | 43 | 44 | virtual const char* TypeName() const = 0; 45 | }; 46 | 47 | inline void retain(RCObj* o) { o->retain(); } 48 | inline void release(RCObj* o) { o->release(); } 49 | 50 | #endif /* defined(__no_web2__RCObj__) */ 51 | -------------------------------------------------------------------------------- /include/SoundFiles.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__SoundFiles__ 18 | #define __taggeddoubles__SoundFiles__ 19 | 20 | #include "VM.hpp" 21 | #include 22 | 23 | const int kMaxSFChannels = 1024; 24 | const int kBufSize = 1024; 25 | 26 | void makeRecordingPath(Arg filename, char* path, int len); 27 | 28 | ExtAudioFileRef sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved); 29 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt); 30 | void sfread(Thread& th, Arg filename, int64_t offset, int64_t frames); 31 | 32 | #endif /* defined(__taggeddoubles__SoundFiles__) */ 33 | -------------------------------------------------------------------------------- /include/Spectrogram.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef taggeddoubles_Spectrogram_h 18 | #define taggeddoubles_Spectrogram_h 19 | 20 | void spectrogram(int size, double* data, int width, int log2bins, const char* path, double dBfloor); 21 | 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/Types.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __boxeddoubles__Types__ 18 | #define __boxeddoubles__Types__ 19 | 20 | #include "Object.hpp" 21 | 22 | enum { 23 | stackEffectUnknown = -1 24 | }; 25 | 26 | enum { 27 | rankUnknown = -2, 28 | rankVariable = -1 29 | }; 30 | 31 | enum { 32 | shapeUnknown = -4, 33 | shapeInfinite = -3, 34 | shapeIndefinite = -2, 35 | shapeFinite = -1 36 | }; 37 | 38 | class StackEffect : public Object 39 | { 40 | int mTakes; 41 | int mLeaves; 42 | 43 | StackEffect() : mTakes(stackEffectUnknown), mLeaves(stackEffectUnknown) {} 44 | StackEffect(int inTakes, int inLeaves) : mTakes(stackEffectUnknown), mLeaves(stackEffectUnknown) {} 45 | }; 46 | 47 | class TypeEnvir; 48 | 49 | struct TypeShape 50 | { 51 | int mRank = rankUnknown; 52 | std::vector mShape; 53 | 54 | TypeShape() {} 55 | TypeShape(TypeShape const& that) : mRank(that.mRank), mShape(that.mShape) {} 56 | TypeShape& operator=(TypeShape const& that) { mRank = that.mRank; mShape = that.mShape; return *this; } 57 | 58 | bool unify(TypeShape const& inThat, TypeShape& outResult) 59 | { 60 | if (mRank == rankUnknown) { 61 | if (inThat.mRank == rankUnknown) { 62 | outResult = *this; 63 | return true; 64 | } 65 | outResult = inThat; 66 | return true; 67 | } 68 | if (mRank != inThat.mRank) 69 | return false; 70 | outResult = inThat; 71 | for (int i = 0; i < mRank; ++i) { 72 | int a = mShape[i]; 73 | int b = inThat.mShape[i]; 74 | int& c = outResult.mShape[i]; 75 | if (a == shapeUnknown) { 76 | c = b; 77 | } else if (b == shapeUnknown) { 78 | c = a; 79 | } else if (a != b) { 80 | return false; 81 | } else { 82 | c = a; 83 | } 84 | } 85 | return true; 86 | } 87 | 88 | // determine shape of auto mapped result 89 | // determing shape of binary operator results 90 | // e.g. [r...] * r -> [r...] 91 | // [[r]] * [r] -> [[r]] 92 | // [r..] * [r] -> [r] maximum rank, minimum shape. 93 | // also with each operators 94 | 95 | }; 96 | 97 | class Type : public Object 98 | { 99 | public: 100 | TypeShape mShape; 101 | 102 | Type() {} 103 | Type(TypeShape const& inShape) : mShape(inShape) {} 104 | virtual ~Type() {} 105 | 106 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) = 0; 107 | 108 | virtual bool isTypeReal() const { return false; } 109 | virtual bool isTypeSignal() const { return false; } 110 | virtual bool isTypeRef() const { return false; } 111 | virtual bool isTypeFun() const { return false; } 112 | virtual bool isTypeForm() const { return false; } 113 | virtual bool isTypeTuple() const { return false; } 114 | }; 115 | 116 | class TypeUnknown : public Type 117 | { 118 | const char* TypeName() const { return "TypeReal"; } 119 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 120 | { 121 | TypeShape shape; 122 | if (!mShape.unify(inThat->mShape, shape)) 123 | return false; 124 | 125 | outResult = inThat; 126 | return true; 127 | } 128 | }; 129 | 130 | class TypeVar : public Object 131 | { 132 | int32_t mID; 133 | P mType; 134 | }; 135 | 136 | class TypeEnvir : public Object 137 | { 138 | std::vector> mTypeVars; 139 | }; 140 | 141 | class TypeReal : public Type 142 | { 143 | public: 144 | TypeReal() {} 145 | TypeReal(TypeShape const& inShape) : Type(inShape) {} 146 | 147 | 148 | const char* TypeName() const { return "TypeReal"; } 149 | 150 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 151 | { 152 | TypeShape shape; 153 | if (!mShape.unify(inThat->mShape, shape)) 154 | return false; 155 | 156 | if (inThat->isTypeReal() || inThat->isTypeSignal()) { 157 | outResult = new TypeReal(shape); 158 | return true; 159 | } 160 | return false; 161 | } 162 | }; 163 | 164 | class TypeSignal : public Type 165 | { 166 | int mSignalShape = shapeUnknown; 167 | public: 168 | 169 | TypeSignal() {} 170 | TypeSignal(TypeShape const& inShape, int inSignalShape) : Type(inShape), mSignalShape(inSignalShape) {} 171 | 172 | const char* TypeName() const { return "TypeSignal"; } 173 | 174 | virtual bool unify(P const& inThat, P& ioEnvir, P& outResult) 175 | { 176 | TypeShape shape; 177 | if (!mShape.unify(inThat->mShape, shape)) 178 | return false; 179 | 180 | if (inThat->isTypeReal()) { 181 | outResult = new TypeReal(shape); 182 | return true; 183 | } 184 | if (inThat->isTypeSignal()) { 185 | // unify signal shape 186 | outResult = new TypeSignal(shape, mSignalShape); 187 | return true; 188 | } 189 | return false; 190 | } 191 | }; 192 | 193 | class TypeRef : public Type 194 | { 195 | public: 196 | P mRefType; 197 | 198 | 199 | const char* TypeName() const { return "TypeRef"; } 200 | }; 201 | 202 | class TypeFun : public Type 203 | { 204 | std::vector> mInTypes; 205 | std::vector> mOutTypes; 206 | 207 | const char* TypeName() const { return "TypeFun"; } 208 | }; 209 | 210 | struct FieldType 211 | { 212 | P mLabel; 213 | P mType; 214 | }; 215 | 216 | class TypeForm : public Type 217 | { 218 | std::vector mFieldTypes; 219 | 220 | const char* TypeName() const { return "TypeForm"; } 221 | }; 222 | 223 | class TypeTuple : public Type 224 | { 225 | std::vector> mTypes; 226 | 227 | const char* TypeName() const { return "TypeTuple"; } 228 | }; 229 | 230 | #endif /* defined(__boxeddoubles__Types__) */ 231 | -------------------------------------------------------------------------------- /include/VM.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __VM_h__ 18 | #define __VM_h__ 19 | 20 | #include "Object.hpp" 21 | #include "symbol.hpp" 22 | #include "rgen.hpp" 23 | #include 24 | #include 25 | #include 26 | 27 | #define USE_LIBEDIT 1 28 | 29 | #if USE_LIBEDIT 30 | #include 31 | #endif 32 | 33 | extern pthread_mutex_t gHelpMutex; 34 | 35 | class Unlocker 36 | { 37 | pthread_mutex_t* mLock; 38 | public: 39 | Unlocker(pthread_mutex_t* inLock) 40 | { 41 | pthread_mutex_unlock(mLock); 42 | } 43 | ~Unlocker() 44 | { 45 | pthread_mutex_lock(mLock); 46 | } 47 | }; 48 | 49 | 50 | class Locker 51 | { 52 | pthread_mutex_t* mLock; 53 | public: 54 | Locker(pthread_mutex_t* inLock) : mLock(inLock) 55 | { 56 | pthread_mutex_lock(mLock); 57 | } 58 | ~Locker() 59 | { 60 | pthread_mutex_unlock(mLock); 61 | } 62 | }; 63 | 64 | const double kDefaultSampleRate = 96000.; 65 | const int kDefaultControlBlockSize = 128; 66 | const int kDefaultVBlockSize = 1; 67 | const int kDefaultZBlockSize = 512; 68 | 69 | struct Rate 70 | { 71 | int blockSize; 72 | double sampleRate; 73 | double nyquistRate; 74 | double invSampleRate; 75 | double invNyquistRate; 76 | double radiansPerSample; 77 | double invBlockSize; 78 | double freqLimit; 79 | 80 | Rate(Rate const& inParent, int inDiv) 81 | { 82 | set(inParent.sampleRate, inParent.blockSize, inDiv); 83 | } 84 | 85 | Rate(double inSampleRate, int inBlockSize) 86 | { 87 | set(inSampleRate, inBlockSize, 1); 88 | } 89 | 90 | Rate(const Rate& that) 91 | { 92 | blockSize = that.blockSize; 93 | sampleRate = that.sampleRate; 94 | nyquistRate = that.nyquistRate; 95 | invSampleRate = that.invSampleRate; 96 | invNyquistRate = that.invNyquistRate; 97 | radiansPerSample = that.radiansPerSample; 98 | invBlockSize = that.invBlockSize; 99 | freqLimit = that.freqLimit; 100 | } 101 | 102 | void set(double inSampleRate, int inBlockSize, int inDiv) 103 | { 104 | blockSize = inBlockSize / inDiv; 105 | sampleRate = inSampleRate / inDiv; 106 | nyquistRate = .5 * sampleRate; 107 | invSampleRate = 1. / sampleRate; 108 | invNyquistRate = 2. * invSampleRate; 109 | radiansPerSample = 2. * M_PI * invSampleRate; 110 | invBlockSize = 1. / blockSize; 111 | freqLimit = std::min(24000., nyquistRate); 112 | } 113 | 114 | bool operator==(Rate const& that) 115 | { 116 | return blockSize == that.blockSize && sampleRate == that.sampleRate; 117 | } 118 | }; 119 | 120 | const size_t kStackSize = 16384; 121 | 122 | class CompileScope; 123 | 124 | const int kMaxTokenLen = 2048; 125 | 126 | enum { 127 | parsingWords, 128 | parsingString, 129 | parsingParens, 130 | parsingLambda, 131 | parsingArray, 132 | parsingEnvir 133 | }; 134 | 135 | class Thread 136 | { 137 | public: 138 | size_t stackBase; 139 | size_t localBase; 140 | std::vector stack; 141 | std::vector local; 142 | P fun; 143 | P mWorkspace; 144 | 145 | P mCompileScope; 146 | 147 | Rate rate; 148 | 149 | RGen rgen; 150 | 151 | // parser 152 | FILE* parserInputFile; 153 | char token[kMaxTokenLen]; 154 | int tokenLen; 155 | 156 | int parsingWhat; 157 | bool fromString; 158 | 159 | // edit line 160 | #if USE_LIBEDIT 161 | EditLine *el; 162 | History *myhistory; 163 | HistEvent ev; 164 | char historyfilename[PATH_MAX]; 165 | #endif 166 | const char *line; 167 | int linelen; 168 | int linepos; 169 | const char* logfilename; 170 | time_t previousTimeStamp; 171 | 172 | Thread(); 173 | Thread(const Thread& parent); 174 | Thread(const Thread& parent, P const& fun); 175 | ~Thread(); 176 | 177 | 178 | const char* curline() const { return line + linepos; } 179 | void prevc() { 180 | if (linepos) --linepos; 181 | } 182 | void unget(int n) { linepos -= n; } 183 | void unget(const char* s) { linepos -= curline() - s; } 184 | char c() { return line[linepos]; } 185 | char d() { return line[linepos] ? line[linepos+1] : 0; } 186 | char getc(); 187 | void getLine(); 188 | void logTimestamp(FILE* logfile); 189 | void toToken(const char* s, int n) { 190 | if (n >= kMaxTokenLen) { post("token too long.\n"); throw errSyntax; } 191 | tokenLen = n; 192 | memcpy(token, s, n); 193 | token[tokenLen] = 0; 194 | } 195 | char* tok() { return token; } 196 | void clearTok() { tokenLen = 0; token[0] = 0; } 197 | void setParseString(const char* inString) 198 | { 199 | if (inString) { line = inString; fromString = true; linepos = 0; } 200 | else fromString = false; 201 | } 202 | 203 | void ApplyIfFun(V& v); 204 | 205 | 206 | V& getLocal(size_t i) 207 | { 208 | return local[localBase + i]; 209 | } 210 | 211 | 212 | void popLocals() 213 | { 214 | local.erase(local.end() - fun->NumLocals(), local.end()); 215 | } 216 | 217 | // stack ops 218 | void push(Arg v) 219 | { 220 | stack.push_back(v); 221 | } 222 | void push(V && v) 223 | { 224 | stack.push_back(std::move(v)); 225 | } 226 | 227 | void push(O o) 228 | { 229 | stack.push_back(o); 230 | } 231 | 232 | void push(double f) 233 | { 234 | stack.push_back(f); 235 | } 236 | 237 | template 238 | void push(P const& p) 239 | { 240 | stack.push_back(p); 241 | } 242 | 243 | void tuck(size_t n, Arg v); 244 | 245 | void pushBool(bool b) { push(b ? 1. : 0.); } 246 | 247 | V pop() 248 | { 249 | if (stackDepth() == 0) 250 | throw errStackUnderflow; 251 | 252 | V v = std::move(stack.back()); 253 | 254 | stack.pop_back(); 255 | return v; 256 | } 257 | 258 | void popn(size_t n) 259 | { 260 | if (stackDepth() < n) 261 | throw errStackUnderflow; 262 | stack.erase(stack.end() - n, stack.end()); 263 | } 264 | 265 | void clearStack() 266 | { 267 | popn(stackDepth()); 268 | } 269 | 270 | V& top() { 271 | if (stackDepth() == 0) 272 | throw errStackUnderflow; 273 | return stack.back(); 274 | } 275 | size_t stackDepth() const { return stack.size() - stackBase; } 276 | size_t numLocals() const { return local.size() - localBase; } 277 | 278 | void setStackBaseTo(size_t newStackBase) { stackBase = newStackBase; } 279 | void setStackBase(size_t n = 0) { stackBase = stack.size() - n; } 280 | void setLocalBase(size_t newLocalBase) { localBase = newLocalBase; } 281 | void setLocalBase() { localBase = local.size(); } 282 | 283 | bool compile(const char* inString, P& fun, bool inTopLevel); 284 | 285 | V popValue(); 286 | int64_t popInt(const char* msg); 287 | double popFloat(const char* msg); 288 | #define POPTYPEDECL(TYPE) P pop##TYPE(const char* msg); 289 | 290 | POPTYPEDECL(Ref); 291 | POPTYPEDECL(ZRef); 292 | POPTYPEDECL(String); 293 | POPTYPEDECL(Fun); 294 | POPTYPEDECL(List); 295 | POPTYPEDECL(Form); 296 | 297 | V popZIn(const char* msg); 298 | V popZInList(const char* msg); 299 | P popVList(const char* msg); 300 | P popZList(const char* msg); 301 | 302 | void printStack(); 303 | void printLocals(); 304 | 305 | void run(Opcode* c); 306 | 307 | void repl(FILE* infile, const char* logfilename); 308 | }; 309 | 310 | class VM 311 | { 312 | public: 313 | const char* prelude_file; 314 | const char* log_file; 315 | 316 | P builtins; 317 | 318 | int printLength; 319 | int printDepth; 320 | int printTotalItems; 321 | 322 | Rate ar; 323 | Rate kr; 324 | 325 | int VblockSize; 326 | 327 | // useful objects 328 | P
_ee; 329 | 330 | P _nilv; 331 | P _nilz; 332 | P _anilv; 333 | P _anilz; 334 | 335 | P inherit; 336 | P newForm; 337 | P newVList; 338 | P newZList; 339 | 340 | List* getNil(int inItemType) const { return inItemType == itemTypeV ? _nilv() : _nilz(); } 341 | Array* getNilArray(int inItemType) const { return inItemType == itemTypeV ? _anilv() : _anilz(); } 342 | 343 | V plusFun; 344 | V mulFun; 345 | V minFun; 346 | V maxFun; 347 | 348 | bool traceon = false; 349 | 350 | #if COLLECT_MINFO 351 | std::atomic totalRetains; 352 | std::atomic totalReleases; 353 | std::atomic totalObjectsAllocated; 354 | std::atomic totalObjectsFreed; 355 | std::atomic totalSignalGenerators; 356 | std::atomic totalStreamGenerators; 357 | #endif 358 | 359 | std::vector bifHelp; 360 | std::vector udfHelp; 361 | 362 | void addBifHelp(std::string const& str) { Locker lock(&gHelpMutex); bifHelp.push_back(str); } 363 | void addUdfHelp(std::string const& str) { Locker lock(&gHelpMutex); udfHelp.push_back(str); } 364 | 365 | void addBifHelp(const char* name, const char* mask = nullptr, const char* help = nullptr) 366 | { 367 | std::string helpstr = name; 368 | if (mask) { 369 | helpstr += " @"; 370 | helpstr += mask; 371 | } 372 | if (help) { 373 | helpstr += " "; 374 | helpstr += help; 375 | } 376 | addBifHelp(helpstr); 377 | } 378 | void addUdfHelp(const char* name, const char* mask = nullptr, const char* help = nullptr) 379 | { 380 | std::string helpstr = name; 381 | if (mask) { 382 | helpstr += " @"; 383 | helpstr += mask; 384 | } 385 | if (help) { 386 | helpstr += " "; 387 | helpstr += help; 388 | } 389 | addUdfHelp(helpstr); 390 | } 391 | 392 | VM(); 393 | ~VM(); 394 | 395 | void setSampleRate(double inSampleRate); 396 | 397 | V def(Arg key, Arg value); 398 | V def(const char* name, Arg value); 399 | V def(const char* name, int takes, int leaves, PrimFun pf, const char* help, Arg value = 0., bool setNoEach = false); 400 | V defmcx(const char* name, int numArgs, PrimFun pf, const char* help, Arg value = 0.); // multi channel expanded 401 | V defautomap(const char* name, const char* mask, PrimFun pf, const char* help, Arg value = 0.); // auto mapped 402 | }; 403 | 404 | extern VM vm; 405 | 406 | struct WorkspaceDef 407 | { 408 | P mName; 409 | }; 410 | 411 | struct LocalDef 412 | { 413 | P mName; 414 | size_t mIndex; 415 | int mTakes; 416 | int mLeaves; 417 | }; 418 | 419 | struct VarDef 420 | { 421 | P mName; 422 | size_t mIndex; 423 | // this info used to generate push opcodes for creating the closure. 424 | int mFromScope; 425 | size_t mFromIndex; 426 | }; 427 | 428 | enum { 429 | scopeUndefined, 430 | scopeBuiltIn, 431 | scopeWorkspace, 432 | scopeLocal, 433 | scopeFunVar 434 | }; 435 | 436 | class CompileScope : public Object 437 | { 438 | public : 439 | P mNext; 440 | std::vector mLocals; 441 | std::vector mVars; 442 | 443 | CompileScope() : mNext(nullptr) {} 444 | CompileScope(P const& inNext) : mNext(inNext) {} 445 | 446 | virtual size_t numLocals() { return mLocals.size(); } 447 | virtual size_t numVars() { return mVars.size(); } 448 | 449 | virtual bool isParen() const { return false; } 450 | 451 | virtual int directLookup(Thread& th, P const& inName, size_t& outIndex, V& outBuiltIn); 452 | virtual int indirectLookup(Thread& th, P const& inName, size_t& outIndex, V& outGlobal) = 0; 453 | virtual int bindVar(Thread& th, P const& inName, size_t& outIndex) = 0; 454 | virtual int innerBindVar(Thread& th, P const& inName, size_t& outIndex); 455 | }; 456 | 457 | class TopCompileScope : public CompileScope 458 | { 459 | public : 460 | std::vector mWorkspaceVars; 461 | 462 | TopCompileScope() {} 463 | 464 | virtual const char* TypeName() const override { return "TopCompileScope"; } 465 | 466 | virtual int directLookup(Thread& th, P const& inName, size_t& outIndex, V& outBuiltIn) override; 467 | virtual int indirectLookup(Thread& th, P const& inName, size_t& outIndex, V& outGlobal) override; 468 | virtual int bindVar(Thread& th, P const& inName, size_t& outIndex) override; 469 | }; 470 | 471 | class InnerCompileScope : public CompileScope 472 | { 473 | public : 474 | InnerCompileScope(P const& inNext) : CompileScope(inNext) {} 475 | 476 | virtual const char* TypeName() const override { return "InnerCompileScope"; } 477 | 478 | virtual int indirectLookup(Thread& th, P const& inName, size_t& outIndex, V& outGlobal) override; 479 | virtual int bindVar(Thread& th, P const& inName, size_t& outIndex) override; 480 | }; 481 | 482 | class ParenCompileScope : public CompileScope 483 | { 484 | public : 485 | ParenCompileScope(P const& inNext) : CompileScope(inNext) {} 486 | 487 | virtual const char* TypeName() const override { return "ParenCompileScope"; } 488 | 489 | virtual bool isParen() const override { return true; } 490 | 491 | virtual CompileScope* nextNonParen() const; 492 | 493 | virtual int directLookup(Thread& th, P const& inName, size_t& outIndex, V& outBuiltIn) override; 494 | virtual int indirectLookup(Thread& th, P const& inName, size_t& outIndex, V& outGlobal) override; 495 | virtual int bindVar(Thread& th, P const& inName, size_t& outIndex) override; 496 | virtual int innerBindVar(Thread& th, P const& inName, size_t& outIndex) override; 497 | }; 498 | 499 | inline void RCObj::retain() const 500 | { 501 | #if COLLECT_MINFO 502 | ++vm.totalRetains; 503 | #endif 504 | ++refcount; 505 | } 506 | 507 | inline void RCObj::release() 508 | { 509 | #if COLLECT_MINFO 510 | ++vm.totalReleases; 511 | #endif 512 | int32_t newRefCount = --refcount; 513 | if (newRefCount == 0) 514 | norefs(); 515 | if (newRefCount < 0) 516 | negrefcount(); 517 | } 518 | 519 | 520 | class SaveStack 521 | { 522 | Thread& th; 523 | size_t saveBase; 524 | public: 525 | SaveStack(Thread& _th, int n = 0) : th(_th), saveBase(th.stackBase) { th.setStackBase(n); } 526 | ~SaveStack() { 527 | th.clearStack(); 528 | th.setStackBaseTo(saveBase); 529 | } 530 | }; 531 | 532 | class ParenStack 533 | { 534 | Thread& th; 535 | size_t saveBase; 536 | public: 537 | ParenStack(Thread& _th) : th(_th), saveBase(th.stackBase) { th.setStackBase(); } 538 | ~ParenStack() { 539 | th.setStackBaseTo(saveBase); 540 | } 541 | }; 542 | 543 | class SaveCompileScope 544 | { 545 | Thread& th; 546 | P cs; 547 | public: 548 | SaveCompileScope(Thread& _th) : th(_th), cs(th.mCompileScope) {} 549 | ~SaveCompileScope() { th.mCompileScope = cs; } 550 | 551 | }; 552 | 553 | 554 | uint64_t timeseed(); 555 | void loadFile(Thread& th, const char* filename); 556 | 557 | 558 | class UseRate 559 | { 560 | Thread& th; 561 | Rate prevRate; 562 | public: 563 | UseRate(Thread& _th, Rate const& inRate) : th(_th), prevRate(th.rate) { th.rate = inRate; } 564 | ~UseRate() { th.rate = prevRate; } 565 | }; 566 | 567 | inline void Gen::setDone() 568 | { 569 | mDone = true; 570 | } 571 | 572 | P extendFormByOne(Thread& th, P const& parent, P const& tmap, Arg value); 573 | 574 | #endif 575 | -------------------------------------------------------------------------------- /include/clz.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | /* 18 | 19 | count leading zeroes function and those that can be derived from it 20 | 21 | */ 22 | 23 | // TODO FIXME Replace with C++20's 24 | 25 | #ifndef _CLZ_ 26 | #define _CLZ_ 27 | 28 | #include 29 | 30 | 31 | static int32_t CLZ( int32_t arg ) 32 | { 33 | if (arg == 0) return 32; 34 | return __builtin_clz(arg); 35 | } 36 | 37 | 38 | static int64_t CLZ( int64_t arg ) 39 | { 40 | if (arg == 0) return 64; 41 | return __builtin_clzll(arg); 42 | } 43 | 44 | 45 | 46 | // count trailing zeroes 47 | inline int32_t CTZ(int32_t x) 48 | { 49 | return 32 - CLZ(~x & (x-1)); 50 | } 51 | 52 | // count leading ones 53 | inline int32_t CLO(int32_t x) 54 | { 55 | return CLZ(~x); 56 | } 57 | 58 | // count trailing ones 59 | inline int32_t CTO(int32_t x) 60 | { 61 | return 32 - CLZ(x & (~x-1)); 62 | } 63 | 64 | // number of bits required to represent x. 65 | inline int32_t NUMBITS(int32_t x) 66 | { 67 | return 32 - CLZ(x); 68 | } 69 | 70 | // log2 of the next power of two greater than or equal to x. 71 | inline int32_t LOG2CEIL(int32_t x) 72 | { 73 | return 32 - CLZ(x - 1); 74 | } 75 | 76 | // log2 of the next power of two greater than or equal to x. 77 | inline int64_t LOG2CEIL(int64_t x) 78 | { 79 | return 64 - CLZ(x - 1); 80 | } 81 | 82 | // next power of two greater than or equal to x 83 | inline int32_t NEXTPOWEROFTWO(int32_t x) 84 | { 85 | return int32_t(1) << LOG2CEIL(x); 86 | } 87 | 88 | // next power of two greater than or equal to x 89 | inline int64_t NEXTPOWEROFTWO(int64_t x) 90 | { 91 | return int64_t(1) << LOG2CEIL(x); 92 | } 93 | 94 | // is x a power of two 95 | inline bool ISPOWEROFTWO(int32_t x) 96 | { 97 | return (x & (x-1)) == 0; 98 | } 99 | 100 | inline bool ISPOWEROFTWO64(int64_t x) 101 | { 102 | return (x & (x-1)) == 0; 103 | } 104 | 105 | // input a series of counting integers, outputs a series of gray codes . 106 | inline int32_t GRAYCODE(int32_t x) 107 | { 108 | return x ^ (x>>1); 109 | } 110 | 111 | // find least significant bit 112 | inline int32_t LSBit(int32_t x) 113 | { 114 | return x & -x; 115 | } 116 | 117 | // find least significant bit position 118 | inline int32_t LSBitPos(int32_t x) 119 | { 120 | return CTZ(x & -x); 121 | } 122 | 123 | // find most significant bit position 124 | inline int32_t MSBitPos(int32_t x) 125 | { 126 | return 31 - CLZ(x); 127 | } 128 | 129 | // find most significant bit 130 | inline int32_t MSBit(int32_t x) 131 | { 132 | return int32_t(1) << MSBitPos(x); 133 | } 134 | 135 | // count number of one bits 136 | inline uint32_t ONES(uint32_t x) 137 | { 138 | uint32_t t; 139 | x = x - ((x >> 1) & 0x55555555); 140 | t = ((x >> 2) & 0x33333333); 141 | x = (x & 0x33333333) + t; 142 | x = (x + (x >> 4)) & 0x0F0F0F0F; 143 | x = x + (x << 8); 144 | x = x + (x << 16); 145 | return x >> 24; 146 | } 147 | 148 | // count number of zero bits 149 | inline uint32_t ZEROES(uint32_t x) 150 | { 151 | return ONES(~x); 152 | } 153 | 154 | 155 | // reverse bits in a word 156 | inline uint32_t BitReverse(uint32_t x) 157 | { 158 | x = ((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1); 159 | x = ((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2); 160 | x = ((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4); 161 | x = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); 162 | return (x >> 16) | (x << 16); 163 | } 164 | 165 | // barrel shifts 166 | inline uint64_t RotateRight (int64_t ix, int64_t s) 167 | { 168 | uint64_t x = ix; 169 | s = s & 63; 170 | return (x << (64-s)) | (x >> s); 171 | } 172 | 173 | inline uint64_t RotateLeft (int64_t ix, int64_t s) 174 | { 175 | uint64_t x = ix; 176 | s = s & 63; 177 | return (x >> (64-s)) | (x << s); 178 | } 179 | 180 | inline uint32_t RotateRight (int32_t ix, int32_t s) 181 | { 182 | uint32_t x = ix; 183 | s = s & 31; 184 | return (x << (32-s)) | (x >> s); 185 | } 186 | 187 | inline uint32_t RotateLeft (int32_t ix, int32_t s) 188 | { 189 | uint32_t x = ix; 190 | s = s & 31; 191 | return (x >> (32-s)) | (x << s); 192 | } 193 | 194 | inline uint8_t RotateRight (int8_t ix, int8_t s) 195 | { 196 | uint8_t x = ix; 197 | s = s & 7; 198 | return (x << (8-s)) | (x >> s); 199 | } 200 | 201 | inline uint8_t RotateLeft (int8_t ix, int8_t s) 202 | { 203 | uint8_t x = ix; 204 | s = s & 7; 205 | return (x >> (8-s)) | (x << s); 206 | } 207 | 208 | #endif 209 | 210 | -------------------------------------------------------------------------------- /include/dsp.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __taggeddoubles__dsp__ 18 | #define __taggeddoubles__dsp__ 19 | 20 | #include 21 | 22 | const int kMinFFTLogSize = 2; 23 | const int kMaxFFTLogSize = 16; 24 | 25 | extern FFTSetupD fftSetups[kMaxFFTLogSize+1]; 26 | 27 | void initFFT(); 28 | void fft (int n, double* ioReal, double* ioImag); 29 | void ifft(int n, double* ioReal, double* ioImag); 30 | 31 | void fft (int n, double* inReal, double* inImag, double* outReal, double* outImag); 32 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag); 33 | 34 | void rfft(int n, double* inReal, double* outReal, double* outImag); 35 | void rifft(int n, double* inReal, double* inImag, double* outReal); 36 | 37 | #endif /* defined(__taggeddoubles__dsp__) */ 38 | -------------------------------------------------------------------------------- /include/elapsedTime.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __elapsedTime_h__ 18 | #define __elapsedTime_h__ 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | void initElapsedTime(); 25 | double elapsedTime(); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif 32 | 33 | -------------------------------------------------------------------------------- /include/makeImage.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | typedef struct Bitmap Bitmap; 18 | 19 | Bitmap* createBitmap(int width, int height); 20 | void freeBitmap(Bitmap* bitmap); 21 | 22 | void setPixel(Bitmap* bitmap, int x, int y, int r, int g, int b, int a); 23 | void fillRect(Bitmap* bitmap, int x, int y, int width, int height, int r, int g, int b, int a); 24 | void writeBitmap(Bitmap* bitmap, const char *path); 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /include/primes.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | 19 | extern const int gLowPrimes[10]; 20 | extern const int gPrimeOffsets[8]; 21 | extern const int gPrimesShift[30]; 22 | 23 | const int kPrimesMaskSize = 33334; 24 | extern uint8_t gPrimesMask[]; 25 | 26 | bool isprime(int64_t n); 27 | 28 | int64_t nextPrime(int64_t x); 29 | -------------------------------------------------------------------------------- /include/rc_ptr.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | // An intrusive reference counting smart pointer. 18 | 19 | template 20 | class P 21 | { 22 | T* p_; 23 | public: 24 | typedef T elem_t; 25 | 26 | P() : p_(nullptr) {} 27 | P(T* p) : p_(p) { if (p_) retain(p_); } 28 | ~P() { if (p_) release(p_); /*p_ = (T*)0xdeaddeaddeaddeadLL;*/ } 29 | 30 | P(P const & r) : p_(r.p_) { if (p_) retain(p_); } 31 | template P(P const & r) : p_(r()) { retain(p_); } 32 | 33 | P(P && r) : p_(r.p_) { r.p_ = nullptr; } 34 | template P(P && r) : p_(r()) { r.p_ = nullptr; } 35 | 36 | P& operator=(P && r) 37 | { 38 | if (this != &r) { 39 | T* oldp = p_; 40 | p_ = r.p_; 41 | r.p_ = nullptr; 42 | if (oldp) release(oldp); 43 | } 44 | return *this; 45 | } 46 | 47 | void swap(P& r) { T* t = p_; p_ = r.p_; r.p_ = t; } 48 | 49 | T& operator*() const { return *p_; } 50 | T* operator->() const { return p_; } 51 | 52 | T* operator()() const { return p_; } 53 | T* get() const { return p_; } 54 | //template operator U*() const { return (U*)p_; } 55 | 56 | bool operator==(P const& that) const { return p_ == that.p_; } 57 | bool operator!=(P const& that) const { return p_ != that.p_; } 58 | bool operator==(T* p) const { return p_ == p; } 59 | bool operator!=(T* p) const { return p_ != p; } 60 | 61 | operator bool () const 62 | { 63 | return p_ != 0; 64 | } 65 | 66 | void set(T* p) 67 | { 68 | if (p != p_) 69 | { 70 | T* oldp = p_; 71 | if (p) retain(p); 72 | p_ = p; 73 | if (oldp) release(oldp); 74 | } 75 | } 76 | 77 | P& operator=(P const& r) 78 | { 79 | set(r.p_); 80 | return *this; 81 | } 82 | 83 | P& operator=(T* p) 84 | { 85 | set(p); 86 | return *this; 87 | } 88 | 89 | template 90 | P& operator=(U* p) 91 | { 92 | set(p); 93 | return *this; 94 | } 95 | 96 | }; 97 | 98 | template 99 | bool operator==(P const & a, P const & b) 100 | { 101 | return a.p_ == b.p_; 102 | } 103 | 104 | template 105 | bool operator!=(P const & a, P const & b) // never throws 106 | { 107 | return a.p_ != b.p_; 108 | } 109 | 110 | template 111 | bool operator==(P const & a, T * b) // never throws 112 | { 113 | return a.p_ == b; 114 | } 115 | 116 | template 117 | bool operator!=(P const & a, T * b) // never throws 118 | { 119 | return a.p_ != b; 120 | } 121 | 122 | template 123 | bool operator==(T * a, P const & b) // never throws 124 | { 125 | return a != b.p_; 126 | } 127 | 128 | template 129 | bool operator!=(T * a, P const & b) // never throws 130 | { 131 | return a == b.p_; 132 | } 133 | 134 | template 135 | bool operator<(P const & a, P const & b) // never throws 136 | { 137 | return a.p_ < b.p_; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /include/rgen.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __rgen_h__ 18 | #define __rgen_h__ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "Hash.hpp" 24 | 25 | inline uint64_t xorshift64star(uint64_t x) { 26 | x ^= x >> 12; // a 27 | x ^= x << 25; // b 28 | x ^= x >> 27; // c 29 | return x * UINT64_C(2685821657736338717); 30 | } 31 | 32 | inline uint64_t xorshift128plus(uint64_t s[2]) { 33 | uint64_t x = s[0]; 34 | uint64_t const y = s[1]; 35 | s[0] = y; 36 | x ^= x << 23; // a 37 | x ^= x >> 17; // b 38 | x ^= y ^ (y >> 26); // c 39 | s[1] = x; 40 | return x + y; 41 | } 42 | 43 | inline uint64_t rotl(const uint64_t x, int k) { 44 | return (x << k) | (x >> (64 - k)); 45 | } 46 | 47 | inline uint64_t xoroshiro128(uint64_t s[2]) { 48 | const uint64_t s0 = s[0]; 49 | uint64_t s1 = s[1]; 50 | const uint64_t result = s0 + s1; 51 | 52 | s1 ^= s0; 53 | s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b 54 | s[1] = rotl(s1, 36); // c 55 | 56 | return result; 57 | } 58 | 59 | inline double itof1(uint64_t i) 60 | { 61 | union { uint64_t i; double f; } u; 62 | u.i = 0x3FF0000000000000LL | (i >> 12); 63 | return u.f - 1.; 64 | } 65 | 66 | const double kScaleR63 = pow(2.,-63); 67 | const double kScaleR31 = pow(2.,-31); 68 | 69 | inline double itof2(uint64_t i, double a) 70 | { 71 | return (double)i * a * kScaleR63 - a; 72 | } 73 | 74 | inline double itof2(uint32_t i, double a) 75 | { 76 | return (double)i * a * kScaleR31 - a; 77 | } 78 | 79 | struct RGen 80 | { 81 | uint64_t s[2]; 82 | 83 | void init(int64_t seed); 84 | int64_t trand(); 85 | 86 | double drand(); // 0 .. 1 87 | double drand2(); // -1 .. 1 88 | double drand8(); // -1/8 .. 1/8 89 | double drand16(); // -1/16 .. 1/16 90 | 91 | double rand(double lo, double hi); 92 | double xrand(double lo, double hi); 93 | double linrand(double lo, double hi); 94 | double trirand(double lo, double hi); 95 | double coin(double p); 96 | 97 | int64_t irand(int64_t lo, int64_t hi); 98 | int64_t irand0(int64_t n); 99 | int64_t irand2(int64_t scale); 100 | int64_t ilinrand(int64_t lo, int64_t hi); 101 | int64_t itrirand(int64_t lo, int64_t hi); 102 | 103 | }; 104 | 105 | 106 | inline void RGen::init(int64_t seed) 107 | { 108 | s[0] = Hash64(seed + 0x43a68b0d0492ba51LL); 109 | s[1] = Hash64(seed + 0x56e376c6e7c29504LL); 110 | } 111 | 112 | inline int64_t RGen::trand() 113 | { 114 | return (int64_t)xoroshiro128(s); 115 | } 116 | 117 | inline double RGen::drand() 118 | { 119 | union { uint64_t i; double f; } u; 120 | u.i = 0x3FF0000000000000LL | ((uint64_t)trand() >> 12); 121 | return u.f - 1.; 122 | } 123 | 124 | inline double RGen::drand2() 125 | { 126 | union { uint64_t i; double f; } u; 127 | u.i = 0x4000000000000000LL | ((uint64_t)trand() >> 12); 128 | return u.f - 3.; 129 | } 130 | 131 | inline double RGen::drand8() 132 | { 133 | union { uint64_t i; double f; } u; 134 | u.i = 0x3FD0000000000000LL | ((uint64_t)trand() >> 12); 135 | return u.f - .375; 136 | } 137 | 138 | inline double RGen::drand16() 139 | { 140 | union { uint64_t i; double f; } u; 141 | u.i = 0x3FC0000000000000LL | ((uint64_t)trand() >> 12); 142 | return u.f - .1875; 143 | } 144 | 145 | inline double RGen::rand(double lo, double hi) 146 | { 147 | return lo + (hi - lo) * drand(); 148 | } 149 | 150 | inline double RGen::xrand(double lo, double hi) 151 | { 152 | return lo * pow(hi / lo, drand()); 153 | } 154 | 155 | inline double RGen::linrand(double lo, double hi) 156 | { 157 | return lo + (hi - lo) * std::min(drand(), drand()); 158 | } 159 | 160 | inline double RGen::trirand(double lo, double hi) 161 | { 162 | return lo + (hi - lo) * (.5 + .5 * (drand() - drand())); 163 | } 164 | 165 | inline double RGen::coin(double p) 166 | { 167 | return drand() < p ? 1. : 0.; 168 | } 169 | 170 | inline int64_t RGen::irand0(int64_t n) 171 | { 172 | return (int64_t)floor(n * drand()); 173 | } 174 | 175 | inline int64_t RGen::irand(int64_t lo, int64_t hi) 176 | { 177 | return lo + (int64_t)floor((hi - lo + 1) * drand()); 178 | } 179 | 180 | inline int64_t RGen::irand2(int64_t scale) 181 | { 182 | double fscale = (double)scale; 183 | return (int64_t)floor((2. * fscale + 1.) * drand() - fscale); 184 | } 185 | 186 | inline int64_t RGen::ilinrand(int64_t lo, int64_t hi) 187 | { 188 | return lo + (int64_t)floor((hi - lo) * std::min(drand(), drand())); 189 | } 190 | 191 | inline int64_t RGen::itrirand(int64_t lo, int64_t hi) 192 | { 193 | double scale = (double)(hi - lo); 194 | return lo + (int64_t)floor(scale * (.5 + .5 * (drand() - drand()))); 195 | } 196 | 197 | #endif 198 | 199 | -------------------------------------------------------------------------------- /include/symbol.hpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef __symbol_h__ 18 | #define __symbol_h__ 19 | 20 | #include "Object.hpp" 21 | 22 | P getsym(const char* name); 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /libmanta/Manta.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTA_H 2 | #define _MANTA_H 3 | 4 | #include "MantaUSB.h" 5 | #include "MantaClient.h" 6 | #include "MantaServer.h" 7 | 8 | /************************************************************************//** 9 | * \class Manta 10 | * \brief Superclass that provides an interface to the Manta 11 | * 12 | * The Manta class is intended to use as a base class within your application. 13 | * In order to use libmanta, subclass this and define implementations for any 14 | * callback functions that you would like your application to be notified of. 15 | * The event functions are declared in the MantaClient interface. 16 | * 17 | * Creating an instance of the Manta class (or a subclass) will not initiate 18 | * a connection to any plugged-in mantas. Before you can start communicating 19 | * with the Manta you must call its Connect() method to connect to a physical 20 | * Manta over USB. 21 | * 22 | * Once connected, your application should periodically call the static method 23 | * HandleEvents(). This will take care of servicing the low-level USB 24 | * communication for all connected Mantas. 25 | ****************************************************************************/ 26 | class Manta : 27 | public MantaUSB, 28 | public MantaClient, 29 | public MantaServer 30 | { 31 | public: 32 | Manta(void); 33 | 34 | /* MantaServer messages implemented here */ 35 | virtual void SetPadLED(LEDState state, int ledID); 36 | virtual void SetPadLEDRow(LEDState state, int row, uint8_t mask); 37 | virtual void SetPadLEDColumn(LEDState state, int column, uint8_t mask); 38 | virtual void SetPadLEDFrame(LEDState state, uint8_t mask[]); 39 | virtual void SetSliderLED(LEDState state, int id, uint8_t mask); 40 | virtual void SetButtonLED(LEDState state, int id); 41 | virtual void ResendLEDState(void); 42 | virtual void ClearPadAndButtonLEDs(void); 43 | virtual void ClearButtonLEDs(void); 44 | virtual void Recalibrate(void); 45 | virtual void SetLEDControl(LEDControlType control, bool state); 46 | virtual void SetTurboMode(bool Enabled); 47 | virtual void SetRawMode(bool Enabled); 48 | virtual void SetMaxSensorValues(int *values); 49 | 50 | private: 51 | /* declare superclass callback implemented by this class */ 52 | virtual void FrameReceived(int8_t *frame); 53 | int ScaleSensorValue(int rawValue, int index); 54 | 55 | static uint8_t byteReverse(uint8_t inByte); 56 | static int CalculateVelocity(int firstValue, int secondValue); 57 | static const int AmberIndex = 0; 58 | static const int RedIndex = 10; 59 | static const int SliderIndex = 7; 60 | static const int ButtonIndex = 6; 61 | static const int ConfigIndex = 9; 62 | static const int AverageMaxSensorValues[53]; 63 | 64 | int MaxSensorValues[53]; 65 | uint8_t LastInReport[InPacketLen]; 66 | uint8_t CurrentOutReport[OutPacketLen]; 67 | bool VelocityWaiting[53]; 68 | /* output modes */ 69 | bool CentroidEnabled; 70 | bool MaximumEnabled; 71 | bool PadFrameEnabled; 72 | }; 73 | 74 | #endif // _MANTA_H 75 | -------------------------------------------------------------------------------- /libmanta/MantaClient.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTACLIENT_H 2 | #define _MANTACLIENT_H 3 | 4 | #include 5 | 6 | /************************************************************************//** 7 | * \class MantaClient 8 | * \brief Interface defining all the Events generated by the Manta 9 | * 10 | * The MantaClient virtual class defines all the Events that could be 11 | * generated by the Manta. Your object should provide implementations 12 | * to any of these events that you'd like to listen for 13 | ****************************************************************************/ 14 | class MantaClient 15 | { 16 | public: 17 | virtual ~MantaClient() {} 18 | /* declare callbacks to be implemented by subclasses */ 19 | virtual void PadEvent(int row, int column, int id, int value) {} 20 | virtual void SliderEvent(int id, int value) {} 21 | virtual void ButtonEvent(int id, int value) {} 22 | virtual void PadVelocityEvent(int row, int column, int id, int velocity) {} 23 | virtual void ButtonVelocityEvent(int id, int velocity) {} 24 | virtual void FrameEvent(uint8_t *frame) {} 25 | virtual void DebugPrint(const char *fmt, ...) {} 26 | }; 27 | #endif /* _MANTACLIENT_H */ 28 | -------------------------------------------------------------------------------- /libmanta/MantaExceptions.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAEXCEPTIONS_H 2 | #define _MANTAEXCEPTIONS_H 3 | 4 | #include 5 | 6 | class LibusbInitException : public std::runtime_error 7 | { 8 | public: 9 | LibusbInitException() : 10 | runtime_error("Error initializing libusb") 11 | { 12 | } 13 | }; 14 | 15 | class MantaNotConnectedException : public std::runtime_error 16 | { 17 | public: 18 | MantaNotConnectedException(MantaUSB *manta) : 19 | runtime_error("Attempted to access the Manta without connecting"), 20 | errorManta(manta) 21 | { 22 | } 23 | MantaUSB *errorManta; 24 | }; 25 | 26 | class MantaNotFoundException : public std::runtime_error 27 | { 28 | public: 29 | MantaNotFoundException() : 30 | runtime_error("Could not find an attached Manta") 31 | { 32 | } 33 | }; 34 | 35 | class MantaOpenException : public std::runtime_error 36 | { 37 | public: 38 | MantaOpenException() : 39 | runtime_error("Could not connect to attached Manta") 40 | { 41 | } 42 | }; 43 | 44 | class MantaCommunicationException : public std::runtime_error 45 | { 46 | public: 47 | MantaCommunicationException(MantaUSB *manta = NULL) : 48 | runtime_error("Communication with Manta interrupted"), 49 | errorManta(manta) 50 | { 51 | } 52 | MantaUSB *errorManta; 53 | }; 54 | 55 | #endif // _MANTAEXCEPTIONS_H 56 | -------------------------------------------------------------------------------- /libmanta/MantaMulti.cpp: -------------------------------------------------------------------------------- 1 | #include "MantaMulti.h" 2 | #include 3 | #include 4 | #include 5 | 6 | MantaMulti::MantaMulti(MantaClient *client) : 7 | ReferenceCount(0) 8 | { 9 | AttachClient(client); 10 | } 11 | 12 | void MantaMulti::AttachClient(MantaClient *client) 13 | { 14 | if(NULL != client) 15 | { 16 | ClientList.push_back(client); 17 | ++ReferenceCount; 18 | } 19 | } 20 | 21 | void MantaMulti::DetachClient(MantaClient *client) 22 | { 23 | list::iterator foundIter; 24 | foundIter = find(ClientList.begin(), ClientList.end(), client); 25 | if(ClientList.end() != foundIter) 26 | { 27 | ClientList.erase(foundIter); 28 | --ReferenceCount; 29 | } 30 | } 31 | 32 | int MantaMulti::GetReferenceCount() 33 | { 34 | return ReferenceCount; 35 | } 36 | 37 | void MantaMulti::PadEvent(int row, int column, int id, int value) 38 | { 39 | for(list::iterator i = ClientList.begin(); 40 | i != ClientList.end(); ++i) 41 | { 42 | (*i)->PadEvent(row, column, id, value); 43 | } 44 | } 45 | 46 | void MantaMulti::SliderEvent(int id, int value) 47 | { 48 | for(list::iterator i = ClientList.begin(); 49 | i != ClientList.end(); ++i) 50 | { 51 | (*i)->SliderEvent(id, value); 52 | } 53 | } 54 | 55 | void MantaMulti::ButtonEvent(int id, int value) 56 | { 57 | for(list::iterator i = ClientList.begin(); 58 | i != ClientList.end(); ++i) 59 | { 60 | (*i)->ButtonEvent(id, value); 61 | } 62 | } 63 | 64 | void MantaMulti::PadVelocityEvent(int row, int column, int id, int velocity) 65 | { 66 | for(list::iterator i = ClientList.begin(); 67 | i != ClientList.end(); ++i) 68 | { 69 | (*i)->PadVelocityEvent(row, column, id, velocity); 70 | } 71 | } 72 | 73 | void MantaMulti::ButtonVelocityEvent(int id, int velocity) 74 | { 75 | for(list::iterator i = ClientList.begin(); 76 | i != ClientList.end(); ++i) 77 | { 78 | (*i)->ButtonVelocityEvent(id, velocity); 79 | } 80 | } 81 | 82 | void MantaMulti::FrameEvent(uint8_t *frame) 83 | { 84 | for(list::iterator i = ClientList.begin(); 85 | i != ClientList.end(); ++i) 86 | { 87 | (*i)->FrameEvent(frame); 88 | } 89 | } 90 | 91 | /* 92 | void MantaMulti::DebugPrint(const char *fmt, ...) 93 | { 94 | if(!ClientList.empty()) 95 | { 96 | va_list args; 97 | char string[256]; 98 | va_start(args, fmt); 99 | vsprintf(string, fmt, args); 100 | va_end (args); 101 | ClientList.front()->DebugPrint(string); 102 | } 103 | } 104 | */ 105 | 106 | -------------------------------------------------------------------------------- /libmanta/MantaMulti.h: -------------------------------------------------------------------------------- 1 | #ifndef MANTAMULTI_H 2 | #define MANTAMULTI_H 3 | 4 | #include "Manta.h" 5 | #include 6 | 7 | using namespace std; 8 | 9 | /************************************************************************//** 10 | * \class MantaMulti 11 | * \brief Superclass that adds functionality for multiple access to a single 12 | * Manta 13 | * 14 | * This class can be used by an application that wants to have multiple 15 | * MantaClients that connect to a single Manta using the MantaServer API. 16 | * If you only need single-access you should use the Manta class instead 17 | ****************************************************************************/ 18 | class MantaMulti : public Manta 19 | { 20 | public: 21 | MantaMulti(MantaClient *client = NULL); 22 | void AttachClient(MantaClient *client); 23 | void DetachClient(MantaClient *client); 24 | int GetReferenceCount(); 25 | 26 | protected: 27 | void PadEvent(int row, int column, int id, int value); 28 | void SliderEvent(int id, int value); 29 | void ButtonEvent(int id, int value); 30 | void PadVelocityEvent(int row, int column, int id, int velocity); 31 | void ButtonVelocityEvent(int id, int velocity); 32 | void FrameEvent(uint8_t *frame); 33 | //void DebugPrint(const char *fmt, ...); 34 | 35 | private: 36 | list ClientList; 37 | int ReferenceCount; 38 | }; 39 | 40 | #endif /* MANTAMULTI_H */ 41 | -------------------------------------------------------------------------------- /libmanta/MantaServer.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTASERVER_H 2 | #define _MANTASERVER_H 3 | 4 | /************************************************************************//** 5 | * \class MantaServer 6 | * \brief Interface defining all the Messages that can be sent to a Manta 7 | * 8 | * The MantaServer virtual class defines all the Messages that the Manta 9 | * understands, as well as the data structures used as arguments. If you 10 | * need a pointer to a Manta in your code, you can make it more general by 11 | * using a MantaServer pointer instead of a pointer to your specific subclass. 12 | ****************************************************************************/ 13 | class MantaServer 14 | { 15 | public: 16 | 17 | enum LEDState { 18 | Off, 19 | Amber, 20 | Red, 21 | All, // only used in SetPadLEDFrame 22 | }; 23 | enum LEDControlType { 24 | PadAndButton, 25 | Slider, 26 | Button 27 | }; 28 | typedef uint8_t LEDFrame[6]; 29 | 30 | virtual ~MantaServer() {} 31 | /* declare callbacks to be implemented by subclasses */ 32 | virtual void SetPadLED(LEDState state, int ledID) = 0; 33 | virtual void SetPadLEDRow(LEDState state, int row, uint8_t mask) = 0; 34 | virtual void SetPadLEDColumn(LEDState state, int column, uint8_t mask) = 0; 35 | virtual void SetPadLEDFrame(LEDState state, uint8_t mask[]) = 0; 36 | virtual void SetSliderLED(LEDState state, int id, uint8_t mask) = 0; 37 | virtual void SetButtonLED(LEDState state, int id) = 0; 38 | virtual void ResendLEDState(void) = 0; 39 | virtual void ClearPadAndButtonLEDs(void) = 0; 40 | virtual void ClearButtonLEDs(void) = 0; 41 | virtual void Recalibrate(void) = 0; 42 | virtual void SetLEDControl(LEDControlType control, bool state) = 0; 43 | virtual void SetTurboMode(bool Enabled) = 0; 44 | virtual void SetRawMode(bool Enabled) = 0; 45 | virtual void SetMaxSensorValues(int *values) = 0; 46 | }; 47 | #endif /* _MANTASERVER_H */ 48 | -------------------------------------------------------------------------------- /libmanta/MantaUSB.cpp: -------------------------------------------------------------------------------- 1 | #include "extern/hidapi/hidapi/hidapi.h" 2 | #include 3 | #include "MantaUSB.h" 4 | #include "MantaExceptions.h" 5 | #include 6 | #include 7 | #include 8 | 9 | MantaUSB::MantaUSB(void) : 10 | SerialNumber(0), 11 | DeviceHandle(NULL) 12 | { 13 | mantaList.push_back(this); 14 | MantaIndex = int(mantaList.size()); 15 | 16 | DebugPrint("%s-%d: Manta %d initialized", __FILE__, __LINE__, MantaIndex); 17 | } 18 | 19 | MantaUSB::~MantaUSB(void) 20 | { 21 | Disconnect(); 22 | mantaList.remove(this); 23 | if(mantaList.empty()) 24 | { 25 | hid_exit(); 26 | } 27 | } 28 | 29 | bool MantaUSB::MessageQueued(void) 30 | { 31 | return GetQueuedTxMessage() != NULL; 32 | } 33 | 34 | /************************************************************************//** 35 | * \brief Writes a USB transfer frame down to the Manta 36 | * \param frame Pointer to the frame to be transmitted 37 | * \param forceQueued Forces this message to be queued instead of merged 38 | * 39 | * WriteFrame() is meant to be called by the Manta subclass, which defines 40 | * methods for the individual messages (setLED, etc). libmanta maintains a 41 | * message queue that gets popped from in the HandleEvents() handler. 42 | * 43 | * The default behavior is that if a message is already queued up for a given 44 | * Manta, subsequent message will be merged into the waiting message instead of 45 | * being further queued (the queued frame will be the end result of all queued 46 | * messages). forceQueued can be set to true to force the message to be queued 47 | * as a separate message instead of being merged 48 | * 49 | * Note: Because WriteFrame() accesses the same message queue that 50 | * HandleEvents() does, they should be protected from each other by a mutex on 51 | * the application level if they're being called from parallel threads. 52 | ****************************************************************************/ 53 | void MantaUSB::WriteFrame(uint8_t *frame, bool forceQueued) 54 | { 55 | if(NULL == DeviceHandle) 56 | { 57 | throw(MantaNotConnectedException(this)); 58 | } 59 | MantaTxQueueEntry *queuedMessage = GetQueuedTxMessage(); 60 | if(queuedMessage && !forceQueued) 61 | { 62 | /* replace the queued packet payload with the new one */ 63 | for(int i = 0; i < OutPacketLen; ++i) 64 | { 65 | /* the first byte of the report is the report ID (0x00) */ 66 | queuedMessage->OutFrame[i+1] = frame[i]; 67 | } 68 | DebugPrint("%s-%d: (WriteFrame) Queued Transfer overwritten on Manta %d", 69 | __FILE__, __LINE__, GetSerialNumber()); 70 | } 71 | else 72 | { 73 | /* no transfer in progress, queue up a new one */ 74 | MantaTxQueueEntry *newMessage = new MantaTxQueueEntry; 75 | newMessage->OutFrame[0] = 0; 76 | newMessage->TargetManta = this; 77 | /* the first byte of the report is the report ID (0x00) */ 78 | memcpy(newMessage->OutFrame + 1, frame, OutPacketLen); 79 | txQueue.push_back(newMessage); 80 | DebugPrint("%s-%d: (WriteFrame) Transfer Queued on Manta %d", 81 | __FILE__, __LINE__, GetSerialNumber()); 82 | } 83 | } 84 | 85 | /************************************************************************//** 86 | * \brief Queries connection status of the Manta 87 | * \returns true if this instance is connected to a physical Manta, false 88 | * if not 89 | * 90 | ****************************************************************************/ 91 | bool MantaUSB::IsConnected(void) 92 | { 93 | return DeviceHandle != NULL; 94 | } 95 | 96 | /************************************************************************//** 97 | * \brief Connects this instance to a Manta 98 | * \param connectionSerial The serial number of the manta to search for. 99 | * 100 | * If connectionSerial is left out or given as 0 then any connected Manta will 101 | * match. If a serial number is given then libmanta will attempt to connect to 102 | * that Manta. If no matching manta is found then Connect will throw a 103 | * MantaNotFoundException. If no exception is thrown then the connection can be 104 | * assumed to have been successful. 105 | * 106 | ****************************************************************************/ 107 | void MantaUSB::Connect(int connectionSerial) 108 | { 109 | #define SERIAL_STRING_SIZE 32 110 | wchar_t serialString[SERIAL_STRING_SIZE]; 111 | 112 | if(IsConnected()) 113 | { 114 | return; 115 | } 116 | 117 | DebugPrint("%s-%d: Attempting to Connect to Manta %d...", 118 | __FILE__, __LINE__, connectionSerial); 119 | if(connectionSerial) 120 | { 121 | swprintf(serialString, SERIAL_STRING_SIZE, L"%d", connectionSerial); 122 | DeviceHandle = hid_open(VendorID, ProductID, serialString); 123 | } 124 | else 125 | { 126 | DeviceHandle = hid_open(VendorID, ProductID, NULL); 127 | } 128 | if(NULL == DeviceHandle) 129 | throw(MantaNotFoundException()); 130 | hid_get_serial_number_string(DeviceHandle, serialString, SERIAL_STRING_SIZE); 131 | SerialNumber = int(wcstol(serialString, NULL, 10)); 132 | int rc = hid_set_nonblocking(DeviceHandle, 1); 133 | printf("hid_set_nonblocking %d\n", rc); 134 | printf("SerialNumber %d\n", SerialNumber); 135 | } 136 | 137 | /************************************************************************//** 138 | * \brief Disconnects this instance from an attached Manta 139 | ****************************************************************************/ 140 | void MantaUSB::Disconnect(void) 141 | { 142 | if(! IsConnected()) 143 | { 144 | return; 145 | } 146 | 147 | DebugPrint("%s-%d: Manta %d Disconnecting...", __FILE__, __LINE__, GetSerialNumber()); 148 | hid_close(DeviceHandle); 149 | DeviceHandle = NULL; 150 | } 151 | 152 | /************************************************************************//** 153 | * \brief Services USB communciation with the Manta 154 | * 155 | * HandleEvents should be called periodically to poll all connected Mantas for 156 | * incoming USB frames as well as to send any messages that have been queued 157 | * up with WriteFrame(). It should be called at least once every 6ms, but you 158 | * may get improved results polling as fast as every 1ms if your application 159 | * supports it. 160 | * 161 | * Note: Because WriteFrame() accesses the same message queue that HandleEvents() 162 | * does, they should be protected from each other by a mutex on the application 163 | * level if they're being called from parallel threads. 164 | ****************************************************************************/ 165 | void MantaUSB::HandleEvents(void) 166 | { 167 | list::iterator i = mantaList.begin(); 168 | /* read from each manta and trigger any events */ 169 | while(mantaList.end() != i) 170 | { 171 | MantaUSB *current = *i; 172 | if(current->IsConnected()) 173 | { 174 | int bytesRead; 175 | int8_t inFrame[InPacketLen]; 176 | 177 | bytesRead = hid_read(current->DeviceHandle, 178 | reinterpret_cast(inFrame), InPacketLen); 179 | if(bytesRead < 0) 180 | { 181 | current->DebugPrint("%s-%d: Read error on Manta %d", 182 | __FILE__, __LINE__, current->GetSerialNumber()); 183 | throw(MantaCommunicationException(current)); 184 | } 185 | else if(bytesRead) 186 | { 187 | current->FrameReceived(inFrame); 188 | } 189 | } 190 | ++i; 191 | } 192 | 193 | /* pop one item off the transmit queue and send down to its target */ 194 | if(! txQueue.empty()) 195 | { 196 | int bytesWritten; 197 | MantaTxQueueEntry *txMessage = txQueue.front(); 198 | txQueue.pop_front(); 199 | bytesWritten = hid_write(txMessage->TargetManta->DeviceHandle, 200 | txMessage->OutFrame, OutPacketLen + 1); 201 | txMessage->TargetManta->DebugPrint("%s-%d: Frame Written to Manta %d", 202 | __FILE__, __LINE__, txMessage->TargetManta->GetSerialNumber()); 203 | for(int j = 0; j < 16; j += 8) 204 | { 205 | uint8_t *frame = txMessage->OutFrame + 1; 206 | txMessage->TargetManta->DebugPrint("\t\t%x %x %x %x %x %x %x %x", 207 | frame[j], frame[j+1], frame[j+2], frame[j+3], frame[j+4], 208 | frame[j+5], frame[j+6], frame[j+7]); 209 | } 210 | delete txMessage; 211 | if(bytesWritten < 0) 212 | { 213 | txMessage->TargetManta->DebugPrint("%s-%d: Write error on Manta %d", 214 | __FILE__, __LINE__, txMessage->TargetManta->GetSerialNumber()); 215 | throw(MantaCommunicationException(txMessage->TargetManta)); 216 | } 217 | } 218 | } 219 | 220 | /************************************************************************//** 221 | * \brief Queries the serial number of the attached Manta 222 | * 223 | * \returns The serial number as an int 224 | ****************************************************************************/ 225 | int MantaUSB::GetSerialNumber(void) 226 | { 227 | return SerialNumber; 228 | } 229 | 230 | /************************************************************************//** 231 | * \brief Returns the Hardware Version (1 or 2) of the attached Manta 232 | * 233 | * \returns The hardware version 234 | ****************************************************************************/ 235 | int MantaUSB::GetHardwareVersion(void) 236 | { 237 | return (SerialNumber < 70) ? 1 : 2; 238 | } 239 | 240 | MantaUSB::MantaTxQueueEntry *MantaUSB::GetQueuedTxMessage() 241 | { 242 | list::iterator i = txQueue.begin(); 243 | /* look for the first queued message matching this manta */ 244 | while(txQueue.end() != i) 245 | { 246 | if((*i)->TargetManta == this) 247 | { 248 | return *i; 249 | } 250 | ++i; 251 | } 252 | return NULL; 253 | } 254 | 255 | /* define static class members */ 256 | list MantaUSB::mantaList; 257 | list MantaUSB::txQueue; 258 | -------------------------------------------------------------------------------- /libmanta/MantaUSB.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAUSB_H 2 | #define _MANTAUSB_H 3 | 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | /* forward-declare hidapi types so we don't need to include the 11 | * whole header file */ 12 | 13 | typedef struct hid_device_ hid_device; 14 | 15 | /************************************************************************//** 16 | * \class MantaUSB 17 | * \brief Superclass that handles the low-level USB communication with the 18 | * Manta 19 | * 20 | * The MantaUSB class handles the low-level USB communication with the Manta 21 | * over hidAPI, a cross-platform HID library. 22 | * 23 | * The public methods of this class are meant to be used by applciations using 24 | * libmanta to manage the connection to a physical Manta, as well as servicing 25 | * the low-level USB drivers by periodically calling the HandleEvents() 26 | * function. 27 | ****************************************************************************/ 28 | class MantaUSB 29 | { 30 | public: 31 | MantaUSB(void); 32 | virtual ~MantaUSB(void); 33 | void WriteFrame(uint8_t *frame, bool forceQueued); 34 | bool IsConnected(void); 35 | void Connect(int connectionSerial = 0); 36 | void Disconnect(); 37 | int GetSerialNumber(void); 38 | int GetHardwareVersion(void); 39 | bool MessageQueued(void); 40 | 41 | static void HandleEvents(void); 42 | 43 | protected: 44 | virtual void FrameReceived(int8_t *frame) = 0; 45 | virtual void DebugPrint(const char *fmt, ...) {} 46 | static const int OutPacketLen = 16; 47 | static const int InPacketLen = 64; 48 | int SerialNumber; 49 | int MantaIndex; 50 | 51 | private: 52 | struct MantaTxQueueEntry 53 | { 54 | MantaUSB *TargetManta; 55 | uint8_t OutFrame[17]; 56 | }; 57 | 58 | MantaTxQueueEntry *GetQueuedTxMessage(); 59 | 60 | static const int Interface = 0; 61 | static const int EndpointIn = 0x81; /* endpoint 0x81 address for IN */ 62 | static const int EndpointOut = 0x02; /* endpoint 1 address for OUT */ 63 | static const int Timeout = 5000; /* timeout in ms */ 64 | static const int VendorID = 0x2424; 65 | static const int ProductID = 0x2424; 66 | 67 | hid_device *DeviceHandle; 68 | 69 | static list mantaList; 70 | static list txQueue; 71 | }; 72 | 73 | #endif // _MANTAUSB_H 74 | -------------------------------------------------------------------------------- /libmanta/MantaVersion.h: -------------------------------------------------------------------------------- 1 | #ifndef _MANTAVERSION_H 2 | #define _MANTAVERSION_H 3 | 4 | #define LIBMANTA_MAJOR_VERSION 1 5 | #define LIBMANTA_MINOR_VERSION 3 6 | 7 | #endif // _MANTAVERSION_H 8 | -------------------------------------------------------------------------------- /libmanta/extern/hidapi/README.txt: -------------------------------------------------------------------------------- 1 | HIDAPI library for Windows, Linux, FreeBSD and Mac OS X 2 | ========================================================= 3 | 4 | About 5 | ====== 6 | 7 | HIDAPI is a multi-platform library which allows an application to interface 8 | with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and Mac 9 | OS X. HIDAPI can be either built as a shared library (.so or .dll) or 10 | can be embedded directly into a target application by adding a single source 11 | file (per platform) and a single header. 12 | 13 | HIDAPI has four back-ends: 14 | * Windows (using hid.dll) 15 | * Linux/hidraw (using the Kernel's hidraw driver) 16 | * Linux/libusb (using libusb-1.0) 17 | * FreeBSD (using libusb-1.0) 18 | * Mac (using IOHidManager) 19 | 20 | On Linux, either the hidraw or the libusb back-end can be used. There are 21 | tradeoffs, and the functionality supported is slightly different. 22 | 23 | Linux/hidraw (linux/hid.c): 24 | This back-end uses the hidraw interface in the Linux kernel. While this 25 | back-end will support both USB and Bluetooth, it has some limitations on 26 | kernels prior to 2.6.39, including the inability to send or receive feature 27 | reports. In addition, it will only communicate with devices which have 28 | hidraw nodes associated with them. Keyboards, mice, and some other devices 29 | which are blacklisted from having hidraw nodes will not work. Fortunately, 30 | for nearly all the uses of hidraw, this is not a problem. 31 | 32 | Linux/FreeBSD/libusb (libusb/hid-libusb.c): 33 | This back-end uses libusb-1.0 to communicate directly to a USB device. This 34 | back-end will of course not work with Bluetooth devices. 35 | 36 | HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses 37 | Fox Toolkit (http://www.fox-toolkit.org). It will build on every platform 38 | which HIDAPI supports. Since it relies on a 3rd party library, building it 39 | is optional but recommended because it is so useful when debugging hardware. 40 | 41 | What Does the API Look Like? 42 | ============================= 43 | The API provides the the most commonly used HID functions including sending 44 | and receiving of input, output, and feature reports. The sample program, 45 | which communicates with a heavily hacked up version of the Microchip USB 46 | Generic HID sample looks like this (with error checking removed for 47 | simplicity): 48 | 49 | #include 50 | #include 51 | #include 52 | #include "hidapi.h" 53 | 54 | #define MAX_STR 255 55 | 56 | int main(int argc, char* argv[]) 57 | { 58 | int res; 59 | unsigned char buf[65]; 60 | wchar_t wstr[MAX_STR]; 61 | hid_device *handle; 62 | int i; 63 | 64 | // Open the device using the VID, PID, 65 | // and optionally the Serial number. 66 | handle = hid_open(0x4d8, 0x3f, NULL); 67 | 68 | // Read the Manufacturer String 69 | res = hid_get_manufacturer_string(handle, wstr, MAX_STR); 70 | wprintf(L"Manufacturer String: %s\n", wstr); 71 | 72 | // Read the Product String 73 | res = hid_get_product_string(handle, wstr, MAX_STR); 74 | wprintf(L"Product String: %s\n", wstr); 75 | 76 | // Read the Serial Number String 77 | res = hid_get_serial_number_string(handle, wstr, MAX_STR); 78 | wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr); 79 | 80 | // Read Indexed String 1 81 | res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); 82 | wprintf(L"Indexed String 1: %s\n", wstr); 83 | 84 | // Toggle LED (cmd 0x80). The first byte is the report number (0x0). 85 | buf[0] = 0x0; 86 | buf[1] = 0x80; 87 | res = hid_write(handle, buf, 65); 88 | 89 | // Request state (cmd 0x81). The first byte is the report number (0x0). 90 | buf[0] = 0x0; 91 | buf[1] = 0x81; 92 | res = hid_write(handle, buf, 65); 93 | 94 | // Read requested state 95 | hid_read(handle, buf, 65); 96 | 97 | // Print out the returned buffer. 98 | for (i = 0; i < 4; i++) 99 | printf("buf[%d]: %d\n", i, buf[i]); 100 | 101 | return 0; 102 | } 103 | 104 | If you have your own simple test programs which communicate with standard 105 | hardware development boards (such as those from Microchip, TI, Atmel, 106 | FreeScale and others), please consider sending me something like the above 107 | for inclusion into the HIDAPI source. This will help others who have the 108 | same hardware as you do. 109 | 110 | License 111 | ======== 112 | HIDAPI may be used by one of three licenses as outlined in LICENSE.txt. 113 | 114 | Download 115 | ========= 116 | HIDAPI can be downloaded from github 117 | git clone git://github.com/signal11/hidapi.git 118 | 119 | Build Instructions 120 | =================== 121 | 122 | This section is long. Don't be put off by this. It's not long because it's 123 | complicated to build HIDAPI; it's quite the opposite. This section is long 124 | because of the flexibility of HIDAPI and the large number of ways in which 125 | it can be built and used. You will likely pick a single build method. 126 | 127 | HIDAPI can be built in several different ways. If you elect to build a 128 | shared library, you will need to build it from the HIDAPI source 129 | distribution. If you choose instead to embed HIDAPI directly into your 130 | application, you can skip the building and look at the provided platform 131 | Makefiles for guidance. These platform Makefiles are located in linux/ 132 | libusb/ mac/ and windows/ and are called Makefile-manual. In addition, 133 | Visual Studio projects are provided. Even if you're going to embed HIDAPI 134 | into your project, it is still beneficial to build the example programs. 135 | 136 | 137 | Prerequisites: 138 | --------------- 139 | 140 | Linux: 141 | ------- 142 | On Linux, you will need to install development packages for libudev, 143 | libusb and optionally Fox-toolkit (for the test GUI). On 144 | Debian/Ubuntu systems these can be installed by running: 145 | sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev 146 | 147 | If you downloaded the source directly from the git repository (using 148 | git clone), you'll need Autotools: 149 | sudo apt-get install autotools-dev 150 | 151 | FreeBSD: 152 | --------- 153 | On FreeBSD you will need to install GNU make, libiconv, and 154 | optionally Fox-Toolkit (for the test GUI). This is done by running 155 | the following: 156 | pkg_add -r gmake libiconv fox16 157 | 158 | If you downloaded the source directly from the git repository (using 159 | git clone), you'll need Autotools: 160 | pkg_add -r autotools 161 | 162 | Mac: 163 | ----- 164 | On Mac, you will need to install Fox-Toolkit if you wish to build 165 | the Test GUI. There are two ways to do this, and each has a slight 166 | complication. Which method you use depends on your use case. 167 | 168 | If you wish to build the Test GUI just for your own testing on your 169 | own computer, then the easiest method is to install Fox-Toolkit 170 | using ports: 171 | sudo port install fox 172 | 173 | If you wish to build the TestGUI app bundle to redistribute to 174 | others, you will need to install Fox-toolkit from source. This is 175 | because the version of fox that gets installed using ports uses the 176 | ports X11 libraries which are not compatible with the Apple X11 177 | libraries. If you install Fox with ports and then try to distribute 178 | your built app bundle, it will simply fail to run on other systems. 179 | To install Fox-Toolkit manually, download the source package from 180 | http://www.fox-toolkit.org, extract it, and run the following from 181 | within the extracted source: 182 | ./configure && make && make install 183 | 184 | Windows: 185 | --------- 186 | On Windows, if you want to build the test GUI, you will need to get 187 | the hidapi-externals.zip package from the download site. This 188 | contains pre-built binaries for Fox-toolkit. Extract 189 | hidapi-externals.zip just outside of hidapi, so that 190 | hidapi-externals and hidapi are on the same level, as shown: 191 | 192 | Parent_Folder 193 | | 194 | +hidapi 195 | +hidapi-externals 196 | 197 | Again, this step is not required if you do not wish to build the 198 | test GUI. 199 | 200 | 201 | Building HIDAPI into a shared library on Unix Platforms: 202 | --------------------------------------------------------- 203 | 204 | On Unix-like systems such as Linux, FreeBSD, Mac, and even Windows, using 205 | Mingw or Cygwin, the easiest way to build a standard system-installed shared 206 | library is to use the GNU Autotools build system. If you checked out the 207 | source from the git repository, run the following: 208 | 209 | ./bootstrap 210 | ./configure 211 | make 212 | make install <----- as root, or using sudo 213 | 214 | If you downloaded a source package (ie: if you did not run git clone), you 215 | can skip the ./bootstrap step. 216 | 217 | ./configure can take several arguments which control the build. The two most 218 | likely to be used are: 219 | --enable-testgui 220 | Enable build of the Test GUI. This requires Fox toolkit to 221 | be installed. Instructions for installing Fox-Toolkit on 222 | each platform are in the Prerequisites section above. 223 | 224 | --prefix=/usr 225 | Specify where you want the output headers and libraries to 226 | be installed. The example above will put the headers in 227 | /usr/include and the binaries in /usr/lib. The default is to 228 | install into /usr/local which is fine on most systems. 229 | 230 | Building the manual way on Unix platforms: 231 | ------------------------------------------- 232 | 233 | Manual Makefiles are provided mostly to give the user and idea what it takes 234 | to build a program which embeds HIDAPI directly inside of it. These should 235 | really be used as examples only. If you want to build a system-wide shared 236 | library, use the Autotools method described above. 237 | 238 | To build HIDAPI using the manual makefiles, change to the directory 239 | of your platform and run make. For example, on Linux run: 240 | cd linux/ 241 | make -f Makefile-manual 242 | 243 | To build the Test GUI using the manual makefiles: 244 | cd testgui/ 245 | make -f Makefile-manual 246 | 247 | Building on Windows: 248 | --------------------- 249 | 250 | To build the HIDAPI DLL on Windows using Visual Studio, build the .sln file 251 | in the windows/ directory. 252 | 253 | To build the Test GUI on windows using Visual Studio, build the .sln file in 254 | the testgui/ directory. 255 | 256 | To build HIDAPI using MinGW or Cygwin using Autotools, use the instructions 257 | in the section titled "Building HIDAPI into a shared library on Unix 258 | Platforms" above. Note that building the Test GUI with MinGW or Cygwin will 259 | require the Windows procedure in the Prerequisites section above (ie: 260 | hidapi-externals.zip). 261 | 262 | To build HIDAPI using MinGW using the Manual Makefiles, see the section 263 | "Building the manual way on Unix platforms" above. 264 | 265 | HIDAPI can also be built using the Windows DDK (now also called the Windows 266 | Driver Kit or WDK). This method was originally required for the HIDAPI build 267 | but not anymore. However, some users still prefer this method. It is not as 268 | well supported anymore but should still work. Patches are welcome if it does 269 | not. To build using the DDK: 270 | 271 | 1. Install the Windows Driver Kit (WDK) from Microsoft. 272 | 2. From the Start menu, in the Windows Driver Kits folder, select Build 273 | Environments, then your operating system, then the x86 Free Build 274 | Environment (or one that is appropriate for your system). 275 | 3. From the console, change directory to the windows/ddk_build/ directory, 276 | which is part of the HIDAPI distribution. 277 | 4. Type build. 278 | 5. You can find the output files (DLL and LIB) in a subdirectory created 279 | by the build system which is appropriate for your environment. On 280 | Windows XP, this directory is objfre_wxp_x86/i386. 281 | 282 | Cross Compiling 283 | ================ 284 | 285 | This section talks about cross compiling HIDAPI for Linux using autotools. 286 | This is useful for using HIDAPI on embedded Linux targets. These 287 | instructions assume the most raw kind of embedded Linux build, where all 288 | prerequisites will need to be built first. This process will of course vary 289 | based on your embedded Linux build system if you are using one, such as 290 | OpenEmbedded or Buildroot. 291 | 292 | For the purpose of this section, it will be assumed that the following 293 | environment variables are exported. 294 | 295 | $ export STAGING=$HOME/out 296 | $ export HOST=arm-linux 297 | 298 | STAGING and HOST can be modified to suit your setup. 299 | 300 | Prerequisites 301 | -------------- 302 | 303 | Note that the build of libudev is the very basic configuration. 304 | 305 | Build Libusb. From the libusb source directory, run: 306 | ./configure --host=$HOST --prefix=$STAGING 307 | make 308 | make install 309 | 310 | Build libudev. From the libudev source directory, run: 311 | ./configure --disable-gudev --disable-introspection --disable-hwdb \ 312 | --host=$HOST --prefix=$STAGING 313 | make 314 | make install 315 | 316 | Building HIDAPI 317 | ---------------- 318 | 319 | Build HIDAPI: 320 | 321 | PKG_CONFIG_DIR= \ 322 | PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \ 323 | PKG_CONFIG_SYSROOT_DIR=$STAGING \ 324 | ./configure --host=$HOST --prefix=$STAGING 325 | 326 | 327 | Signal 11 Software - 2010-04-11 328 | 2010-07-28 329 | 2011-09-10 330 | 2012-05-01 331 | 2012-07-03 332 | -------------------------------------------------------------------------------- /libmanta/extern/hidapi/hidapi/hidapi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | 8/22/2009 9 | 10 | Copyright 2009, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | http://github.com/signal11/hidapi . 21 | ********************************************************/ 22 | 23 | /** @file 24 | * @defgroup API hidapi API 25 | */ 26 | 27 | #ifndef HIDAPI_H__ 28 | #define HIDAPI_H__ 29 | 30 | #include 31 | 32 | #ifdef _WIN32 33 | #define HID_API_EXPORT __declspec(dllexport) 34 | #define HID_API_CALL 35 | #else 36 | #define HID_API_EXPORT /**< API export macro */ 37 | #define HID_API_CALL /**< API call macro */ 38 | #endif 39 | 40 | #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | struct hid_device_; 46 | typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ 47 | 48 | /** hidapi info structure */ 49 | struct hid_device_info { 50 | /** Platform-specific device path */ 51 | char *path; 52 | /** Device Vendor ID */ 53 | unsigned short vendor_id; 54 | /** Device Product ID */ 55 | unsigned short product_id; 56 | /** Serial Number */ 57 | wchar_t *serial_number; 58 | /** Device Release Number in binary-coded decimal, 59 | also known as Device Version Number */ 60 | unsigned short release_number; 61 | /** Manufacturer String */ 62 | wchar_t *manufacturer_string; 63 | /** Product string */ 64 | wchar_t *product_string; 65 | /** Usage Page for this Device/Interface 66 | (Windows/Mac only). */ 67 | unsigned short usage_page; 68 | /** Usage for this Device/Interface 69 | (Windows/Mac only).*/ 70 | unsigned short usage; 71 | /** The USB interface which this logical device 72 | represents. Valid on both Linux implementations 73 | in all cases, and valid on the Windows implementation 74 | only if the device contains more than one interface. */ 75 | int interface_number; 76 | 77 | /** Pointer to the next device */ 78 | struct hid_device_info *next; 79 | }; 80 | 81 | 82 | /** @brief Initialize the HIDAPI library. 83 | 84 | This function initializes the HIDAPI library. Calling it is not 85 | strictly necessary, as it will be called automatically by 86 | hid_enumerate() and any of the hid_open_*() functions if it is 87 | needed. This function should be called at the beginning of 88 | execution however, if there is a chance of HIDAPI handles 89 | being opened by different threads simultaneously. 90 | 91 | @ingroup API 92 | 93 | @returns 94 | This function returns 0 on success and -1 on error. 95 | */ 96 | int HID_API_EXPORT HID_API_CALL hid_init(void); 97 | 98 | /** @brief Finalize the HIDAPI library. 99 | 100 | This function frees all of the static data associated with 101 | HIDAPI. It should be called at the end of execution to avoid 102 | memory leaks. 103 | 104 | @ingroup API 105 | 106 | @returns 107 | This function returns 0 on success and -1 on error. 108 | */ 109 | int HID_API_EXPORT HID_API_CALL hid_exit(void); 110 | 111 | /** @brief Enumerate the HID Devices. 112 | 113 | This function returns a linked list of all the HID devices 114 | attached to the system which match vendor_id and product_id. 115 | If @p vendor_id and @p product_id are both set to 0, then 116 | all HID devices will be returned. 117 | 118 | @ingroup API 119 | @param vendor_id The Vendor ID (VID) of the types of device 120 | to open. 121 | @param product_id The Product ID (PID) of the types of 122 | device to open. 123 | 124 | @returns 125 | This function returns a pointer to a linked list of type 126 | struct #hid_device, containing information about the HID devices 127 | attached to the system, or NULL in the case of failure. Free 128 | this linked list by calling hid_free_enumeration(). 129 | */ 130 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); 131 | 132 | /** @brief Free an enumeration Linked List 133 | 134 | This function frees a linked list created by hid_enumerate(). 135 | 136 | @ingroup API 137 | @param devs Pointer to a list of struct_device returned from 138 | hid_enumerate(). 139 | */ 140 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); 141 | 142 | /** @brief Open a HID device using a Vendor ID (VID), Product ID 143 | (PID) and optionally a serial number. 144 | 145 | If @p serial_number is NULL, the first device with the 146 | specified VID and PID is opened. 147 | 148 | @ingroup API 149 | @param vendor_id The Vendor ID (VID) of the device to open. 150 | @param product_id The Product ID (PID) of the device to open. 151 | @param serial_number The Serial Number of the device to open 152 | (Optionally NULL). 153 | 154 | @returns 155 | This function returns a pointer to a #hid_device object on 156 | success or NULL on failure. 157 | */ 158 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); 159 | 160 | /** @brief Open a HID device by its path name. 161 | 162 | The path name be determined by calling hid_enumerate(), or a 163 | platform-specific path name can be used (eg: /dev/hidraw0 on 164 | Linux). 165 | 166 | @ingroup API 167 | @param path The path name of the device to open 168 | 169 | @returns 170 | This function returns a pointer to a #hid_device object on 171 | success or NULL on failure. 172 | */ 173 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); 174 | 175 | /** @brief Write an Output report to a HID device. 176 | 177 | The first byte of @p data[] must contain the Report ID. For 178 | devices which only support a single report, this must be set 179 | to 0x0. The remaining bytes contain the report data. Since 180 | the Report ID is mandatory, calls to hid_write() will always 181 | contain one more byte than the report contains. For example, 182 | if a hid report is 16 bytes long, 17 bytes must be passed to 183 | hid_write(), the Report ID (or 0x0, for devices with a 184 | single report), followed by the report data (16 bytes). In 185 | this example, the length passed in would be 17. 186 | 187 | hid_write() will send the data on the first OUT endpoint, if 188 | one exists. If it does not, it will send the data through 189 | the Control Endpoint (Endpoint 0). 190 | 191 | @ingroup API 192 | @param device A device handle returned from hid_open(). 193 | @param data The data to send, including the report number as 194 | the first byte. 195 | @param length The length in bytes of the data to send. 196 | 197 | @returns 198 | This function returns the actual number of bytes written and 199 | -1 on error. 200 | */ 201 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); 202 | 203 | /** @brief Read an Input report from a HID device with timeout. 204 | 205 | Input reports are returned 206 | to the host through the INTERRUPT IN endpoint. The first byte will 207 | contain the Report number if the device uses numbered reports. 208 | 209 | @ingroup API 210 | @param device A device handle returned from hid_open(). 211 | @param data A buffer to put the read data into. 212 | @param length The number of bytes to read. For devices with 213 | multiple reports, make sure to read an extra byte for 214 | the report number. 215 | @param milliseconds timeout in milliseconds or -1 for blocking wait. 216 | 217 | @returns 218 | This function returns the actual number of bytes read and 219 | -1 on error. 220 | */ 221 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); 222 | 223 | /** @brief Read an Input report from a HID device. 224 | 225 | Input reports are returned 226 | to the host through the INTERRUPT IN endpoint. The first byte will 227 | contain the Report number if the device uses numbered reports. 228 | 229 | @ingroup API 230 | @param device A device handle returned from hid_open(). 231 | @param data A buffer to put the read data into. 232 | @param length The number of bytes to read. For devices with 233 | multiple reports, make sure to read an extra byte for 234 | the report number. 235 | 236 | @returns 237 | This function returns the actual number of bytes read and 238 | -1 on error. 239 | */ 240 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); 241 | 242 | /** @brief Set the device handle to be non-blocking. 243 | 244 | In non-blocking mode calls to hid_read() will return 245 | immediately with a value of 0 if there is no data to be 246 | read. In blocking mode, hid_read() will wait (block) until 247 | there is data to read before returning. 248 | 249 | Nonblocking can be turned on and off at any time. 250 | 251 | @ingroup API 252 | @param device A device handle returned from hid_open(). 253 | @param nonblock enable or not the nonblocking reads 254 | - 1 to enable nonblocking 255 | - 0 to disable nonblocking. 256 | 257 | @returns 258 | This function returns 0 on success and -1 on error. 259 | */ 260 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); 261 | 262 | /** @brief Send a Feature report to the device. 263 | 264 | Feature reports are sent over the Control endpoint as a 265 | Set_Report transfer. The first byte of @p data[] must 266 | contain the Report ID. For devices which only support a 267 | single report, this must be set to 0x0. The remaining bytes 268 | contain the report data. Since the Report ID is mandatory, 269 | calls to hid_send_feature_report() will always contain one 270 | more byte than the report contains. For example, if a hid 271 | report is 16 bytes long, 17 bytes must be passed to 272 | hid_send_feature_report(): the Report ID (or 0x0, for 273 | devices which do not use numbered reports), followed by the 274 | report data (16 bytes). In this example, the length passed 275 | in would be 17. 276 | 277 | @ingroup API 278 | @param device A device handle returned from hid_open(). 279 | @param data The data to send, including the report number as 280 | the first byte. 281 | @param length The length in bytes of the data to send, including 282 | the report number. 283 | 284 | @returns 285 | This function returns the actual number of bytes written and 286 | -1 on error. 287 | */ 288 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); 289 | 290 | /** @brief Get a feature report from a HID device. 291 | 292 | Make sure to set the first byte of @p data[] to the Report 293 | ID of the report to be read. Make sure to allow space for 294 | this extra byte in @p data[]. 295 | 296 | @ingroup API 297 | @param device A device handle returned from hid_open(). 298 | @param data A buffer to put the read data into, including 299 | the Report ID. Set the first byte of @p data[] to the 300 | Report ID of the report to be read. 301 | @param length The number of bytes to read, including an 302 | extra byte for the report ID. The buffer can be longer 303 | than the actual report. 304 | 305 | @returns 306 | This function returns the number of bytes read and 307 | -1 on error. 308 | */ 309 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); 310 | 311 | /** @brief Close a HID device. 312 | 313 | @ingroup API 314 | @param device A device handle returned from hid_open(). 315 | */ 316 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); 317 | 318 | /** @brief Get The Manufacturer String from a HID device. 319 | 320 | @ingroup API 321 | @param device A device handle returned from hid_open(). 322 | @param string A wide string buffer to put the data into. 323 | @param maxlen The length of the buffer in multiples of wchar_t. 324 | 325 | @returns 326 | This function returns 0 on success and -1 on error. 327 | */ 328 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); 329 | 330 | /** @brief Get The Product String from a HID device. 331 | 332 | @ingroup API 333 | @param device A device handle returned from hid_open(). 334 | @param string A wide string buffer to put the data into. 335 | @param maxlen The length of the buffer in multiples of wchar_t. 336 | 337 | @returns 338 | This function returns 0 on success and -1 on error. 339 | */ 340 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); 341 | 342 | /** @brief Get The Serial Number String from a HID device. 343 | 344 | @ingroup API 345 | @param device A device handle returned from hid_open(). 346 | @param string A wide string buffer to put the data into. 347 | @param maxlen The length of the buffer in multiples of wchar_t. 348 | 349 | @returns 350 | This function returns 0 on success and -1 on error. 351 | */ 352 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); 353 | 354 | /** @brief Get a string from a HID device, based on its string index. 355 | 356 | @ingroup API 357 | @param device A device handle returned from hid_open(). 358 | @param string_index The index of the string to get. 359 | @param string A wide string buffer to put the data into. 360 | @param maxlen The length of the buffer in multiples of wchar_t. 361 | 362 | @returns 363 | This function returns 0 on success and -1 on error. 364 | */ 365 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); 366 | 367 | /** @brief Get a string describing the last error which occurred. 368 | 369 | @ingroup API 370 | @param device A device handle returned from hid_open(). 371 | 372 | @returns 373 | This function returns a string containing the last error 374 | which occurred or NULL if none has occurred. 375 | */ 376 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); 377 | 378 | #ifdef __cplusplus 379 | } 380 | #endif 381 | 382 | #endif 383 | 384 | -------------------------------------------------------------------------------- /libmanta/extern/hidapi/m4/ax_pthread.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_pthread.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro figures out how to build C programs using POSIX threads. It 12 | # sets the PTHREAD_LIBS output variable to the threads library and linker 13 | # flags, and the PTHREAD_CFLAGS output variable to any special C compiler 14 | # flags that are needed. (The user can also force certain compiler 15 | # flags/libs to be tested by setting these environment variables.) 16 | # 17 | # Also sets PTHREAD_CC to any special C compiler that is needed for 18 | # multi-threaded programs (defaults to the value of CC otherwise). (This 19 | # is necessary on AIX to use the special cc_r compiler alias.) 20 | # 21 | # NOTE: You are assumed to not only compile your program with these flags, 22 | # but also link it with them as well. e.g. you should link with 23 | # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS 24 | # 25 | # If you are only building threads programs, you may wish to use these 26 | # variables in your default LIBS, CFLAGS, and CC: 27 | # 28 | # LIBS="$PTHREAD_LIBS $LIBS" 29 | # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 30 | # CC="$PTHREAD_CC" 31 | # 32 | # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant 33 | # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name 34 | # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). 35 | # 36 | # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the 37 | # PTHREAD_PRIO_INHERIT symbol is defined when compiling with 38 | # PTHREAD_CFLAGS. 39 | # 40 | # ACTION-IF-FOUND is a list of shell commands to run if a threads library 41 | # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it 42 | # is not found. If ACTION-IF-FOUND is not specified, the default action 43 | # will define HAVE_PTHREAD. 44 | # 45 | # Please let the authors know if this macro fails on any platform, or if 46 | # you have any other suggestions or comments. This macro was based on work 47 | # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help 48 | # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by 49 | # Alejandro Forero Cuervo to the autoconf macro repository. We are also 50 | # grateful for the helpful feedback of numerous users. 51 | # 52 | # Updated for Autoconf 2.68 by Daniel Richard G. 53 | # 54 | # LICENSE 55 | # 56 | # Copyright (c) 2008 Steven G. Johnson 57 | # Copyright (c) 2011 Daniel Richard G. 58 | # 59 | # This program is free software: you can redistribute it and/or modify it 60 | # under the terms of the GNU General Public License as published by the 61 | # Free Software Foundation, either version 3 of the License, or (at your 62 | # option) any later version. 63 | # 64 | # This program is distributed in the hope that it will be useful, but 65 | # WITHOUT ANY WARRANTY; without even the implied warranty of 66 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 67 | # Public License for more details. 68 | # 69 | # You should have received a copy of the GNU General Public License along 70 | # with this program. If not, see . 71 | # 72 | # As a special exception, the respective Autoconf Macro's copyright owner 73 | # gives unlimited permission to copy, distribute and modify the configure 74 | # scripts that are the output of Autoconf when processing the Macro. You 75 | # need not follow the terms of the GNU General Public License when using 76 | # or distributing such scripts, even though portions of the text of the 77 | # Macro appear in them. The GNU General Public License (GPL) does govern 78 | # all other use of the material that constitutes the Autoconf Macro. 79 | # 80 | # This special exception to the GPL applies to versions of the Autoconf 81 | # Macro released by the Autoconf Archive. When you make and distribute a 82 | # modified version of the Autoconf Macro, you may extend this special 83 | # exception to the GPL to apply to your modified version as well. 84 | 85 | #serial 18 86 | 87 | AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) 88 | AC_DEFUN([AX_PTHREAD], [ 89 | AC_REQUIRE([AC_CANONICAL_HOST]) 90 | AC_LANG_PUSH([C]) 91 | ax_pthread_ok=no 92 | 93 | # We used to check for pthread.h first, but this fails if pthread.h 94 | # requires special compiler flags (e.g. on True64 or Sequent). 95 | # It gets checked for in the link test anyway. 96 | 97 | # First of all, check if the user has set any of the PTHREAD_LIBS, 98 | # etcetera environment variables, and if threads linking works using 99 | # them: 100 | if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then 101 | save_CFLAGS="$CFLAGS" 102 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 103 | save_LIBS="$LIBS" 104 | LIBS="$PTHREAD_LIBS $LIBS" 105 | AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) 106 | AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) 107 | AC_MSG_RESULT($ax_pthread_ok) 108 | if test x"$ax_pthread_ok" = xno; then 109 | PTHREAD_LIBS="" 110 | PTHREAD_CFLAGS="" 111 | fi 112 | LIBS="$save_LIBS" 113 | CFLAGS="$save_CFLAGS" 114 | fi 115 | 116 | # We must check for the threads library under a number of different 117 | # names; the ordering is very important because some systems 118 | # (e.g. DEC) have both -lpthread and -lpthreads, where one of the 119 | # libraries is broken (non-POSIX). 120 | 121 | # Create a list of thread flags to try. Items starting with a "-" are 122 | # C compiler flags, and other items are library names, except for "none" 123 | # which indicates that we try without any flags at all, and "pthread-config" 124 | # which is a program returning the flags for the Pth emulation library. 125 | 126 | ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" 127 | 128 | # The ordering *is* (sometimes) important. Some notes on the 129 | # individual items follow: 130 | 131 | # pthreads: AIX (must check this before -lpthread) 132 | # none: in case threads are in libc; should be tried before -Kthread and 133 | # other compiler flags to prevent continual compiler warnings 134 | # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) 135 | # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) 136 | # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) 137 | # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) 138 | # -pthreads: Solaris/gcc 139 | # -mthreads: Mingw32/gcc, Lynx/gcc 140 | # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it 141 | # doesn't hurt to check since this sometimes defines pthreads too; 142 | # also defines -D_REENTRANT) 143 | # ... -mt is also the pthreads flag for HP/aCC 144 | # pthread: Linux, etcetera 145 | # --thread-safe: KAI C++ 146 | # pthread-config: use pthread-config program (for GNU Pth library) 147 | 148 | case ${host_os} in 149 | solaris*) 150 | 151 | # On Solaris (at least, for some versions), libc contains stubbed 152 | # (non-functional) versions of the pthreads routines, so link-based 153 | # tests will erroneously succeed. (We need to link with -pthreads/-mt/ 154 | # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather 155 | # a function called by this macro, so we could check for that, but 156 | # who knows whether they'll stub that too in a future libc.) So, 157 | # we'll just look for -pthreads and -lpthread first: 158 | 159 | ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" 160 | ;; 161 | 162 | darwin*) 163 | ax_pthread_flags="-pthread $ax_pthread_flags" 164 | ;; 165 | esac 166 | 167 | if test x"$ax_pthread_ok" = xno; then 168 | for flag in $ax_pthread_flags; do 169 | 170 | case $flag in 171 | none) 172 | AC_MSG_CHECKING([whether pthreads work without any flags]) 173 | ;; 174 | 175 | -*) 176 | AC_MSG_CHECKING([whether pthreads work with $flag]) 177 | PTHREAD_CFLAGS="$flag" 178 | ;; 179 | 180 | pthread-config) 181 | AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) 182 | if test x"$ax_pthread_config" = xno; then continue; fi 183 | PTHREAD_CFLAGS="`pthread-config --cflags`" 184 | PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" 185 | ;; 186 | 187 | *) 188 | AC_MSG_CHECKING([for the pthreads library -l$flag]) 189 | PTHREAD_LIBS="-l$flag" 190 | ;; 191 | esac 192 | 193 | save_LIBS="$LIBS" 194 | save_CFLAGS="$CFLAGS" 195 | LIBS="$PTHREAD_LIBS $LIBS" 196 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 197 | 198 | # Check for various functions. We must include pthread.h, 199 | # since some functions may be macros. (On the Sequent, we 200 | # need a special flag -Kthread to make this header compile.) 201 | # We check for pthread_join because it is in -lpthread on IRIX 202 | # while pthread_create is in libc. We check for pthread_attr_init 203 | # due to DEC craziness with -lpthreads. We check for 204 | # pthread_cleanup_push because it is one of the few pthread 205 | # functions on Solaris that doesn't have a non-functional libc stub. 206 | # We try pthread_create on general principles. 207 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include 208 | static void routine(void *a) { a = 0; } 209 | static void *start_routine(void *a) { return a; }], 210 | [pthread_t th; pthread_attr_t attr; 211 | pthread_create(&th, 0, start_routine, 0); 212 | pthread_join(th, 0); 213 | pthread_attr_init(&attr); 214 | pthread_cleanup_push(routine, 0); 215 | pthread_cleanup_pop(0) /* ; */])], 216 | [ax_pthread_ok=yes], 217 | []) 218 | 219 | LIBS="$save_LIBS" 220 | CFLAGS="$save_CFLAGS" 221 | 222 | AC_MSG_RESULT($ax_pthread_ok) 223 | if test "x$ax_pthread_ok" = xyes; then 224 | break; 225 | fi 226 | 227 | PTHREAD_LIBS="" 228 | PTHREAD_CFLAGS="" 229 | done 230 | fi 231 | 232 | # Various other checks: 233 | if test "x$ax_pthread_ok" = xyes; then 234 | save_LIBS="$LIBS" 235 | LIBS="$PTHREAD_LIBS $LIBS" 236 | save_CFLAGS="$CFLAGS" 237 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 238 | 239 | # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. 240 | AC_MSG_CHECKING([for joinable pthread attribute]) 241 | attr_name=unknown 242 | for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do 243 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], 244 | [int attr = $attr; return attr /* ; */])], 245 | [attr_name=$attr; break], 246 | []) 247 | done 248 | AC_MSG_RESULT($attr_name) 249 | if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then 250 | AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, 251 | [Define to necessary symbol if this constant 252 | uses a non-standard name on your system.]) 253 | fi 254 | 255 | AC_MSG_CHECKING([if more special flags are required for pthreads]) 256 | flag=no 257 | case ${host_os} in 258 | aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; 259 | osf* | hpux*) flag="-D_REENTRANT";; 260 | solaris*) 261 | if test "$GCC" = "yes"; then 262 | flag="-D_REENTRANT" 263 | else 264 | flag="-mt -D_REENTRANT" 265 | fi 266 | ;; 267 | esac 268 | AC_MSG_RESULT(${flag}) 269 | if test "x$flag" != xno; then 270 | PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" 271 | fi 272 | 273 | AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], 274 | ax_cv_PTHREAD_PRIO_INHERIT, [ 275 | AC_LINK_IFELSE([ 276 | AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], 277 | [ax_cv_PTHREAD_PRIO_INHERIT=yes], 278 | [ax_cv_PTHREAD_PRIO_INHERIT=no]) 279 | ]) 280 | AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], 281 | AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) 282 | 283 | LIBS="$save_LIBS" 284 | CFLAGS="$save_CFLAGS" 285 | 286 | # More AIX lossage: must compile with xlc_r or cc_r 287 | if test x"$GCC" != xyes; then 288 | AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) 289 | else 290 | PTHREAD_CC=$CC 291 | fi 292 | else 293 | PTHREAD_CC="$CC" 294 | fi 295 | 296 | AC_SUBST(PTHREAD_LIBS) 297 | AC_SUBST(PTHREAD_CFLAGS) 298 | AC_SUBST(PTHREAD_CC) 299 | 300 | # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: 301 | if test x"$ax_pthread_ok" = xyes; then 302 | ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) 303 | : 304 | else 305 | ax_pthread_ok=no 306 | $2 307 | fi 308 | AC_LANG_POP 309 | ])dnl AX_PTHREAD 310 | -------------------------------------------------------------------------------- /libmanta/extern/hidapi/m4/pkg.m4: -------------------------------------------------------------------------------- 1 | # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- 2 | # 3 | # Copyright © 2004 Scott James Remnant . 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | # 19 | # As a special exception to the GNU General Public License, if you 20 | # distribute this file as part of a program that contains a 21 | # configuration script generated by Autoconf, you may include it under 22 | # the same distribution terms that you use for the rest of that program. 23 | 24 | # PKG_PROG_PKG_CONFIG([MIN-VERSION]) 25 | # ---------------------------------- 26 | AC_DEFUN([PKG_PROG_PKG_CONFIG], 27 | [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) 28 | m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) 29 | AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl 30 | if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then 31 | AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) 32 | fi 33 | if test -n "$PKG_CONFIG"; then 34 | _pkg_min_version=m4_default([$1], [0.9.0]) 35 | AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) 36 | if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then 37 | AC_MSG_RESULT([yes]) 38 | else 39 | AC_MSG_RESULT([no]) 40 | PKG_CONFIG="" 41 | fi 42 | 43 | fi[]dnl 44 | ])# PKG_PROG_PKG_CONFIG 45 | 46 | # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) 47 | # 48 | # Check to see whether a particular set of modules exists. Similar 49 | # to PKG_CHECK_MODULES(), but does not set variables or print errors. 50 | # 51 | # 52 | # Similar to PKG_CHECK_MODULES, make sure that the first instance of 53 | # this or PKG_CHECK_MODULES is called, or make sure to call 54 | # PKG_CHECK_EXISTS manually 55 | # -------------------------------------------------------------- 56 | AC_DEFUN([PKG_CHECK_EXISTS], 57 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 58 | if test -n "$PKG_CONFIG" && \ 59 | AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then 60 | m4_ifval([$2], [$2], [:]) 61 | m4_ifvaln([$3], [else 62 | $3])dnl 63 | fi]) 64 | 65 | 66 | # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) 67 | # --------------------------------------------- 68 | m4_define([_PKG_CONFIG], 69 | [if test -n "$PKG_CONFIG"; then 70 | if test -n "$$1"; then 71 | pkg_cv_[]$1="$$1" 72 | else 73 | PKG_CHECK_EXISTS([$3], 74 | [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], 75 | [pkg_failed=yes]) 76 | fi 77 | else 78 | pkg_failed=untried 79 | fi[]dnl 80 | ])# _PKG_CONFIG 81 | 82 | # _PKG_SHORT_ERRORS_SUPPORTED 83 | # ----------------------------- 84 | AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], 85 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) 86 | if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then 87 | _pkg_short_errors_supported=yes 88 | else 89 | _pkg_short_errors_supported=no 90 | fi[]dnl 91 | ])# _PKG_SHORT_ERRORS_SUPPORTED 92 | 93 | 94 | # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], 95 | # [ACTION-IF-NOT-FOUND]) 96 | # 97 | # 98 | # Note that if there is a possibility the first call to 99 | # PKG_CHECK_MODULES might not happen, you should be sure to include an 100 | # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac 101 | # 102 | # 103 | # -------------------------------------------------------------- 104 | AC_DEFUN([PKG_CHECK_MODULES], 105 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 106 | AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl 107 | AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl 108 | 109 | pkg_failed=no 110 | AC_MSG_CHECKING([for $1]) 111 | 112 | _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) 113 | _PKG_CONFIG([$1][_LIBS], [libs], [$2]) 114 | 115 | m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS 116 | and $1[]_LIBS to avoid the need to call pkg-config. 117 | See the pkg-config man page for more details.]) 118 | 119 | if test $pkg_failed = yes; then 120 | _PKG_SHORT_ERRORS_SUPPORTED 121 | if test $_pkg_short_errors_supported = yes; then 122 | $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` 123 | else 124 | $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` 125 | fi 126 | # Put the nasty error message in config.log where it belongs 127 | echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD 128 | 129 | ifelse([$4], , [AC_MSG_ERROR(dnl 130 | [Package requirements ($2) were not met: 131 | 132 | $$1_PKG_ERRORS 133 | 134 | Consider adjusting the PKG_CONFIG_PATH environment variable if you 135 | installed software in a non-standard prefix. 136 | 137 | _PKG_TEXT 138 | ])], 139 | [AC_MSG_RESULT([no]) 140 | $4]) 141 | elif test $pkg_failed = untried; then 142 | ifelse([$4], , [AC_MSG_FAILURE(dnl 143 | [The pkg-config script could not be found or is too old. Make sure it 144 | is in your PATH or set the PKG_CONFIG environment variable to the full 145 | path to pkg-config. 146 | 147 | _PKG_TEXT 148 | 149 | To get pkg-config, see .])], 150 | [$4]) 151 | else 152 | $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS 153 | $1[]_LIBS=$pkg_cv_[]$1[]_LIBS 154 | AC_MSG_RESULT([yes]) 155 | ifelse([$3], , :, [$3]) 156 | fi[]dnl 157 | ])# PKG_CHECK_MODULES 158 | -------------------------------------------------------------------------------- /src/ErrorCodes.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "ErrorCodes.hpp" 18 | 19 | const char* errString[kNumErrors] = { 20 | "halt", "failed", "indefinite operation", "wrong type", "out of range", "syntax", "internal bug", 21 | "wrong state", "not found", "stack overflow", "stack underflow", 22 | "inconsistent inheritance", "undefined operation", "user quit" 23 | }; 24 | -------------------------------------------------------------------------------- /src/MathFuns.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "MathFuns.hpp" 18 | #include "VM.hpp" 19 | 20 | double gSineTable[kSineTableSize+1]; 21 | double gDBAmpTable[kDBAmpTableSize+2]; 22 | double gDecayTable[kDecayTableSize+1]; 23 | double gFirstOrderCoeffTable[kFirstOrderCoeffTableSize+1]; 24 | 25 | 26 | inline double freqToTableF(double freq) 27 | { 28 | return std::clamp(3. * log2(freq * .05), 0., 28.999); 29 | } 30 | 31 | Z gFreqToTable[20001]; 32 | 33 | static void initFreqToTable() 34 | { 35 | for (int freq = 0; freq < 20001; ++freq) { 36 | gFreqToTable[freq] = freqToTableF(freq); 37 | } 38 | } 39 | 40 | inline double freqToTable(double freq) 41 | { 42 | double findex = std::clamp(freq, 0., 20000.); 43 | double iindex = floor(findex); 44 | return lut(gFreqToTable, (int)iindex, findex - iindex); 45 | } 46 | 47 | //////////////////////////////////////////////////////////////////////////////////// 48 | 49 | void fillSineTable() 50 | { 51 | for (int i = 0; i < kSineTableSize; ++i) { 52 | gSineTable[i] = sin(gSineTableOmega * i); 53 | } 54 | gSineTable[kSineTableSize] = gSineTable[0]; 55 | } 56 | 57 | void fillDBAmpTable() 58 | { 59 | for (int i = 0; i < kDBAmpTableSize+2; ++i) { 60 | double dbgain = i * kInvDBAmpScale - kDBAmpOffset; 61 | double amp = pow(10., .05 * dbgain); 62 | gDBAmpTable[i] = amp; 63 | } 64 | } 65 | 66 | void fillDecayTable() 67 | { 68 | for (int i = 0; i < kDecayTableSize+1; ++i) { 69 | gDecayTable[i] = exp(log001 * i * .001); 70 | } 71 | } 72 | 73 | void fillFirstOrderCoeffTable() 74 | { 75 | double k = M_PI * kInvFirstOrderCoeffTableSize; 76 | for (int i = 0; i < kFirstOrderCoeffTableSize+1; ++i) { 77 | double x = k * i; 78 | Z b = 2. - cos(x); 79 | gFirstOrderCoeffTable[i] = b - sqrt(b*b - 1.); 80 | } 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/Opcode.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Opcode.hpp" 18 | #include "clz.hpp" 19 | 20 | const char* opcode_name[kNumOpcodes] = 21 | { 22 | "BAD OPCODE", 23 | "opNone", 24 | "opPushImmediate", 25 | "opPushLocalVar", 26 | "opPushFunVar", 27 | "opPushWorkspaceVar", 28 | 29 | "opPushFun", 30 | 31 | "opCallImmediate", 32 | "opCallLocalVar", 33 | "opCallFunVar", 34 | "opCallWorkspaceVar", 35 | 36 | "opDot", 37 | "opComma", 38 | "opBindLocal", 39 | "opBindLocalFromList", 40 | "opBindWorkspaceVar", 41 | "opBindWorkspaceVarFromList", 42 | 43 | "opParens", 44 | "opNewVList", 45 | "opNewZList", 46 | "opNewForm", 47 | "opInherit", 48 | "opEach", 49 | "opReturn" 50 | }; 51 | 52 | 53 | static void printOpcode(Thread& th, Opcode* c) 54 | { 55 | V& v = c->v; 56 | post("%p %s ", c, opcode_name[c->op]); 57 | switch (c->op) { 58 | case opPushImmediate : 59 | case opPushWorkspaceVar : 60 | case opPushFun : 61 | case opCallImmediate : 62 | case opCallWorkspaceVar : 63 | case opDot : 64 | case opComma : 65 | case opInherit : 66 | case opNewForm : 67 | case opBindWorkspaceVar : 68 | case opBindWorkspaceVarFromList : 69 | case opParens : 70 | case opNewVList : 71 | case opNewZList : 72 | v.printShort(th); 73 | break; 74 | 75 | case opPushLocalVar : 76 | case opPushFunVar : 77 | case opCallLocalVar : 78 | case opCallFunVar : 79 | case opBindLocal : 80 | case opBindLocalFromList : 81 | post("%lld", (int64_t)v.i); 82 | break; 83 | case opEach : 84 | post("%llx", (int64_t)v.i); 85 | break; 86 | 87 | case opNone : 88 | case opReturn : 89 | break; 90 | 91 | default : 92 | post("BAD OPCODE\n"); 93 | } 94 | post("\n"); 95 | } 96 | 97 | void Thread::run(Opcode* opc) 98 | { 99 | Thread& th = *this; 100 | try { 101 | for (;;++opc) { 102 | 103 | V& v = opc->v; 104 | 105 | if (vm.traceon) { 106 | post("stack : "); th.printStack(); post("\n"); 107 | printOpcode(th, opc); 108 | } 109 | 110 | switch (opc->op) { 111 | case opNone : 112 | break; 113 | 114 | case opPushImmediate : 115 | push(v); 116 | break; 117 | 118 | case opPushLocalVar : 119 | push(getLocal(v.i)); 120 | break; 121 | 122 | case opPushFunVar : 123 | push(fun->mVars[v.i]); 124 | break; 125 | 126 | case opPushWorkspaceVar : 127 | push(fun->Workspace()->mustGet(th, v)); 128 | break; 129 | 130 | case opPushFun : { 131 | push(new Fun(th, (FunDef*)v.o())); 132 | } break; 133 | 134 | case opCallImmediate : 135 | v.apply(th); 136 | break; 137 | 138 | case opCallLocalVar : 139 | getLocal(v.i).apply(th); 140 | break; 141 | 142 | case opCallFunVar : 143 | fun->mVars[v.i].apply(th); 144 | break; 145 | 146 | case opCallWorkspaceVar : 147 | fun->Workspace()->mustGet(th, v).apply(th); 148 | break; 149 | 150 | case opDot : { 151 | V ioValue; 152 | if (!pop().dot(th, v, ioValue)) 153 | notFound(v); 154 | push(ioValue); 155 | break; 156 | } 157 | case opComma : 158 | push(pop().comma(th, v)); 159 | break; 160 | 161 | case opBindLocal : 162 | getLocal(v.i) = pop(); 163 | break; 164 | case opBindWorkspaceVar : { 165 | V value = pop(); 166 | if (value.isList() && !value.isFinite()) { 167 | post("WARNING: binding a possibly infinite list at the top level can leak unbounded memory!\n"); 168 | } else if (value.isFun()) { 169 | const char* mask = value.GetAutoMapMask(); 170 | const char* help = value.OneLineHelp(); 171 | if (mask || help) { 172 | char* name = ((String*)v.o())->s; 173 | vm.addUdfHelp(name, mask, help); 174 | } 175 | } 176 | fun->Workspace() = fun->Workspace()->putImpure(v, value); // workspace mutation 177 | th.mWorkspace = th.mWorkspace->putImpure(v, value); // workspace mutation 178 | } break; 179 | 180 | case opBindLocalFromList : 181 | case opBindWorkspaceVarFromList : 182 | { 183 | V list = pop(); 184 | BothIn in(list); 185 | while (1) { 186 | if (opc->op == opNone) { 187 | break; 188 | } else { 189 | V value; 190 | if (in.one(th, value)) { 191 | post("not enough items in list for = [..]\n"); 192 | throw errFailed; 193 | } 194 | if (opc->op == opBindLocalFromList) { 195 | getLocal(opc->v.i) = value; 196 | } else if (opc->op == opBindWorkspaceVarFromList) { 197 | v = opc->v; 198 | if (value.isList() && !value.isFinite()) { 199 | post("WARNING: binding a possibly infinite list at the top level can leak unbounded memory!\n"); 200 | } else if (value.isFun()) { 201 | const char* mask = value.GetAutoMapMask(); 202 | const char* help = value.OneLineHelp(); 203 | if (mask || help) { 204 | char* name = ((String*)v.o())->s; 205 | vm.addUdfHelp(name, mask, help); 206 | } 207 | } 208 | fun->Workspace() = fun->Workspace()->putImpure(v, value); // workspace mutation 209 | th.mWorkspace = th.mWorkspace->putImpure(v, value); // workspace mutation 210 | } 211 | } 212 | ++opc; 213 | } 214 | } break; 215 | case opParens : { 216 | ParenStack ss(th); 217 | run(((Code*)v.o())->getOps()); 218 | } break; 219 | case opNewVList : { 220 | V x; 221 | { 222 | SaveStack ss(th); 223 | run(((Code*)v.o())->getOps()); 224 | size_t len = stackDepth(); 225 | vm.newVList->apply_n(th, len); 226 | x = th.pop(); 227 | } 228 | th.push(x); 229 | } break; 230 | case opNewZList : { 231 | V x; 232 | { 233 | SaveStack ss(th); 234 | run(((Code*)v.o())->getOps()); 235 | size_t len = stackDepth(); 236 | vm.newZList->apply_n(th, len); 237 | x = th.pop(); 238 | } 239 | th.push(x); 240 | } break; 241 | case opInherit : { 242 | V result; 243 | { 244 | SaveStack ss(th); 245 | run(((Code*)v.o())->getOps()); 246 | size_t depth = stackDepth(); 247 | if (depth < 1) { 248 | result = vm._ee; 249 | } else if (depth > 1) { 250 | fprintf(stderr, "more arguments than keys for form.\n"); 251 | throw errFailed; 252 | } else { 253 | vm.inherit->apply_n(th, 1); 254 | result = th.pop(); 255 | } 256 | } 257 | th.push(result); 258 | } break; 259 | case opNewForm : { 260 | V result; 261 | { 262 | SaveStack ss(th); 263 | run(((Code*)v.o())->getOps()); 264 | size_t depth = stackDepth(); 265 | TableMap* tmap = (TableMap*)th.top().o(); 266 | size_t numArgs = tmap->mSize; 267 | if (depth == numArgs+1) { 268 | // no inheritance, must insert zero for parent. 269 | th.tuck(numArgs+1, V(0.)); 270 | } else if (depth < numArgs+1) { 271 | fprintf(stderr, "fewer arguments than keys for form.\n"); 272 | throw errStackUnderflow; 273 | } else if (depth > numArgs+2) { 274 | fprintf(stderr, "more arguments than keys for form.\n"); 275 | throw errFailed; 276 | } 277 | vm.newForm->apply_n(th, numArgs+2); 278 | result = th.pop(); 279 | } 280 | th.push(result); 281 | } break; 282 | case opEach : 283 | push(new EachOp(pop(), (int)v.i)); 284 | break; 285 | 286 | case opReturn : return; 287 | 288 | default : 289 | post("BAD OPCODE\n"); 290 | throw errInternalError; 291 | } 292 | } 293 | } catch (...) { 294 | post("backtrace: %s ", opcode_name[opc->op]); 295 | opc->v.printShort(th); 296 | post("\n"); 297 | throw; 298 | } 299 | } 300 | 301 | Code::~Code() { } 302 | 303 | void Code::shrinkToFit() 304 | { 305 | std::vector(ops.begin(), ops.end()).swap(ops); 306 | } 307 | 308 | void Code::add(int _op, Arg v) 309 | { 310 | ops.push_back(Opcode(_op, v)); 311 | } 312 | 313 | void Code::add(int _op, double f) 314 | { 315 | add(_op, V(f)); 316 | } 317 | 318 | void Code::addAll(const P &that) 319 | { 320 | for (Opcode& op : that->ops) { 321 | ops.push_back(op); 322 | } 323 | } 324 | 325 | void Code::decompile(Thread& th, std::string& out) 326 | { 327 | for (Opcode& c : ops) { 328 | V& v = c.v; 329 | switch (c.op) { 330 | case opPushImmediate : { 331 | std::string s; 332 | v.printShort(th, s); 333 | out += s; 334 | break; 335 | } 336 | case opPushWorkspaceVar : 337 | case opPushFun : 338 | case opCallImmediate : 339 | case opCallWorkspaceVar : 340 | case opDot : 341 | case opComma : 342 | case opInherit : 343 | case opNewForm : 344 | case opBindWorkspaceVar : 345 | case opBindWorkspaceVarFromList : 346 | case opParens : 347 | case opNewVList : 348 | case opNewZList : 349 | v.printShort(th); 350 | break; 351 | 352 | case opPushLocalVar : 353 | case opPushFunVar : 354 | case opCallLocalVar : 355 | case opCallFunVar : 356 | case opBindLocal : 357 | case opBindLocalFromList : 358 | post("%lld", (int64_t)v.i); 359 | break; 360 | case opEach : 361 | post("%llx", (int64_t)v.i); 362 | break; 363 | 364 | case opNone : 365 | case opReturn : 366 | break; 367 | 368 | default : 369 | post("BAD OPCODE\n"); 370 | } 371 | } 372 | } 373 | 374 | -------------------------------------------------------------------------------- /src/Play.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Play.hpp" 18 | #include 19 | #include 20 | #include 21 | 22 | #include "SoundFiles.hpp" 23 | 24 | pthread_mutex_t gPlayerMutex = PTHREAD_MUTEX_INITIALIZER; 25 | 26 | const int kMaxChannels = 32; 27 | 28 | struct AUPlayer* gAllPlayers = nullptr; 29 | 30 | 31 | 32 | struct AUPlayer 33 | { 34 | AUPlayer(Thread& inThread, int inNumChannels, ExtAudioFileRef inXAFRef = nullptr) 35 | : th(inThread), count(0), done(false), prev(nullptr), next(gAllPlayers), outputUnit(nullptr), numChannels(inNumChannels), xaf(inXAFRef) 36 | { 37 | gAllPlayers = this; 38 | if (next) next->prev = this; 39 | } 40 | 41 | ~AUPlayer() { 42 | if (next) next->prev = prev; 43 | 44 | if (prev) prev->next = next; 45 | else gAllPlayers = next; 46 | 47 | if (xaf) { 48 | ExtAudioFileDispose(xaf); 49 | char cmd[1100]; 50 | snprintf(cmd, 1100, "open \"%s\"", path.c_str()); 51 | system(cmd); 52 | } 53 | } 54 | 55 | Thread th; 56 | int count; 57 | bool done; 58 | AUPlayer* prev; 59 | AUPlayer* next; 60 | AudioComponentInstance outputUnit; 61 | int numChannels; 62 | ZIn in[kMaxChannels]; 63 | ExtAudioFileRef xaf = nullptr; 64 | std::string path; 65 | 66 | }; 67 | 68 | static void stopPlayer(AUPlayer* player) 69 | { 70 | AudioComponentInstance outputUnit = player->outputUnit; 71 | player->outputUnit = nullptr; 72 | if (outputUnit) { 73 | 74 | OSStatus err = AudioOutputUnitStop(outputUnit); 75 | if (err) post("AudioOutputUnitStop err %d\n", (int)err); 76 | err = AudioComponentInstanceDispose(outputUnit); 77 | if (err) post("AudioComponentInstanceDispose outputUnit err %d\n", (int)err); 78 | 79 | } 80 | delete player; 81 | } 82 | 83 | void stopPlaying() 84 | { 85 | Locker lock(&gPlayerMutex); 86 | 87 | AUPlayer* player = gAllPlayers; 88 | while (player) { 89 | AUPlayer* next = player->next; 90 | stopPlayer(player); 91 | player = next; 92 | } 93 | } 94 | 95 | void stopPlayingIfDone() 96 | { 97 | Locker lock(&gPlayerMutex); 98 | 99 | AUPlayer* player = gAllPlayers; 100 | while (player) { 101 | AUPlayer* next = player->next; 102 | if (player->done) 103 | stopPlayer(player); 104 | player = next; 105 | } 106 | } 107 | 108 | static void* stopDonePlayers(void* x) 109 | { 110 | while(1) { 111 | sleep(1); 112 | stopPlayingIfDone(); 113 | } 114 | return nullptr; 115 | } 116 | 117 | bool gWatchdogRunning = false; 118 | pthread_t watchdog; 119 | 120 | static bool fillBufferList(AUPlayer* player, int inNumberFrames, AudioBufferList* ioData) 121 | { 122 | if (player->done) { 123 | zeroAll: 124 | for (int i = 0; i < (int)ioData->mNumberBuffers; ++i) { 125 | memset((float*)ioData->mBuffers[i].mData, 0, inNumberFrames * sizeof(float)); 126 | } 127 | return true; 128 | } 129 | ZIn* in = player->in; 130 | bool done = true; 131 | for (int i = 0; i < (int)ioData->mNumberBuffers; ++i) { 132 | int n = inNumberFrames; 133 | if (i >= player->numChannels) { 134 | memset(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize); 135 | } else { 136 | try { 137 | float* buf = (float*)ioData->mBuffers[i].mData; 138 | bool imdone = in[i].fill(player->th, n, buf, 1); 139 | if (n < inNumberFrames) { 140 | memset((float*)ioData->mBuffers[i].mData + n, 0, (inNumberFrames - n) * sizeof(float)); 141 | } 142 | done = done && imdone; 143 | } catch (int err) { 144 | if (err <= -1000 && err > -1000 - kNumErrors) { 145 | post("\nerror: %s\n", errString[-1000 - err]); 146 | } else { 147 | post("\nerror: %d\n", err); 148 | } 149 | post("exception in real time. stopping player.\n"); 150 | done = true; 151 | goto zeroAll; 152 | } catch (std::bad_alloc& xerr) { 153 | post("\nnot enough memory\n"); 154 | post("exception in real time. stopping player.\n"); 155 | done = true; 156 | goto zeroAll; 157 | } catch (...) { 158 | post("\nunknown error\n"); 159 | post("exception in real time. stopping player.\n"); 160 | done = true; 161 | goto zeroAll; 162 | } 163 | } 164 | } 165 | 166 | return done; 167 | } 168 | 169 | static void recordPlayer(AUPlayer* player, int inNumberFrames, AudioBufferList const* inData) 170 | { 171 | if (!player->xaf) return; 172 | 173 | OSStatus err = ExtAudioFileWriteAsync(player->xaf, inNumberFrames, inData); // initialize async. 174 | if (err) printf("ExtAudioFileWriteAsync err %d\n", (int)err); 175 | } 176 | 177 | static OSStatus inputCallback( void * inRefCon, 178 | AudioUnitRenderActionFlags * ioActionFlags, 179 | const AudioTimeStamp * inTimeStamp, 180 | UInt32 inBusNumber, 181 | UInt32 inNumberFrames, 182 | AudioBufferList * ioData) 183 | { 184 | 185 | AUPlayer* player = (AUPlayer*)inRefCon; 186 | 187 | bool done = fillBufferList(player, inNumberFrames, ioData); 188 | recordPlayer(player, inNumberFrames, ioData); 189 | 190 | if (done) { 191 | player->done = true; 192 | } 193 | return noErr; 194 | } 195 | 196 | 197 | static AudioComponentInstance openAU(UInt32 inType, UInt32 inSubtype, UInt32 inManuf) 198 | { 199 | AudioComponentDescription desc; 200 | desc.componentType = inType; 201 | desc.componentSubType = inSubtype; 202 | desc.componentManufacturer = inManuf; 203 | desc.componentFlags = 0; 204 | desc.componentFlagsMask = 0; 205 | 206 | AudioComponent comp = AudioComponentFindNext(nullptr, &desc); 207 | if (!comp) { 208 | return nullptr; 209 | } 210 | 211 | AudioComponentInstance au = nullptr; 212 | AudioComponentInstanceNew(comp, &au); 213 | 214 | return au; 215 | } 216 | 217 | static OSStatus createGraph(AUPlayer* player) 218 | { 219 | OSStatus err = noErr; 220 | AudioComponentInstance outputUnit = openAU('auou', 'def ', 'appl'); 221 | if (!outputUnit) { 222 | post("open output unit failed\n"); 223 | return 'fail'; 224 | } 225 | 226 | player->outputUnit = outputUnit; 227 | 228 | UInt32 flags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; 229 | AudioStreamBasicDescription fmt = { vm.ar.sampleRate, kAudioFormatLinearPCM, flags, 4, 1, 4, (UInt32)player->numChannels, 32, 0 }; 230 | 231 | err = AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &fmt, sizeof(fmt)); 232 | if (err) { 233 | post("set outputUnit client format failed\n"); 234 | return err; 235 | } 236 | 237 | AURenderCallbackStruct cbs; 238 | 239 | cbs.inputProc = inputCallback; 240 | cbs.inputProcRefCon = player; 241 | 242 | err = AudioUnitSetProperty(outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &cbs, sizeof(cbs)); 243 | if (err) { 244 | post("set render callback failed\n"); 245 | return err; 246 | } 247 | 248 | err = AudioUnitInitialize(outputUnit); 249 | if (err) { 250 | post("initialize output unit failed\n"); 251 | return err; 252 | } 253 | 254 | err = AudioOutputUnitStart(outputUnit); 255 | if (err) { 256 | post("start output unit failed\n"); 257 | return err; 258 | } 259 | 260 | post("start output unit OK\n"); 261 | 262 | return noErr; 263 | } 264 | 265 | void playWithAudioUnit(Thread& th, V& v) 266 | { 267 | if (!v.isList()) wrongType("play : s", "List", v); 268 | 269 | Locker lock(&gPlayerMutex); 270 | 271 | AUPlayer *player; 272 | 273 | if (v.isZList()) { 274 | player = new AUPlayer(th, 1); 275 | player->in[0].set(v); 276 | player->numChannels = 1; 277 | } else { 278 | if (!v.isFinite()) indefiniteOp("play : s", ""); 279 | P s = (List*)v.o(); 280 | s = s->pack(th, kMaxChannels); 281 | if (!s()) { 282 | post("Too many channels. Max is %d.\n", kMaxChannels); 283 | return; 284 | } 285 | Array* a = s->mArray(); 286 | 287 | int asize = (int)a->size(); 288 | 289 | player = new AUPlayer(th, asize); 290 | for (int i = 0; i < asize; ++i) { 291 | player->in[i].set(a->at(i)); 292 | } 293 | s = nullptr; 294 | a = nullptr; 295 | } 296 | v.o = nullptr; // try to prevent leak. 297 | 298 | std::atomic_thread_fence(std::memory_order_seq_cst); 299 | 300 | if (!gWatchdogRunning) { 301 | pthread_create(&watchdog, nullptr, stopDonePlayers, nullptr); 302 | gWatchdogRunning = true; 303 | } 304 | 305 | { 306 | OSStatus err = noErr; 307 | err = createGraph(player); 308 | if (err) { 309 | post("play failed: %d '%4.4s'\n", (int)err, (char*)&err); 310 | throw errFailed; 311 | } 312 | } 313 | } 314 | 315 | 316 | void recordWithAudioUnit(Thread& th, V& v, Arg filename) 317 | { 318 | if (!v.isList()) wrongType("play : s", "List", v); 319 | 320 | Locker lock(&gPlayerMutex); 321 | 322 | AUPlayer *player; 323 | 324 | char path[1024]; 325 | ExtAudioFileRef xaf = nullptr; 326 | 327 | if (v.isZList()) { 328 | makeRecordingPath(filename, path, 1024); 329 | xaf = sfcreate(th, path, 1, 0., false); 330 | if (!xaf) { 331 | printf("couldn't create recording file \"%s\"\n", path); 332 | return; 333 | } 334 | 335 | player = new AUPlayer(th, 1, xaf); 336 | player->in[0].set(v); 337 | player->numChannels = 1; 338 | } else { 339 | if (!v.isFinite()) indefiniteOp("play : s", ""); 340 | P s = (List*)v.o(); 341 | s = s->pack(th, kMaxChannels); 342 | if (!s()) { 343 | post("Too many channels. Max is %d.\n", kMaxChannels); 344 | return; 345 | } 346 | Array* a = s->mArray(); 347 | 348 | int numChannels = (int)a->size(); 349 | 350 | makeRecordingPath(filename, path, 1024); 351 | xaf = sfcreate(th, path, numChannels, 0., false); 352 | if (!xaf) { 353 | printf("couldn't create recording file \"%s\"\n", path); 354 | return; 355 | } 356 | 357 | player = new AUPlayer(th, numChannels, xaf); 358 | for (int i = 0; i < numChannels; ++i) { 359 | player->in[i].set(a->at(i)); 360 | } 361 | s = nullptr; 362 | a = nullptr; 363 | } 364 | v.o = nullptr; // try to prevent leak. 365 | 366 | player->path = path; 367 | 368 | { 369 | OSStatus err = ExtAudioFileWriteAsync(xaf, 0, nullptr); // initialize async. 370 | if (err) printf("init ExtAudioFileWriteAsync err %d\n", (int)err); 371 | } 372 | 373 | std::atomic_thread_fence(std::memory_order_seq_cst); 374 | 375 | if (!gWatchdogRunning) { 376 | pthread_create(&watchdog, nullptr, stopDonePlayers, nullptr); 377 | gWatchdogRunning = true; 378 | } 379 | 380 | { 381 | OSStatus err = noErr; 382 | err = createGraph(player); 383 | if (err) { 384 | post("play failed: %d '%4.4s'\n", (int)err, (char*)&err); 385 | throw errFailed; 386 | } 387 | } 388 | } 389 | 390 | -------------------------------------------------------------------------------- /src/RCObj.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "RCObj.hpp" 18 | #include "VM.hpp" 19 | 20 | 21 | RCObj::RCObj() 22 | : refcount(0) 23 | { 24 | #if COLLECT_MINFO 25 | ++vm.totalObjectsAllocated; 26 | #endif 27 | } 28 | 29 | RCObj::RCObj(RCObj const& that) 30 | : refcount(0) 31 | { 32 | #if COLLECT_MINFO 33 | ++vm.totalObjectsAllocated; 34 | #endif 35 | } 36 | 37 | RCObj::~RCObj() 38 | { 39 | #if COLLECT_MINFO 40 | ++vm.totalObjectsFreed; 41 | #endif 42 | } 43 | 44 | 45 | void RCObj::norefs() 46 | { 47 | refcount = -999; 48 | delete this; 49 | } 50 | 51 | 52 | void RCObj::negrefcount() 53 | { 54 | post("RELEASING WITH NEGATIVE REFCOUNT %s %p %d\n", TypeName(), this, refcount.load()); 55 | } 56 | void RCObj::alreadyDead() 57 | { 58 | post("RETAINING ALREADY DEAD OBJECT %s %p\n", TypeName(), this); 59 | } 60 | -------------------------------------------------------------------------------- /src/SoundFiles.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "SoundFiles.hpp" 18 | #include 19 | 20 | extern char gSessionTime[256]; 21 | 22 | 23 | class SFReaderOutputChannel; 24 | 25 | class SFReader : public Object 26 | { 27 | ExtAudioFileRef mXAF; 28 | int64_t mFramesRemaining; 29 | SFReaderOutputChannel* mOutputs; 30 | int mNumChannels; 31 | AudioBufferList* mABL; 32 | bool mFinished = false; 33 | 34 | public: 35 | 36 | SFReader(ExtAudioFileRef inXAF, int inNumChannels, int64_t inDuration); 37 | 38 | ~SFReader(); 39 | 40 | virtual const char* TypeName() const override { return "SFReader"; } 41 | 42 | P createOutputs(Thread& th); 43 | 44 | bool pull(Thread& th); 45 | void fulfillOutputs(int blockSize); 46 | void produceOutputs(int shrinkBy); 47 | }; 48 | 49 | class SFReaderOutputChannel : public Gen 50 | { 51 | friend class SFReader; 52 | P mSFReader; 53 | SFReaderOutputChannel* mNextOutput = nullptr; 54 | Z* mDummy = nullptr; 55 | 56 | public: 57 | SFReaderOutputChannel(Thread& th, SFReader* inSFReader) 58 | : Gen(th, itemTypeZ, true), mSFReader(inSFReader) 59 | { 60 | } 61 | 62 | ~SFReaderOutputChannel() 63 | { 64 | if (mDummy) free(mDummy); 65 | } 66 | 67 | virtual void norefs() override 68 | { 69 | mOut = nullptr; 70 | mSFReader = nullptr; 71 | } 72 | 73 | virtual const char* TypeName() const override { return "SFReaderOutputChannel"; } 74 | 75 | virtual void pull(Thread& th) override 76 | { 77 | if (mSFReader->pull(th)) { 78 | end(); 79 | } 80 | } 81 | 82 | }; 83 | 84 | SFReader::SFReader(ExtAudioFileRef inXAF, int inNumChannels, int64_t inDuration) 85 | : mXAF(inXAF), mNumChannels(inNumChannels), mFramesRemaining(inDuration), mABL(nullptr) 86 | { 87 | mABL = (AudioBufferList*)calloc(1, sizeof(AudioBufferList) + (mNumChannels - 1) * sizeof(AudioBuffer)); 88 | } 89 | 90 | SFReader::~SFReader() 91 | { 92 | ExtAudioFileDispose(mXAF); free(mABL); 93 | SFReaderOutputChannel* output = mOutputs; 94 | do { 95 | SFReaderOutputChannel* next = output->mNextOutput; 96 | delete output; 97 | output = next; 98 | } while (output); 99 | } 100 | 101 | void SFReader::fulfillOutputs(int blockSize) 102 | { 103 | mABL->mNumberBuffers = mNumChannels; 104 | SFReaderOutputChannel* output = mOutputs; 105 | size_t bufSize = blockSize * sizeof(Z); 106 | for (int i = 0; output; ++i, output = output->mNextOutput){ 107 | Z* out; 108 | if (output->mOut) 109 | out = output->mOut->fulfillz(blockSize); 110 | else { 111 | if (!output->mDummy) 112 | output->mDummy = (Z*)calloc(output->mBlockSize, sizeof(Z)); 113 | 114 | out = output->mDummy; 115 | } 116 | 117 | mABL->mBuffers[i].mNumberChannels = 1; 118 | mABL->mBuffers[i].mData = out; 119 | mABL->mBuffers[i].mDataByteSize = (UInt32)bufSize; 120 | memset(out, 0, bufSize); 121 | }; 122 | } 123 | 124 | void SFReader::produceOutputs(int shrinkBy) 125 | { 126 | SFReaderOutputChannel* output = mOutputs; 127 | do { 128 | if (output->mOut) 129 | output->produce(shrinkBy); 130 | output = output->mNextOutput; 131 | } while (output); 132 | } 133 | 134 | P SFReader::createOutputs(Thread& th) 135 | { 136 | P s = new List(itemTypeV, mNumChannels); 137 | 138 | // fill s->mArray with ola's output channels. 139 | SFReaderOutputChannel* last = nullptr; 140 | P a = s->mArray; 141 | for (int i = 0; i < mNumChannels; ++i) { 142 | SFReaderOutputChannel* c = new SFReaderOutputChannel(th, this); 143 | if (last) last->mNextOutput = c; 144 | else mOutputs = c; 145 | last = c; 146 | a->add(new List(c)); 147 | } 148 | 149 | return s; 150 | } 151 | 152 | bool SFReader::pull(Thread& th) 153 | { 154 | if (mFramesRemaining == 0) 155 | mFinished = true; 156 | 157 | if (mFinished) 158 | return true; 159 | 160 | SFReaderOutputChannel* output = mOutputs; 161 | int blockSize = output->mBlockSize; 162 | if (mFramesRemaining > 0) 163 | blockSize = (int)std::min(mFramesRemaining, (int64_t)blockSize); 164 | 165 | fulfillOutputs(blockSize); 166 | 167 | // read file here. 168 | UInt32 framesRead = blockSize; 169 | OSStatus err = ExtAudioFileRead(mXAF, &framesRead, mABL); 170 | 171 | if (err || framesRead == 0) { 172 | mFinished = true; 173 | } 174 | 175 | produceOutputs(blockSize - framesRead); 176 | if (mFramesRemaining > 0) mFramesRemaining -= blockSize; 177 | 178 | return mFinished; 179 | } 180 | 181 | void sfread(Thread& th, Arg filename, int64_t offset, int64_t frames) 182 | { 183 | const char* path = ((String*)filename.o())->s; 184 | 185 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path); 186 | if (!cfpath) { 187 | post("failed to create path\n"); 188 | return; 189 | } 190 | CFReleaser cfpathReleaser(cfpath); 191 | 192 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false); 193 | if (!url) { 194 | post("failed to create url\n"); 195 | return; 196 | } 197 | CFReleaser urlReleaser(url); 198 | 199 | ExtAudioFileRef xaf; 200 | OSStatus err = ExtAudioFileOpenURL(url, &xaf); 201 | 202 | cfpathReleaser.release(); 203 | urlReleaser.release(); 204 | 205 | if (err) { 206 | post("failed to open file %d\n", (int)err); 207 | return; 208 | } 209 | 210 | AudioStreamBasicDescription fileFormat; 211 | 212 | UInt32 propSize = sizeof(fileFormat); 213 | err = ExtAudioFileGetProperty(xaf, kExtAudioFileProperty_FileDataFormat, &propSize, &fileFormat); 214 | 215 | int numChannels = fileFormat.mChannelsPerFrame; 216 | 217 | AudioStreamBasicDescription clientFormat = { 218 | th.rate.sampleRate, 219 | kAudioFormatLinearPCM, 220 | kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, 221 | static_cast(sizeof(double)), 222 | 1, 223 | static_cast(sizeof(double)), 224 | static_cast(numChannels), 225 | 64, 226 | 0 227 | }; 228 | 229 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat); 230 | if (err) { 231 | post("failed to set client data format\n"); 232 | ExtAudioFileDispose(xaf); 233 | return; 234 | } 235 | 236 | err = ExtAudioFileSeek(xaf, offset); 237 | if (err) { 238 | post("seek failed %d\n", (int)err); 239 | ExtAudioFileDispose(xaf); 240 | return; 241 | } 242 | 243 | SFReader* sfr = new SFReader(xaf, numChannels, -1); 244 | 245 | th.push(sfr->createOutputs(th)); 246 | } 247 | 248 | ExtAudioFileRef sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved) 249 | { 250 | if (fileSampleRate == 0.) 251 | fileSampleRate = th.rate.sampleRate; 252 | 253 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path); 254 | if (!cfpath) { 255 | post("failed to create path '%s'\n", path); 256 | return nullptr; 257 | } 258 | CFReleaser cfpathReleaser(cfpath); 259 | 260 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false); 261 | if (!url) { 262 | post("failed to create url\n"); 263 | return nullptr; 264 | } 265 | CFReleaser urlReleaser(url); 266 | 267 | AudioStreamBasicDescription fileFormat = { 268 | fileSampleRate, 269 | kAudioFormatLinearPCM, 270 | kAudioFormatFlagsNativeFloatPacked, 271 | static_cast(sizeof(float) * numChannels), 272 | 1, 273 | static_cast(sizeof(float) * numChannels), 274 | static_cast(numChannels), 275 | 32, 276 | 0 277 | }; 278 | 279 | int interleavedChannels = interleaved ? numChannels : 1; 280 | UInt32 interleavedBit = interleaved ? 0 : kAudioFormatFlagIsNonInterleaved; 281 | 282 | AudioStreamBasicDescription clientFormat = { 283 | th.rate.sampleRate, 284 | kAudioFormatLinearPCM, 285 | kAudioFormatFlagsNativeFloatPacked | interleavedBit, 286 | static_cast(sizeof(float) * interleavedChannels), 287 | 1, 288 | static_cast(sizeof(float) * interleavedChannels), 289 | static_cast(numChannels), 290 | 32, 291 | 0 292 | }; 293 | 294 | ExtAudioFileRef xaf; 295 | OSStatus err = ExtAudioFileCreateWithURL(url, kAudioFileWAVEType, &fileFormat, nullptr, kAudioFileFlags_EraseFile, &xaf); 296 | 297 | if (err) { 298 | post("failed to create file '%s'. err: %d\n", path, (int)err); 299 | return nullptr; 300 | } 301 | 302 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat); 303 | if (err) { 304 | post("failed to set client data format\n"); 305 | ExtAudioFileDispose(xaf); 306 | return nullptr; 307 | } 308 | 309 | return xaf; 310 | } 311 | 312 | std::atomic gFileCount = 0; 313 | 314 | void makeRecordingPath(Arg filename, char* path, int len) 315 | { 316 | if (filename.isString()) { 317 | const char* recDir = getenv("SAPF_RECORDINGS"); 318 | if (!recDir || strlen(recDir)==0) recDir = "/tmp"; 319 | snprintf(path, len, "%s/%s.wav", recDir, ((String*)filename.o())->s); 320 | } else { 321 | int32_t count = ++gFileCount; 322 | snprintf(path, len, "/tmp/sapf-%s-%04d.wav", gSessionTime, count); 323 | } 324 | } 325 | 326 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt) 327 | { 328 | std::vector in; 329 | 330 | int numChannels = 0; 331 | 332 | if (v.isZList()) { 333 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of frames", ""); 334 | numChannels = 1; 335 | in.push_back(ZIn(v)); 336 | } else { 337 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of channels", ""); 338 | P s = (List*)v.o(); 339 | s = s->pack(th); 340 | Array* a = s->mArray(); 341 | numChannels = (int)a->size(); 342 | 343 | if (numChannels > kMaxSFChannels) 344 | throw errOutOfRange; 345 | 346 | bool allIndefinite = true; 347 | for (int i = 0; i < numChannels; ++i) { 348 | V va = a->at(i); 349 | if (va.isFinite()) allIndefinite = false; 350 | in.push_back(ZIn(va)); 351 | va.o = nullptr; 352 | } 353 | 354 | s = nullptr; 355 | a = nullptr; 356 | 357 | if (allIndefinite) indefiniteOp(">sf : s - all channels have indefinite number of frames", ""); 358 | } 359 | v.o = nullptr; 360 | 361 | char path[1024]; 362 | 363 | makeRecordingPath(filename, path, 1024); 364 | 365 | ExtAudioFileRef xaf = sfcreate(th, path, numChannels, 0., true); 366 | if (!xaf) return; 367 | 368 | std::valarray buf(0., numChannels * kBufSize); 369 | AudioBufferList abl; 370 | abl.mNumberBuffers = 1; 371 | abl.mBuffers[0].mNumberChannels = numChannels; 372 | abl.mBuffers[0].mData = &buf[0]; 373 | abl.mBuffers[0].mDataByteSize = kBufSize * sizeof(float); 374 | 375 | int64_t framesPulled = 0; 376 | int64_t framesWritten = 0; 377 | bool done = false; 378 | while (!done) { 379 | int minn = kBufSize; 380 | memset(&buf[0], 0, kBufSize * numChannels); 381 | for (int i = 0; i < numChannels; ++i) { 382 | int n = kBufSize; 383 | bool imdone = in[i].fill(th, n, &buf[0]+i, numChannels); 384 | framesPulled += n; 385 | if (imdone) done = true; 386 | minn = std::min(n, minn); 387 | } 388 | 389 | abl.mBuffers[0].mDataByteSize = minn * sizeof(float); 390 | OSStatus err = ExtAudioFileWrite(xaf, minn, &abl); 391 | if (err) { 392 | post("ExtAudioFileWrite failed %d\n", (int)err); 393 | break; 394 | } 395 | 396 | framesWritten += minn; 397 | } 398 | 399 | post("wrote file '%s' %d channels %g secs\n", path, numChannels, framesWritten * th.rate.invSampleRate); 400 | 401 | ExtAudioFileDispose(xaf); 402 | 403 | if (openIt) { 404 | char cmd[1100]; 405 | snprintf(cmd, 1100, "open \"%s\"", path); 406 | system(cmd); 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/Spectrogram.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Spectrogram.hpp" 18 | #include "makeImage.hpp" 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | static void makeColorTable(unsigned char* table); 25 | 26 | static double bessi0(double x) 27 | { 28 | //returns the modified Bessel function I_0(x) for any real x 29 | //from numerical recipes 30 | double ax, ans; 31 | double y; 32 | 33 | if((ax=fabs(x))<3.75){ 34 | y=x/3.75; 35 | y *= y; 36 | ans =1.0+y*(3.5156229+y*(3.0899424+y*(1.2067492 37 | +y*(0.2659732+y*(0.360768e-1+y*0.45813e-2))))); 38 | } 39 | else{ 40 | y=3.75/ax; 41 | ans = (exp(ax)/sqrt(ax))*(0.39894228+y*(0.1328592e-1 42 | +y*(0.225319e-2+y*(-0.157565e-2+y*(0.916281e-2 43 | +y*(-0.2057706e-1+y*(0.2635537e-1+y*(-0.1647633e-1 44 | +y*0.392377e-2)))))))); 45 | } 46 | 47 | return ans; 48 | } 49 | 50 | static double i0(double x) 51 | { 52 | const double epsilon = 1e-18; 53 | int n = 1; 54 | double S = 1., D = 1., T; 55 | 56 | while (D > epsilon * S) { 57 | T = x / (2 * n++); 58 | D *= T * T; 59 | S += D; 60 | } 61 | return S; 62 | } 63 | 64 | static void calcKaiserWindowD(size_t size, double* window, double stopBandAttenuation) 65 | { 66 | size_t M = size - 1; 67 | size_t N = M-1; 68 | #if VERBOSE 69 | printf("FillKaiser %d %g\n", M, stopBandAttenuation); 70 | #endif 71 | 72 | double alpha = 0.; 73 | if (stopBandAttenuation <= -50.) 74 | alpha = 0.1102 * (-stopBandAttenuation - 8.7); 75 | else if (stopBandAttenuation < -21.) 76 | alpha = 0.5842 * pow(-stopBandAttenuation - 21., 0.4) + 0.07886 * (-stopBandAttenuation - 21.); 77 | 78 | double p = N / 2; 79 | double kk = 1.0 / i0(alpha); 80 | 81 | for(unsigned int k = 0; k < M; k++ ) 82 | { 83 | double x = (k-p) / p; 84 | 85 | // Kaiser window 86 | window[k+1] *= kk * bessi0(alpha * sqrt(1.0 - x*x) ); 87 | } 88 | window[0] = 0.; 89 | window[size-1] = 0.; 90 | #if VERBOSE 91 | printf("done\n"); 92 | #endif 93 | } 94 | 95 | const int border = 8; 96 | 97 | void spectrogram(int size, double* data, int width, int log2bins, const char* path, double dBfloor) 98 | { 99 | int numRealFreqs = 1 << log2bins; 100 | 101 | int log2n = log2bins + 1; 102 | int n = 1 << log2n; 103 | int nOver2 = n / 2; 104 | 105 | 106 | double scale = 1./nOver2; 107 | 108 | int64_t paddedSize = size + n; 109 | double* paddedData = (double*)calloc(paddedSize, sizeof(double)); 110 | memcpy(paddedData + nOver2, data, size * sizeof(double)); 111 | 112 | 113 | double* dBMags = (double*)calloc(numRealFreqs + 1, sizeof(double)); 114 | 115 | double hopSize = size <= n ? 0 : (double)(size - n) / (double)(width - 1); 116 | 117 | double* window = (double*)calloc(n, sizeof(double)); 118 | for (int i = 0; i < n; ++i) window[i] = 1.; 119 | calcKaiserWindowD(n, window, -180.); 120 | 121 | unsigned char table[1028]; 122 | makeColorTable(table); 123 | 124 | int heightOfAmplitudeView = 128; 125 | int heightOfFFT = numRealFreqs+1; 126 | int totalHeight = heightOfAmplitudeView+heightOfFFT+3*border; 127 | int topOfSpectrum = heightOfAmplitudeView + 2*border; 128 | int totalWidth = width+2*border; 129 | Bitmap* b = createBitmap(totalWidth, totalHeight); 130 | fillRect(b, 0, 0, totalWidth, totalHeight, 160, 160, 160, 255); 131 | fillRect(b, border, border, width, heightOfAmplitudeView, 0, 0, 0, 255); 132 | 133 | FFTSetupD fftSetup = vDSP_create_fftsetupD(log2n, kFFTRadix2); 134 | 135 | double* windowedData = (double*)calloc(n, sizeof(double)); 136 | double* interleavedData = (double*)calloc(n, sizeof(double)); 137 | double* resultData = (double*)calloc(n, sizeof(double)); 138 | DSPDoubleSplitComplex interleaved; 139 | interleaved.realp = interleavedData; 140 | interleaved.imagp = interleavedData + nOver2; 141 | DSPDoubleSplitComplex result; 142 | result.realp = resultData; 143 | result.imagp = resultData + nOver2; 144 | double maxmag = 0.; 145 | 146 | double hpos = nOver2; 147 | for (int i = 0; i < width; ++i) { 148 | size_t ihpos = (size_t)hpos; 149 | 150 | // do analysis 151 | // find peak 152 | double peak = 1e-20; 153 | for (int w = 0; w < n; ++w) { 154 | double x = paddedData[w+ihpos]; 155 | x = fabs(x); 156 | if (x > peak) peak = x; 157 | } 158 | 159 | for (int64_t w = 0; w < n; ++w) windowedData[w] = window[w] * paddedData[w+ihpos]; 160 | 161 | vDSP_ctozD((DSPDoubleComplex*)windowedData, 2, &interleaved, 1, nOver2); 162 | 163 | vDSP_fft_zropD(fftSetup, &interleaved, 1, &result, 1, log2n, kFFTDirection_Forward); 164 | 165 | dBMags[0] = result.realp[0] * scale; 166 | dBMags[numRealFreqs] = result.imagp[0] * scale; 167 | if (dBMags[0] > maxmag) maxmag = dBMags[0]; 168 | if (dBMags[numRealFreqs] > maxmag) maxmag = dBMags[numRealFreqs]; 169 | for (int64_t j = 1; j < numRealFreqs-1; ++j) { 170 | double x = result.realp[j] * scale; 171 | double y = result.imagp[j] * scale; 172 | dBMags[j] = sqrt(x*x + y*y); 173 | if (dBMags[j] > maxmag) maxmag = dBMags[j]; 174 | } 175 | 176 | double invmag = 1.; 177 | dBMags[0] = 20.*log2(dBMags[0]*invmag); 178 | dBMags[numRealFreqs] = 20.*log10(dBMags[numRealFreqs]*invmag); 179 | for (int64_t j = 0; j <= numRealFreqs-1; ++j) { 180 | dBMags[j] = 20.*log10(dBMags[j]*invmag); 181 | } 182 | 183 | 184 | 185 | // set pixels 186 | { 187 | double peakdB = 20.*log10(peak); 188 | int peakColorIndex = 256. - peakdB * (256. / dBfloor); 189 | int peakIndex = heightOfAmplitudeView - peakdB * (heightOfAmplitudeView / dBfloor); 190 | if (peakIndex < 0) peakIndex = 0; 191 | if (peakIndex > heightOfAmplitudeView) peakIndex = heightOfAmplitudeView; 192 | if (peakColorIndex < 0) peakColorIndex = 0; 193 | if (peakColorIndex > 255) peakColorIndex = 255; 194 | 195 | unsigned char* t = table + 4*peakColorIndex; 196 | fillRect(b, i+border, border+128-peakIndex, 1, peakIndex, t[0], t[1], t[2], t[3]); 197 | } 198 | 199 | for (int j = 0; j < numRealFreqs; ++j) { 200 | int colorIndex = 256. - dBMags[j] * (256. / dBfloor); 201 | if (colorIndex < 0) colorIndex = 0; 202 | if (colorIndex > 255) colorIndex = 255; 203 | 204 | unsigned char* t = table + 4*colorIndex; 205 | 206 | setPixel(b, i+border, numRealFreqs-j+topOfSpectrum, t[0], t[1], t[2], t[3]); 207 | } 208 | 209 | hpos += hopSize; 210 | } 211 | 212 | vDSP_destroy_fftsetupD(fftSetup); 213 | 214 | writeBitmap(b, path); 215 | freeBitmap(b); 216 | free(dBMags); 217 | free(paddedData); 218 | free(window); 219 | free(windowedData); 220 | free(interleavedData); 221 | free(resultData); 222 | } 223 | 224 | 225 | static void makeColorTable(unsigned char* table) 226 | { 227 | // white >> red >> yellow >> green >> cyan >> blue >> magenta >> pink >> black 228 | // 0 -20 -40 -60 -80 -100 -120 -140 -160 229 | // 255 224 192 160 128 96 64 32 0 230 | 231 | int colors[9][4] = { 232 | { 0, 0, 64, 255}, // dk blue 233 | { 0, 0, 255, 255}, // blue 234 | {255, 0, 0, 255}, // red 235 | {255, 255, 0, 255}, // yellow 236 | {255, 255, 255, 255} // white 237 | }; 238 | 239 | for (int j = 0; j < 4; ++j) { 240 | for (int i = 0; i < 64; ++i) { 241 | for (int k = 0; k < 4; ++k) { 242 | int x = (colors[j][k] * (64 - i) + colors[j+1][k] * i) / 64; 243 | if (x > 255) x = 255; 244 | table[j*64*4 + i*4 + k + 4] = x; 245 | } 246 | } 247 | } 248 | 249 | table[0] = 0; 250 | table[1] = 0; 251 | table[2] = 0; 252 | table[3] = 255; 253 | } 254 | 255 | -------------------------------------------------------------------------------- /src/Types.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "Types.hpp" 18 | -------------------------------------------------------------------------------- /src/dsp.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "dsp.hpp" 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | FFTSetupD fftSetups[kMaxFFTLogSize+1]; 24 | 25 | void initFFT() 26 | { 27 | for (int i = kMinFFTLogSize; i <= kMaxFFTLogSize; ++i) { 28 | fftSetups[i] = vDSP_create_fftsetupD(i, kFFTRadix2); 29 | } 30 | } 31 | 32 | void fft(int n, double* inReal, double* inImag, double* outReal, double* outImag) 33 | { 34 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 35 | 36 | DSPDoubleSplitComplex in; 37 | DSPDoubleSplitComplex out; 38 | 39 | in.realp = inReal; 40 | in.imagp = inImag; 41 | out.realp = outReal; 42 | out.imagp = outImag; 43 | 44 | vDSP_fft_zopD(fftSetups[log2n], &in, 1, &out, 1, log2n, FFT_FORWARD); 45 | 46 | double scale = 2. / n; 47 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n); 48 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, n); 49 | } 50 | 51 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag) 52 | { 53 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 54 | 55 | DSPDoubleSplitComplex in; 56 | DSPDoubleSplitComplex out; 57 | 58 | in.realp = inReal; 59 | in.imagp = inImag; 60 | out.realp = outReal; 61 | out.imagp = outImag; 62 | 63 | vDSP_fft_zopD(fftSetups[log2n], &in, 1, &out, 1, log2n, FFT_INVERSE); 64 | 65 | double scale = .5; 66 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n); 67 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, n); 68 | } 69 | 70 | void fft(int n, double* ioReal, double* ioImag) 71 | { 72 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 73 | 74 | DSPDoubleSplitComplex io; 75 | 76 | io.realp = ioReal; 77 | io.imagp = ioImag; 78 | 79 | vDSP_fft_zipD(fftSetups[log2n], &io, 1, log2n, FFT_FORWARD); 80 | 81 | double scale = 2. / n; 82 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, n); 83 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, n); 84 | } 85 | 86 | void ifft(int n, double* ioReal, double* ioImag) 87 | { 88 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 89 | 90 | DSPDoubleSplitComplex io; 91 | 92 | io.realp = ioReal; 93 | io.imagp = ioImag; 94 | 95 | vDSP_fft_zipD(fftSetups[log2n], &io, 1, log2n, FFT_INVERSE); 96 | 97 | double scale = .5; 98 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, n); 99 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, n); 100 | } 101 | 102 | 103 | void rfft(int n, double* inReal, double* outReal, double* outImag) 104 | { 105 | int n2 = n/2; 106 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 107 | 108 | DSPDoubleSplitComplex in; 109 | DSPDoubleSplitComplex out; 110 | 111 | vDSP_ctozD((DSPDoubleComplex*)inReal, 1, &in, 1, n2); 112 | 113 | out.realp = outReal; 114 | out.imagp = outImag; 115 | 116 | vDSP_fft_zropD(fftSetups[log2n], &in, 1, &out, 1, log2n, FFT_FORWARD); 117 | 118 | double scale = 2. / n; 119 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n2); 120 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, n2); 121 | 122 | out.realp[n2] = out.imagp[0]; 123 | out.imagp[0] = 0.; 124 | out.imagp[n2] = 0.; 125 | } 126 | 127 | 128 | void rifft(int n, double* inReal, double* inImag, double* outReal) 129 | { 130 | int n2 = n/2; 131 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1); 132 | 133 | DSPDoubleSplitComplex in; 134 | 135 | in.realp = inReal; 136 | in.imagp = inImag; 137 | 138 | //in.imagp[0] = in.realp[n2]; 139 | in.imagp[0] = 0.; 140 | 141 | vDSP_fft_zripD(fftSetups[log2n], &in, 1, log2n, FFT_INVERSE); 142 | 143 | vDSP_ztocD(&in, 1, (DSPDoubleComplex*)outReal, 2, n2); 144 | 145 | double scale = .5; 146 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n); 147 | } 148 | 149 | 150 | #define USE_VFORCE 1 151 | 152 | inline void complex_expD_conj(double& re, double& im) 153 | { 154 | double rho = expf(re); 155 | re = rho * cosf(im); 156 | im = rho * sinf(im); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/elapsedTime.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "elapsedTime.hpp" 18 | #include 19 | #include 20 | 21 | extern "C" { 22 | 23 | static double gHostClockFreq; 24 | 25 | void initElapsedTime() 26 | { 27 | struct mach_timebase_info info; 28 | mach_timebase_info(&info); 29 | gHostClockFreq = 1e9 * ((double)info.numer / (double)info.denom); 30 | } 31 | 32 | double elapsedTime() 33 | { 34 | return (double)mach_absolute_time() / gHostClockFreq; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "VM.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "primes.hpp" 23 | #include 24 | #include 25 | #include 26 | #include "Manta.h" 27 | 28 | class MyManta : public Manta 29 | { 30 | virtual void PadEvent(int row, int column, int id, int value) { 31 | printf("pad %d %d %d %d\n", row, column, id, value); 32 | } 33 | virtual void SliderEvent(int id, int value) { 34 | printf("slider %d %d\n", id, value); 35 | } 36 | virtual void ButtonEvent(int id, int value) { 37 | printf("button %d %d\n", id, value); 38 | } 39 | virtual void PadVelocityEvent(int row, int column, int id, int velocity) { 40 | printf("pad vel %d %d %d %d\n", row, column, id, velocity); 41 | 42 | } 43 | virtual void ButtonVelocityEvent(int id, int velocity) { 44 | printf("button vel %d %d\n", id, velocity); 45 | } 46 | virtual void FrameEvent(uint8_t *frame) {} 47 | virtual void DebugPrint(const char *fmt, ...) {} 48 | }; 49 | 50 | Manta* manta(); 51 | Manta* manta() 52 | { 53 | static MyManta* sManta = new MyManta(); 54 | return sManta; 55 | } 56 | 57 | /* issue: 58 | 59 | [These comments are very old and I have not checked if they are still relevant.] 60 | 61 | TableData alloc should use new 62 | 63 | bugs: 64 | 65 | itd should have a tail time. currently the ugen stops as soon as its input, cutting off the delayed signal. 66 | 67 | + should not stop until both inputs stop? 68 | other additive binops: - avg2 sumsq 69 | 70 | no, use a operator 71 | 72 | --- 73 | 74 | adsrg (gate a d s r --> out) envelope generator with gate. 75 | adsr (dur a d s r --> out) envelope generator with duration. 76 | evgg - (gate levels times curves suspt --> out) envelope generator with gate. suspt is the index of the sustain level. 77 | evg - (dur levels times curves suspt --> out) envelope generator with duration. suspt is the index of the sustain level. 78 | 79 | blip (freq phase nharm --> out) band limited impulse oscillator. 80 | dsf1 (freq phase nharm lharm hmul --> out) sum of sines oscillator. 81 | 82 | formant (freq formfreq bwfreq --> out) formant oscillator 83 | 84 | svf (in freq rq --> [lp hp bp bs]) state variable filter. 85 | moogf (in freq rq --> out) moog ladder low pass filter. 86 | 87 | */ 88 | 89 | extern void AddCoreOps(); 90 | extern void AddMathOps(); 91 | extern void AddStreamOps(); 92 | extern void AddLFOps(); 93 | extern void AddUGenOps(); 94 | extern void AddSetOps(); 95 | extern void AddRandomOps(); 96 | extern void AddMidiOps(); 97 | 98 | const char* gVersionString = "0.1.21"; 99 | 100 | static void usage() 101 | { 102 | fprintf(stdout, "sapf [-r sample-rate][-p prelude-file]\n"); 103 | fprintf(stdout, "\n"); 104 | fprintf(stdout, "sapf [-h]\n"); 105 | fprintf(stdout, " print this help\n"); 106 | fprintf(stdout, "\n"); 107 | } 108 | 109 | int main (int argc, const char * argv[]) 110 | { 111 | post("------------------------------------------------\n"); 112 | post("A tool for the expression of sound as pure form.\n"); 113 | post("------------------------------------------------\n"); 114 | post("--- version %s\n", gVersionString); 115 | 116 | for (int i = 1; i < argc;) { 117 | int c = argv[i][0]; 118 | if (c == '-') { 119 | c = argv[i][1]; 120 | switch (c) { 121 | case 'r' : { 122 | if (argc <= i+1) { post("expected sample rate after -r\n"); return 1; } 123 | 124 | double sr = atof(argv[i+1]); 125 | if (sr < 1000. || sr > 768000.) { post("sample rate out of range.\n"); return 1; } 126 | vm.setSampleRate(sr); 127 | post("sample rate set to %g\n", vm.ar.sampleRate); 128 | i += 2; 129 | } break; 130 | case 'p' : { 131 | if (argc <= i+1) { post("expected prelude file name after -p\n"); return 1; } 132 | vm.prelude_file = argv[i+1]; 133 | i += 2; 134 | } break; 135 | case 'h' : { 136 | usage(); 137 | exit(0); 138 | } break; 139 | default: 140 | post("unrecognized option -%c\n", c); 141 | } 142 | } else { 143 | post("expected option, got \"%s\"\n", argv[i]); 144 | ++i; 145 | } 146 | } 147 | 148 | 149 | vm.addBifHelp("Argument Automapping legend:"); 150 | vm.addBifHelp(" a - as is. argument is not automapped."); 151 | vm.addBifHelp(" z - argument is expected to be a signal or scalar, streams are auto mapped."); 152 | vm.addBifHelp(" k - argument is expected to be a scalar, signals and streams are automapped."); 153 | vm.addBifHelp(""); 154 | 155 | AddCoreOps(); 156 | AddMathOps(); 157 | AddStreamOps(); 158 | AddRandomOps(); 159 | AddUGenOps(); 160 | AddMidiOps(); 161 | AddSetOps(); 162 | 163 | 164 | vm.log_file = getenv("SAPF_LOG"); 165 | if (!vm.log_file) { 166 | const char* home_dir = getenv("HOME"); 167 | char logfilename[PATH_MAX]; 168 | snprintf(logfilename, PATH_MAX, "%s/sapf-log.txt", home_dir); 169 | vm.log_file = strdup(logfilename); 170 | } 171 | 172 | __block Thread th; 173 | 174 | auto m = manta(); 175 | try { 176 | m->Connect(); 177 | } catch(...) { 178 | } 179 | printf("Manta %s connected.\n", m->IsConnected() ? "is" : "IS NOT"); 180 | 181 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 182 | /*** see at bottom for better way ***/ 183 | while(true) { 184 | try { 185 | MantaUSB::HandleEvents(); 186 | usleep(5000); 187 | } catch(...) { 188 | sleep(1); 189 | } 190 | } 191 | }); 192 | 193 | if (!vm.prelude_file) { 194 | vm.prelude_file = getenv("SAPF_PRELUDE"); 195 | } 196 | if (vm.prelude_file) { 197 | loadFile(th, vm.prelude_file); 198 | } 199 | 200 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 201 | th.repl(stdin, vm.log_file); 202 | exit(0); 203 | }); 204 | 205 | CFRunLoopRun(); 206 | 207 | return 0; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /src/makeImage.mm: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #import "makeImage.hpp" 18 | #import 19 | 20 | struct Bitmap 21 | { 22 | NSBitmapImageRep* rep; 23 | unsigned char* data; 24 | int bytesPerRow; 25 | }; 26 | 27 | Bitmap* createBitmap(int width, int height) 28 | { 29 | Bitmap* bitmap = (Bitmap*)calloc(1, sizeof(Bitmap)); 30 | bitmap->rep = 31 | [[NSBitmapImageRep alloc] 32 | initWithBitmapDataPlanes: nullptr 33 | pixelsWide: width 34 | pixelsHigh: height 35 | bitsPerSample: 8 36 | samplesPerPixel: 4 37 | hasAlpha: YES 38 | isPlanar: NO 39 | colorSpaceName: NSCalibratedRGBColorSpace 40 | bitmapFormat: NSAlphaNonpremultipliedBitmapFormat 41 | bytesPerRow: 0 42 | bitsPerPixel: 32 43 | ]; 44 | 45 | bitmap->data = [bitmap->rep bitmapData]; 46 | bitmap->bytesPerRow = (int)[bitmap->rep bytesPerRow]; 47 | return bitmap; 48 | } 49 | 50 | void setPixel(Bitmap* bitmap, int x, int y, int r, int g, int b, int a) 51 | { 52 | size_t index = bitmap->bytesPerRow * y + 4 * x; 53 | unsigned char* data = bitmap->data; 54 | 55 | data[index+0] = r; 56 | data[index+1] = g; 57 | data[index+2] = b; 58 | data[index+3] = a; 59 | } 60 | 61 | void fillRect(Bitmap* bitmap, int x, int y, int width, int height, int r, int g, int b, int a) 62 | { 63 | unsigned char* data = bitmap->data; 64 | for (int j = y; j < y + height; ++j) { 65 | size_t index = bitmap->bytesPerRow * j + 4 * x; 66 | for (int i = x; i < x + width; ++i) { 67 | data[index+0] = r; 68 | data[index+1] = g; 69 | data[index+2] = b; 70 | data[index+3] = a; 71 | index += 4; 72 | } 73 | } 74 | } 75 | 76 | void writeBitmap(Bitmap* bitmap, const char *path) 77 | { 78 | //NSData* data = [bitmap->rep TIFFRepresentation]; 79 | //NSDictionary* properties = @{ NSImageCompressionFactor: @.9 }; 80 | NSDictionary* properties = nullptr; 81 | NSData* data = [bitmap->rep representationUsingType: NSJPEGFileType properties: properties]; 82 | NSString* nsstr = [NSString stringWithUTF8String: path]; 83 | [data writeToFile: nsstr atomically: YES]; 84 | } 85 | 86 | void freeBitmap(Bitmap* bitmap) 87 | { 88 | //[bitmap->rep release]; 89 | free(bitmap); 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/symbol.cpp: -------------------------------------------------------------------------------- 1 | // SAPF - Sound As Pure Form 2 | // Copyright (C) 2019 James McCartney 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "symbol.hpp" 18 | #include "VM.hpp" 19 | #include "Hash.hpp" 20 | #include 21 | #include 22 | 23 | const int kSymbolTableSize = 4096; 24 | const int kSymbolTableMask = kSymbolTableSize - 1; 25 | 26 | // global atomic symbol table 27 | volatile std::atomic sSymbolTable[kSymbolTableSize]; 28 | 29 | 30 | 31 | static String* SymbolTable_lookup(String* list, const char* name, int32_t hash) 32 | { 33 | while (list) { 34 | if (list->hash == hash && strcmp(list->s, name) == 0) 35 | return list; 36 | list = list->nextSymbol; 37 | } 38 | return nullptr; 39 | } 40 | 41 | static String* SymbolTable_lookup(const char* name, int hash) 42 | { 43 | return SymbolTable_lookup(sSymbolTable[hash & kSymbolTableMask].load(), name, hash); 44 | } 45 | 46 | static String* SymbolTable_lookup(const char* name) 47 | { 48 | uintptr_t hash = Hash(name); 49 | return SymbolTable_lookup(name, (int)hash); 50 | } 51 | 52 | P getsym(const char* name) 53 | { 54 | // thread safe 55 | 56 | int32_t hash = Hash(name); 57 | int32_t binIndex = hash & kSymbolTableMask; 58 | volatile std::atomic* bin = &sSymbolTable[binIndex]; 59 | while (1) { 60 | // get the head of the list. 61 | String* head = bin->load(); 62 | // search the list for the symbol 63 | String* existingSymbol = head; 64 | while (existingSymbol) { 65 | if (existingSymbol->hash == hash && strcmp(existingSymbol->s, name) == 0) { 66 | return existingSymbol; 67 | } 68 | existingSymbol = existingSymbol->nextSymbol; 69 | } 70 | String* newSymbol = new String(name, hash, head); 71 | if (bin->compare_exchange_weak(head, newSymbol)) { 72 | newSymbol->retain(); 73 | return newSymbol; 74 | } 75 | delete newSymbol; 76 | } 77 | } 78 | --------------------------------------------------------------------------------