& 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 playWithPlayer(Thread& th, V& v);
20 | void recordWithPlayer(Thread& th, V& v, Arg filename);
21 |
22 | void stopPlaying();
23 | void stopPlayingIfDone();
24 |
25 |
--------------------------------------------------------------------------------
/include/PortableBuffers.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifndef SAPF_AUDIOTOOLBOX
4 | #include
5 | #include
6 |
7 | #include "VM.hpp"
8 |
9 | struct PortableBuffer {
10 | uint32_t numChannels;
11 | uint32_t size;
12 | void *data;
13 |
14 | PortableBuffer();
15 | };
16 |
17 | class PortableBuffers {
18 | public:
19 | PortableBuffers(int inNumChannels);
20 | ~PortableBuffers();
21 |
22 | uint32_t numChannels();
23 | void setNumChannels(size_t i, uint32_t numChannels);
24 | void setData(size_t i, void *data);
25 | void setSize(size_t i, uint32_t size);
26 |
27 | std::vector buffers;
28 | std::vector interleaved;
29 | };
30 | #endif // SAPF_AUDIOTOOLBOX
31 |
--------------------------------------------------------------------------------
/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/SndfileSoundFile.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifndef SAPF_AUDIOTOOLBOX
4 | #include "PortableBuffers.hpp"
5 |
6 | #include
7 | #include
8 | #include
9 | #include "AsyncAudioFileWriter.hpp"
10 |
11 | class SndfileSoundFile {
12 | public:
13 | SndfileSoundFile(std::string path, std::unique_ptr writer,
14 | SNDFILE *inSndfile, int inNumChannels);
15 | ~SndfileSoundFile();
16 |
17 | uint32_t numChannels() const;
18 | int pull(uint32_t *framesRead, PortableBuffers& buffers);
19 | // write to file synchronously (blocking). Only functions if create was called with async=false
20 | // bufs is expected to contain only a single buffer with the specified number of channels
21 | // and the buffer data already interleaved (for wav output), as floats. It should have
22 | // the exact amount of frames as indicated by numFrames.
23 | void write(int numFrames, const PortableBuffers& bufs) const;
24 |
25 | // write to file asynchronously (non-blocking). Only functions if create was called with async=true
26 | // captures the current data in the buffers and submits it to be written asynchronously.
27 | // will be flushed when this object is destructed.
28 | void writeAsync(const RtBuffers& buffers, unsigned int nBufferFrames) const;
29 |
30 | static std::unique_ptr open(const char *path);
31 | // async parameter determines whether async writing should be supported (writeAsync) or not (write).
32 | // they are mutually exclusive.
33 | static std::unique_ptr create(const char *path, int numChannels, double threadSampleRate,
34 | double fileSampleRate, bool async);
35 | private:
36 | // file path
37 | const std::string mPath;
38 |
39 | // for async output (recording)
40 | const std::unique_ptr mWriter;
41 |
42 | // for synchronous input / output
43 | SNDFILE* const mSndfile;
44 | // TODO: actually used for output?
45 | const int mNumChannels;
46 | };
47 | #endif // SAPF_AUDIOTOOLBOX
48 |
--------------------------------------------------------------------------------
/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 |
22 | #include "AudioToolboxBuffers.hpp"
23 | #include "PortableBuffers.hpp"
24 | #ifdef SAPF_AUDIOTOOLBOX
25 | typedef AudioToolboxBuffers AudioBuffers;
26 | #else
27 | typedef PortableBuffers AudioBuffers;
28 | #endif
29 |
30 | #include "AudioToolboxSoundFile.hpp"
31 | #include "SndfileSoundFile.hpp"
32 | #ifdef SAPF_AUDIOTOOLBOX
33 | typedef AudioToolboxSoundFile SoundFile;
34 | #else
35 | typedef SndfileSoundFile SoundFile;
36 | #endif
37 |
38 | const int kMaxSFChannels = 1024;
39 | const int kBufSize = 1024;
40 |
41 | void makeRecordingPath(Arg filename, char* path, int len);
42 |
43 | #ifdef SAPF_AUDIOTOOLBOX
44 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved);
45 | #else
46 | // async indicates whether async writing should be supported, or only synchronous writing. They are mutually exclusive.
47 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved, bool async);
48 | #endif
49 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt);
50 | void sfread(Thread& th, Arg filename, int64_t offset, int64_t frames);
51 |
52 | #endif /* defined(__taggeddoubles__SoundFiles__) */
53 |
--------------------------------------------------------------------------------
/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/StreamOps.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Object.hpp"
4 | #include "ZArr.hpp"
5 |
6 | void hanning_(Thread& th, Prim* prim);
7 | void hamming_(Thread& th, Prim* prim);
8 | void blackman_(Thread& th, Prim* prim);
9 | #ifdef SAPF_ACCELERATE
10 | void wseg_apply_window(Z* segbuf, Z* window, int n);
11 | #else
12 | void wseg_apply_window(Z* segbuf, ZArr window, int n);
13 | #endif
--------------------------------------------------------------------------------
/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/ZArr.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 __ZArr_h__
18 | #define __ZArr_h__
19 | #ifndef SAPF_ACCELERATE
20 | #define _USE_MATH_DEFINES
21 | #include
22 | #include "Object.hpp"
23 | #include
24 |
25 | #if SAMPLE_IS_DOUBLE
26 | typedef Eigen::Map> ZArr;
27 | typedef Eigen::Map> CZArr;
28 | typedef xsimd::batch ZBatch;
29 | typedef int64_t Z_INT_EQUIV;
30 | #else
31 | typedef Eigen::Map> ZArr;
32 | typedef Eigen::Map> CZArr;
33 | typedef xsimd::batch ZBatch;
34 | typedef int32_t Z_INT_EQUIV;
35 | #endif
36 |
37 | constexpr size_t zbatch_size = ZBatch::size;
38 |
39 | // create mutable Eigen Map over an existing array, without copying
40 | ZArr zarr(Z *vec, int n, int stride);
41 |
42 | // create immutable Eigen Map over an existing array, without copying
43 | CZArr czarr(const Z *vec, int n, int stride);
44 |
45 | #endif
46 | #endif
--------------------------------------------------------------------------------
/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 |
29 |
30 | static int32_t CLZ( int32_t arg )
31 | {
32 | if (arg == 0) return 32;
33 | return __builtin_clz(arg);
34 | }
35 |
36 |
37 | static int64_t CLZ( int64_t arg )
38 | {
39 | if (arg == 0) return 64;
40 | return __builtin_clzll(arg);
41 | }
42 |
43 |
44 |
45 | // count trailing zeroes
46 | inline int32_t CTZ(int32_t x)
47 | {
48 | return 32 - CLZ(~x & (x-1));
49 | }
50 |
51 | // count leading ones
52 | inline int32_t CLO(int32_t x)
53 | {
54 | return CLZ(~x);
55 | }
56 |
57 | // count trailing ones
58 | inline int32_t CTO(int32_t x)
59 | {
60 | return 32 - CLZ(x & (~x-1));
61 | }
62 |
63 | // number of bits required to represent x.
64 | inline int32_t NUMBITS(int32_t x)
65 | {
66 | return 32 - CLZ(x);
67 | }
68 |
69 | // log2 of the next power of two greater than or equal to x.
70 | inline int32_t LOG2CEIL(int32_t x)
71 | {
72 | return 32 - CLZ(x - 1);
73 | }
74 |
75 | // log2 of the next power of two greater than or equal to x.
76 | inline int64_t LOG2CEIL(int64_t x)
77 | {
78 | return 64 - CLZ(x - 1);
79 | }
80 |
81 | // next power of two greater than or equal to x
82 | inline int32_t NEXTPOWEROFTWO(int32_t x)
83 | {
84 | return int32_t(1) << LOG2CEIL(x);
85 | }
86 |
87 | // next power of two greater than or equal to x
88 | inline int64_t NEXTPOWEROFTWO(int64_t x)
89 | {
90 | return int64_t(1) << LOG2CEIL(x);
91 | }
92 |
93 | // is x a power of two
94 | inline bool ISPOWEROFTWO(int32_t x)
95 | {
96 | return (x & (x-1)) == 0;
97 | }
98 |
99 | inline bool ISPOWEROFTWO64(int64_t x)
100 | {
101 | return (x & (x-1)) == 0;
102 | }
103 |
104 | // input a series of counting integers, outputs a series of gray codes .
105 | inline int32_t GRAYCODE(int32_t x)
106 | {
107 | return x ^ (x>>1);
108 | }
109 |
110 | // find least significant bit
111 | inline int32_t LSBit(int32_t x)
112 | {
113 | return x & -x;
114 | }
115 |
116 | // find least significant bit position
117 | inline int32_t LSBitPos(int32_t x)
118 | {
119 | return CTZ(x & -x);
120 | }
121 |
122 | // find most significant bit position
123 | inline int32_t MSBitPos(int32_t x)
124 | {
125 | return 31 - CLZ(x);
126 | }
127 |
128 | // find most significant bit
129 | inline int32_t MSBit(int32_t x)
130 | {
131 | return int32_t(1) << MSBitPos(x);
132 | }
133 |
134 | // count number of one bits
135 | inline uint32_t ONES(uint32_t x)
136 | {
137 | uint32_t t;
138 | x = x - ((x >> 1) & 0x55555555);
139 | t = ((x >> 2) & 0x33333333);
140 | x = (x & 0x33333333) + t;
141 | x = (x + (x >> 4)) & 0x0F0F0F0F;
142 | x = x + (x << 8);
143 | x = x + (x << 16);
144 | return x >> 24;
145 | }
146 |
147 | // count number of zero bits
148 | inline uint32_t ZEROES(uint32_t x)
149 | {
150 | return ONES(~x);
151 | }
152 |
153 |
154 | // reverse bits in a word
155 | inline uint32_t BitReverse(uint32_t x)
156 | {
157 | x = ((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1);
158 | x = ((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2);
159 | x = ((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4);
160 | x = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8);
161 | return (x >> 16) | (x << 16);
162 | }
163 |
164 | // barrel shifts
165 | inline uint64_t RotateRight (int64_t ix, int64_t s)
166 | {
167 | uint64_t x = ix;
168 | s = s & 63;
169 | return (x << (64-s)) | (x >> s);
170 | }
171 |
172 | inline uint64_t RotateLeft (int64_t ix, int64_t s)
173 | {
174 | uint64_t x = ix;
175 | s = s & 63;
176 | return (x >> (64-s)) | (x << s);
177 | }
178 |
179 | inline uint32_t RotateRight (int32_t ix, int32_t s)
180 | {
181 | uint32_t x = ix;
182 | s = s & 31;
183 | return (x << (32-s)) | (x >> s);
184 | }
185 |
186 | inline uint32_t RotateLeft (int32_t ix, int32_t s)
187 | {
188 | uint32_t x = ix;
189 | s = s & 31;
190 | return (x >> (32-s)) | (x << s);
191 | }
192 |
193 | inline uint8_t RotateRight (int8_t ix, int8_t s)
194 | {
195 | uint8_t x = ix;
196 | s = s & 7;
197 | return (x << (8-s)) | (x >> s);
198 | }
199 |
200 | inline uint8_t RotateLeft (int8_t ix, int8_t s)
201 | {
202 | uint8_t x = ix;
203 | s = s & 7;
204 | return (x >> (8-s)) | (x << s);
205 | }
206 |
207 | #endif
208 |
209 |
--------------------------------------------------------------------------------
/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 | #ifdef SAPF_ACCELERATE
21 | #include
22 | #else
23 | #include
24 | #endif // SAPF_ACCELERATE
25 |
26 | const int kMinFFTLogSize = 2;
27 | const int kMaxFFTLogSize = 16;
28 |
29 | class FFT {
30 | public:
31 | ~FFT();
32 | void init(size_t log2n);
33 | void forward(double *inReal, double *inImag, double *outReal, double *outImag);
34 | void backward(double *inReal, double *inImag, double *outReal, double *outImag);
35 | void forward_in_place(double *ioReal, double *ioImag);
36 | void backward_in_place(double *ioReal, double *ioImag);
37 | void forward_real(double *inReal, double *outReal, double *outImag);
38 | void backward_real(double *inReal, double *inImag, double *outReal);
39 |
40 | size_t n;
41 | size_t log2n;
42 | private:
43 | #ifdef SAPF_ACCELERATE
44 | FFTSetupD setup;
45 | #else
46 | fftw_complex *in;
47 | fftw_complex *out;
48 | double *in_out_real;
49 | fftw_plan forward_out_of_place_plan;
50 | fftw_plan backward_out_of_place_plan;
51 | fftw_plan forward_in_place_plan;
52 | fftw_plan backward_in_place_plan;
53 | fftw_plan forward_real_plan;
54 | fftw_plan backward_real_plan;
55 | #endif // SAPF_ACCELERATE
56 | };
57 |
58 | extern FFT ffts[kMaxFFTLogSize+1];
59 |
60 | void initFFT();
61 | void fft (int n, double* ioReal, double* ioImag);
62 | void ifft(int n, double* ioReal, double* ioImag);
63 |
64 | void fft (int n, double* inReal, double* inImag, double* outReal, double* outImag);
65 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag);
66 |
67 | void rfft(int n, double* inReal, double* outReal, double* outImag);
68 | void rifft(int n, double* inReal, double* inImag, double* outReal);
69 |
70 | #endif /* defined(__taggeddoubles__dsp__) */
71 |
--------------------------------------------------------------------------------
/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/lock.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifdef SAPF_APPLE_LOCK
4 | #include
5 |
6 | typedef os_unfair_lock Lock;
7 |
8 | #define LOCK_DECLARE(name) mutable Lock name = OS_UNFAIR_LOCK_INIT
9 |
10 | class SpinLocker
11 | {
12 | Lock& lock;
13 | public:
14 | SpinLocker(Lock& inLock) : lock(inLock)
15 | {
16 | os_unfair_lock_lock(&lock);
17 | }
18 | ~SpinLocker()
19 | {
20 | os_unfair_lock_unlock(&lock);
21 | }
22 | };
23 |
24 | #else
25 | #include
26 | #include
27 | #include
28 |
29 | typedef std::shared_mutex Lock;
30 |
31 | #define LOCK_DECLARE(name) mutable Lock name
32 |
33 | class SpinLocker
34 | {
35 | std::unique_lock w_lock;
36 | public:
37 | SpinLocker(Lock& inLock) : w_lock(inLock)
38 | {}
39 | ~SpinLocker()
40 | {}
41 | };
42 |
43 | #endif // SAPF_APPLE_LOCK
44 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'sapf',
3 | ['cpp'],
4 | meson_version : '>=1.1'
5 | )
6 |
7 | cpp = meson.get_compiler('cpp')
8 | sources = [
9 | 'src/AudioToolboxBuffers.cpp',
10 | 'src/AudioToolboxSoundFile.cpp',
11 | 'src/CoreOps.cpp',
12 | 'src/DelayUGens.cpp',
13 | 'src/dsp.cpp',
14 | 'src/elapsedTime.cpp',
15 | 'src/ErrorCodes.cpp',
16 | 'src/FilterUGens.cpp',
17 | 'src/MathFuns.cpp',
18 | 'src/MathOps.cpp',
19 | 'src/Midi.cpp',
20 | 'src/MultichannelExpansion.cpp',
21 | 'src/Object.cpp',
22 | 'src/Opcode.cpp',
23 | 'src/OscilUGens.cpp',
24 | 'src/Parser.cpp',
25 | 'src/Play.cpp',
26 | 'src/PortableBuffers.cpp',
27 | 'src/primes.cpp',
28 | 'src/RCObj.cpp',
29 | 'src/RandomOps.cpp',
30 | 'src/SetOps.cpp',
31 | 'src/SndfileSoundFile.cpp',
32 | 'src/SoundFiles.cpp',
33 | 'src/Spectrogram.cpp',
34 | 'src/StreamOps.cpp',
35 | 'src/symbol.cpp',
36 | 'src/Types.cpp',
37 | 'src/UGen.cpp',
38 | 'src/VM.cpp',
39 | 'src/Buffers.cpp',
40 | 'src/AsyncAudioFileWriter.cpp',
41 | 'src/ZArr.cpp',
42 | ]
43 | deps = []
44 | cpp_args = ['-std=c++17']
45 | link_args = []
46 | cpp_compiler = meson.get_compiler('cpp')
47 |
48 | # support clang compilation
49 | if cpp_compiler.get_id() == 'clang'
50 | cpp_args += ['-D_USE_MATH_DEFINES']
51 | link_args += ['-lpthread']
52 | endif
53 |
54 | # libedit
55 | if host_machine.system() == 'windows'
56 | deps += dependency('readline', required: true)
57 | deps += dependency('history', required: true)
58 | cpp_args += ['-DUSE_LIBEDIT=0', '-D_POSIX_THREAD_SAFE_FUNCTIONS']
59 | else
60 | deps += dependency('libedit', required: true)
61 | cpp_args += ['-DUSE_LIBEDIT=1']
62 | endif
63 |
64 | if get_option('asan')
65 | cpp_args += ['-fsanitize=address', '-O1', '-fno-omit-frame-pointer', '-g']
66 | link_args += ['-fsanitize=address']
67 | endif
68 |
69 | if get_option('accelerate')
70 | add_project_arguments('-DSAPF_ACCELERATE', language: ['cpp', 'objcpp'])
71 | link_args += ['-framework', 'Accelerate']
72 | else
73 | deps += dependency('fftw3', required: true, version: '>=3')
74 | deps += dependency('eigen3', required: true)
75 | deps += dependency('xsimd', required: true)
76 | endif
77 |
78 | if get_option('apple_lock')
79 | add_project_arguments('-DSAPF_APPLE_LOCK', language: ['cpp', 'objcpp'])
80 | endif
81 |
82 | if get_option('audiotoolbox')
83 | add_project_arguments('-DSAPF_AUDIOTOOLBOX', language: ['cpp', 'objcpp'])
84 | link_args += ['-framework', 'AudioToolbox']
85 | else
86 | deps += dependency('rtaudio', required: true, version: '>=5.2')
87 | if not cpp.has_header('RtAudio.h') and cpp.has_header('rtaudio/RtAudio.h')
88 | add_project_arguments('-DSAPF_RTAUDIO_H=', language: ['cpp', 'objcpp'])
89 | endif
90 |
91 | deps += dependency('sndfile', required: true)
92 | endif
93 |
94 | if get_option('carbon')
95 | add_project_arguments('-DSAPF_CARBON', language: ['cpp', 'objcpp'])
96 | link_args += ['-framework', 'Carbon']
97 | endif
98 |
99 | if get_option('cocoa')
100 | add_project_arguments('-DSAPF_COCOA', language: ['cpp', 'objcpp'])
101 | sources += ['src/makeImage.mm']
102 | add_languages('objcpp', required : true)
103 | link_args += ['-framework', 'Cocoa']
104 | else
105 | sources += 'src/makeImage.cpp'
106 | endif
107 |
108 | if get_option('corefoundation')
109 | add_project_arguments('-DSAPF_COREFOUNDATION', language: ['cpp', 'objcpp'])
110 | link_args += ['-framework', 'CoreFoundation']
111 | endif
112 |
113 | if get_option('coremidi')
114 | add_project_arguments('-DSAPF_COREMIDI', language: ['cpp', 'objcpp'])
115 | link_args += ['-framework', 'CoreMIDI']
116 | endif
117 |
118 | if get_option('dispatch')
119 | cpp_args += ['-fblocks']
120 | add_project_arguments('-DSAPF_DISPATCH', language: ['cpp', 'objcpp'])
121 | endif
122 |
123 | if get_option('mach_time')
124 | add_project_arguments('-DSAPF_MACH_TIME', language: ['cpp', 'objcpp'])
125 | endif
126 |
127 | if get_option('manta')
128 | add_project_arguments('-DSAPF_MANTA', language: ['cpp', 'objcpp'])
129 | endif
130 |
131 | # main should only be in release build because otherwise it conflicts with the main
132 | # method added by doctest in test builds
133 | release_sources = sources + 'src/main.cpp'
134 |
135 | # build targeting the native system this was built on. If buildtype is debug,
136 | # no target architecture will be specified, to ensure the best debugging experience.
137 | executable(
138 | 'sapf',
139 | release_sources,
140 | include_directories: [include_directories('include')],
141 | dependencies: deps,
142 | cpp_args : cpp_args + (get_option('buildtype').startswith('debug') ? [] : ['-march=native']),
143 | link_args : link_args
144 | )
145 |
146 | # The below builds target specific architectures. Because this program uses
147 | # SIMD, the best performance is achieved when a user uses the most recent architecture supported
148 | # by their CPU. However, users will get the best results (even better performance) if they build the
149 | # library themselves (target `sapf` above with `--buildtype release`)
150 |
151 | # should have maximum compatibility with ancient x86_64 cpus
152 | executable(
153 | 'sapf_x86_64',
154 | release_sources,
155 | include_directories: [include_directories('include')],
156 | dependencies: deps,
157 | cpp_args : cpp_args + ['-march=x86-64'],
158 | link_args : link_args,
159 | build_by_default: false
160 | )
161 |
162 | # for somewhat older x86 CPUs (2008ish)
163 | executable(
164 | 'sapf_x86_64_v2',
165 | release_sources,
166 | include_directories: [include_directories('include')],
167 | dependencies: deps,
168 | cpp_args : cpp_args + ['-march=x86-64-v2'],
169 | link_args : link_args,
170 | build_by_default: false
171 | )
172 |
173 | # for more cutting edge x86 cpus
174 | executable(
175 | 'sapf_x86_64_v3',
176 | release_sources,
177 | include_directories: [include_directories('include')],
178 | dependencies: deps,
179 | cpp_args : cpp_args + ['-march=x86-64-v3'],
180 | link_args : link_args,
181 | build_by_default: false
182 | )
183 |
184 | # support M1 chip and newer
185 | executable(
186 | 'sapf_arm_m1',
187 | release_sources,
188 | include_directories: [include_directories('include')],
189 | dependencies: deps,
190 | cpp_args : cpp_args + ['-march=armv8.4-a', '-mcpu=apple-m1'],
191 | link_args : link_args,
192 | build_by_default: false
193 | )
194 |
195 | # support M3 chip
196 | executable(
197 | 'sapf_arm_m3',
198 | release_sources,
199 | include_directories: [include_directories('include')],
200 | dependencies: deps,
201 | cpp_args : cpp_args + ['-march=armv8.5-a', '-mcpu=apple-m3'],
202 | link_args : link_args,
203 | build_by_default: false
204 | )
205 |
206 | # tests
207 | test_deps = deps + dependency('doctest', required: true)
208 | test_sources = sources + [
209 | 'test/doctest.cpp',
210 | 'test/test_MathOps.cpp',
211 | 'test/test_AsyncAudioFileWriter.cpp',
212 | 'test/test_SndfileSoundFile.cpp',
213 | 'test/test_OscilUgens.cpp',
214 | 'test/test_StreamOps.cpp',
215 | 'test/test_MathOps.cpp'
216 | ]
217 | test_includes = [include_directories('include'), include_directories('test/helpers')]
218 | test_cpp_args = cpp_args + '-DTEST_BUILD'
219 |
220 | # Just like the main executable, this respects the buildtype param. The target architecture is
221 | # omitted if it's a debug build, ensuring the best debugging experience
222 | test_sapf = executable(
223 | 'test_sapf',
224 | test_sources,
225 | include_directories: test_includes,
226 | dependencies: test_deps,
227 | cpp_args : test_cpp_args + (get_option('buildtype').startswith('debug') ? [] : ['-march=native']),
228 | link_args : link_args,
229 | build_by_default: false
230 | )
231 |
232 | test('all', test_sapf)
--------------------------------------------------------------------------------
/meson.options:
--------------------------------------------------------------------------------
1 | option('accelerate', type : 'boolean', value : false)
2 | option('apple_lock', type : 'boolean', value : false)
3 | option('asan', type : 'boolean', value : false)
4 | option('audiotoolbox', type : 'boolean', value : false)
5 | option('carbon', type : 'boolean', value : false)
6 | option('cocoa', type : 'boolean', value : false)
7 | option('corefoundation', type : 'boolean', value : false)
8 | option('coremidi', type : 'boolean', value : false)
9 | option('dispatch', type : 'boolean', value : false)
10 | option('mach_time', type : 'boolean', value : false)
11 | option('manta', type : 'boolean', value : false)
12 |
--------------------------------------------------------------------------------
/nix/libdispatch/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | cmake,
3 | lib,
4 | fetchFromGitHub,
5 | stdenv,
6 | }:
7 |
8 | stdenv.mkDerivation (finalAttrs: {
9 | pname = "libdispatch";
10 | version = "1.3";
11 |
12 | src = fetchFromGitHub {
13 | owner = "swiftlang";
14 | repo = "swift-corelibs-libdispatch";
15 | rev = "swift-6.0.3-RELEASE";
16 | hash = "sha256-vZ0JddR31wyFOjmSqEuzIJNBQIUgcLpjabbnlSF3LqY=";
17 | };
18 |
19 | nativeBuildInputs = [
20 | cmake
21 | ];
22 |
23 | meta = {
24 | homepage = "https://github.com/swiftlang/swift-corelibs-libdispatch";
25 | description = "The libdispatch Project, (a.k.a. Grand Central Dispatch), for concurrency on multicore hardware";
26 | license = lib.licenses.asl20;
27 | platforms = lib.platforms.unix;
28 | };
29 | })
30 |
--------------------------------------------------------------------------------
/src/AsyncAudioFileWriter.cpp:
--------------------------------------------------------------------------------
1 | #ifndef SAPF_AUDIOTOOLBOX
2 | #include "AsyncAudioFileWriter.hpp"
3 |
4 | AsyncAudioFileWriter::AsyncAudioFileWriter(const std::string &path, const int samplerate, const int numChannels)
5 | : mNumChannels{numChannels}, mChunkSize{maxChunkSize - maxChunkSize % numChannels},
6 | mRingBuffer{std::make_unique >()},
7 | mRingWriteBuffer(mChunkSize), mWriteBuffer(mChunkSize),
8 | mRunning{true} {
9 | SF_INFO sfinfo;
10 | sfinfo.channels = numChannels;
11 | sfinfo.samplerate = samplerate;
12 | sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
13 |
14 | mFile = sf_open(path.c_str(), SFM_WRITE, &sfinfo);
15 | if (!mFile) {
16 | printf("failed to open %s\n", path.c_str());
17 | throw errNotFound;
18 | }
19 |
20 | mWriterThread = std::thread(&AsyncAudioFileWriter::writeLoop, this);
21 | }
22 |
23 |
24 | // stop async thread and flush all remaining data to file
25 | AsyncAudioFileWriter::~AsyncAudioFileWriter() {
26 | {
27 | std::lock_guard lock{mBufferMutex};
28 | mRunning = false;
29 | }
30 | mDataAvailableCondition.notify_one();
31 | mWriterThread.join();
32 |
33 | sf_close(mFile);
34 | }
35 |
36 | // TODO: note writeBuff has ability to pass a callback, which could potentially be used
37 | // to notify the writeLoop earlier, but would require picking a "count_to_callback".
38 | // Would have to see if it performs better this way.
39 | void AsyncAudioFileWriter::writeAsync(const RtBuffers& buffers, const unsigned int nBufferFrames) {
40 | const auto totalValuesToWrite{mNumChannels * nBufferFrames};
41 | assert(static_cast(mNumChannels) == buffers.count());
42 | // index of the interleaved value in the final interleaved audio data
43 | size_t valueIdx{0};
44 | while (valueIdx < totalValuesToWrite) {
45 | size_t valuesToWrite{0};
46 | for (; valuesToWrite < mChunkSize && valueIdx < totalValuesToWrite; ++valuesToWrite,++valueIdx) {
47 | mRingWriteBuffer[valuesToWrite] = buffers.data(static_cast(valueIdx % mNumChannels))[valueIdx / mNumChannels];
48 | }
49 |
50 | // optimistically write to the ring buffer until we're done or we failed to make progress
51 | size_t totalWritten{0};
52 | size_t written{0};
53 | do {
54 | written = mRingBuffer->writeBuff(mRingWriteBuffer.data() + totalWritten, valuesToWrite - totalWritten);
55 | // it's possible this is "missed" by the writeLoop, but that's okay - this is just
56 | // an optimistic attempt to write. We'll revert to locking later.
57 | mDataAvailableCondition.notify_one();
58 | totalWritten += written;
59 | } while (written > 0 && totalWritten != valuesToWrite);
60 |
61 | // either we're done or we weren't able to write
62 | // if we're done, we can return. As mentioned earlier, it's possible that the writeLoop missed
63 | // the notification. That's fine. The thread will get notified again the next call, or it
64 | // will be notified in the destructor.
65 | if (totalWritten == valuesToWrite) {continue;}
66 |
67 | do {
68 | // block until we can write more. We can't be greedy and wait until we can write EVERYTHING
69 | // we have remaining, because the ring buffer is of limited size. So we have to wait until we can
70 | // at least fill the buffer, or finish off what we have remaining
71 | // WARNING: if this blocks, meaning the writeLoop can't keep up with
72 | // the audio thread, it will block the realtime audio thread! There's nothing we can really do in that
73 | // case - increasing the ring buffer size just delays the issue temporarily.
74 | {
75 | std::unique_lock lock{mBufferMutex};
76 | mSpaceAvailableCondition.wait(lock, [this, totalWritten, valuesToWrite] {
77 | return mRingBuffer->writeAvailable() >= valuesToWrite - totalWritten;
78 | });
79 | }
80 | // write again from where we left off
81 | totalWritten += mRingBuffer->writeBuff(mRingWriteBuffer.data() + totalWritten, valuesToWrite - totalWritten);
82 | mDataAvailableCondition.notify_one();
83 | } while (totalWritten != valuesToWrite);
84 | }
85 | // same reasoning goes here for writeLoop potentially missing the notification, but that being okay.
86 | }
87 |
88 | void AsyncAudioFileWriter::writeLoop() {
89 | while (true) {
90 | // optimistic attempt to read from the buffer
91 | auto read{mRingBuffer->readBuff(mWriteBuffer.data(), mChunkSize)};
92 | if (read == 0) {
93 | // either we've stopped, or we have no data - time to wait and see
94 | {
95 | std::unique_lock lock{mBufferMutex};
96 | mDataAvailableCondition.wait(lock, [this] { return !mRunning || mRingBuffer->readAvailable(); });
97 | }
98 | // Since we're the only consumer, we know running is still false, or readAvailable is still positive.
99 | // so we don't still need to hold the lock
100 | // TODO: We copy from ringBuffer into writeBuffer, then write to file.
101 | // Technically, it may be possible to skip this intermediate copy, but it would require
102 | // dealing with cases where we "wrap around" the end of the ring, thus needing to modify
103 | // ringbuffer to support this.
104 | read = mRingBuffer->readBuff(mWriteBuffer.data(), mChunkSize);
105 | if (read == 0) {
106 | // implies readAvailable was 0, so running was definitely false.
107 | // we've stopped, so we can exit
108 | return;
109 | }
110 | }
111 |
112 | if (const auto written{sf_write_float(mFile, mWriteBuffer.data(), static_cast(read))}; written <= 0) {
113 | const auto error{sf_strerror(mFile)};
114 | printf("failed to write audio data to file - %s\n", error);
115 | }
116 | mSpaceAvailableCondition.notify_one();
117 | }
118 | }
119 |
120 | #endif
--------------------------------------------------------------------------------
/src/AudioToolboxBuffers.cpp:
--------------------------------------------------------------------------------
1 | #ifdef SAPF_AUDIOTOOLBOX
2 | #include "AudioToolboxBuffers.hpp"
3 |
4 | AudioToolboxBuffers::AudioToolboxBuffers(int inNumChannels) {
5 | this->abl = (AudioBufferList*)calloc(1, sizeof(AudioBufferList) + (inNumChannels - 1) * sizeof(AudioBuffer));
6 | this->abl->mNumberBuffers = inNumChannels;
7 | }
8 |
9 | AudioToolboxBuffers::~AudioToolboxBuffers() {
10 | free(this->abl);
11 | }
12 |
13 | uint32_t AudioToolboxBuffers::numChannels() {
14 | return this->abl->mNumberBuffers;
15 | }
16 |
17 | void AudioToolboxBuffers::setNumChannels(size_t i, uint32_t numChannels) {
18 | this->abl->mBuffers[i].mNumberChannels = numChannels;
19 | }
20 |
21 | void AudioToolboxBuffers::setData(size_t i, void *data) {
22 | this->abl->mBuffers[i].mData = data;
23 | }
24 |
25 | void AudioToolboxBuffers::setSize(size_t i, uint32_t size) {
26 | this->abl->mBuffers[i].mDataByteSize = size;
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/src/AudioToolboxSoundFile.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #ifdef SAPF_AUDIOTOOLBOX
3 | #include "AudioToolboxSoundFile.hpp"
4 |
5 | AudioToolboxSoundFile::AudioToolboxSoundFile(ExtAudioFileRef inXAF, uint32_t inNumChannels, std::string inPath)
6 | : mXAF(inXAF), mNumChannels(inNumChannels), mPath(inPath)
7 | {}
8 |
9 | AudioToolboxSoundFile::~AudioToolboxSoundFile() {
10 | ExtAudioFileDispose(this->mXAF);
11 | }
12 |
13 | uint32_t AudioToolboxSoundFile::numChannels() {
14 | return this->mNumChannels;
15 | }
16 |
17 | int AudioToolboxSoundFile::pull(uint32_t *framesRead, AudioBuffers& buffers) {
18 | return ExtAudioFileRead(this->mXAF, framesRead, buffers.abl);
19 | }
20 |
21 | std::unique_ptr AudioToolboxSoundFile::open(const char* path, const double threadSampleRate) {
22 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path);
23 | if (!cfpath) {
24 | post("failed to create path\n");
25 | return nullptr;
26 | }
27 | CFReleaser cfpathReleaser(cfpath);
28 |
29 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false);
30 | if (!url) {
31 | post("failed to create url\n");
32 | return nullptr;
33 | }
34 | CFReleaser urlReleaser(url);
35 |
36 | ExtAudioFileRef xaf;
37 | OSStatus err = ExtAudioFileOpenURL(url, &xaf);
38 |
39 | cfpathReleaser.release();
40 | urlReleaser.release();
41 |
42 | if (err) {
43 | post("failed to open file %d\n", (int)err);
44 | return nullptr;
45 | }
46 |
47 | AudioStreamBasicDescription fileFormat;
48 |
49 | UInt32 propSize = sizeof(fileFormat);
50 | err = ExtAudioFileGetProperty(xaf, kExtAudioFileProperty_FileDataFormat, &propSize, &fileFormat);
51 |
52 | int numChannels = fileFormat.mChannelsPerFrame;
53 |
54 | AudioStreamBasicDescription clientFormat = {
55 | threadSampleRate,
56 | kAudioFormatLinearPCM,
57 | kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved,
58 | static_cast(sizeof(double)),
59 | 1,
60 | static_cast(sizeof(double)),
61 | static_cast(numChannels),
62 | 64,
63 | 0
64 | };
65 |
66 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
67 | if (err) {
68 | post("failed to set client data format\n");
69 | ExtAudioFileDispose(xaf);
70 | return {};
71 | }
72 |
73 | err = ExtAudioFileSeek(xaf, 0);
74 | if (err) {
75 | post("seek failed %d\n", (int)err);
76 | ExtAudioFileDispose(xaf);
77 | return {};
78 | }
79 |
80 | return std::make_unique(xaf, numChannels, path);
81 | }
82 |
83 | std::unique_ptr AudioToolboxSoundFile::create(const char* path, int numChannels,
84 | double threadSampleRate, double fileSampleRate,
85 | bool interleaved) {
86 | if (fileSampleRate == 0.)
87 | fileSampleRate = threadSampleRate;
88 |
89 | CFStringRef cfpath = CFStringCreateWithFileSystemRepresentation(0, path);
90 | if (!cfpath) {
91 | post("failed to create path '%s'\n", path);
92 | return nullptr;
93 | }
94 | CFReleaser cfpathReleaser(cfpath);
95 |
96 | CFURLRef url = CFURLCreateWithFileSystemPath(0, cfpath, kCFURLPOSIXPathStyle, false);
97 | if (!url) {
98 | post("failed to create url\n");
99 | return nullptr;
100 | }
101 | CFReleaser urlReleaser(url);
102 |
103 | AudioStreamBasicDescription fileFormat = {
104 | fileSampleRate,
105 | kAudioFormatLinearPCM,
106 | kAudioFormatFlagsNativeFloatPacked,
107 | static_cast(sizeof(float) * numChannels),
108 | 1,
109 | static_cast(sizeof(float) * numChannels),
110 | static_cast(numChannels),
111 | 32,
112 | 0
113 | };
114 |
115 | int interleavedChannels = interleaved ? numChannels : 1;
116 | UInt32 interleavedBit = interleaved ? 0 : kAudioFormatFlagIsNonInterleaved;
117 |
118 | AudioStreamBasicDescription clientFormat = {
119 | threadSampleRate,
120 | kAudioFormatLinearPCM,
121 | kAudioFormatFlagsNativeFloatPacked | interleavedBit,
122 | static_cast(sizeof(float) * interleavedChannels),
123 | 1,
124 | static_cast(sizeof(float) * interleavedChannels),
125 | static_cast(numChannels),
126 | 32,
127 | 0
128 | };
129 |
130 | ExtAudioFileRef xaf;
131 | OSStatus err = ExtAudioFileCreateWithURL(url, kAudioFileWAVEType, &fileFormat, nullptr, kAudioFileFlags_EraseFile, &xaf);
132 |
133 | if (err) {
134 | post("failed to create file '%s'. err: %d\n", path, (int)err);
135 | return nullptr;
136 | }
137 |
138 | err = ExtAudioFileSetProperty(xaf, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
139 | if (err) {
140 | post("failed to set client data format\n");
141 | ExtAudioFileDispose(xaf);
142 | return nullptr;
143 | }
144 |
145 | return std::make_unique(xaf, numChannels, path);
146 | }
147 | #endif // SAPF_AUDIOTOOLBOX
148 |
--------------------------------------------------------------------------------
/src/Buffers.cpp:
--------------------------------------------------------------------------------
1 | #include "Buffers.hpp"
2 |
3 | #if defined(SAPF_AUDIOTOOLBOX)
4 | AUBuffers::AUBuffers(AudioBufferList *inIoData)
5 | : ioData(inIoData)
6 | {}
7 |
8 | uint32_t AUBuffers::count() const {
9 | return this->ioData->mNumberBuffers;
10 | }
11 |
12 | float *AUBuffers::data(int channel) const {
13 | return (float*) this->ioData->mBuffers[channel].mData;
14 | }
15 |
16 | uint32_t AUBuffers::size(int channel) const {
17 | return this->ioData->mBuffers[channel].mDataByteSize;
18 | }
19 | #else
20 |
21 | RtBuffers::RtBuffers(float *inOut, uint32_t inCount, uint32_t inSize)
22 | : out(inOut), theCount(inCount), theSize(inSize)
23 | {}
24 |
25 | RtBuffers::RtBuffers(uint32_t inCount, uint32_t inSize)
26 | : out(new float[inCount*inSize]), theCount(inCount), theSize(inSize) {
27 | }
28 |
29 | uint32_t RtBuffers::count() const {
30 | return this->theCount;
31 | }
32 |
33 | float *RtBuffers::data(int channel) const {
34 | return this->out + channel * this->theSize;
35 | }
36 |
37 | uint32_t RtBuffers::size(int channel) const {
38 | return this->theSize;
39 | }
40 | #endif
--------------------------------------------------------------------------------
/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/PortableBuffers.cpp:
--------------------------------------------------------------------------------
1 | #ifndef SAPF_AUDIOTOOLBOX
2 | #include "PortableBuffers.hpp"
3 |
4 | PortableBuffer::PortableBuffer()
5 | : numChannels(0), size(0), data(nullptr)
6 | {}
7 |
8 | PortableBuffers::PortableBuffers(int inNumChannels)
9 | : buffers(inNumChannels)
10 | {}
11 |
12 | PortableBuffers::~PortableBuffers() {}
13 |
14 | uint32_t PortableBuffers::numChannels() {
15 | return this->buffers.size();
16 | }
17 |
18 | void PortableBuffers::setNumChannels(size_t i, uint32_t numChannels) {
19 | this->buffers[i].numChannels = numChannels;
20 | }
21 |
22 | void PortableBuffers::setData(size_t i, void *data) {
23 | this->buffers[i].data = data;
24 | }
25 |
26 | void PortableBuffers::setSize(size_t i, uint32_t size) {
27 | this->buffers[i].size = size;
28 | this->interleaved.resize(size * this->numChannels());
29 | }
30 | #endif // SAPF_AUDIOTOOLBOX
31 |
--------------------------------------------------------------------------------
/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/SndfileSoundFile.cpp:
--------------------------------------------------------------------------------
1 | #ifndef SAPF_AUDIOTOOLBOX
2 | #include "SndfileSoundFile.hpp"
3 | #include "SoundFiles.hpp"
4 | #include
5 |
6 |
7 | SndfileSoundFile::SndfileSoundFile(std::string path, std::unique_ptr writer, SNDFILE *inSndfile,
8 | const int inNumChannels) :
9 | mPath{std::move(path)}, mWriter{std::move(writer)},
10 | mSndfile{inSndfile}, mNumChannels{inNumChannels} {
11 | }
12 |
13 | SndfileSoundFile::~SndfileSoundFile() {
14 | if (this->mSndfile) {
15 | sf_close(this->mSndfile);
16 | }
17 | }
18 |
19 | uint32_t SndfileSoundFile::numChannels() const {
20 | return this->mNumChannels;
21 | }
22 |
23 | int SndfileSoundFile::pull(uint32_t *framesRead, PortableBuffers& buffers) {
24 | buffers.interleaved.resize(*framesRead * this->mNumChannels * sizeof(double));
25 | double *interleaved = (double *) buffers.interleaved.data();
26 | sf_count_t framesReallyRead = sf_readf_double(this->mSndfile, interleaved, *framesRead);
27 |
28 | int result = 0;
29 | if(framesReallyRead >= 0) {
30 | *framesRead = framesReallyRead;
31 | } else {
32 | *framesRead = 0;
33 | result = framesReallyRead;
34 | }
35 |
36 | for(int ch = 0; ch < this->mNumChannels; ch++) {
37 | double *buf = (double *) buffers.buffers[ch].data;
38 | for(sf_count_t frame = 0; frame < framesReallyRead; frame++) {
39 | buf[frame] = interleaved[frame * this->mNumChannels + ch];
40 | }
41 | }
42 |
43 | return result;
44 | }
45 |
46 | void SndfileSoundFile::write(const int numFrames, const PortableBuffers& bufs) const {
47 | if (const auto written{sf_write_float(mSndfile, (const float*) bufs.buffers[0].data, numFrames * bufs.buffers[0].numChannels)}; written <= 0) {
48 | const auto error{sf_strerror(mSndfile)};
49 | printf("failed to write audio data to file - %s\n", error);
50 | }
51 | }
52 |
53 | void SndfileSoundFile::writeAsync(const RtBuffers& buffers, const unsigned int nBufferFrames) const {
54 | mWriter->writeAsync(buffers, nBufferFrames);
55 | }
56 |
57 | std::unique_ptr SndfileSoundFile::open(const char *path) {
58 | SNDFILE *sndfile = nullptr;
59 | SF_INFO sfinfo = {0};
60 |
61 | if((sndfile = sf_open(path, SFM_READ, &sfinfo)) == nullptr) {
62 | post("failed to open file %s\n", sf_strerror(NULL));
63 | sf_close(sndfile);
64 | return nullptr;
65 | }
66 |
67 | uint32_t numChannels = sfinfo.channels;
68 |
69 | sf_count_t seek_result;
70 | if((seek_result = sf_seek(sndfile, 0, SEEK_SET) < 0)) {
71 | post("failed to seek file %d\n", seek_result);
72 | sf_close(sndfile);
73 | return nullptr;
74 | }
75 |
76 | // TODO: to be implemented in https://github.com/chairbender/sapf/pull/8
77 | return nullptr;
78 | }
79 |
80 | // NOTE: ATTOW, interleaved is always passed as true, and
81 | // the fileSampleRate is always passed as 0, so the thread sample rate is always used.
82 | // (>sf / >sfo doesn't even provide a way to specify the sample rate)
83 | std::unique_ptr SndfileSoundFile::create(const char *path, const int numChannels,
84 | const double threadSampleRate, double fileSampleRate, const bool async) {
85 | if (fileSampleRate == 0.)
86 | fileSampleRate = threadSampleRate;
87 |
88 | std::unique_ptr writer;
89 | SNDFILE *sndfile = nullptr;
90 | if (async) {
91 | writer = std::make_unique(path, fileSampleRate, numChannels);
92 | } else {
93 | SF_INFO sfinfo{
94 | .samplerate = static_cast(fileSampleRate),
95 | .channels = numChannels,
96 | .format = SF_FORMAT_WAV | SF_FORMAT_FLOAT};
97 | sndfile = sf_open(path, SFM_WRITE, &sfinfo);
98 | if (!sndfile) {
99 | const auto error{sf_strerror(sndfile)};
100 | printf("failed to open %s: %s\n", path, error);
101 | throw errNotFound;
102 | }
103 | }
104 |
105 | return std::make_unique(path, std::move(writer), sndfile, numChannels);
106 | }
107 | #endif // SAPF_AUDIOTOOLBOX
108 |
--------------------------------------------------------------------------------
/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 | class SFReaderOutputChannel;
23 |
24 | class SFReader : public Object
25 | {
26 | std::unique_ptr mSoundFile;
27 | AudioBuffers mBuffers;
28 | SFReaderOutputChannel* mOutputs;
29 | int64_t mFramesRemaining;
30 | bool mFinished = false;
31 |
32 | public:
33 |
34 | SFReader(std::unique_ptr inSoundFile, int64_t inDuration);
35 |
36 | ~SFReader();
37 |
38 | virtual const char* TypeName() const override { return "SFReader"; }
39 |
40 | P createOutputs(Thread& th);
41 |
42 | bool pull(Thread& th);
43 | void fulfillOutputs(int blockSize);
44 | void produceOutputs(int shrinkBy);
45 | };
46 |
47 | class SFReaderOutputChannel : public Gen
48 | {
49 | friend class SFReader;
50 | P mSFReader;
51 | SFReaderOutputChannel* mNextOutput = nullptr;
52 | Z* mDummy = nullptr;
53 |
54 | public:
55 | SFReaderOutputChannel(Thread& th, SFReader* inSFReader)
56 | : Gen(th, itemTypeZ, true), mSFReader(inSFReader)
57 | {
58 | }
59 |
60 | ~SFReaderOutputChannel()
61 | {
62 | if (mDummy) free(mDummy);
63 | }
64 |
65 | virtual void norefs() override
66 | {
67 | mOut = nullptr;
68 | mSFReader = nullptr;
69 | }
70 |
71 | virtual const char* TypeName() const override { return "SFReaderOutputChannel"; }
72 |
73 | virtual void pull(Thread& th) override
74 | {
75 | if (mSFReader->pull(th)) {
76 | end();
77 | }
78 | }
79 |
80 | };
81 |
82 | SFReader::SFReader(std::unique_ptr inSoundFile, int64_t inDuration) :
83 | mSoundFile(std::move(inSoundFile)),
84 | mBuffers(mSoundFile->numChannels()),
85 | mFramesRemaining(inDuration)
86 | {
87 |
88 | }
89 |
90 | SFReader::~SFReader()
91 | {
92 | SFReaderOutputChannel* output = mOutputs;
93 | do {
94 | SFReaderOutputChannel* next = output->mNextOutput;
95 | delete output;
96 | output = next;
97 | } while (output);
98 | }
99 |
100 | void SFReader::fulfillOutputs(int blockSize)
101 | {
102 | SFReaderOutputChannel* output = mOutputs;
103 | size_t bufSize = blockSize * sizeof(Z);
104 | for (int i = 0; output; ++i, output = output->mNextOutput){
105 | Z* out;
106 | if (output->mOut)
107 | out = output->mOut->fulfillz(blockSize);
108 | else {
109 | if (!output->mDummy)
110 | output->mDummy = (Z*)calloc(output->mBlockSize, sizeof(Z));
111 |
112 | out = output->mDummy;
113 | }
114 |
115 | this->mBuffers.setNumChannels(i, 1);
116 | this->mBuffers.setData(i, out);
117 | this->mBuffers.setSize(i, bufSize);
118 |
119 | memset(out, 0, bufSize);
120 | };
121 | }
122 |
123 | void SFReader::produceOutputs(int shrinkBy)
124 | {
125 | SFReaderOutputChannel* output = mOutputs;
126 | do {
127 | if (output->mOut)
128 | output->produce(shrinkBy);
129 | output = output->mNextOutput;
130 | } while (output);
131 | }
132 |
133 | P SFReader::createOutputs(Thread& th)
134 | {
135 | const uint32_t numChannels = mSoundFile->numChannels();
136 | P s = new List(itemTypeV, numChannels);
137 |
138 | // fill s->mArray with ola's output channels.
139 | SFReaderOutputChannel* last = nullptr;
140 | P a = s->mArray;
141 | for (uint32_t i = 0; i < numChannels; ++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_t framesRead = blockSize;
169 | int err = mSoundFile->pull(&framesRead, mBuffers);
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 | #ifdef SAPF_AUDIOTOOLBOX
186 | std::unique_ptr soundFile = SoundFile::open(path, th.rate.sampleRate);
187 | #else
188 | std::unique_ptr soundFile = SoundFile::open(path);
189 | #endif
190 |
191 | if(soundFile != nullptr) {
192 | SFReader* sfr = new SFReader(std::move(soundFile), -1);
193 | th.push(sfr->createOutputs(th));
194 | }
195 | }
196 |
197 | #ifdef SAPF_AUDIOTOOLBOX
198 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved)
199 | {
200 | return SoundFile::create(path, numChannels, th.rate.sampleRate, fileSampleRate, interleaved);
201 | }
202 | #else
203 | std::unique_ptr sfcreate(Thread& th, const char* path, int numChannels, double fileSampleRate, bool interleaved, bool async)
204 | {
205 | return SoundFile::create(path, numChannels, th.rate.sampleRate, fileSampleRate, async);
206 | }
207 | #endif
208 |
209 | std::atomic gFileCount = 0;
210 |
211 | void makeRecordingPath(Arg filename, char* path, int len)
212 | {
213 | if (filename.isString()) {
214 | const char* recDir = getenv("SAPF_RECORDINGS");
215 | if (!recDir || strlen(recDir)==0) {
216 | #ifdef _WIN32
217 | recDir = getenv("TEMP");
218 | if (!recDir || strlen(recDir)==0)
219 | recDir = getenv("TMP");
220 | #else
221 | recDir = "/tmp";
222 | #endif
223 | if (!recDir || strlen(recDir)==0)
224 | recDir = ".";
225 | }
226 | snprintf(path, len, "%s/%s.wav", recDir, ((String*)filename.o())->s);
227 | } else {
228 | int32_t count = ++gFileCount;
229 | #ifdef _WIN32
230 | const char* tempDir = getenv("TEMP");
231 | if (!tempDir || strlen(tempDir)==0)
232 | tempDir = getenv("TMP");
233 | if (!tempDir || strlen(tempDir)==0)
234 | tempDir = ".";
235 | #else
236 | const char* tempDir = "/tmp";
237 | #endif
238 | snprintf(path, len, "%s/sapf-%s-%04d.wav", tempDir, gSessionTime, count);
239 |
240 | }
241 | }
242 |
243 | void sfwrite(Thread& th, V& v, Arg filename, bool openIt)
244 | {
245 | std::vector in;
246 |
247 | int numChannels = 0;
248 |
249 | if (v.isZList()) {
250 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of frames", "");
251 | numChannels = 1;
252 | in.push_back(ZIn(v));
253 | } else {
254 | if (!v.isFinite()) indefiniteOp(">sf : s - indefinite number of channels", "");
255 | P s = (List*)v.o();
256 | s = s->pack(th);
257 | Array* a = s->mArray();
258 | numChannels = (int)a->size();
259 |
260 | if (numChannels > kMaxSFChannels)
261 | throw errOutOfRange;
262 |
263 | bool allIndefinite = true;
264 | for (int i = 0; i < numChannels; ++i) {
265 | V va = a->at(i);
266 | if (va.isFinite()) allIndefinite = false;
267 | in.push_back(ZIn(va));
268 | va.o = nullptr;
269 | }
270 |
271 | s = nullptr;
272 | a = nullptr;
273 |
274 | if (allIndefinite) indefiniteOp(">sf : s - all channels have indefinite number of frames", "");
275 | }
276 | v.o = nullptr;
277 |
278 | char path[1024];
279 |
280 | makeRecordingPath(filename, path, 1024);
281 |
282 |
283 | #ifdef SAPF_AUDIOTOOLBOX
284 | std::unique_ptr soundFile = sfcreate(th, path, numChannels, 0., true);
285 | #else
286 | std::unique_ptr soundFile = sfcreate(th, path, numChannels, 0., true, false);
287 | #endif
288 | if (!soundFile) return;
289 |
290 | std::valarray buf(0., numChannels * kBufSize);
291 | AudioBuffers bufs(1);
292 | bufs.setNumChannels(0, numChannels);
293 | bufs.setData(0, &buf[0]);
294 | bufs.setSize(0, kBufSize * sizeof(float));
295 |
296 | int64_t framesPulled = 0;
297 | int64_t framesWritten = 0;
298 | bool done = false;
299 | while (!done) {
300 | int minn = kBufSize;
301 | memset(&buf[0], 0, kBufSize * numChannels);
302 | for (int i = 0; i < numChannels; ++i) {
303 | int n = kBufSize;
304 | bool imdone = in[i].fill(th, n, &buf[0]+i, numChannels);
305 | framesPulled += n;
306 | if (imdone) done = true;
307 | minn = std::min(n, minn);
308 | }
309 |
310 | bufs.setSize(0, numChannels * minn * sizeof(float));
311 | // TODO: move into a SoundFile method
312 | #ifdef SAPF_AUDIOTOOLBOX
313 | OSStatus err = ExtAudioFileWrite(soundFile->mXAF, minn, bufs.abl);
314 | if (err) {
315 | post("file writing failed %d\n", (int)err);
316 | break;
317 | }
318 | #else
319 | soundFile->write(minn, bufs);
320 | #endif // SAPF_AUDIOTOOLBOX
321 | framesWritten += minn;
322 | }
323 |
324 | post("wrote file '%s' %d channels %g secs\n", path, numChannels, framesWritten * th.rate.invSampleRate);
325 |
326 | soundFile = nullptr;
327 |
328 | if (openIt) {
329 | char cmd[1100];
330 | #ifdef _WIN32
331 | snprintf(cmd, 1100, "start \"\" \"%s\"", path);
332 | #else
333 | snprintf(cmd, 1100, "open \"%s\"", path);
334 | #endif
335 | system(cmd);
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/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 | #ifdef SAPF_ACCELERATE
20 | #include
21 | #endif // SAPF_ACCELERATE
22 | #include
23 | #include
24 | #include
25 |
26 | static void makeColorTable(unsigned char* table);
27 |
28 | static double bessi0(double x)
29 | {
30 | //returns the modified Bessel function I_0(x) for any real x
31 | //from numerical recipes
32 | double ax, ans;
33 | double y;
34 |
35 | if((ax=fabs(x))<3.75){
36 | y=x/3.75;
37 | y *= y;
38 | ans =1.0+y*(3.5156229+y*(3.0899424+y*(1.2067492
39 | +y*(0.2659732+y*(0.360768e-1+y*0.45813e-2)))));
40 | }
41 | else{
42 | y=3.75/ax;
43 | ans = (exp(ax)/sqrt(ax))*(0.39894228+y*(0.1328592e-1
44 | +y*(0.225319e-2+y*(-0.157565e-2+y*(0.916281e-2
45 | +y*(-0.2057706e-1+y*(0.2635537e-1+y*(-0.1647633e-1
46 | +y*0.392377e-2))))))));
47 | }
48 |
49 | return ans;
50 | }
51 |
52 | static double i0(double x)
53 | {
54 | const double epsilon = 1e-18;
55 | int n = 1;
56 | double S = 1., D = 1., T;
57 |
58 | while (D > epsilon * S) {
59 | T = x / (2 * n++);
60 | D *= T * T;
61 | S += D;
62 | }
63 | return S;
64 | }
65 |
66 | static void calcKaiserWindowD(size_t size, double* window, double stopBandAttenuation)
67 | {
68 | size_t M = size - 1;
69 | size_t N = M-1;
70 | #if VERBOSE
71 | printf("FillKaiser %d %g\n", M, stopBandAttenuation);
72 | #endif
73 |
74 | double alpha = 0.;
75 | if (stopBandAttenuation <= -50.)
76 | alpha = 0.1102 * (-stopBandAttenuation - 8.7);
77 | else if (stopBandAttenuation < -21.)
78 | alpha = 0.5842 * pow(-stopBandAttenuation - 21., 0.4) + 0.07886 * (-stopBandAttenuation - 21.);
79 |
80 | double p = N / 2;
81 | double kk = 1.0 / i0(alpha);
82 |
83 | for(unsigned int k = 0; k < M; k++ )
84 | {
85 | double x = (k-p) / p;
86 |
87 | // Kaiser window
88 | window[k+1] *= kk * bessi0(alpha * sqrt(1.0 - x*x) );
89 | }
90 | window[0] = 0.;
91 | window[size-1] = 0.;
92 | #if VERBOSE
93 | printf("done\n");
94 | #endif
95 | }
96 |
97 | const int border = 8;
98 |
99 | void spectrogram(int size, double* data, int width, int log2bins, const char* path, double dBfloor)
100 | {
101 | #ifdef SAPF_ACCELERATE
102 | int numRealFreqs = 1 << log2bins;
103 |
104 | int log2n = log2bins + 1;
105 | int n = 1 << log2n;
106 | int nOver2 = n / 2;
107 |
108 |
109 | double scale = 1./nOver2;
110 |
111 | int64_t paddedSize = size + n;
112 | double* paddedData = (double*)calloc(paddedSize, sizeof(double));
113 | memcpy(paddedData + nOver2, data, size * sizeof(double));
114 |
115 |
116 | double* dBMags = (double*)calloc(numRealFreqs + 1, sizeof(double));
117 |
118 | double hopSize = size <= n ? 0 : (double)(size - n) / (double)(width - 1);
119 |
120 | double* window = (double*)calloc(n, sizeof(double));
121 | for (int i = 0; i < n; ++i) window[i] = 1.;
122 | calcKaiserWindowD(n, window, -180.);
123 |
124 | unsigned char table[1028];
125 | makeColorTable(table);
126 |
127 | int heightOfAmplitudeView = 128;
128 | int heightOfFFT = numRealFreqs+1;
129 | int totalHeight = heightOfAmplitudeView+heightOfFFT+3*border;
130 | int topOfSpectrum = heightOfAmplitudeView + 2*border;
131 | int totalWidth = width+2*border;
132 | Bitmap* b = createBitmap(totalWidth, totalHeight);
133 | fillRect(b, 0, 0, totalWidth, totalHeight, 160, 160, 160, 255);
134 | fillRect(b, border, border, width, heightOfAmplitudeView, 0, 0, 0, 255);
135 |
136 | FFTSetupD fftSetup = vDSP_create_fftsetupD(log2n, kFFTRadix2);
137 |
138 | double* windowedData = (double*)calloc(n, sizeof(double));
139 | double* interleavedData = (double*)calloc(n, sizeof(double));
140 | double* resultData = (double*)calloc(n, sizeof(double));
141 | DSPDoubleSplitComplex interleaved;
142 | interleaved.realp = interleavedData;
143 | interleaved.imagp = interleavedData + nOver2;
144 | DSPDoubleSplitComplex result;
145 | result.realp = resultData;
146 | result.imagp = resultData + nOver2;
147 | double maxmag = 0.;
148 |
149 | double hpos = nOver2;
150 | for (int i = 0; i < width; ++i) {
151 | size_t ihpos = (size_t)hpos;
152 |
153 | // do analysis
154 | // find peak
155 | double peak = 1e-20;
156 | for (int w = 0; w < n; ++w) {
157 | double x = paddedData[w+ihpos];
158 | x = fabs(x);
159 | if (x > peak) peak = x;
160 | }
161 |
162 | for (int64_t w = 0; w < n; ++w) windowedData[w] = window[w] * paddedData[w+ihpos];
163 |
164 | vDSP_ctozD((DSPDoubleComplex*)windowedData, 2, &interleaved, 1, nOver2);
165 |
166 | vDSP_fft_zropD(fftSetup, &interleaved, 1, &result, 1, log2n, kFFTDirection_Forward);
167 |
168 | dBMags[0] = result.realp[0] * scale;
169 | dBMags[numRealFreqs] = result.imagp[0] * scale;
170 | if (dBMags[0] > maxmag) maxmag = dBMags[0];
171 | if (dBMags[numRealFreqs] > maxmag) maxmag = dBMags[numRealFreqs];
172 | for (int64_t j = 1; j < numRealFreqs-1; ++j) {
173 | double x = result.realp[j] * scale;
174 | double y = result.imagp[j] * scale;
175 | dBMags[j] = sqrt(x*x + y*y);
176 | if (dBMags[j] > maxmag) maxmag = dBMags[j];
177 | }
178 |
179 | double invmag = 1.;
180 | dBMags[0] = 20.*log2(dBMags[0]*invmag);
181 | dBMags[numRealFreqs] = 20.*log10(dBMags[numRealFreqs]*invmag);
182 | for (int64_t j = 0; j <= numRealFreqs-1; ++j) {
183 | dBMags[j] = 20.*log10(dBMags[j]*invmag);
184 | }
185 |
186 |
187 |
188 | // set pixels
189 | {
190 | double peakdB = 20.*log10(peak);
191 | int peakColorIndex = 256. - peakdB * (256. / dBfloor);
192 | int peakIndex = heightOfAmplitudeView - peakdB * (heightOfAmplitudeView / dBfloor);
193 | if (peakIndex < 0) peakIndex = 0;
194 | if (peakIndex > heightOfAmplitudeView) peakIndex = heightOfAmplitudeView;
195 | if (peakColorIndex < 0) peakColorIndex = 0;
196 | if (peakColorIndex > 255) peakColorIndex = 255;
197 |
198 | unsigned char* t = table + 4*peakColorIndex;
199 | fillRect(b, i+border, border+128-peakIndex, 1, peakIndex, t[0], t[1], t[2], t[3]);
200 | }
201 |
202 | for (int j = 0; j < numRealFreqs; ++j) {
203 | int colorIndex = 256. - dBMags[j] * (256. / dBfloor);
204 | if (colorIndex < 0) colorIndex = 0;
205 | if (colorIndex > 255) colorIndex = 255;
206 |
207 | unsigned char* t = table + 4*colorIndex;
208 |
209 | setPixel(b, i+border, numRealFreqs-j+topOfSpectrum, t[0], t[1], t[2], t[3]);
210 | }
211 |
212 | hpos += hopSize;
213 | }
214 |
215 | vDSP_destroy_fftsetupD(fftSetup);
216 |
217 | writeBitmap(b, path);
218 | freeBitmap(b);
219 | free(dBMags);
220 | free(paddedData);
221 | free(window);
222 | free(windowedData);
223 | free(interleavedData);
224 | free(resultData);
225 | #else
226 | // TODO cross platform spectrogram
227 | #endif // SAPF_ACCELERATE
228 | }
229 |
230 |
231 | static void makeColorTable(unsigned char* table)
232 | {
233 | // white >> red >> yellow >> green >> cyan >> blue >> magenta >> pink >> black
234 | // 0 -20 -40 -60 -80 -100 -120 -140 -160
235 | // 255 224 192 160 128 96 64 32 0
236 |
237 | int colors[9][4] = {
238 | { 0, 0, 64, 255}, // dk blue
239 | { 0, 0, 255, 255}, // blue
240 | {255, 0, 0, 255}, // red
241 | {255, 255, 0, 255}, // yellow
242 | {255, 255, 255, 255} // white
243 | };
244 |
245 | for (int j = 0; j < 4; ++j) {
246 | for (int i = 0; i < 64; ++i) {
247 | for (int k = 0; k < 4; ++k) {
248 | int x = (colors[j][k] * (64 - i) + colors[j+1][k] * i) / 64;
249 | if (x > 255) x = 255;
250 | table[j*64*4 + i*4 + k + 4] = x;
251 | }
252 | }
253 | }
254 |
255 | table[0] = 0;
256 | table[1] = 0;
257 | table[2] = 0;
258 | table[3] = 255;
259 | }
260 |
261 |
--------------------------------------------------------------------------------
/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/ZArr.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 "ZArr.hpp"
18 | #ifndef SAPF_ACCELERATE
19 |
20 | ZArr zarr(Z *vec, const int n, const int stride) {
21 | return ZArr(vec, n, Eigen::InnerStride<>(stride));
22 | }
23 |
24 | CZArr czarr(const Z *vec, const int n, const int stride) {
25 | return CZArr(vec, n, Eigen::InnerStride<>(stride));
26 | }
27 | #endif
--------------------------------------------------------------------------------
/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 | void FFT::init(size_t log2n) {
23 | this->n = pow(2, log2n);
24 | this->log2n = log2n;
25 |
26 | #ifdef SAPF_ACCELERATE
27 | this->setup = vDSP_create_fftsetupD(this->log2n, kFFTRadix2);
28 | #else
29 | this->in = (fftw_complex *) fftw_malloc(this->n * sizeof(fftw_complex));
30 | this->out = (fftw_complex *) fftw_malloc(this->n * sizeof(fftw_complex));
31 | // "Here, n is the “logical” size of the DFT, not necessarily the
32 | // physical size of the array. In particular, the real (double) array
33 | // has n elements, while the complex (fftw_complex) array has n/2+1
34 | // elements (where the division is rounded down). For an in-place
35 | // transform, in and out are aliased to the same array, which must be
36 | // big enough to hold both; so, the real array would actually have
37 | // 2*(n/2+1) elements, where the elements beyond the first n are unused
38 | // padding."
39 | // - https://fftw.org/fftw3_doc/One_002dDimensional-DFTs-of-Real-Data.html
40 | this->in_out_real = (double *) fftw_malloc(2 * (this->n / 2 + 1) * sizeof(double));
41 |
42 | this->forward_out_of_place_plan = fftw_plan_dft_1d(this->n, this->in, this->out, FFTW_FORWARD, FFTW_ESTIMATE);
43 | this->backward_out_of_place_plan = fftw_plan_dft_1d(this->n, this->in, this->out, FFTW_BACKWARD, FFTW_ESTIMATE);
44 | this->forward_in_place_plan = fftw_plan_dft_1d(this->n, this->in, this->in, FFTW_FORWARD, FFTW_ESTIMATE);
45 | this->backward_in_place_plan = fftw_plan_dft_1d(this->n, this->in, this->in, FFTW_BACKWARD, FFTW_ESTIMATE);
46 | this->forward_real_plan = fftw_plan_dft_r2c_1d(this->n, this->in_out_real, (fftw_complex *) this->in_out_real, FFTW_ESTIMATE);
47 | this->backward_real_plan = fftw_plan_dft_c2r_1d(this->n, (fftw_complex *) this->in_out_real, this->in_out_real, FFTW_ESTIMATE);
48 | #endif // SAPF_ACCELERATE
49 | }
50 |
51 | FFT::~FFT() {
52 | #ifdef SAPF_ACCELERATE
53 | vDSP_destroy_fftsetupD(this->setup);
54 | #else
55 | fftw_destroy_plan(forward_out_of_place_plan);
56 | fftw_destroy_plan(backward_out_of_place_plan);
57 | fftw_destroy_plan(forward_in_place_plan);
58 | fftw_destroy_plan(backward_in_place_plan);
59 | fftw_destroy_plan(forward_real_plan);
60 | fftw_destroy_plan(backward_real_plan);
61 |
62 | fftw_free(this->in);
63 | fftw_free(this->out);
64 | fftw_free(this->in_out_real);
65 | #endif // SAPF_ACCELERATE
66 | }
67 |
68 | void FFT::forward(double *inReal, double *inImag, double *outReal, double *outImag) {
69 | double scale = 2. / this->n;
70 | #ifdef SAPF_ACCELERATE
71 | DSPDoubleSplitComplex in;
72 | DSPDoubleSplitComplex out;
73 |
74 | in.realp = inReal;
75 | in.imagp = inImag;
76 | out.realp = outReal;
77 | out.imagp = outImag;
78 |
79 | vDSP_fft_zopD(this->setup, &in, 1, &out, 1, this->log2n, FFT_FORWARD);
80 |
81 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, this->n);
82 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, this->n);
83 | #else
84 | for(size_t i = 0; i < this->n; i++) {
85 | this->in[i][0] = inReal[i];
86 | this->in[i][1] = inImag[i];
87 | }
88 | fftw_execute(this->forward_out_of_place_plan);
89 | for(size_t i = 0; i < this->n; i++) {
90 | outReal[i] = this->out[i][0] * scale;
91 | outImag[i] = this->out[i][1] * scale;
92 | }
93 | #endif // SAPF_ACCELERATE
94 | }
95 |
96 | void FFT::backward(double *inReal, double *inImag, double *outReal, double *outImag) {
97 | double scale = .5;
98 | #ifdef SAPF_ACCELERATE
99 | DSPDoubleSplitComplex in;
100 | DSPDoubleSplitComplex out;
101 |
102 | in.realp = inReal;
103 | in.imagp = inImag;
104 | out.realp = outReal;
105 | out.imagp = outImag;
106 |
107 | vDSP_fft_zopD(this->setup, &in, 1, &out, 1, this->log2n, FFT_INVERSE);
108 |
109 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, this->n);
110 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, this->n);
111 | #else
112 | for(size_t i = 0; i < this->n; i++) {
113 | this->in[i][0] = inReal[i];
114 | this->in[i][1] = inImag[i];
115 | }
116 | fftw_execute(this->backward_out_of_place_plan);
117 | for(size_t i = 0; i < this->n; i++) {
118 | outReal[i] = this->out[i][0] * scale;
119 | outImag[i] = this->out[i][1] * scale;
120 | }
121 | #endif // SAPF_ACCELERATE
122 | }
123 |
124 | void FFT::forward_in_place(double *ioReal, double *ioImag) {
125 | double scale = 2. / this->n;
126 | #ifdef SAPF_ACCELERATE
127 | DSPDoubleSplitComplex io;
128 |
129 | io.realp = ioReal;
130 | io.imagp = ioImag;
131 |
132 | vDSP_fft_zipD(this->setup, &io, 1, this->log2n, FFT_FORWARD);
133 |
134 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, this->n);
135 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, this->n);
136 | #else
137 | for(size_t i = 0; i < this->n; i++) {
138 | this->in[i][0] = ioReal[i];
139 | this->in[i][1] = ioImag[i];
140 | }
141 | fftw_execute(this->forward_in_place_plan);
142 | for(size_t i = 0; i < this->n; i++) {
143 | ioReal[i] = this->in[i][0] * scale;
144 | ioImag[i] = this->in[i][1] * scale;
145 | }
146 | #endif // SAPF_ACCELERATE
147 | }
148 |
149 | void FFT::backward_in_place(double *ioReal, double *ioImag) {
150 | double scale = .5;
151 | #ifdef SAPF_ACCELERATE
152 | DSPDoubleSplitComplex io;
153 |
154 | io.realp = ioReal;
155 | io.imagp = ioImag;
156 |
157 | vDSP_fft_zipD(this->setup, &io, 1, this->log2n, FFT_INVERSE);
158 |
159 | vDSP_vsmulD(ioReal, 1, &scale, ioReal, 1, this->n);
160 | vDSP_vsmulD(ioImag, 1, &scale, ioImag, 1, this->n);
161 | #else
162 | for(size_t i = 0; i < this->n; i++) {
163 | this->in[i][0] = ioReal[i];
164 | this->in[i][1] = ioImag[i];
165 | }
166 | fftw_execute(this->backward_in_place_plan);
167 | for(size_t i = 0; i < this->n; i++) {
168 | ioReal[i] = this->in[i][0] * scale;
169 | ioImag[i] = this->in[i][1] * scale;
170 | }
171 | #endif // SAPF_ACCELERATE
172 | }
173 |
174 | void FFT::forward_real(double *inReal, double *outReal, double *outImag) {
175 | double scale = 2. / n;
176 | int n2 = this->n/2;
177 | #ifdef SAPF_ACCELERATE
178 | DSPDoubleSplitComplex in;
179 | DSPDoubleSplitComplex out;
180 |
181 | vDSP_ctozD((DSPDoubleComplex*)inReal, 1, &in, 1, n2);
182 |
183 | out.realp = outReal;
184 | out.imagp = outImag;
185 |
186 | vDSP_fft_zropD(this->setup, &in, 1, &out, 1, this->log2n, FFT_FORWARD);
187 |
188 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n2);
189 | vDSP_vsmulD(outImag, 1, &scale, outImag, 1, n2);
190 |
191 | out.realp[n2] = out.imagp[0];
192 | out.imagp[0] = 0.;
193 | out.imagp[n2] = 0.;
194 | #else
195 | for(size_t i = 0; i < this->n; i++) {
196 | this->in_out_real[i] = inReal[i];
197 | }
198 | fftw_execute(this->forward_real_plan);
199 | for(size_t i = 0; i < n2; i++) {
200 | outReal[i] = this->in_out_real[2*i] * scale;
201 | outImag[i] = this->in_out_real[2*i+1] * scale;
202 | }
203 | #endif // SAPF_ACCELERATE
204 | }
205 |
206 | void FFT::backward_real(double *inReal, double *inImag, double *outReal) {
207 | double scale = .5;
208 | int n2 = this->n/2;
209 | #ifdef SAPF_ACCELERATE
210 | DSPDoubleSplitComplex in;
211 |
212 | in.realp = inReal;
213 | in.imagp = inImag;
214 |
215 | //in.imagp[0] = in.realp[n2];
216 | in.imagp[0] = 0.;
217 |
218 | vDSP_fft_zripD(this->setup, &in, 1, this->log2n, FFT_INVERSE);
219 |
220 | vDSP_ztocD(&in, 1, (DSPDoubleComplex*)outReal, 2, n2);
221 |
222 | vDSP_vsmulD(outReal, 1, &scale, outReal, 1, n);
223 | #else
224 | for(size_t i = 0; i < n2; i++) {
225 | this->in_out_real[2*i] = inReal[i];
226 | this->in_out_real[2*i+1] = inImag[i];
227 | }
228 | fftw_execute(this->backward_real_plan);
229 | for(size_t i = 0; i < this->n; i++) {
230 | outReal[i] = this->in_out_real[i] * scale;
231 | }
232 | #endif // SAPF_ACCELERATE
233 | }
234 |
235 | FFT ffts[kMaxFFTLogSize+1];
236 |
237 |
238 |
239 | void initFFT()
240 | {
241 | for (int i = kMinFFTLogSize; i <= kMaxFFTLogSize; ++i) {
242 | ffts[i].init(i);
243 | }
244 | }
245 |
246 | void fft(int n, double* inReal, double* inImag, double* outReal, double* outImag)
247 | {
248 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
249 | ffts[log2n].forward(inReal, inImag, outReal, outImag);
250 | }
251 |
252 | void ifft(int n, double* inReal, double* inImag, double* outReal, double* outImag)
253 | {
254 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
255 | ffts[log2n].backward(inReal, inImag, outReal, outImag);
256 | }
257 |
258 | void fft(int n, double* ioReal, double* ioImag)
259 | {
260 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
261 | ffts[log2n].forward_in_place(ioReal, ioImag);
262 | }
263 |
264 | void ifft(int n, double* ioReal, double* ioImag)
265 | {
266 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
267 | ffts[log2n].backward_in_place(ioReal, ioImag);
268 | }
269 |
270 |
271 | void rfft(int n, double* inReal, double* outReal, double* outImag)
272 | {
273 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
274 | ffts[log2n].forward_real(inReal, outReal, outImag);
275 | }
276 |
277 |
278 | void rifft(int n, double* inReal, double* inImag, double* outReal)
279 | {
280 | int log2n = n == 0 ? 0 : 64 - __builtin_clzll(n - 1);
281 | ffts[log2n].backward_real(inReal, inImag, outReal);
282 | }
283 |
284 |
285 | #define USE_VFORCE 1
286 |
287 | inline void complex_expD_conj(double& re, double& im)
288 | {
289 | double rho = expf(re);
290 | re = rho * cosf(im);
291 | im = rho * sinf(im);
292 | }
293 |
294 |
--------------------------------------------------------------------------------
/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 | #ifdef SAPF_MACH_TIME
19 | #include
20 | #else
21 | #include
22 | #endif // SAPF_MACH_TIME
23 | #include
24 |
25 | extern "C" {
26 |
27 | static double gHostClockFreq;
28 |
29 | void initElapsedTime()
30 | {
31 | #ifdef SAPF_MACH_TIME
32 | struct mach_timebase_info info;
33 | mach_timebase_info(&info);
34 | gHostClockFreq = 1e9 * ((double)info.numer / (double)info.denom);
35 | #else
36 | gHostClockFreq = 0.0;
37 | #endif // SAPF_MACH_TIME
38 | }
39 |
40 | double elapsedTime()
41 | {
42 | #ifdef SAPF_MACH_TIME
43 | return (double)mach_absolute_time() / gHostClockFreq;
44 | #else
45 | return (double) std::chrono::duration_cast(
46 | std::chrono::steady_clock::now().time_since_epoch()
47 | ).count() / 10e9;
48 | #endif // SAPF_MACH_TIME
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/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 | // TODO: Is this even needed in this file?
20 | #if USE_LIBEDIT
21 | #include
22 | #endif
23 | #include
24 | #include
25 | #include "primes.hpp"
26 | #include
27 | #ifdef SAPF_DISPATCH
28 | #include
29 | #else
30 | #include
31 | #endif // SAPF_DISPATCH
32 | #ifdef SAPF_COREFOUNDATION
33 | #include
34 | #endif // SAPF_COREFOUNDATION
35 |
36 | #ifdef SAPF_MANTA
37 | #include "Manta.h"
38 |
39 | class MyManta : public Manta
40 | {
41 | virtual void PadEvent(int row, int column, int id, int value) {
42 | printf("pad %d %d %d %d\n", row, column, id, value);
43 | }
44 | virtual void SliderEvent(int id, int value) {
45 | printf("slider %d %d\n", id, value);
46 | }
47 | virtual void ButtonEvent(int id, int value) {
48 | printf("button %d %d\n", id, value);
49 | }
50 | virtual void PadVelocityEvent(int row, int column, int id, int velocity) {
51 | printf("pad vel %d %d %d %d\n", row, column, id, velocity);
52 |
53 | }
54 | virtual void ButtonVelocityEvent(int id, int velocity) {
55 | printf("button vel %d %d\n", id, velocity);
56 | }
57 | virtual void FrameEvent(uint8_t *frame) {}
58 | virtual void DebugPrint(const char *fmt, ...) {}
59 | };
60 |
61 | Manta* manta();
62 | Manta* manta()
63 | {
64 | static MyManta* sManta = new MyManta();
65 | return sManta;
66 | }
67 |
68 | static void mantaLoop() {
69 | /*** see at bottom for better way ***/
70 | while(true) {
71 | try {
72 | MantaUSB::HandleEvents();
73 | usleep(5000);
74 | } catch(...) {
75 | sleep(1);
76 | }
77 | }
78 | }
79 | #endif // SAPF_MANTA
80 |
81 | /* issue:
82 |
83 | [These comments are very old and I have not checked if they are still relevant.]
84 |
85 | TableData alloc should use new
86 |
87 | bugs:
88 |
89 | itd should have a tail time. currently the ugen stops as soon as its input, cutting off the delayed signal.
90 |
91 | + should not stop until both inputs stop?
92 | other additive binops: - avg2 sumsq
93 |
94 | no, use a operator
95 |
96 | ---
97 |
98 | adsrg (gate a d s r --> out) envelope generator with gate.
99 | adsr (dur a d s r --> out) envelope generator with duration.
100 | evgg - (gate levels times curves suspt --> out) envelope generator with gate. suspt is the index of the sustain level.
101 | evg - (dur levels times curves suspt --> out) envelope generator with duration. suspt is the index of the sustain level.
102 |
103 | blip (freq phase nharm --> out) band limited impulse oscillator.
104 | dsf1 (freq phase nharm lharm hmul --> out) sum of sines oscillator.
105 |
106 | formant (freq formfreq bwfreq --> out) formant oscillator
107 |
108 | svf (in freq rq --> [lp hp bp bs]) state variable filter.
109 | moogf (in freq rq --> out) moog ladder low pass filter.
110 |
111 | */
112 |
113 | extern void AddCoreOps();
114 | extern void AddMathOps();
115 | extern void AddStreamOps();
116 | extern void AddLFOps();
117 | extern void AddUGenOps();
118 | extern void AddSetOps();
119 | extern void AddRandomOps();
120 | extern void AddMidiOps();
121 |
122 | const char* gVersionString = "0.1.21";
123 |
124 | static void usage()
125 | {
126 | fprintf(stdout, "sapf [-r sample-rate][-p prelude-file]\n");
127 | fprintf(stdout, "\n");
128 | fprintf(stdout, "sapf [-h]\n");
129 | fprintf(stdout, " print this help\n");
130 | fprintf(stdout, "\n");
131 | }
132 |
133 | static void replLoop(Thread th) {
134 | th.repl(stdin, vm.log_file);
135 | exit(0);
136 | }
137 |
138 | int main (int argc, const char * argv[])
139 | {
140 | post("------------------------------------------------\n");
141 | post("A tool for the expression of sound as pure form.\n");
142 | post("------------------------------------------------\n");
143 | post("--- version %s\n", gVersionString);
144 |
145 | for (int i = 1; i < argc;) {
146 | int c = argv[i][0];
147 | if (c == '-') {
148 | c = argv[i][1];
149 | switch (c) {
150 | case 'r' : {
151 | if (argc <= i+1) { post("expected sample rate after -r\n"); return 1; }
152 |
153 | double sr = atof(argv[i+1]);
154 | if (sr < 1000. || sr > 768000.) { post("sample rate out of range.\n"); return 1; }
155 | vm.setSampleRate(sr);
156 | post("sample rate set to %g\n", vm.ar.sampleRate);
157 | i += 2;
158 | } break;
159 | case 'p' : {
160 | if (argc <= i+1) { post("expected prelude file name after -p\n"); return 1; }
161 | vm.prelude_file = argv[i+1];
162 | i += 2;
163 | } break;
164 | case 'h' : {
165 | usage();
166 | exit(0);
167 | } break;
168 | default:
169 | post("unrecognized option -%c\n", c);
170 | }
171 | } else {
172 | post("expected option, got \"%s\"\n", argv[i]);
173 | ++i;
174 | }
175 | }
176 |
177 |
178 | vm.addBifHelp("Argument Automapping legend:");
179 | vm.addBifHelp(" a - as is. argument is not automapped.");
180 | vm.addBifHelp(" z - argument is expected to be a signal or scalar, streams are auto mapped.");
181 | vm.addBifHelp(" k - argument is expected to be a scalar, signals and streams are automapped.");
182 | vm.addBifHelp("");
183 |
184 | AddCoreOps();
185 | AddMathOps();
186 | AddStreamOps();
187 | AddRandomOps();
188 | AddUGenOps();
189 | AddMidiOps();
190 | AddSetOps();
191 |
192 |
193 | vm.log_file = getenv("SAPF_LOG");
194 | if (!vm.log_file) {
195 | #ifdef _WIN32
196 | const char* home_dir = getenv("USERPROFILE");
197 | #else
198 | const char* home_dir = getenv("HOME");
199 | #endif
200 | char logfilename[PATH_MAX];
201 | snprintf(logfilename, PATH_MAX, "%s/sapf-log.txt", home_dir);
202 | vm.log_file = strdup(logfilename);
203 | }
204 |
205 | #ifdef SAPF_DISPATCH
206 | __block
207 | #endif
208 | Thread th;
209 |
210 | #ifdef SAPF_MANTA
211 | auto m = manta();
212 | try {
213 | m->Connect();
214 | } catch(...) {
215 | }
216 | printf("Manta %s connected.\n", m->IsConnected() ? "is" : "IS NOT");
217 |
218 | #ifdef SAPF_DISPATCH
219 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
220 | mantaLoop();
221 | });
222 | #else
223 | std::thread mantaThread([&]() {
224 | mantaLoop();
225 | });
226 | #endif // SAPF_DISPATCH
227 | #endif // SAPF_MANTA
228 |
229 | if (!vm.prelude_file) {
230 | vm.prelude_file = getenv("SAPF_PRELUDE");
231 | }
232 | if (vm.prelude_file) {
233 | loadFile(th, vm.prelude_file);
234 | }
235 |
236 | #ifdef SAPF_DISPATCH
237 | #ifdef SAPF_COREFOUNDATION
238 | // TODO does dispatch_async + CFRunLoopRun have any benefit over dispatch_sync?
239 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
240 | replLoop(th);
241 | });
242 |
243 | CFRunLoopRun();
244 | #else
245 | dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
246 | replLoop(th);
247 | });
248 | #endif // SAPF_COREFOUNDATION
249 | #else
250 | std::thread replThread([&]() {
251 | replLoop(th);
252 | });
253 |
254 | replThread.join();
255 | #endif // SAPF_DISPATCH
256 |
257 | return 0;
258 | }
259 |
260 |
--------------------------------------------------------------------------------
/src/makeImage.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 "makeImage.hpp"
18 | #include
19 |
20 | struct Bitmap {
21 | // TODO
22 | };
23 |
24 | Bitmap* createBitmap(int width, int height) {
25 | // TODO
26 | Bitmap *bitmap = (Bitmap *) calloc(1, sizeof(Bitmap));
27 | return bitmap;
28 | }
29 |
30 | void writeBitmap(Bitmap* bitmap, const char *path) {
31 | // TODO
32 | }
33 |
34 | void freeBitmap(Bitmap* bitmap) {
35 | // TODO
36 | }
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/subprojects/doctest.wrap:
--------------------------------------------------------------------------------
1 | [wrap-file]
2 | directory = doctest-2.4.11
3 | source_url = https://github.com/doctest/doctest/archive/refs/tags/v2.4.11.tar.gz
4 | source_filename = doctest-2.4.11.tar.gz
5 | source_hash = 632ed2c05a7f53fa961381497bf8069093f0d6628c5f26286161fbd32a560186
6 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/doctest_2.4.11-1/doctest-2.4.11.tar.gz
7 | wrapdb_version = 2.4.11-1
8 |
9 | [provide]
10 | dependency_names = doctest
11 |
--------------------------------------------------------------------------------
/subprojects/eigen.wrap:
--------------------------------------------------------------------------------
1 | [wrap-file]
2 | directory = eigen-3.4.0
3 | source_url = https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.bz2
4 | source_filename = eigen-3.4.0.tar.bz2
5 | source_hash = b4c198460eba6f28d34894e3a5710998818515104d6e74e5cc331ce31e46e626
6 | patch_filename = eigen_3.4.0-2_patch.zip
7 | patch_url = https://wrapdb.mesonbuild.com/v2/eigen_3.4.0-2/get_patch
8 | patch_hash = cb764fd9fec02d94aaa2ec673d473793c0d05da4f4154c142f76ef923ea68178
9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/eigen_3.4.0-2/eigen-3.4.0.tar.bz2
10 | wrapdb_version = 3.4.0-2
11 |
12 | [provide]
13 | eigen3 = eigen_dep
14 |
--------------------------------------------------------------------------------
/test/doctest.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 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
18 | #include "doctest.h"
--------------------------------------------------------------------------------
/test/helpers/ArrHelpers.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 __ArrHelpers_h__
18 | #define __ArrHelpers_h__
19 |
20 | #define CHECK_ARR(expected, actual, n) \
21 | do { \
22 | LOOP(i,n) { CHECK(actual[i] == doctest::Approx(expected[i]).epsilon(1e-9)); } \
23 | } while (0)
24 |
25 | #endif
--------------------------------------------------------------------------------
/test/test_AsyncAudioFileWriter.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 | #ifndef SAPF_AUDIOTOOLBOX
18 |
19 | #include "doctest.h"
20 | #include "AsyncAudioFileWriter.hpp"
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | using std::string, std::filesystem::exists, std::filesystem::remove, std::filesystem::file_size;
27 |
28 | TEST_CASE("AsyncAudioFileWriter writing tests") {
29 | const string testFileName{"test_async_audio_output.wav"};
30 | constexpr int sampleRate{44100};
31 | uint32_t bufferSize, numChannels, numFrames;
32 |
33 | if (exists(testFileName)) {
34 | remove(testFileName);
35 | }
36 |
37 | SUBCASE("small single channel") {
38 | bufferSize = 1024;
39 | numChannels = 1;
40 | numFrames = 1024;
41 | }
42 |
43 | SUBCASE("large single channel") {
44 | bufferSize = 1024;
45 | numChannels = 1;
46 | // value that should exceed the max ring buffer size (1024 * 1024)
47 | numFrames = 1024*1200;
48 | }
49 |
50 | SUBCASE("large stereo channel") {
51 | bufferSize = 1024;
52 | numChannels = 2;
53 | numFrames = 1024*520;
54 | }
55 |
56 | SUBCASE("small 7.1 channel") {
57 | bufferSize = 1024;
58 | numChannels = 8;
59 | numFrames = 1024;
60 | }
61 |
62 | SUBCASE("large 7.1 channel") {
63 | bufferSize = 1024;
64 | numChannels = 8;
65 | numFrames = 1024*140;
66 | }
67 |
68 | SUBCASE("6 channels") {
69 | bufferSize = 1024;
70 | numChannels = 6;
71 | numFrames = 1024*100;
72 | }
73 |
74 | SUBCASE("9 channels") {
75 | bufferSize = 1024;
76 | numChannels = 9;
77 | numFrames = 1024*100;
78 | }
79 |
80 | CAPTURE(numChannels);
81 | CAPTURE(numFrames);
82 | CAPTURE(bufferSize);
83 |
84 | // fill the buffers with a non-repeating pattern that also differs between each channel and keep writing
85 | // until we've written the desired number of frames
86 | {
87 | RtBuffers buffers{numChannels, bufferSize};
88 | AsyncAudioFileWriter writer(testFileName, sampleRate, numChannels);
89 | for (size_t bufStartFrame = 0; bufStartFrame < numFrames; bufStartFrame+=bufferSize) {
90 | for (size_t frame = 0; frame < bufferSize; frame++) {
91 | for (int channel = 0; channel < numChannels; channel++) {
92 | buffers.data(channel)[frame] = -1.0f + ((frame + bufStartFrame + channel) / numFrames)*2;
93 | }
94 | }
95 | writer.writeAsync(buffers, bufferSize);
96 | }
97 | }
98 |
99 | // ensure file was closed (by trying to open it again)
100 | {
101 | std::ifstream testFile{testFileName, std::ios::binary};
102 | CHECK(testFile.is_open());
103 | }
104 |
105 | // check the file contents
106 | SF_INFO sfinfo;
107 | SNDFILE *sndfile = sf_open(testFileName.c_str(), SFM_READ, &sfinfo);
108 | REQUIRE(sndfile != nullptr);
109 | CHECK(sfinfo.channels == numChannels);
110 | CHECK(sfinfo.samplerate == sampleRate);
111 |
112 | std::vector buffer(bufferSize * numChannels);
113 |
114 | sf_count_t framesRead{0};
115 | sf_count_t totalFrames{0};
116 |
117 | while ((framesRead = sf_readf_float(sndfile, buffer.data(), bufferSize)) > 0) {
118 | for (size_t frame = 0; frame < framesRead; frame++) {
119 | for (size_t channel = 0; channel < numChannels; channel++) {
120 | float expectedValue = -1.0f + ((frame + totalFrames + channel) / numFrames) * 2;
121 | CHECK(buffer[frame * numChannels + channel] == doctest::Approx(expectedValue).epsilon(1e-5));
122 | }
123 | }
124 | totalFrames += framesRead;
125 | }
126 |
127 | CHECK(totalFrames == numFrames);
128 | sf_close(sndfile);
129 |
130 | // Clean up the test file
131 | if (exists(testFileName)) {
132 | remove(testFileName);
133 | }
134 | }
135 |
136 | #endif
137 |
--------------------------------------------------------------------------------
/test/test_OscilUgens.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 "Object.hpp"
19 | #include "VM.hpp"
20 | #include "doctest.h"
21 | #include "ArrHelpers.hpp"
22 | #include "OscilUGens.hpp"
23 |
24 | // non-vectorized version for comparison
25 | void sinosc_calc(Z phase, Z freqmul, int n, Z* out, Z* freq, int freqStride) {
26 | for (int i = 0; i < n; ++i) {
27 | out[i] = phase;
28 | phase += *freq * freqmul;
29 | freq += freqStride;
30 | if (phase >= kTwoPi) phase -= kTwoPi;
31 | else if (phase < 0.) phase += kTwoPi;
32 | }
33 | LOOP(i,n) { out[i] = sin(out[i]); }
34 | }
35 |
36 | TEST_CASE("SinOsc SIMD") {
37 | const int n = 100;
38 | Z out[n];
39 | Z freq[n];
40 | Z expected[n];
41 | Z startFreq = 400;
42 | Z freqmul = 1;
43 | Z iphase;
44 | int freqStride;
45 | Thread th;
46 | th.rate.radiansPerSample = freqmul;
47 | LOOP(i,n) { freq[i] = sin(i/(double)n)*400 + 200; }
48 |
49 | SUBCASE("") {
50 | iphase = 0;
51 | freqStride = 1;
52 | }
53 |
54 | SUBCASE("") {
55 | iphase = .3;
56 | freqStride = 1;
57 | }
58 |
59 | SUBCASE("") {
60 | iphase = 0;
61 | freqStride = 0;
62 | }
63 |
64 | SUBCASE("") {
65 | iphase = .3;
66 | freqStride = 0;
67 | }
68 | CAPTURE(iphase);
69 | CAPTURE(freqStride);
70 |
71 | Z phase = sc_wrap(iphase, 0., 1.) * kTwoPi;
72 |
73 | SinOsc osc(th, startFreq, iphase);
74 | osc.calc(n, out, freq, freqStride);
75 | sinosc_calc(phase, freqmul, n, expected, freq, freqStride);
76 | CHECK_ARR(expected, out, n);
77 | }
78 |
79 | // non-vectorized version for comparison
80 | void sinoscpm_calc(Z freqmul, int n, Z* out, Z* freq, Z* phasemod, int freqStride, int phasemodStride) {
81 | Z phase = 0.;
82 | for (int i = 0; i < n; ++i) {
83 | out[i] = phase + *phasemod * kTwoPi;
84 | phase += *freq * freqmul;
85 | freq += freqStride;
86 | phasemod += phasemodStride;
87 | if (phase >= kTwoPi) phase -= kTwoPi;
88 | else if (phase < 0.) phase += kTwoPi;
89 | }
90 | LOOP(i,n) { out[i] = sin(out[i]); }
91 | }
92 |
93 | TEST_CASE("SinOscPM SIMD") {
94 | const int n = 100;
95 | Z out[n];
96 | Z freq[n];
97 | Z phasemod[n];
98 | Z expected[n];
99 | Z startFreq = 400;
100 | Z freqmul = 1;
101 | int phasemodStride = 1;
102 | Z iphase;
103 | int freqStride;
104 | Thread th;
105 | th.rate.radiansPerSample = freqmul;
106 | LOOP(i,n) { freq[i] = sin(i/(double)n)*400 + 200; }
107 | LOOP(i,n) { phasemod[i] = cos(i/(double)n); }
108 |
109 | SUBCASE("") {
110 | freqStride = 1;
111 | }
112 | SUBCASE("") {
113 | freqStride = 0;
114 | }
115 |
116 | CAPTURE(freqStride);
117 |
118 | SinOscPM osc(th, startFreq, iphase);
119 | osc.calc(n, out, freq, phasemod, freqStride, phasemodStride);
120 | sinoscpm_calc(freqmul, n, expected, freq, phasemod, freqStride, phasemodStride);
121 | CHECK_ARR(expected, out, n);
122 | }
123 |
124 | static void zeroTable(size_t n, Z* table)
125 | {
126 | memset(table, 0, n * sizeof(Z));
127 | }
128 |
129 | const int kWaveTableSize = 16384;
130 | const size_t kWaveTableSize2 = kWaveTableSize / 2;
131 | void fillwavetable_calc(int n, Z* amps, int ampStride, Z* phases, int phaseStride, Z smooth, Z* table) {
132 | const Z two_pi = 2. * M_PI;
133 |
134 | Z real[kWaveTableSize2];
135 | Z imag[kWaveTableSize2];
136 |
137 | zeroTable(kWaveTableSize2, real);
138 | zeroTable(kWaveTableSize2, imag);
139 |
140 |
141 | Z w = M_PI_2 / n;
142 | for (int i = 0; i < n; ++i) {
143 | Z smoothAmp = smooth == 0. ? 1. : pow(cos(w*i), smooth);
144 | real[i+1] = *amps * smoothAmp;
145 | imag[i+1] = (*phases - .25) * two_pi;
146 | amps += ampStride;
147 | phases += phaseStride;
148 | }
149 |
150 | for(size_t i = 0; i < kWaveTableSize2; i++) {
151 | Z radius = real[i];
152 | Z angle = imag[i];
153 | real[i] = radius * cos(angle);
154 | imag[i] = radius * sin(angle);
155 | }
156 | rifft(kWaveTableSize, real, imag, table);
157 | }
158 |
159 | TEST_CASE("fillWaveTable SIMD") {
160 | const int n = 100;
161 | Z amps[n];
162 | int ampStride = 1;
163 | Z phases[n];
164 | int phaseStride = 1;
165 | Z smooth = 1;
166 | Z out[kWaveTableSize];
167 | Z expected[kWaveTableSize];
168 | LOOP(i,n) { amps[i] = sin(i/(double)n)/2. + .5; }
169 | LOOP(i,n) { phases[i] = cos(i/(double)n); }
170 |
171 | SUBCASE("") {
172 | ampStride = 1;
173 | phaseStride = 1;
174 | }
175 |
176 | initFFT();
177 | fillWaveTable(n, amps, ampStride, phases, phaseStride, smooth, out);
178 | fillwavetable_calc(n , amps, ampStride, phases, phaseStride, smooth, expected);
179 | CHECK_ARR(expected, out, n);
180 | }
--------------------------------------------------------------------------------
/test/test_SndfileSoundFile.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 | #ifndef SAPF_AUDIOTOOLBOX
18 |
19 | #include "doctest.h"
20 | #include "SndfileSoundFile.hpp"
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | using std::string, std::filesystem::exists, std::filesystem::remove, std::filesystem::file_size;
28 |
29 | TEST_CASE("SndfileSoundFile writing tests") {
30 | const string testFileName{"test_async_audio_output.wav"};
31 | constexpr int sampleRate{44100};
32 | uint32_t bufferSize, numChannels, numFrames;
33 |
34 | if (exists(testFileName)) {
35 | remove(testFileName);
36 | }
37 |
38 | SUBCASE("small single channel") {
39 | bufferSize = 1024;
40 | numChannels = 1;
41 | numFrames = 1024;
42 | }
43 |
44 | SUBCASE("large single channel") {
45 | bufferSize = 1024;
46 | numChannels = 1;
47 | numFrames = 1024*1200;
48 | }
49 |
50 | SUBCASE("large stereo channel") {
51 | bufferSize = 1024;
52 | numChannels = 2;
53 | numFrames = 1024*520;
54 | }
55 |
56 | SUBCASE("small 7.1 channel") {
57 | bufferSize = 1024;
58 | numChannels = 8;
59 | numFrames = 1024;
60 | }
61 |
62 | SUBCASE("large 7.1 channel") {
63 | bufferSize = 1024;
64 | numChannels = 8;
65 | numFrames = 1024*140;
66 | }
67 |
68 | SUBCASE("6 channels") {
69 | bufferSize = 1024;
70 | numChannels = 6;
71 | numFrames = 1024*100;
72 | }
73 |
74 | SUBCASE("9 channels") {
75 | bufferSize = 1024;
76 | numChannels = 9;
77 | numFrames = 1024*100;
78 | }
79 |
80 | CAPTURE(numChannels);
81 | CAPTURE(numFrames);
82 | CAPTURE(bufferSize);
83 |
84 | // fill the buffer (interleaved) with a non-repeating pattern that also differs between each channel and keep writing
85 | // until we've written the desired number of frames
86 | {
87 | std::valarray buf(0., numChannels * bufferSize);
88 | PortableBuffers bufs{1};
89 | const auto sndfile = SndfileSoundFile::create(testFileName.c_str(), numChannels,
90 | sampleRate, 0., false);
91 | bufs.setNumChannels(0, numChannels);
92 | bufs.setData(0, &buf[0]);
93 | bufs.setSize(0, bufferSize * sizeof(float));
94 |
95 |
96 | for (size_t bufStartFrame = 0; bufStartFrame < numFrames; bufStartFrame+=bufferSize) {
97 | for (size_t frame = 0; frame < bufferSize; frame++) {
98 | for (int channel = 0; channel < numChannels; channel++) {
99 | buf[frame * numChannels + channel] = -1.0f + ((frame + bufStartFrame + channel) / numFrames)*2;
100 | }
101 | }
102 | sndfile->write(bufferSize, bufs);
103 | }
104 | }
105 |
106 | // ensure file was closed (by trying to open it again)
107 | {
108 | std::ifstream testFile{testFileName, std::ios::binary};
109 | CHECK(testFile.is_open());
110 | }
111 |
112 | // check the file contents
113 | SF_INFO sfinfo;
114 | SNDFILE *sndfile = sf_open(testFileName.c_str(), SFM_READ, &sfinfo);
115 | REQUIRE(sndfile != nullptr);
116 | CHECK(sfinfo.channels == numChannels);
117 | CHECK(sfinfo.samplerate == sampleRate);
118 |
119 | std::vector buffer(bufferSize * numChannels);
120 |
121 | sf_count_t framesRead{0};
122 | sf_count_t totalFrames{0};
123 |
124 | while ((framesRead = sf_readf_float(sndfile, buffer.data(), bufferSize)) > 0) {
125 | for (size_t frame = 0; frame < framesRead; frame++) {
126 | for (size_t channel = 0; channel < numChannels; channel++) {
127 | float expectedValue = -1.0f + ((frame + totalFrames + channel) / numFrames) * 2;
128 | CHECK(buffer[frame * numChannels + channel] == doctest::Approx(expectedValue).epsilon(1e-5));
129 | }
130 | }
131 | totalFrames += framesRead;
132 | }
133 |
134 | CHECK(totalFrames == numFrames);
135 | sf_close(sndfile);
136 |
137 | // Clean up the test file
138 | if (exists(testFileName)) {
139 | remove(testFileName);
140 | }
141 | }
142 |
143 | #endif
144 |
--------------------------------------------------------------------------------
/test/test_StreamOps.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 "Object.hpp"
18 | #include "VM.hpp"
19 | #include "doctest.h"
20 | #include "ArrHelpers.hpp"
21 | #include "ZArr.hpp"
22 | #include "StreamOps.hpp"
23 |
24 | // non-vectorized version for comparison
25 | void hann_calc(Z* out, int n) {
26 | for (int i = 0; i < n; i++) {
27 | out[i] = 0.5 * (1 - cos(2*M_PI*i/n));
28 | }
29 | }
30 |
31 | TEST_CASE("hanning simd") {
32 | const int n = 100;
33 | Z expected[n];
34 | Thread th;
35 | th.push(n);
36 |
37 | hann_calc(expected, n);
38 | hanning_(th, nullptr);
39 | P outZList = th.popZList("hanning");
40 | Z* out = outZList->mArray->z();
41 |
42 | CHECK_ARR(expected, out, n);
43 | }
44 |
45 | void hamm_calc(Z* out, int n) {
46 | for (int i = 0; i < n; i++) {
47 | out[i] = 0.54 - .46 * cos(2*M_PI*i/n);
48 | }
49 | }
50 |
51 | TEST_CASE("hamming simd") {
52 | const int n = 100;
53 | Z expected[n];
54 | Thread th;
55 | th.push(n);
56 |
57 | hamm_calc(expected, n);
58 | hamming_(th, nullptr);
59 | P outZList = th.popZList("hamming");
60 | Z* out = outZList->mArray->z();
61 |
62 | CHECK_ARR(expected, out, n);
63 | }
64 |
65 | void blackman_calc(Z* out, int n) {
66 | for (int i = 0; i < n; i++) {
67 | out[i] = 0.42
68 | - .5 * cos(2*M_PI*i/n)
69 | + .08 * cos(4*M_PI*i/n);
70 | }
71 | }
72 |
73 | TEST_CASE("blackman simd") {
74 | const int n = 100;
75 | Z expected[n];
76 | Thread th;
77 | th.push(n);
78 |
79 | blackman_calc(expected, n);
80 | blackman_(th, nullptr);
81 | P outZList = th.popZList("blackman");
82 | Z* out = outZList->mArray->z();
83 |
84 | CHECK_ARR(expected, out, n);
85 | }
86 |
87 | void calc_winseg_apply_window(Z* segbuf, Z* window, int n) {
88 | LOOP(i,n) { segbuf[i] = segbuf[i] * window[i]; }
89 | }
90 |
91 | TEST_CASE("WinSegment apply window simd") {
92 | const int n = 100;
93 | Z blackman[n];
94 | blackman_calc(blackman, n);
95 | Z segbuf_expected[n];
96 | Z segbuf_actual[n];
97 | LOOP(i, n) { segbuf_expected[i] = sin(i / n); }
98 | LOOP(i, n) { segbuf_actual[i] = sin(i / n); }
99 |
100 | calc_winseg_apply_window(segbuf_expected, blackman, n);
101 | #ifdef SAPF_ACCELERATE
102 | wseg_apply_window(segbuf_actual, blackman, n);
103 | #else
104 | wseg_apply_window(segbuf_actual, zarr(blackman, 1, n), n);
105 | #endif
106 |
107 | CHECK_ARR(segbuf_expected, segbuf_actual, n);
108 | }
109 |
--------------------------------------------------------------------------------