├── Animation.cpp ├── Animation.h ├── BoundedValue.h ├── Box.cpp ├── Box.h ├── ConcurrentResource.h ├── Data.h ├── Doppler.cpp ├── Doppler.h ├── DrawInterpolator.h ├── DrewLib.h ├── Functions.h ├── GLUT.h ├── History.h ├── ImageEffect.cpp ├── ImageEffect.h ├── Interpolator.h ├── LICENSE ├── Multi.h ├── NonlinearInterpolator.h ├── OpenGL.h ├── OpenGLWindow.h ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp ├── PluginProcessor.h ├── Points.h ├── PolyPtr.h ├── Polynomial.h ├── PolynomialSpline.h ├── PrintToFile.h ├── README.md ├── Resampler.cpp ├── Resampler.h ├── SelectionBox.cpp ├── SelectionBox.h ├── SoundSource.cpp ├── SoundSource.h ├── Spline.h ├── StackArray.h ├── StringFunctions.cpp ├── StringFunctions.h ├── TextUtils.cpp ├── TextUtils.h ├── View2D.cpp └── View2D.h /Animation.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Animation.cpp 3 | // 4 | // Created by Andrew Barker on 4/24/16. 5 | // 6 | // 7 | 8 | #include "Animation.h" 9 | 10 | Animation::Animation() noexcept 11 | : durationInSeconds(1), currentTimeInSeconds(1) 12 | { 13 | } 14 | 15 | Animation::Animation(const float durationInSeconds, 16 | const bool startImmediately) noexcept 17 | : durationInSeconds(durationInSeconds), 18 | currentTimeInSeconds(startImmediately ? 0 : durationInSeconds) 19 | { 20 | } 21 | 22 | void Animation::advance(const float frameRate) noexcept 23 | { 24 | currentTimeInSeconds += 1.0f / frameRate; 25 | } 26 | 27 | void Animation::restart() noexcept 28 | { 29 | currentTimeInSeconds = 0; 30 | } 31 | 32 | void Animation::restart(const float newDurationInSeconds) noexcept 33 | { 34 | currentTimeInSeconds = 0; 35 | durationInSeconds = newDurationInSeconds; 36 | } 37 | 38 | void Animation::finish() noexcept 39 | { 40 | currentTimeInSeconds = durationInSeconds; 41 | } 42 | 43 | float Animation::getProgress() const noexcept 44 | { 45 | if (currentTimeInSeconds < durationInSeconds) 46 | return currentTimeInSeconds / durationInSeconds; 47 | else 48 | return 1.0f; 49 | } 50 | 51 | bool Animation::isPlaying() const noexcept 52 | { 53 | return currentTimeInSeconds < durationInSeconds; 54 | } 55 | 56 | float Animation::getDuration() const noexcept 57 | { 58 | return durationInSeconds; 59 | } 60 | -------------------------------------------------------------------------------- /Animation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Animation.h 3 | // 4 | // Created by Andrew Barker on 4/24/16. 5 | // 6 | // 7 | 8 | #ifndef Animation_h 9 | #define Animation_h 10 | 11 | class Animation 12 | { 13 | public: 14 | Animation() noexcept; 15 | Animation(float durationInSeconds, bool startImmediately = false) noexcept; 16 | void advance(float frameRate) noexcept; 17 | void restart() noexcept; 18 | void restart(float durationInSeconds) noexcept; 19 | void finish() noexcept; 20 | float getProgress() const noexcept; 21 | bool isPlaying() const noexcept; 22 | float getDuration() const noexcept; 23 | private: 24 | float durationInSeconds, currentTimeInSeconds; 25 | }; 26 | 27 | #endif /* Animation_h */ 28 | -------------------------------------------------------------------------------- /BoundedValue.h: -------------------------------------------------------------------------------- 1 | /* 2 | BoundedValue.h 3 | 4 | Abstracts the concept of an ordered value that must be in the range of [min, max] 5 | 6 | Copyright (C) 2016 Andrew Barker 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . 20 | 21 | The author can be contacted via email at andrew.barker.12345@gmail.com. 22 | */ 23 | 24 | #ifndef BoundedValue_h 25 | #define BoundedValue_h 26 | 27 | #include 28 | 29 | template 30 | class BoundedValue 31 | { 32 | public: 33 | /** sets up a value of zero with a bounds of [0, 0] */ 34 | BoundedValue () noexcept; 35 | /** sets up with a specified value and bounds of [min, max] */ 36 | BoundedValue (T value, T min, T max) noexcept; 37 | 38 | /** returns the value */ 39 | T getValue () const noexcept; 40 | /** sets the value and makes sure it is inside the specified bounds */ 41 | void setValue (T value) noexcept; 42 | 43 | /** returns the minimum allowed value for the bounded value */ 44 | T getMin () const noexcept; 45 | /** sets the minimum allowed value for the bounded value */ 46 | void setMin (T min) noexcept; 47 | 48 | /** returns the maximum allowed value for the bounded value */ 49 | T getMax () const noexcept; 50 | /** sets the maximum allowed value for the bounded value */ 51 | void setMax (T max) noexcept; 52 | 53 | /** returns the difference between the min and max for the value */ 54 | T range () const noexcept; 55 | 56 | /** returns the value normalized to it's range, between [0, 1] */ 57 | T normalized () const noexcept; 58 | 59 | /** allows implicit conversion to the type of the bounded value */ 60 | operator T () const noexcept; 61 | 62 | /** sets the value and makes sure it is inside the specified bounds */ 63 | BoundedValue& operator = (T value) noexcept; 64 | /** adds the the specified amount to the value with bounds checking */ 65 | BoundedValue& operator += (T addValue) noexcept; 66 | /** subtracts the specified amount from the value with bounds checking */ 67 | BoundedValue& operator -= (T subValue) noexcept; 68 | /** multiplies the value by the specified amount with bounds checking */ 69 | BoundedValue& operator *= (T multValue) noexcept; 70 | /** divides the value by the specified amount with bounds checking */ 71 | BoundedValue& operator /= (T divValue) noexcept; 72 | /** sets the value to the remainder of value / divValue with bounds checking */ 73 | BoundedValue& operator %= (T divValue) noexcept; 74 | 75 | private: 76 | T _value, _min, _max; 77 | }; 78 | 79 | // implementation 80 | 81 | template 82 | BoundedValue::BoundedValue () noexcept 83 | : _value (0), 84 | _min (0), 85 | _max (0) 86 | {} 87 | 88 | template 89 | BoundedValue::BoundedValue (const T value, 90 | const T min, 91 | const T max) noexcept 92 | : _value (value), 93 | _min (min), 94 | _max (max) 95 | { 96 | if (_min > _max) { 97 | _min = max; 98 | _max = min; 99 | } 100 | } 101 | 102 | template 103 | T BoundedValue::getValue () const noexcept { return _value; } 104 | 105 | template 106 | void BoundedValue::setValue (const T value) noexcept 107 | { 108 | if (getMin() <= value && value <= getMax()) 109 | _value = value; 110 | else if (value < getMin()) 111 | _value = getMin(); 112 | else 113 | _value = getMax(); 114 | } 115 | 116 | template 117 | T BoundedValue::getMin () const noexcept { return _min; } 118 | 119 | template 120 | void BoundedValue::setMin (const T min) noexcept 121 | { 122 | if (min <= getMax()) { 123 | _min = min; 124 | setValue(getValue()); 125 | } else { 126 | _min = min; 127 | _max = min; 128 | _value = min; 129 | } 130 | } 131 | 132 | template 133 | T BoundedValue::getMax () const noexcept { return _max; } 134 | 135 | template 136 | void BoundedValue::setMax (const T max) noexcept 137 | { 138 | if (max >= getMin()) { 139 | _max = max; 140 | setValue(getValue()); 141 | } else { 142 | _min = max; 143 | _max = max; 144 | _value = max; 145 | } 146 | } 147 | 148 | template 149 | BoundedValue::operator T () const noexcept { return _value; } 150 | 151 | template 152 | BoundedValue& BoundedValue::operator = (const T value) noexcept 153 | { 154 | setValue(value); 155 | return *this; 156 | } 157 | 158 | template 159 | BoundedValue& BoundedValue::operator += (const T value) noexcept 160 | { 161 | setValue(getValue() + value); 162 | return *this; 163 | } 164 | 165 | template 166 | BoundedValue& BoundedValue::operator -= (const T value) noexcept 167 | { 168 | setValue(getValue() - value); 169 | return *this; 170 | } 171 | 172 | template 173 | BoundedValue& BoundedValue::operator *= (const T value) noexcept 174 | { 175 | setValue(getValue() * value); 176 | return *this; 177 | } 178 | 179 | template 180 | BoundedValue& BoundedValue::operator /= (T value) noexcept 181 | { 182 | setValue(getValue() / value); 183 | return *this; 184 | } 185 | 186 | template 187 | BoundedValue& BoundedValue::operator %= (T value) noexcept 188 | { 189 | setValue(std::fmod(getValue(), value)); 190 | return *this; 191 | } 192 | 193 | template 194 | T BoundedValue::range () const noexcept 195 | { return getValue().getMax() - getValue().getMin(); } 196 | 197 | template 198 | T BoundedValue::normalized () const noexcept 199 | { return (getValue() - getMin()) * range(); } 200 | 201 | #endif /* BoundedValue_h */ -------------------------------------------------------------------------------- /Box.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Box.cpp 3 | // 4 | // Created by Andrew Barker on 9/19/16. 5 | // 6 | // 7 | 8 | #include "Box.h" 9 | #include "OpenGL.h" 10 | #include 11 | 12 | 13 | Box::Box() noexcept 14 | : top (0), 15 | bottom (0), 16 | left (0), 17 | right (0) 18 | { 19 | } 20 | 21 | Box::Box(const float top, 22 | const float bottom, 23 | const float left, 24 | const float right) noexcept 25 | : top (std::max(top, bottom)), 26 | bottom (std::min(top, bottom)), 27 | left (std::min(left, right)), 28 | right (std::max(left, right)) 29 | { 30 | } 31 | 32 | Box::Box(const Point& pt, 33 | const float radius) noexcept 34 | : top (pt.y + radius), 35 | bottom (pt.y - radius), 36 | left (pt.x - radius), 37 | right (pt.x + radius) 38 | { 39 | } 40 | 41 | bool Box::operator== (const Box& box) const noexcept 42 | { 43 | return top == box.getTop() && 44 | bottom == box.getBottom() && 45 | left == box.getLeft() && 46 | right == box.getRight(); 47 | } 48 | 49 | bool Box::operator!= (const Box& box) const noexcept 50 | { 51 | return top != box.getTop() || 52 | bottom != box.getBottom() || 53 | left != box.getLeft() || 54 | right != box.getRight(); 55 | } 56 | 57 | float Box::getTop() const noexcept { return top; } 58 | void Box::setTop(const float newTop) noexcept 59 | { 60 | if (newTop > bottom) 61 | top = newTop; 62 | else { 63 | top = bottom; 64 | bottom = newTop; 65 | } 66 | } 67 | 68 | float Box::getBottom() const noexcept { return bottom; } 69 | void Box::setBottom(const float newBottom) noexcept 70 | { 71 | if (newBottom < top) 72 | bottom = newBottom; 73 | else { 74 | bottom = top; 75 | top = newBottom; 76 | } 77 | } 78 | 79 | float Box::getLeft() const noexcept { return left; } 80 | void Box::setLeft(const float newLeft) noexcept 81 | { 82 | if (newLeft < right) 83 | left = newLeft; 84 | else { 85 | left = right; 86 | right = newLeft; 87 | } 88 | } 89 | 90 | float Box::getRight() const noexcept { return right; } 91 | void Box::setRight(const float newRight) noexcept 92 | { 93 | if (newRight > left) 94 | right = newRight; 95 | else { 96 | right = left; 97 | left = newRight; 98 | } 99 | } 100 | 101 | float Box::width () const noexcept { return getRight() - getLeft(); } 102 | float Box::height () const noexcept { return getTop() - getBottom(); } 103 | float Box::area () const noexcept { return width() * height(); } 104 | float Box::centerX() const noexcept { return getLeft() + 0.5f * width(); } 105 | float Box::centerY() const noexcept { return getBottom() + 0.5f * height(); } 106 | 107 | void Box::drawVerticies () const 108 | { 109 | for (const auto& pt : boundaryPoints()) 110 | glVertex2f(pt.x, pt.y); 111 | // glVertex2f(getLeft(), getTop()); 112 | // glVertex2f(getRight(), getTop()); 113 | // glVertex2f(getRight(), getBottom()); 114 | // glVertex2f(getLeft(), getBottom()); 115 | } 116 | void Box::drawOutline () const 117 | { 118 | glBegin(GL_LINE_LOOP); 119 | drawVerticies(); 120 | glEnd(); 121 | } 122 | void Box::drawFill () const 123 | { 124 | glBegin(GL_QUADS); 125 | drawVerticies(); 126 | glEnd(); 127 | } 128 | 129 | std::vector> Box::boundaryPoints () const noexcept 130 | { 131 | return {{getLeft(), getTop()}, 132 | {getRight(), getTop()}, 133 | {getRight(), getBottom()}, 134 | {getLeft(), getBottom()}}; 135 | } 136 | 137 | bool Box::contains (const Point& pt) const noexcept 138 | { 139 | return getLeft() < pt.x && pt.x < getRight() && 140 | getBottom() < pt.y && pt.y < getTop(); 141 | } 142 | 143 | bool Box::overlaps (const Box& other) const noexcept 144 | { 145 | return ! (getBottom() > other.getTop() || getTop() < other.getBottom() || 146 | getLeft() > other.getRight() || getRight() < other.getLeft()); 147 | } 148 | 149 | void Box::crop (const Box& crop) noexcept 150 | { 151 | if (getTop() > crop.getTop()) 152 | setTop(crop.getTop()); 153 | if (getBottom() < crop.getBottom()) 154 | setBottom(crop.getBottom()); 155 | if (getLeft() < crop.getLeft()) 156 | setLeft(crop.getLeft()); 157 | if (getRight() > crop.getRight()) 158 | setRight(crop.getRight()); 159 | } 160 | 161 | Box Box::combinedWith (const Box& other) const noexcept 162 | { 163 | return { std::max(getTop(), other.getTop()), std::min(getBottom(), other.getBottom()), 164 | std::min(getLeft(), other.getLeft()), std::max(getRight(), other.getRight()) }; 165 | } 166 | 167 | Box Box::scaled (const float hScale, 168 | const float vScale) const noexcept 169 | { 170 | const float dWidth = width() * 0.5f * (1 - hScale); 171 | const float dHeight = height() * 0.5f * (1 - vScale); 172 | const float top = getTop() - dHeight; 173 | const float bottom = getBottom() + dHeight; 174 | const float left = getLeft() + dWidth; 175 | const float right = getRight() - dWidth; 176 | return {top, bottom, left, right}; 177 | } 178 | 179 | void Box::move (const float dx, 180 | const float dy) noexcept 181 | { 182 | *this = {getTop() + dy, getBottom() + dy, getLeft() + dx, getRight() + dx}; 183 | } 184 | 185 | 186 | //float width (const Box& b) noexcept 187 | //{ 188 | // return b.getRight() - b.getLeft(); 189 | //} 190 | // 191 | //float height (const Box& b) noexcept 192 | //{ 193 | // return b.getTop() - b.getBottom(); 194 | //} 195 | // 196 | //float area (const Box& b) noexcept 197 | //{ 198 | // return width(b) * height(b); 199 | //} 200 | // 201 | //void drawVerticies (const Box& b) 202 | //{ 203 | // glVertex2f(b.getLeft(), b.getTop()); 204 | // glVertex2f(b.getRight(), b.getTop()); 205 | // glVertex2f(b.getRight(), b.getBottom()); 206 | // glVertex2f(b.getLeft(), b.getBottom()); 207 | //} 208 | // 209 | //void drawOutline (const Box& b) 210 | //{ 211 | // glBegin(GL_LINE_LOOP); 212 | // drawVerticies(b); 213 | // glEnd(); 214 | //} 215 | // 216 | //void drawFill (const Box& b) 217 | //{ 218 | // glBegin(GL_QUADS); 219 | // drawVerticies(b); 220 | // glEnd(); 221 | //} 222 | // 223 | //std::vector> boundaryPoints (const Box& b) noexcept 224 | //{ 225 | // return {{b.getLeft(), b.getTop()}, 226 | // {b.getRight(), b.getTop()}, 227 | // {b.getRight(), b.getBottom()}, 228 | // {b.getLeft(), b.getBottom()}}; 229 | //} 230 | // 231 | //bool contain (const Box& b, 232 | // const Point& pt) noexcept 233 | //{ 234 | // return b.getLeft() < pt.x && pt.x < b.getRight() && 235 | // b.getBottom() < pt.y && pt.y < b.getTop(); 236 | //} 237 | // 238 | //bool overlap (const Box& b1, 239 | // const Box& b2) noexcept 240 | //{ 241 | // return ! (b1.getBottom() > b2.getTop() || b1.getTop() < b2.getBottom() || 242 | // b1.getLeft() > b2.getRight() || b1.getRight() < b2.getLeft()); 243 | //} 244 | // 245 | //void crop (Box& b, 246 | // const Box& crop) noexcept 247 | //{ 248 | // if (b.getTop() > crop.getTop()) 249 | // b.setTop(crop.getTop()); 250 | // if (b.getBottom() < crop.getBottom()) 251 | // b.setBottom(crop.getBottom()); 252 | // if (b.getLeft() < crop.getLeft()) 253 | // b.setLeft(crop.getLeft()); 254 | // if (b.getRight() > crop.getRight()) 255 | // b.setRight(crop.getRight()); 256 | //} 257 | // 258 | //Box combined (const Box& b1, 259 | // const Box& b2) noexcept 260 | //{ 261 | // return { std::max(b1.getTop(), b2.getTop()), std::min(b1.getBottom(), b2.getBottom()), 262 | // std::min(b1.getLeft(), b2.getLeft()), std::max(b1.getRight(), b2.getRight()) }; 263 | //} 264 | // 265 | //Box getScaled (const Box& b, 266 | // const float hScale, 267 | // const float vScale) noexcept 268 | //{ 269 | // const float dWidth = width(b) * 0.5f * (1 - hScale); 270 | // const float dHeight = height(b) * 0.5f * (1 - vScale); 271 | // const float top = b.getTop() - dHeight; 272 | // const float bottom = b.getBottom() + dHeight; 273 | // const float left = b.getLeft() + dWidth; 274 | // const float right = b.getRight() - dWidth; 275 | // return {top, bottom, left, right}; 276 | //} 277 | // 278 | //void move(Box& b, 279 | // const float dx, 280 | // const float dy) noexcept 281 | //{ 282 | // b.top += dy; 283 | // b.bottom += dy; 284 | // b.left += dx; 285 | // b.right += dx; 286 | //} 287 | // 288 | ////void placeWithin(Box& b, 289 | //// const Box& placeWithin, 290 | //// const std::vector& noOverlap) noexcept 291 | ////{ 292 | //// int dirCount = 0; 293 | //// for (const auto & x : noOverlap) { 294 | //// if (overlap(b, x)) { 295 | //// move(b, 296 | //// } 297 | //// } 298 | ////} 299 | -------------------------------------------------------------------------------- /Box.h: -------------------------------------------------------------------------------- 1 | // 2 | // Box.h 3 | // 4 | // Created by Andrew Barker on 9/19/16. 5 | // 6 | // 7 | 8 | #ifndef Box_h 9 | #define Box_h 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include 13 | 14 | //template 15 | //struct Point { T x, y; }; 16 | 17 | /** A 2D rectangle enforcing that top > bottom and left < right */ 18 | class Box 19 | { 20 | public: 21 | /** creates an empty box */ 22 | Box () noexcept; 23 | /** creates a box from the specified coordinates of its edges */ 24 | Box (float top, float bottom, float left, float right) noexcept; 25 | /** creates a square box centered around a point and width and height of 2 * radius */ 26 | Box (const Point& pt, float radius) noexcept; 27 | 28 | /** returns true if both boxes have the same edge coordinates */ 29 | bool operator== (const Box& box) const noexcept; 30 | /** returns true if both boxes don't have the same edge coordinates */ 31 | bool operator!= (const Box& box) const noexcept; 32 | 33 | /** returns the y coordinate of the box's top edge */ 34 | float getTop () const noexcept; 35 | /** sets the y coordinate of the box's bottom edge */ 36 | void setTop (float top) noexcept; 37 | /** returns the y coordinate of the box's bottom edge */ 38 | float getBottom () const noexcept; 39 | /** sets the Y coordinate of the box's bottom edge */ 40 | void setBottom (float bottom) noexcept; 41 | /** returns the x coordinate of the box's left edge */ 42 | float getLeft () const noexcept; 43 | /** sets the x coordinate of the box's left edge */ 44 | void setLeft (float left) noexcept; 45 | /** returns the x coordinate of the box's right edge */ 46 | float getRight () const noexcept; 47 | /** sets the x coordinate of the box's right edge */ 48 | void setRight (float right) noexcept; 49 | 50 | /** returns the width of the box */ 51 | float width () const noexcept; 52 | /** returns the height of the box */ 53 | float height () const noexcept; 54 | /** returns the area of the box */ 55 | float area () const noexcept; 56 | /** returns the x coordinate horizontal center of the box */ 57 | float centerX() const noexcept; 58 | /** returns the y coordinate vertical center of the box */ 59 | float centerY() const noexcept; 60 | 61 | /** calls glVertex2f() for all the corner points of the box */ 62 | void drawVerticies () const; 63 | /** draws the outline of the box using OpenGL */ 64 | void drawOutline () const; 65 | /** draws a fill of the box using OpenGL */ 66 | void drawFill () const; 67 | 68 | /** returns all the corner points of the box */ 69 | std::vector> boundaryPoints () const noexcept; 70 | 71 | /** returns true if the specified point is inside the box */ 72 | bool contains (const Point& pt) const noexcept; 73 | /** returns true if this box overlaps at all with another */ 74 | bool overlaps (const Box& other) const noexcept; 75 | /** crops this box by another */ 76 | void crop (const Box& crop) noexcept; 77 | /** returns a box that is the minimum needed to contain this box and another */ 78 | Box combinedWith (const Box& other) const noexcept; 79 | /** returns a box that is this box horizontally and vertically scaled as specified */ 80 | Box scaled (float hScale, float vScale) const noexcept; 81 | /** moves this box by a given x/y distance */ 82 | void move (float dx, float dy) noexcept; 83 | // friend void move(Box& b, 84 | // float dx, 85 | // float dy) noexcept; 86 | 87 | private: 88 | float top, bottom, left, right; 89 | }; 90 | 91 | //float width (const Box& b) noexcept; 92 | //float height (const Box& b) noexcept; 93 | //float area (const Box& b) noexcept; 94 | //void drawVerticies (const Box& b); 95 | //void drawOutline (const Box& b); 96 | //void drawFill (const Box& b); 97 | //std::vector> boundaryPoints(const Box& b) noexcept; 98 | //bool contain (const Box& b, 99 | // const Point& pt) noexcept; 100 | //bool overlap (const Box& b1, 101 | // const Box& b2) noexcept; 102 | //void crop (Box& b, 103 | // const Box& crop) noexcept; 104 | //Box combined (const Box& b1, 105 | // const Box& b2) noexcept; 106 | //Box getScaled (const Box& b, 107 | // float hScale, 108 | // float vScale) noexcept; 109 | //void move(Box& b, 110 | // float dx, 111 | // float dy) noexcept; 112 | ////void placeWithin(Box& b, 113 | //// const Box& placeWithin, 114 | //// const std::vector& noOverlap) noexcept; 115 | 116 | #endif /* Box_h */ -------------------------------------------------------------------------------- /Data.h: -------------------------------------------------------------------------------- 1 | // 2 | // Data.h 3 | // 4 | // Created by Andrew Barker on 6/15/14. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef Data_h 28 | #define Data_h 29 | 30 | #define numAzimuthSteps 180 31 | #define numElevationSteps 90 32 | #define numTimeSteps 128 33 | #define numDistanceSteps 20 34 | #define distanceBegin 0.1 35 | #define distanceEnd 3.25 36 | #define sampleRate_HRTF 44100.0 37 | #define sphereRad 0.09 38 | #define threshold 0.00000001 39 | #define earElevation 90 40 | #define earAzimuth 85 41 | 42 | 43 | //#include 44 | //#include 45 | // 46 | //// note: these dimensions determine the size of the dvf/hrir data. if these are 47 | //// too large then the application may run out of RAM. 48 | //// (especially for 32-bit @ about 2GB) 49 | //#define numAzimuthSteps 180 50 | //#define numElevationSteps 90 51 | //#define numTimeSteps 128 52 | //#define numDistanceSteps 20 53 | //#define distanceBegin 0.1 54 | //#define distanceEnd 3.25 55 | //#define sampleRate_HRTF 44100 56 | //#define sphereRad 0.09 57 | //#define threshold 0.00000001 58 | //#define earElevation 90 59 | //#define earAzimuth 85 60 | // 61 | //// a singleton class to hold the hrir and dvf data so that multiple plugin instances may run without having to consume unecessary RAM 62 | //class Data 63 | //{ 64 | //public: 65 | // static Data& getSingleton() //Return our instance 66 | // { 67 | // return mInstance; 68 | // } 69 | // 70 | // void load() 71 | // { 72 | // 73 | // } 74 | // 75 | // void clear() 76 | // { 77 | // 78 | // } 79 | // 80 | // float***** HRIR; 81 | // //float HRIR[numDistanceSteps][numAzimuthSteps][numElevationSteps][2][numTimeSteps]; 82 | // 83 | //private: 84 | // static Data mInstance; 85 | // 86 | // Data() // read in data on construction 87 | // { 88 | ////// // binary hrtf file name 89 | ////// File f; 90 | ////// f.getSpecialLocation(File::currentApplicationFile); 91 | ////// //juce::File f = juce::File::getSpecialLocation(juce::File::currentApplicationFile); 92 | ////// String path = f.getFullPathName(); 93 | //////// probably not necessary... 94 | ////////#ifdef _WIN32 95 | //////// path += "\"; 96 | ////////#else 97 | //////// path += "/"; 98 | ////////#endif 99 | ////// path += "/Contents/3dAudioData.bin"; 100 | //////// char fullPath[300]; 101 | //////// strcpy(fullPath, path.toRawUTF8()); 102 | //// char* fileName = (char*)"/Users/AndrewBarker/Documents/Programming/3dAudio/SphericalHRIR2.bin"/*"SphericalHRIR.bin"*/; 103 | //// // basic read (should be cross platform) 104 | //// // open the stream 105 | //// std::ifstream is(/*path.getCharPointer()*/fileName); 106 | //// if (is.good()) { 107 | //// // determine the file length 108 | //// is.seekg(0, is.end); 109 | //// size_t size = is.tellg(); 110 | //// is.seekg(0, is.beg); 111 | //// // create a temp array to store the data 112 | //// char* dataBuffer = new char[size]; 113 | //// // load the data 114 | //// is.read(dataBuffer, size); 115 | //// // close the file 116 | //// is.close(); 117 | //// // pack data into HRIR array 118 | //// int index = 0; 119 | //// for (int d = 0; d < numDistanceSteps; d++) { 120 | //// for (int a = 0; a < numAzimuthSteps; a++) { 121 | //// for (int e = 0; e < numElevationSteps; e++) { 122 | //// for (int ch = 0; ch < 2; ch++) { 123 | //// for (int t = 0; t < numTimeSteps; t++) { 124 | //// HRIR[d][a][e][ch][t] = *((float*) &dataBuffer[index*sizeof(float)]); 125 | //// //HRIR[d][a][e][ch][t] = 0; 126 | //// index++; 127 | //// } 128 | //// } 129 | //// } 130 | //// } 131 | //// } 132 | //// // cleanup the temp array 133 | //// delete[] dataBuffer; 134 | //// } else { 135 | //// // failed to open hrtf binary file, load up zeros 136 | //// int index = 0; 137 | //// for (int d = 0; d < numDistanceSteps; d++) { 138 | //// for (int a = 0; a < numAzimuthSteps; a++) { 139 | //// for (int e = 0; e < numElevationSteps; e++) { 140 | //// for (int ch = 0; ch < 2; ch++) { 141 | //// for (int t = 0; t < numTimeSteps; t++) { 142 | //// HRIR[d][a][e][ch][t] = 0; 143 | //// index++; 144 | //// } 145 | //// } 146 | //// } 147 | //// } 148 | //// } 149 | //// } 150 | // } 151 | // 152 | // ~Data() {} 153 | // 154 | // // Dont forget to declare these two. You want to make sure they 155 | // // are unaccessable otherwise you may accidently get copies of 156 | // // your singleton appearing. 157 | // Data(Data const&); // Don't Implement 158 | // void operator=(Data const&); // Don't implement 159 | //}; 160 | // 161 | //Data Data::mInstance; 162 | 163 | #endif 164 | -------------------------------------------------------------------------------- /Doppler.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Doppler.cpp 3 | // ThreeDAudio 4 | // 5 | // Created by Andrew Barker on 4/16/15. 6 | // 7 | // 8 | /* 9 | 3DAudio: simulates surround sound audio for headphones 10 | Copyright (C) 2016 Andrew Barker 11 | 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see . 24 | 25 | The author can be contacted via email at andrew.barker.12345@gmail.com. 26 | */ 27 | 28 | #include "Doppler.h" 29 | 30 | void Doppler::process(const float distance, const int bufferSize, const float* input, float* output) noexcept 31 | { 32 | const auto delay = distance / speedOfSound * sampleRate; 33 | const auto cBufSize = buffer.size(); 34 | if (delayPrev == -1) { 35 | delayPrev = delay; 36 | prevSampleDelayedIdx = delay; 37 | } 38 | const auto slope = (delay - delayPrev) / bufferSize; 39 | const auto a0 = delayPrev; 40 | const auto a1 = slopePrev; 41 | float _a2, _a3; 42 | if ((slope > 0 && slopePrev < 0) || (slope < 0 && slopePrev > 0)) { // use 3rd degree polynomial interpolation for sample delay 43 | _a2 = 2 * (slope - slopePrev) / bufferSize; 44 | _a3 = (slopePrev - slope) / (bufferSize * bufferSize); 45 | } 46 | else { // use 2nd degree polynomial interpolation for sample delay 47 | _a2 = (slope - slopePrev) / (2 * bufferSize); 48 | _a3 = 0; 49 | } 50 | const auto a2 = _a2, a3 = _a3; 51 | const auto denom = a1*bufferSize + a2*bufferSize*bufferSize + a3*bufferSize*bufferSize*bufferSize; 52 | const auto delayScale = denom == 0 ? 0 : (delay - delayPrev) / denom; // check for div by 0 53 | for (int n = 0; n < bufferSize; ++n) { 54 | const auto delayedIdx = a0 + (a1*n + a2*n*n + a3*n*n*n) * delayScale; 55 | auto fidx = bufferInIdx + delayedIdx; 56 | while (fidx >= cBufSize) 57 | fidx -= cBufSize; 58 | while (fidx < 0) 59 | fidx += cBufSize; 60 | const int idxP1 = int(fidx) + 1 == cBufSize ? 0 : int(fidx) + 1; 61 | const int prevIdxP1 = int(prevSampleDelayedIdx) + 1 == cBufSize ? 62 | 0 : int(prevSampleDelayedIdx) + 1; 63 | bool forwards = true; 64 | if (idxP1 < prevIdxP1 && !(idxP1 + cBufSize - prevIdxP1 < prevIdxP1 - idxP1)) 65 | forwards = false; 66 | if (forwards) { 67 | for (int i = prevIdxP1; i != idxP1; i = i + 1 == cBufSize ? 0 : i + 1) { 68 | const auto blend = (i - prevSampleDelayedIdx < 0 ? 69 | cBufSize - prevSampleDelayedIdx + i : i - prevSampleDelayedIdx) 70 | / 71 | (fidx - prevSampleDelayedIdx < 0 ? 72 | cBufSize - prevSampleDelayedIdx + fidx : fidx - prevSampleDelayedIdx); 73 | buffer[i] += prevSample + (input[n] - prevSample) * blend; 74 | } 75 | } 76 | else { 77 | for (int i = idxP1; i != prevIdxP1; i = i + 1 == cBufSize ? 0 : i + 1) { 78 | const auto blend = (i - fidx < 0 ? 79 | cBufSize - fidx + i : i - fidx) 80 | / 81 | (prevSampleDelayedIdx - fidx < 0 ? 82 | cBufSize + prevSampleDelayedIdx - fidx : prevSampleDelayedIdx - fidx); 83 | buffer[i] += prevSample + (input[n] - prevSample) * (1 - blend); 84 | } 85 | } 86 | prevSample = input[n]; 87 | prevSampleDelayedIdx = fidx; 88 | bufferInIdx = bufferInIdx + 1 == cBufSize ? 0 : bufferInIdx + 1; 89 | } 90 | slopePrev = slope; 91 | delayPrev = delay; 92 | for (int n = 0; n < bufferSize; ++n) { 93 | output[n] = buffer[bufferOutIdx]; 94 | buffer[bufferOutIdx] = 0; 95 | bufferOutIdx = bufferOutIdx + 1 == cBufSize ? 0 : bufferOutIdx + 1; 96 | } 97 | } 98 | 99 | void Doppler::allocate(const float maxDistance, const int maxBufferSize, const float speedOfSoundToPlanAllocationSize) 100 | { 101 | const auto maxDelayInSamples = maxDistance / speedOfSoundToPlanAllocationSize * sampleRate; 102 | buffer.resize(maxBufferSize + maxDelayInSamples); 103 | reset(); 104 | } 105 | 106 | void Doppler::free() noexcept 107 | { 108 | buffer.clear(); 109 | } 110 | 111 | void Doppler::reset() noexcept 112 | { 113 | for (auto& x : buffer) 114 | x = 0; 115 | delayPrev = -1; 116 | slopePrev = 0; 117 | bufferInIdx = bufferOutIdx = 0; 118 | prevSample = 0; 119 | } 120 | 121 | void Doppler::setSampleRate(const float _sampleRate) noexcept 122 | { 123 | sampleRate = _sampleRate; 124 | } 125 | 126 | void Doppler::setSpeedOfSound(const float _speedOfSound) noexcept 127 | { 128 | speedOfSound = _speedOfSound; 129 | } 130 | 131 | //#include 132 | // 133 | //void Doppler::process(const float dist, const int N, const float* x, float* y) noexcept 134 | //{ 135 | // // time delay in samples, measured from the first sample of the next buffer to the beginning of the next buffer's delayed waveform 136 | //// float d; // the delay actually used for this buffer 137 | //// const float dDesired = dist*ONE_OVER_SoW*Fs; // the desired delay (the one passed in this buffer) 138 | //// // the following shit is just to smooth the distance changes when they are comming erraticly from the user input thread as opposed to the nice consistent changes that the interpolators generate on playback 139 | //// const float deviance = maxChange*((float)N); // how much the delay changes from the previous buffer for this buffer 140 | //// const float dif = std::abs(dDesired-dPrev); // how much distance change is there to still be made up 141 | //// const float totalDif = std::abs(dDesired-dDesiredPrev); // total distance change between each "step" 142 | //// if (dif > INIT_MAX_CHANGE*N) 143 | //// { 144 | //// if (dDesired < dPrev) 145 | //// d = dPrev - deviance; 146 | //// else 147 | //// d = dPrev + deviance; 148 | //// if (/*std::abs(dDesired-d)*/dif > totalDif*0.5) // less than half way there 149 | //// maxChange *= 1.05;//2.0; 150 | //// else // more than half way there 151 | //// maxChange /= 1.05;//0.5; 152 | //// } 153 | //// else 154 | //// { 155 | //// dDesiredPrev = dDesired; 156 | //// d = dDesired; 157 | //// maxChange = INIT_MAX_CHANGE; 158 | //// } 159 | // const float d = dist/speedOfSound*Fs; 160 | // //const float d = dist*ONE_OVER_SoW*Fs; 161 | // const int numInputs = inputs.size(); 162 | // // load the new input 163 | // //if (inputs.size() == 0) 164 | // if (oldest == newest) 165 | // { // the first input cannot be stretched yet since we have only been passed one delay value so far 166 | // //inputs.emplace_back(DelayedInput(x, N, d, d, (float)N, N)); 167 | // inputs[newest].load(x, N, d, d, (float)N, N); 168 | // //inputs[newest].load(x, N, d, d, (float)N, N, N, N); 169 | // } 170 | // else // all following inputs can be stretched with potentially different begin + end delays 171 | // { // for the 2nd buffer, N should equal NPrev, if not then the audio stretching will be slightly off for just that buffer 172 | // //int NPrev = inputs.back().input.size(); 173 | // //float NstrPrev = inputs.back().dEnd - inputs.back().dBegin; 174 | // //inputs.emplace_back(DelayedInput(x, N, dPrev, d, NstrPrev, NPrev)); 175 | // int prev = newest-1; 176 | // if (prev < 0) 177 | // prev = numInputs-1; 178 | // const int NPrev = inputs[prev].inputLength; 179 | // const float NstrPrev = inputs[prev].dEnd - inputs[prev].dBegin; 180 | // inputs[newest].load(x, N, dPrev, d, NstrPrev, NPrev); 181 | // } 182 | // newest = (newest+1) % numInputs; 183 | // // zero output array 184 | // for (int n = 0; n < N; ++n) 185 | // y[n] = 0; 186 | // // loop though inputs and compute the samples for this buffer 187 | // //for (int i = 0; i < inputs.size(); ++i) 188 | // int next, begin, end; 189 | // for (int i = oldest; i != newest;) 190 | // { 191 | // next = (i+1) % numInputs; 192 | // // if the input is needed for the current buffer 193 | // if (inputs[i].dBegin <= N-1) 194 | // { 195 | // begin = std::max(0, /*((int)inputs[i].dBegin+1)*/ (int)std::ceil(inputs[i].dBegin)); 196 | // end = std::min(N, /*((int)inputs[i].dEnd+1)*/ (int)std::ceil(inputs[i].dEnd)); 197 | //// if (i < inputs.size()-1) 198 | //// { 199 | // for (int n = begin; n < (const int)end; ++n) 200 | // y[n] += inputs[i].valueAt(n, inputs[next/*i+1*/].input[0]); 201 | //// } 202 | //// else 203 | //// { 204 | //// for (int n = begin; n < end; ++n) 205 | //// y[n] += inputs[i].valueAt(n, 0); 206 | //// } 207 | // } 208 | // inputs[i].dBegin -= N; 209 | // inputs[i].dEnd -= N; 210 | // inputs[i].shift += N; 211 | // if (inputs[i].dEnd < 0) 212 | // { 213 | // //inputs.erase(inputs.begin()+i); 214 | // //--i; 215 | // oldest = next; 216 | // } 217 | // i = next; 218 | // } 219 | // dPrev = d; 220 | //} 221 | // 222 | ////void Doppler::process(const float* dists, int N, const float* x, float* y) noexcept 223 | ////{ 224 | //// 225 | ////} 226 | // 227 | //void Doppler::reset() noexcept 228 | //{ 229 | // //inputs.clear(); 230 | // oldest = 0; 231 | // newest = 0; 232 | // dPrev = 0; 233 | //} 234 | // 235 | //void Doppler::setSampleRate(const float sampleRate) noexcept 236 | //{ 237 | // Fs = sampleRate; 238 | //} 239 | // 240 | ////void Doppler::setSpeedOfSound(const float newSpeedOfSound) 241 | ////{ 242 | //// if (speedOfSound != newSpeedOfSound) 243 | //// { 244 | //// allocate(); 245 | //// } 246 | //// speedOfSound = newSpeedOfSound; 247 | ////} 248 | // 249 | //float Doppler::getSpeedOfSound() const noexcept 250 | //{ 251 | // return speedOfSound; 252 | //} 253 | // 254 | //void Doppler::allocate(const float max_Dist, const int max_N, const float newSpeedOfSound) 255 | //{ 256 | // // maximum time delay in samples that must be supported 257 | // maxDistance = max_Dist; 258 | // maxN = max_N; 259 | // speedOfSound = newSpeedOfSound; 260 | // allocate(); 261 | //// const float maxDelayInSamples = maxDistance/speedOfSound*Fs; 262 | //// inputs.resize(std::ceil(maxDelayInSamples/maxN)); 263 | //// for (auto& input : inputs) 264 | //// input.allocate(maxN); 265 | //// reset(); 266 | //} 267 | // 268 | //void Doppler::allocate() 269 | //{ 270 | // const float maxDelayInSamples = maxDistance/speedOfSound*Fs; 271 | // inputs.resize(std::max(3, (int)std::ceil(maxDelayInSamples/maxN))); // need at least 3 inputs otherwise stuff gets weird... 272 | // for (auto& input : inputs) 273 | // input.allocate(maxN); 274 | // inputs.shrink_to_fit(); 275 | // reset(); 276 | //} 277 | // 278 | //void Doppler::free() 279 | //{ 280 | // inputs.clear(); 281 | // inputs.shrink_to_fit(); 282 | //} 283 | // 284 | // 285 | //Doppler::Doppler() noexcept 286 | //{ 287 | //} 288 | // 289 | //Doppler::~Doppler() 290 | //{ 291 | //} 292 | -------------------------------------------------------------------------------- /Doppler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Doppler.h 3 | // ThreeDAudio 4 | // 5 | // Created by Andrew Barker on 4/2/18. 6 | // 7 | // 8 | /* 9 | 3DAudio: simulates surround sound audio for headphones 10 | Copyright (C) 2016 Andrew Barker 11 | 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see . 24 | 25 | The author can be contacted via email at andrew.barker.12345@gmail.com. 26 | */ 27 | 28 | #ifndef __Doppler__ 29 | #define __Doppler__ 30 | 31 | #include 32 | 33 | static constexpr float defaultSpeedOfSound = 343.0f; // in meters/sec 34 | 35 | class Doppler 36 | { 37 | public: 38 | /*Doppler() noexcept; 39 | ~Doppler();*/ 40 | /** process an input audio buffer at certain distance from the listener such that the doppler effect is applied to output */ 41 | void process(float distance, int bufferSize, const float* input, float* output) noexcept; 42 | /** allocate enough memory for the doppler effect given a maximum sound source distance (in meters), maximum buffer size, and minimum speed of sound (in m/s) */ 43 | void allocate(float maxDistance, int maxBufferSize, float speedOfSoundToPlanAllocationSize); 44 | /** free all memory */ 45 | void free() noexcept; 46 | /** reset the doppler effect state */ 47 | void reset() noexcept; 48 | /** specify the sample rate of the audio being processed */ 49 | void setSampleRate(float sampleRate) noexcept; 50 | /** set the speed of sound for the doppler effect */ 51 | void setSpeedOfSound(float speedOfSound) noexcept; 52 | private: 53 | // circular buffer for holding delayed input 54 | std::vector buffer; 55 | // next index to insert input in circular buffer 56 | float bufferInIdx = 0; 57 | // next index to output from circular buffer 58 | float bufferOutIdx = 0; 59 | // sample rate (in Hz) 60 | float sampleRate = 44100; 61 | // in meters / sec 62 | float speedOfSound = defaultSpeedOfSound; 63 | // previous buffer's delay at end (in samples) 64 | float delayPrev = -1; 65 | // distance delay over input sample slope at begining of current input buffer / end of last input buffer 66 | float slopePrev = 0; 67 | // previous sample put into circular buffer 68 | float prevSample = 0; 69 | // previous sample's delay compensated index 70 | float prevSampleDelayedIdx = 0; 71 | }; 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | // OLD DOPPLER EFFECT 94 | //#include "PolynomialSpline.h" 95 | // 96 | //static constexpr float defaultSpeedOfSound = 343.0f; // in meters/sec 97 | ////#define SPEED_OF_WAVE 343 //60 //100 // in meters/sec 98 | ////#define ONE_OVER_SoW 0.16//0.00291545189// 0.016 // in sec/meters 99 | ////#define INIT_MAX_CHANGE 0.01 100 | // 101 | //// structure to hold previous input buffers with their relative delays and stretching 102 | //typedef struct _DelayedInput 103 | //{ 104 | // std::vector input; // an input buffer 105 | // int inputLength = 0; // the length of the buffer actually used 106 | // float dBegin = 0; // delay in samples from the current buffer's start to the beginning of input 107 | // float dEnd = 0; // delay in samples from the current buffer's start to the end of input (beginning sample of next input) 108 | // int shift = 0; 109 | // int prevSign = 1; 110 | // //CubicFunctionalSpline spline; 111 | // LightweightCubicFunctionalSpline spline; // a 3rd order 2D polynomial curve for generating smoothly stretched output buffers 112 | // //_DelayedInput 113 | // void load(const float* buf, int N, 114 | // float delayBegin, float delayEnd, 115 | // float prevNstr, int prevN) noexcept 116 | // { 117 | // //input = std::vector(buf, buf+N); 118 | // for (int i = 0; i < N; ++i) 119 | // input[i] = buf[i]; 120 | // const int inputSize = input.size(); 121 | // for (int i = N; i < inputSize; ++i) 122 | // input[i] = 0; 123 | // inputLength = N; 124 | // shift = 0; 125 | // float yBegin, yEnd; 126 | // if (delayBegin < delayEnd+N) 127 | // { 128 | // dBegin = delayBegin; 129 | // dEnd = delayEnd+N; 130 | // yBegin = 0; 131 | // yEnd = N; 132 | // const float splinePts[4][2] = 133 | // { {dBegin-prevNstr, yBegin-prevSign*prevN}, 134 | // {dBegin, yBegin}, 135 | // {dEnd, yEnd}, 136 | // {dEnd+(dEnd-dBegin), yEnd+N} }; // problem here is that if we switch sign the next buffer, the end of this spline's slope will not be perfectly continuous with the begining of the next spline, we're not set up to know the future here... however this seems preferable to forcing the audio to lag one buffer in order to compute a perfectly continuous spline segment, plus the discontinuity should be rather small if the sound barrier is approached gradually. 137 | // spline.calc(splinePts); 138 | //// spline = CubicFunctionalSpline 139 | //// ({dBegin-prevNstr, yBegin-prevSign*prevN}, 140 | //// {dBegin,yBegin}, {dEnd,yEnd}, 141 | //// {dEnd+(dEnd-dBegin), yEnd+N}); 142 | // prevSign = 1; 143 | // } 144 | // else // averaged faster than SPEED_OF_WAVE for change in distance btw buffers, so audio gets flipped backwards 145 | // { 146 | // dBegin = delayEnd+N; 147 | // dEnd = delayBegin; 148 | // yBegin = N; 149 | // yEnd = 0; 150 | // const float splinePts[4][2] = 151 | // { {dBegin-prevNstr, yBegin-prevSign*prevN}, 152 | // {dBegin, yBegin}, 153 | // {dEnd, yEnd}, 154 | // {dEnd+(dEnd-dBegin), yEnd-N} }; 155 | // spline.calc(splinePts); 156 | //// spline = CubicFunctionalSpline 157 | //// ({dBegin-prevNstr, yBegin-prevSign*prevN}, 158 | //// {dBegin,yBegin}, {dEnd,yEnd}, 159 | //// {dEnd+(dEnd-dBegin), yEnd-N}); 160 | // prevSign = -1; 161 | // } 162 | //// spline = CubicFunctionalSpline 163 | //// ({dBegin-prevNstr, yBegin-prevN}, 164 | //// {dBegin,yBegin}, {dEnd,yEnd}, 165 | //// {dEnd+(dEnd-dBegin), yEnd+N}); 166 | // } 167 | // void allocate(int maxN) 168 | // { 169 | // input.resize(maxN, 0); 170 | // input.shrink_to_fit(); // not tested thoroughly 171 | // spline.allocate(); 172 | // } 173 | // float valueAt(float s, float nextInputSample) noexcept 174 | // { 175 | // //float index = spline.pointAt(s+shift)[0]; 176 | // float index; 177 | // spline.pointAt(s+shift, &index); 178 | // int low = index; //floor(index); 179 | // int high = low + 1; 180 | // float k = high - index; 181 | // float value = k*input[low]; 182 | // if (high < inputLength/*input.size()*/) 183 | // value += (1-k)*input[high]; 184 | // else 185 | // value += (1-k)*nextInputSample; 186 | // return value; 187 | // //return k*input[low] + (1-k)*input[high]; 188 | // } 189 | //} DelayedInput; 190 | // 191 | //class Doppler 192 | //{ 193 | //public: 194 | // Doppler() {}; 195 | // ~Doppler() {}; 196 | // // output N processed samples in y given a current distance dist (in meters), and N input samples in x 197 | // void process(float dist, int N, const float* x, float* y) noexcept; 198 | // // processes with an array of N distances, one for each sample 199 | // //void process(const float* dists, int N, const float* x, float* y) noexcept; 200 | // // reset the processing state 201 | // void reset() noexcept; 202 | // void setSampleRate(float sampleRate) noexcept; 203 | // //void setSpeedOfSound(float newSpeedOfSound); 204 | // float getSpeedOfSound() const noexcept; 205 | // // pre-allocate all internal storage prior to realtime audio processing 206 | // void allocate(float maxDist, int max_N, float newSpeedOfSound); 207 | // void free(); 208 | //private: 209 | // void allocate(); 210 | // std::vector inputs; // storage for delayed and stretched input audio 211 | // float Fs = 44100; // sample rate (in Hz) 212 | // float dPrev = 0; // previous buffer's delay (in samples) 213 | // //float maxChange = INIT_MAX_CHANGE; // in percent of input buffer samples 214 | // //float dDesiredPrev = 0; 215 | // // indecies for the range of inputs yet to be played back 216 | // int oldest = 0; 217 | // int newest = 0; 218 | // float maxN = 0; 219 | // float maxDistance = 0; 220 | // float speedOfSound = defaultSpeedOfSound; // in meters / sec 221 | //}; 222 | 223 | #endif /* defined(__Doppler__) */ 224 | -------------------------------------------------------------------------------- /DrawInterpolator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrawInterpolator.h 3 | // 4 | // Created by Andrew Barker on 9/13/16. 5 | // 6 | // 7 | 8 | #ifndef DrawInterpolator_h 9 | #define DrawInterpolator_h 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | 13 | #include 14 | #include 15 | 16 | #include "OpenGL.h" 17 | #include "Interpolator.h" 18 | #include "StackArray.h" 19 | #include "Box.h" 20 | #include "Multi.h" 21 | 22 | 23 | class InterpolatorLook 24 | { 25 | public: 26 | enum Dimensionality { TWO_D, THREE_D }; 27 | enum LineType { CONTINUOUS, DASHED, DOTTED }; 28 | template 29 | InterpolatorLook (const Interpolator* interp, Dimensionality dim) noexcept; 30 | float begin; 31 | float end; 32 | Dimensionality drawingMode; 33 | std::vector dimensionsToDraw; 34 | int numVertices; 35 | LineType lineType; 36 | float lineSize; 37 | Colour beginColor; 38 | Colour endColor; 39 | float numColorCycles; 40 | float colorCyclePhase; // 0 to 1 41 | }; 42 | 43 | template 44 | InterpolatorLook::InterpolatorLook (const Interpolator* interp, 45 | const Dimensionality drawingMode) noexcept 46 | : begin (interp->getInputRange()[0]), 47 | end (interp->getInputRange()[1]), 48 | drawingMode (drawingMode), 49 | dimensionsToDraw (((drawingMode == TWO_D) ? std::vector{0, 1} 50 | : std::vector{0, 1, 2})), 51 | numVertices (100), 52 | lineType (CONTINUOUS), 53 | lineSize (2), 54 | beginColor (Colour::fromFloatRGBA(1, 0, 0, 1)), 55 | endColor (Colour::fromFloatRGBA(0, 0, 1, 1)), 56 | numColorCycles (1.5), 57 | colorCyclePhase (0) 58 | { 59 | } 60 | 61 | static GLenum getGLMode(const InterpolatorLook& look) noexcept 62 | { 63 | switch (look.lineType) { 64 | case InterpolatorLook::CONTINUOUS: 65 | return GL_LINE_STRIP; 66 | case InterpolatorLook::DASHED: 67 | return GL_LINES; 68 | case InterpolatorLook::DOTTED: 69 | return GL_POINTS; 70 | }; 71 | } 72 | 73 | static Colour getInterpolatedColor(const Colour beginColor, 74 | const Colour endColor, 75 | const int numCycles, 76 | const float phase, 77 | const float position) noexcept 78 | { 79 | cauto dColor = std::fmod(2 * ((numCycles - 0.5f) * position + phase), 2.0f); 80 | cauto color = dColor < 1 ? beginColor.interpolatedWith(endColor, dColor) 81 | : endColor.interpolatedWith(beginColor, dColor - 1); 82 | return color; 83 | } 84 | 85 | static void glInterpolatedColor(const InterpolatorLook& look, 86 | const float position) 87 | { 88 | glColour(getInterpolatedColor(look.beginColor, look.endColor, look.numColorCycles, look.colorCyclePhase, position)); 89 | } 90 | 91 | template 92 | void draw(const ParametricInterpolator* interp, 93 | const InterpolatorLook& look) 94 | { 95 | cauto inputRange = interp->getInputRange(); 96 | cauto begin = std::max(inputRange[0], look.begin); 97 | cauto percentOfEnd = (interp->getType() == InterpolatorType::CLOSED_PARAMETRIC && interp->getNumPoints() == 2) ? 0.5f : 0.9999999f; 98 | cauto end = std::min(inputRange[1] * percentOfEnd, look.end); 99 | cauto length = end - begin; 100 | cauto interval = length / look.numVertices; 101 | 102 | GLboolean prevAntiAliasing; 103 | glGetBooleanv(GL_LINE_SMOOTH, &prevAntiAliasing); 104 | glEnable(GL_LINE_SMOOTH); // enable antialiasing 105 | glInterpolatedColor(look, 0); 106 | GLfloat prevGLSize; 107 | if (look.lineType == InterpolatorLook::DOTTED) { 108 | glGetFloatv(GL_POINT_SIZE, &prevGLSize); 109 | glPointSize(look.lineSize); 110 | } else { 111 | glGetFloatv(GL_LINE_WIDTH, &prevGLSize); 112 | glLineWidth(look.lineSize); 113 | } 114 | cauto glMode = getGLMode(look); 115 | 116 | glBegin(glMode); 117 | cauto numDimensions = interp->getNumDimensions(); 118 | STACK_ARRAY(float, pt, numDimensions - 1); 119 | for (auto t = begin; t < end; t += interval) { 120 | if (interp->pointAt(t, pt)) { 121 | if (look.drawingMode == InterpolatorLook::TWO_D) 122 | glVertex2f(pt[0], pt[1]); 123 | else 124 | glVertex3f(pt[0], pt[1], pt[2]); 125 | } 126 | glInterpolatedColor(look, (t - begin) / length); 127 | } 128 | glEnd(); 129 | 130 | if (look.lineType == InterpolatorLook::DOTTED) 131 | glPointSize(prevGLSize); 132 | else 133 | glLineWidth(prevGLSize); 134 | if (prevAntiAliasing == GL_FALSE) 135 | glDisable(GL_LINE_SMOOTH); // disable antialiasing 136 | } 137 | 138 | template 139 | void draw(const FunctionalInterpolator* interp, 140 | const InterpolatorLook& look) 141 | { 142 | const auto pts = interp->getPoints(); 143 | const auto inputRange = interp->getInputRange(); 144 | const auto begin = std::max(inputRange[0], look.begin); 145 | const auto end = std::min(inputRange[1], look.end); 146 | const float interval = (end - begin) / look.numVertices; 147 | if (interval <= 0.0000001f) 148 | return; // avoid inf loop below 149 | const int numDimensions = interp->getNumDimensions(); 150 | STACK_ARRAY(float, pt, numDimensions-1); 151 | float x = begin; 152 | int splineIndex = 0; 153 | int prevSplineIndex = 0; 154 | bool dontReset = false; 155 | bool doAgain = true; 156 | GLboolean prevAntiAliasing; 157 | glGetBooleanv(GL_LINE_SMOOTH, &prevAntiAliasing); 158 | glEnable(GL_LINE_SMOOTH); // enable antialiasing 159 | glInterpolatedColor(look, 0); 160 | GLfloat prevGLSize; 161 | if (look.lineType == InterpolatorLook::DOTTED) { 162 | glGetFloatv(GL_POINT_SIZE, &prevGLSize); 163 | glPointSize(look.lineSize); 164 | } else { 165 | glGetFloatv(GL_LINE_WIDTH, &prevGLSize); 166 | glLineWidth(look.lineSize); 167 | } 168 | const auto glMode = getGLMode(look); 169 | glBegin(glMode); 170 | AGAIN: // man this got complicated... 171 | int count = 0; 172 | while (x <= end) { 173 | if (++count > look.numVertices) // trying to avoid inf loop at all costs 174 | break; 175 | // only draw the dotted line over the portions that are not open/empty segments, 176 | if (interp->pointAtSmart(x, pt, splineIndex)) { 177 | if (prevSplineIndex+2 <= splineIndex ? pts[prevSplineIndex+1][0] != pts[splineIndex][0] : true) { 178 | if (look.drawingMode == InterpolatorLook::TWO_D) 179 | glVertex2f(x, pt[look.dimensionsToDraw[1]-1]); 180 | else 181 | glVertex3f(x, pt[look.dimensionsToDraw[1]-1], pt[look.dimensionsToDraw[2]-1]); 182 | } else if (!dontReset) { // avoiding drawing anything between 2 or more pts with the same x val 183 | glEnd(); 184 | glBegin(glMode); 185 | dontReset = false; 186 | } 187 | x += interval; 188 | // draw verticies at any points that would otherwise get skipped over 189 | int lastSplineIndex = -1; 190 | for (int i = 1; splineIndex+i < pts.size() && x > pts[splineIndex+i][0]; ++i) { 191 | lastSplineIndex = splineIndex + i; 192 | if (pts[lastSplineIndex][0] != pts[lastSplineIndex-1][0] 193 | && interp->getSplineShape(lastSplineIndex-1) != SplineShape::EMPTY) { 194 | if (look.drawingMode == InterpolatorLook::TWO_D) 195 | glVertex2f(pts[lastSplineIndex][0], 196 | pts[lastSplineIndex][look.dimensionsToDraw[1]]); 197 | else 198 | glVertex3f(pts[lastSplineIndex][0], 199 | pts[lastSplineIndex][look.dimensionsToDraw[1]], 200 | pts[lastSplineIndex][look.dimensionsToDraw[2]]); 201 | } else { 202 | glEnd(); 203 | glBegin(glMode); 204 | } 205 | } 206 | if (lastSplineIndex >= 0) { 207 | if (look.drawingMode == InterpolatorLook::TWO_D) 208 | glVertex2f(pts[lastSplineIndex][0], 209 | pts[lastSplineIndex][look.dimensionsToDraw[1]]); 210 | else 211 | glVertex3f(pts[lastSplineIndex][0], 212 | pts[lastSplineIndex][look.dimensionsToDraw[1]], 213 | pts[lastSplineIndex][look.dimensionsToDraw[2]]); 214 | dontReset = true; 215 | } 216 | } else { 217 | // finish drawing any unfinished GL_LINE segments 218 | if (look.drawingMode == InterpolatorLook::TWO_D) 219 | glVertex2f(pts[splineIndex][0], 220 | pts[splineIndex][look.dimensionsToDraw[1]]); 221 | else 222 | glVertex3f(pts[splineIndex][0], 223 | pts[splineIndex][look.dimensionsToDraw[1]], 224 | pts[splineIndex][look.dimensionsToDraw[2]]); 225 | // gotta reset the gl line drawing state so we don't possibly draw a single line segment across an open segment 226 | glEnd(); 227 | glBegin(glMode); 228 | if (splineIndex + 1 < pts.size()) 229 | x = pts[splineIndex + 1][0]; // skip the loop to the begining of the next segment since this one is either open or spatially nonexistant in x 230 | else 231 | x = end; // goto last iteration of while loop 232 | } 233 | glInterpolatedColor(look, (x - begin) / (end - begin)); 234 | prevSplineIndex = splineIndex; 235 | } 236 | if (doAgain && x - interval < end // make sure the last x point is the end of specified range 237 | && (pts.back()[0] <= look.end ? pts[pts.size()-2][0] < pts.back()[0] : true)) { 238 | x = end; 239 | doAgain = false; 240 | goto AGAIN; 241 | } 242 | glEnd(); 243 | if (look.lineType == InterpolatorLook::DOTTED) 244 | glPointSize(prevGLSize); 245 | else 246 | glLineWidth(prevGLSize); 247 | if (prevAntiAliasing == GL_FALSE) 248 | glDisable(GL_LINE_SMOOTH); // disable antialiasing 249 | } 250 | 251 | class PointLook 252 | { 253 | public: 254 | float radius; 255 | float mouseOverRadius; 256 | Colour color; 257 | Colour selectedColor; 258 | float animationDuration; 259 | }; 260 | 261 | class DrawablePointState 262 | { 263 | public: 264 | // need copy of these for each interp... 265 | Multi mouseOverAnimations; 266 | Multi selectAnimations; 267 | std::vector prevMouseOvers; 268 | std::vector prevSelecteds; 269 | }; 270 | 271 | static void drawCircleOutline(const Point position, 272 | const float radius, 273 | const int segments, 274 | const float windowWidth, 275 | const float windowHeight) 276 | { 277 | // GLboolean prevAntiAliasing; 278 | // glGetBooleanv(GL_LINE_SMOOTH, &prevAntiAliasing); 279 | // glEnable(GL_LINE_SMOOTH); // enable antialiasing 280 | glBegin(GL_LINE_LOOP); 281 | cauto aspect = windowHeight / windowWidth; 282 | for (int n = 0; n <= segments; ++n) { 283 | cauto t = 2 * M_PI * (float)n / segments; 284 | glVertex2f(position.x + aspect * radius * std::sin(t), 285 | position.y + radius * std::cos(t)); 286 | } 287 | glEnd(); 288 | // if (prevAntiAliasing == GL_FALSE) 289 | // glDisable(GL_LINE_SMOOTH); // disable antialiasing 290 | } 291 | 292 | static void drawCircle(const Point position, 293 | const float radius, 294 | const int segments, 295 | const float windowWidth, 296 | const float windowHeight) 297 | { 298 | glBegin(GL_TRIANGLE_FAN); 299 | glVertex2f(position.x, position.y); 300 | cauto aspect = windowHeight / windowWidth; 301 | for (int n = 0; n <= segments; ++n) { 302 | cauto t = 2 * M_PI * (float)n / segments; 303 | glVertex2f(position.x + aspect * radius * std::sin(t), 304 | position.y + radius * std::cos(t)); 305 | } 306 | glEnd(); 307 | GLboolean antiAliasing; 308 | glGetBooleanv(GL_LINE_SMOOTH, &antiAliasing); 309 | if (antiAliasing) { 310 | GLfloat prevLineWidth; 311 | glGetFloatv(GL_LINE_WIDTH, &prevLineWidth); 312 | glLineWidth(1); 313 | drawCircleOutline(position, radius, segments, windowWidth, windowHeight); 314 | glLineWidth(prevLineWidth); 315 | } 316 | } 317 | 318 | template 319 | std::vector> convertPoints(const std::vector>& points, 320 | const int dimX = 0, 321 | const int dimY = 1) 322 | { 323 | std::vector> pts (points.size()); 324 | for (int i = 0; i < points.size(); ++i) { 325 | pts[i].x = points[i][dimX]; 326 | pts[i].y = points[i][dimY]; 327 | } 328 | return pts; 329 | } 330 | 331 | template 332 | void drawPoints2D(const std::vector>& points, 333 | const PointLook& look, 334 | DrawablePointState& state, 335 | const Point& mousePosition, 336 | const OpenGLWindow& window, 337 | const Box& selectBox, 338 | const Box& antiSelectBox, 339 | int& mouseOverPointIndex, 340 | bool mouseOverEnabled = true, 341 | const float viewWidth = 1.0, 342 | const std::array& range = {0, 0}) 343 | { 344 | GLfloat prevLineWidth; 345 | glGetFloatv(GL_LINE_WIDTH, &prevLineWidth); 346 | GLboolean prevAntiAliasing; 347 | glGetBooleanv(GL_LINE_SMOOTH, &prevAntiAliasing); 348 | glEnable(GL_LINE_SMOOTH); // enable antialiasing 349 | mouseOverPointIndex = -1; 350 | auto resized = false; 351 | if (state.prevMouseOvers.size() != points.size()) { 352 | state.prevMouseOvers.resize(points.size(), false); 353 | resized = true; 354 | } 355 | if (state.prevSelecteds.size() != points.size()) { 356 | state.prevSelecteds.resize(points.size(), false); 357 | resized = true; 358 | } 359 | Colour color; 360 | float radius; 361 | for (int i = 0; i < points.size(); ++i) { 362 | cauto x = points[i].point[0]; 363 | cauto y = points[i].point[1]; 364 | if (range[0] <= x && x <= range[1]) { 365 | radius = look.radius; 366 | cauto yRadius = pixelsToNormalized(look.mouseOverRadius, window.height); 367 | cauto xRadius = pixelsToNormalized(look.mouseOverRadius, window.width) * viewWidth;// radius * viewWidth * window.getAspect(); 368 | const Box b { y + yRadius, y - yRadius, 369 | x - xRadius, x + xRadius }; 370 | cauto mouseOver = mouseOverEnabled && 371 | ((mouseOverPointIndex == -1 && b.contains(mousePosition)) || selectBox.contains({x, y})) && 372 | !antiSelectBox.contains({x, y}); 373 | if (mouseOver) { 374 | auto animation = state.mouseOverAnimations.get(i); 375 | if (state.prevMouseOvers[i] && animation) { 376 | animation->advance(window.frameRate); 377 | radius = look.radius + (look.mouseOverRadius - look.radius) * animation->getProgress(); 378 | color = look.color.interpolatedWith(look.selectedColor, animation->getProgress()); 379 | glColour(color); 380 | if (!animation->isPlaying()) 381 | state.mouseOverAnimations.remove(i); 382 | } else if (!state.prevMouseOvers[i]){ 383 | state.mouseOverAnimations.add(i, look.animationDuration, true); 384 | color = look.color; 385 | radius = look.radius; 386 | } else { 387 | color = look.selectedColor; 388 | radius = look.mouseOverRadius; 389 | } 390 | state.prevMouseOvers[i] = true; 391 | mouseOverPointIndex = i; 392 | } else { 393 | if (state.prevMouseOvers[i] && !resized) 394 | state.mouseOverAnimations.add(i, look.animationDuration, true); 395 | auto animation = state.mouseOverAnimations.get(i); 396 | if (animation) { 397 | animation->advance(window.frameRate); 398 | radius = look.mouseOverRadius + (look.radius - look.mouseOverRadius) * animation->getProgress(); 399 | color = look.selectedColor.interpolatedWith(look.color, animation->getProgress()); 400 | if (!animation->isPlaying()) 401 | state.mouseOverAnimations.remove(i); 402 | } else { 403 | radius = look.radius; 404 | color = look.color; 405 | } 406 | } 407 | if (points[i].selected) { 408 | if (!state.prevSelecteds[i] /*&& !resized*/) 409 | state.selectAnimations.add(i, look.animationDuration, true); 410 | state.prevSelecteds[i] = true; 411 | auto rad = 2 * radius; 412 | auto lineWidth = 1.5f; 413 | auto animation = state.selectAnimations.get(i); 414 | if (animation) { 415 | cauto progress = animation->getProgress(); 416 | lineWidth = 10 - (10 - lineWidth) * progress;// 15 - 13.5f * progress; 417 | color = look.color.interpolatedWith(look.selectedColor, progress).withAlpha(progress); 418 | glColour(color); 419 | rad = (2 - progress) * radius; 420 | animation->getProgress(); 421 | animation->advance(window.frameRate); 422 | if (!animation->isPlaying()) 423 | state.selectAnimations.remove(i); 424 | } else { 425 | radius -= 1;//look.radius - 1; 426 | rad = radius + 1.5f; 427 | color = look.selectedColor; 428 | glColour(color); 429 | } 430 | glLineWidth(lineWidth); 431 | drawCircleOutline({x, y}, pixelsToNormalized(rad, window.height), 12, window.width / viewWidth, window.height); 432 | } else { 433 | if (state.prevSelecteds[i] && !resized) 434 | state.selectAnimations.add(i, look.animationDuration, true); 435 | state.prevSelecteds[i] = false; 436 | auto rad = 2 * radius; 437 | auto lineWidth = 1.5f; 438 | auto animation = state.selectAnimations.get(i); 439 | if (animation) { 440 | cauto progress = animation->getProgress(); 441 | color = look.selectedColor.interpolatedWith(look.color, progress).withAlpha(1 - progress); 442 | glColour(color); 443 | rad = (1 + progress) * radius; 444 | animation->getProgress(); 445 | animation->advance(window.frameRate); 446 | if (!animation->isPlaying()) 447 | state.selectAnimations.remove(i); 448 | glLineWidth(lineWidth + (10 - lineWidth) * progress); 449 | drawCircleOutline({x, y}, pixelsToNormalized(rad, window.height), 12, window.width / viewWidth, window.height); 450 | } 451 | } 452 | if (!mouseOver) 453 | state.prevMouseOvers[i] = false; 454 | glColour(color.withAlpha(1.0f)); 455 | drawCircle({x, y}, pixelsToNormalized(radius, window.height), 12, window.width / viewWidth, window.height); 456 | } 457 | } 458 | if (prevAntiAliasing == GL_FALSE) 459 | glDisable(GL_LINE_SMOOTH); // disable antialiasing 460 | 461 | glDisable(GL_POLYGON_SMOOTH); 462 | glDisable(GL_POINT_SMOOTH); 463 | glDisable(GL_LINE_SMOOTH); 464 | glLineWidth(prevLineWidth); 465 | } 466 | 467 | template 468 | void draw(const Interpolator* interp, 469 | const InterpolatorLook& look) 470 | { 471 | if (!interp) 472 | return; 473 | if (interp->getPoints().size() < 2) // no path to draw 474 | return; 475 | if (interp->getType() == InterpolatorType::FUNCTIONAL) { 476 | draw(dynamic_cast* const>(interp), look); 477 | } else { // parametric 478 | draw(dynamic_cast* const>(interp), look); 479 | } 480 | } 481 | 482 | template 483 | void draw(const Interpolator* interp, 484 | const InterpolatorLook& look, 485 | GLuint& displayList) 486 | { 487 | if (displayList != 0) { 488 | glCallList(displayList); 489 | return; 490 | } else { 491 | glDeleteLists(displayList, 1); 492 | displayList = glGenLists(1); 493 | glNewList(displayList, GL_COMPILE_AND_EXECUTE); 494 | draw(interp, look); 495 | glEndList(); 496 | } 497 | } 498 | 499 | #endif /* DrawInterpolator_h */ 500 | -------------------------------------------------------------------------------- /DrewLib.h: -------------------------------------------------------------------------------- 1 | // 2 | // DrewLib.h 3 | // Andrew Barker's C++ convenience library. 4 | // 5 | // Created by Andrew Barker on 11/13/16. 6 | // 7 | // 8 | 9 | #ifndef DrewLib_h 10 | #define DrewLib_h 11 | 12 | #define cauto const auto 13 | #define cint const int 14 | #define cfloat const float 15 | 16 | #ifdef WIN32 17 | static constexpr float M_PI = 3.14159265358979323846264338327950288; 18 | static constexpr float M_PI_2 = 1.57079632679489661923132169163975144; 19 | #endif 20 | 21 | #include "Functions.h" 22 | 23 | #endif /* DrewLib_h */ 24 | -------------------------------------------------------------------------------- /Functions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.h 3 | // 4 | // Created by Andrew Barker on 4/26/14. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef Functions_h 28 | #define Functions_h 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "DrewLib.h" 35 | 36 | //inline float lagrangeInterpolate(const float y1, const float y2, const float y3, const float y4, const float x) noexcept 37 | //{ 38 | // const float xm1 = x-1.0; 39 | // const float xm2 = x-2.0; 40 | // const float xm3 = x-3.0; 41 | // const float xm4 = x-4.0; 42 | // 43 | //// float a0 = (xm2)*(xm3)*(xm4)/(-6.0)*y1; 44 | //// float a1 = (xm1)*(xm3)*(xm4)/(2.0)*y2; 45 | //// float a2 = (xm1)*(xm2)*(xm4)/(-2.0)*y3; 46 | //// float a3 = (xm1)*(xm2)*(xm3)/(6.0)*y4; 47 | // 48 | // // the need for speed ... 49 | // const float a0 = (xm2)*(xm3)*(xm4)*-0.1666666666666666667*y1; 50 | // const float a1 = (xm1)*(xm3)*(xm4)*0.5*y2; 51 | // const float a2 = (xm1)*(xm2)*(xm4)*-0.5*y3; 52 | // const float a3 = (xm1)*(xm2)*(xm3)*0.1666666666666666667*y4; 53 | // 54 | // return a0 + a1 + a2 + a3; 55 | //// return ((x-2.0)*(x-3.0)*(x-4.0)/(-6.0)*y1 + (x-1.0)*(x-3.0)*(x-4.0)/(2.0)*y2 56 | //// + (x-1.0)*(x-2.0)*(x-4.0)/(-2.0)*y3 + (x-1.0)*(x-2.0)*(x-3.0)/(6.0)*y4); 57 | //} 58 | // 59 | //// the vectorized version of the one above 60 | //inline void lagrangeInterpolate(const float* y1, const float* y2, const float* y3, const float* y4, const int N, const float x, float* output) noexcept 61 | //{ 62 | // const float xm1 = x-1.0; 63 | // const float xm2 = x-2.0; 64 | // const float xm3 = x-3.0; 65 | // const float xm4 = x-4.0; 66 | // const float a1 = (xm2)*(xm3)*(xm4)*-0.1666666666666666667; 67 | // const float a2 = (xm1)*(xm3)*(xm4)*0.5; 68 | // const float a3 = (xm1)*(xm2)*(xm4)*-0.5; 69 | // const float a4 = (xm1)*(xm2)*(xm3)*0.1666666666666666667; 70 | // for (int n = 0; n < N; ++n) 71 | // output[n] = a1*y1[n] + a2*y2[n] + a3*y3[n] + a4*y4[n]; 72 | //} 73 | 74 | /** true if Set s, contains value v */ 75 | template 76 | bool contains(const Set& s, const Value& v) noexcept 77 | { 78 | return std::find(s.begin(), s.end(), v) != s.end(); 79 | } 80 | 81 | /** rotates the specified indices (must be sorted) by delta. the non-rotated indices may be displaced in the same direction but their relative ordering is preserved. */ 82 | template 83 | void partial_rotate(std::vector& v, const std::vector& indicesToRotate, const int delta) { 84 | cauto copy = v; 85 | std::vector> other (copy.size() - indicesToRotate.size()); 86 | std::vector inPlace (copy.size(), false); 87 | for (int i = 0, j = 0, k = 0; i < copy.size(); ++i) { 88 | if (k == indicesToRotate.size() || i != indicesToRotate[k]) 89 | other[j++] = {i, copy[i]}; 90 | else 91 | ++k; 92 | } 93 | for (int i = 0, j; i < indicesToRotate.size(); ++i) { 94 | j = indicesToRotate[i] + delta; 95 | while (j < 0) 96 | j += copy.size(); 97 | while (j >= copy.size()) 98 | j -= copy.size(); 99 | v[j] = copy[indicesToRotate[i]]; 100 | inPlace[j] = true; 101 | } 102 | for (int i = 0, j; i < other.size(); ++i) { 103 | j = other[i].first; 104 | while (inPlace[j]) 105 | j = (j+1) % copy.size(); 106 | v[j] = other[i].second; 107 | inPlace[j] = true; 108 | } 109 | } 110 | 111 | // should be in C++17 112 | namespace std { 113 | template 114 | constexpr T hypot(const T x, const T y, const T z) noexcept 115 | { 116 | return std::sqrt(x*x + y*y + z*z); 117 | } 118 | 119 | template 120 | constexpr const T& clamp( const T& v, const T& lo, const T& hi, Compare comp ) 121 | { 122 | return assert( !comp(hi, lo) ), 123 | comp(v, lo) ? lo : comp(hi, v) ? hi : v; 124 | } 125 | 126 | template 127 | constexpr const T& clamp( const T& v, const T& lo, const T& hi ) 128 | { 129 | return clamp( v, lo, hi, std::less<>() ); 130 | } 131 | } 132 | 133 | template 134 | T boundsCheck(const T value, const T min, const T max) 135 | { 136 | return std::max(std::min(value, max), min); 137 | } 138 | 139 | // circular input buffer convolution 140 | inline void convolve(const float *cBuf, const int cBufIdx, const int cBufN, 141 | const float *h, const int Nh, const float hScale, 142 | float* output, const int N) noexcept 143 | { 144 | // for each output sample 145 | for (int n = 0; n < N; ++n) { 146 | // for each overlaping sample of the two signals 147 | float sum = 0; 148 | int i = (cBufIdx + n) % cBufN; 149 | for (int j = 0; j < Nh; ++j) { 150 | sum += cBuf[i] * h[j]; 151 | if (--i < 0) 152 | i = cBufN - 1; 153 | } 154 | output[n] = sum * hScale; 155 | } 156 | } 157 | 158 | inline void convolve(const float *cBuf, const int cBufIdx, const int cBufN, 159 | const float *hs, const int Nh, const int numHs, const float *hScales, const int ch, 160 | float *output, const int N) noexcept 161 | { 162 | // for each output sample 163 | for (int n = 0; n < N; ++n) { 164 | const float L = N / float(numHs - 1); 165 | const float ndL = n / L; 166 | const int hi = ndL; 167 | const int hIdx1 = 2 * hi + ch; 168 | const int hIdx2 = 2 * (hi + 1) + ch; 169 | const float *h1 = &hs[hIdx1 * Nh], 170 | *h2 = &hs[hIdx2 * Nh]; 171 | int i = (cBufIdx + n) % cBufN; 172 | float sum1 = 0, sum2 = 0; 173 | // for each overlaping sample of the two signals 174 | for (int j = 0; j < Nh; ++j) { 175 | sum1 += cBuf[i] * h1[j]; 176 | sum2 += cBuf[i] * h2[j]; 177 | if (--i < 0) 178 | i = cBufN - 1; 179 | } 180 | const float hBlend = ndL - hi; //std::fmod(n, L); 181 | output[n] = sum1 * hScales[hIdx1] * (1-hBlend) + sum2 * hScales[hIdx2] * hBlend; 182 | } 183 | } 184 | 185 | // time domain convolution 186 | /*static*/ 187 | inline void convolve(const float *x, int Nx, const float *h, int Nh, float *output) noexcept 188 | { 189 | const int Nxm1 = Nx-1; 190 | const int Nhm1 = Nh-1; 191 | const int L = Nx+Nhm1;//Nx+Nh-1; 192 | int xi = 0; 193 | int xf = 0; 194 | int hi = 0; 195 | 196 | // for each output sample 197 | for (int i = 0; i < L; ++i) 198 | { 199 | // for each overlaping sample of two signals 200 | const int M = xf-xi+1; 201 | float sum = 0; 202 | for (int j = 0; j < M; ++j) 203 | sum += x[xf-j] * h[hi+j]; 204 | 205 | // shifting 206 | if (xf < Nxm1/*Nx-1*/) 207 | ++xf; 208 | else if (hi < Nhm1/*Nh-1*/) 209 | ++hi; 210 | 211 | if (i > Nhm1/*Nh-1*/) 212 | ++xi; 213 | 214 | output[i] = sum; 215 | } 216 | } 217 | 218 | // only compute convolution for the output samples from beginIndex to endIndex inclusive 219 | /*static*/ 220 | inline void convolve(const float *x, const int Nx, const float *h, const int Nh, float *output, const int beginIndex, const int endIndex) noexcept 221 | { 222 | const int Nxm1 = Nx-1; 223 | const int Nhm1 = Nh-1; 224 | //const int L = Nx+Nhm1;//Nx+Nh-1; 225 | int xi, xf, hi; 226 | 227 | // find xi xf and hi starting at begin index 228 | if (beginIndex > Nhm1/*Nh-1*/) 229 | xi = beginIndex - Nh; 230 | else 231 | xi = 0; 232 | 233 | if (beginIndex <= Nxm1/*Nx-1*/) 234 | { 235 | xf = beginIndex; 236 | hi = 0; 237 | } 238 | else 239 | { 240 | xf = Nxm1/*Nx-1*/; 241 | hi = beginIndex - Nxm1/*(Nx-1)*/; 242 | if (hi > Nhm1/*Nh-1*/) 243 | hi = Nhm1/*Nh-1*/; 244 | } 245 | 246 | // for each output sample 247 | for (int i = beginIndex; i <= endIndex; ++i) 248 | { 249 | // for each overlaping sample of two signals 250 | const int M = xf-xi+1; 251 | float sum = 0; 252 | for (int j = 0; j < M; ++j) 253 | sum += x[xf-j] * h[hi+j]; 254 | 255 | // shifting 256 | if (xf < Nxm1/*Nx-1*/) 257 | ++xf; 258 | else if (hi < Nhm1/*Nh-1*/) 259 | ++hi; 260 | 261 | if (i > Nhm1/*Nh-1*/) 262 | ++xi; 263 | 264 | output[i] = sum; 265 | } 266 | } 267 | 268 | //template 269 | //T inner_product(InputIt1 first1, InputIt1 last1, 270 | // InputIt2 first2, T value) 271 | //{ 272 | // while (first1 != last1) { 273 | // value += (*first1) * (*first2); 274 | // --first1; 275 | // ++first2; 276 | // } 277 | // return value; 278 | //} 279 | // 280 | //inline void convolve2(float *x, const int Nx, float *h, const int Nh, float *output, const int beginIndex, const int endIndex) 281 | //{ 282 | // const int L = Nx+Nh-1; 283 | // int xi, xf, hi; 284 | // 285 | // // find xi xf and hi starting at begin index 286 | // if (beginIndex > Nh-1) 287 | // xi = beginIndex - Nh; 288 | // else 289 | // xi = 0; 290 | // 291 | // if (beginIndex <= Nx-1) 292 | // { 293 | // xf = beginIndex; 294 | // hi = 0; 295 | // } 296 | // else 297 | // { 298 | // xf = Nx-1; 299 | // hi = beginIndex - (Nx-1); 300 | // if (hi > Nh-1) 301 | // hi = Nh-1; 302 | // } 303 | // 304 | // // for each output sample 305 | // for (int i = beginIndex; i <= endIndex; ++i) 306 | // { 307 | // // for each overlaping sample of two signals 308 | // const int M = xf-xi+1; 309 | // output[i] = inner_product(&x[xf], &x[xf-M], &h[hi], 0); 310 | // 311 | // // shifting 312 | // if (xf < Nx-1) 313 | // ++xf; 314 | // else if (hi < Nh-1) 315 | // ++hi; 316 | // 317 | // if (i > Nh-1) 318 | // ++xi; 319 | // } 320 | //} 321 | 322 | inline float toDegrees(float radians) noexcept 323 | { 324 | return radians * 180 / M_PI; 325 | } 326 | 327 | inline float toRadians(float degrees) noexcept 328 | { 329 | return degrees * M_PI / 180; 330 | } 331 | 332 | inline void XYZtoRAE(const float* xyz, float* rae) noexcept 333 | { 334 | rae[0] = std::sqrt(xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]); 335 | if (rae[0] != 0) 336 | rae[2] = std::acos(xyz[1]/rae[0]); 337 | else // avoid divide by zero 338 | rae[2] = 0; 339 | 340 | if (xyz[0] < 0) 341 | rae[1] = std::atan(xyz[2]/xyz[0]) + M_PI; 342 | else if (xyz[0] > 0) 343 | rae[1] = std::atan(xyz[2]/xyz[0]); 344 | else // avoid divide by zero 345 | if (xyz[2] < 0) 346 | rae[1] = -M_PI_2; 347 | else 348 | rae[1] = M_PI_2; 349 | 350 | while (rae[1] > 2.0*M_PI) 351 | rae[1] -= 2.0*M_PI; 352 | 353 | while (rae[1] < 0) 354 | rae[1] += 2.0*M_PI; 355 | } 356 | 357 | inline void RAEtoXYZ(const float* rae, float* xyz) noexcept 358 | { 359 | xyz[0] = rae[0]*std::sin(rae[2])*std::cos(rae[1]); 360 | xyz[1] = rae[0]*std::cos(rae[2]); 361 | xyz[2] = rae[0]*std::sin(rae[2])*std::sin(rae[1]); 362 | } 363 | 364 | inline float angleBetween(const std::vector& a, 365 | const std::vector& b) noexcept 366 | { 367 | if (a.size() != b.size()) 368 | return -1; 369 | float dotProduct = 0, magA = 0, magB = 0; 370 | for (int i = 0; i < a.size(); ++i) { 371 | dotProduct += a[i] * b[i]; 372 | magA += a[i] * a[i]; 373 | magB += b[i] * b[i]; 374 | } 375 | return std::acos(dotProduct / (std::sqrt(magA) * std::sqrt(magB))); 376 | } 377 | 378 | template 379 | std::vector toVector(const float* array, const std::size_t N) 380 | { 381 | std::vector vector (N); 382 | for (int i = 0; i < N; ++i) 383 | vector[i] = array[i]; 384 | return vector; 385 | } 386 | 387 | //inline void XYZtoRAE(const float (&xyz)[3], float (&rae)[3]) 388 | //{ 389 | // rae[0] = std::sqrt(xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]); 390 | // if (rae[0] != 0) 391 | // rae[2] = std::acos(xyz[1]/rae[0]); 392 | // else // avoid divide by zero 393 | // rae[2] = 0; 394 | // 395 | // if (xyz[0] < 0) 396 | // rae[1] = std::atan(xyz[2]/xyz[0]) + M_PI; 397 | // else if (xyz[0] > 0) 398 | // rae[1] = std::atan(xyz[2]/xyz[0]); 399 | // else // avoid divide by zero 400 | // rae[1] = 0; 401 | // 402 | // while (rae[1] > 2.0*M_PI) 403 | // rae[1] -= 2.0*M_PI; 404 | // 405 | // while (rae[1] < 0) 406 | // rae[1] += 2.0*M_PI; 407 | //} 408 | // 409 | //inline void RAEtoXYZ(const float (&rae)[3], float (&xyz)[3]) 410 | //{ 411 | // xyz[0] = rae[0]*std::sin(rae[2])*std::cos(rae[1]); 412 | // xyz[1] = rae[0]*std::cos(rae[2]); 413 | // xyz[2] = rae[0]*std::sin(rae[2])*std::sin(rae[1]); 414 | //} 415 | // 416 | //// more C++ er 417 | //inline void XYZtoRAE(const std::array& xyz, std::array& rae) 418 | //{ 419 | // rae[0] = std::sqrt(xyz[0]*xyz[0] + xyz[1]*xyz[1] + xyz[2]*xyz[2]); 420 | // if (rae[0] != 0) 421 | // rae[2] = std::acos(xyz[1]/rae[0]); 422 | // else // avoid divide by zero 423 | // rae[2] = 0; 424 | // 425 | // if (xyz[0] < 0) 426 | // rae[1] = std::atan(xyz[2]/xyz[0]) + M_PI; 427 | // else if (xyz[0] > 0) 428 | // rae[1] = std::atan(xyz[2]/xyz[0]); 429 | // else // avoid divide by zero 430 | // rae[1] = 0; 431 | // 432 | // while (rae[1] > 2.0*M_PI) 433 | // rae[1] -= 2.0*M_PI; 434 | // 435 | // while (rae[1] < 0) 436 | // rae[1] += 2.0*M_PI; 437 | //} 438 | // 439 | //inline void RAEtoXYZ(const std::array& rae, std::array& xyz) 440 | //{ 441 | // xyz[0] = rae[0]*std::sin(rae[2])*std::cos(rae[1]); 442 | // xyz[1] = rae[0]*std::cos(rae[2]); 443 | // xyz[2] = rae[0]*std::sin(rae[2])*std::sin(rae[1]); 444 | //} 445 | 446 | 447 | 448 | //// x assumed to be between -1.0 and +1.0 449 | //static double raisedCosine(double x) 450 | //{ 451 | // double y; 452 | // 453 | // // B between 0 (square pulse) and 1 (sinc-like pulse) 454 | // // double B = 0.3; 455 | // 456 | // //length = 1/T = 1.0 -> T = 1.0 457 | // 458 | // // if (fabs(x) <= (1-B)/2.0) { 459 | // // y = 1; 460 | // // } if (fabs(x) <= (1+B)/2.0) { 461 | // // y = 0.5*(1.0 + cos(M_PI/B * (fabs(x) - (1.0-B)/2.0))); 462 | // // } else { 463 | // // y = 0; 464 | // // } 465 | // 466 | // // y = cos(M_PI/2.0 * x); 467 | // y = 0.5*(1.0 + cos(M_PI * x)); 468 | // 469 | // return y; 470 | //} 471 | 472 | //// cubic interpolation of 4 equally spaced data points 473 | //// mu is the parametric variable between 0 to 1 and gives the interpolated value between values y1 and y2 474 | //static float cubicInterpolate(float y0, float y1, float y2, float y3, float mu) 475 | //{ 476 | // float a0,a1,a2,a3,mu2; 477 | // 478 | // mu2 = mu*mu; 479 | // a0 = y3 - y2 - y0 + y1; 480 | // a1 = y0 - y1 - a0; 481 | // a2 = y2 - y0; 482 | // a3 = y1; 483 | // 484 | // return (a0*mu*mu2 + a1*mu2 + a2*mu + a3); 485 | //} 486 | 487 | #endif // Functions_h 488 | -------------------------------------------------------------------------------- /GLUT.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLUT.h 3 | // 4 | // Created by Andrew Barker on 9/20/16. 5 | // 6 | // 7 | 8 | #ifndef GLUT_h 9 | #define GLUT_h 10 | 11 | //#ifdef __APPLE__ 12 | // #include 13 | //#else // windows or linux 14 | // #define FREEGLUT_STATIC 1 15 | // #include 16 | //#endif 17 | 18 | //#include 19 | #include "Points.h" 20 | 21 | // screw linking to other libraries when doing cross-platform dev... 22 | namespace glpp { 23 | 24 | static void lookAt (const PointXYZ& eye, 25 | const PointXYZ& lookAt, 26 | const PointXYZ& up) 27 | { 28 | cauto forward = normalized((lookAt - eye)); 29 | cauto right = normalized(crossProduct(forward, up)); 30 | cauto nUp = crossProduct(right, forward); 31 | 32 | GLfloat m[] = { 33 | right.x, nUp.x, -forward.x, 0, 34 | right.y, nUp.y, -forward.y, 0, 35 | right.z, nUp.z, -forward.z, 0, 36 | 0, 0, 0, 1 37 | }; 38 | 39 | glMultMatrixf(m); 40 | glTranslatef(-eye.x, -eye.y, -eye.z); 41 | } 42 | 43 | // Replaces gluPerspective. Sets the frustum to perspective mode. 44 | // fovY - Field of vision in degrees in the y direction 45 | // aspect - Aspect ratio of the viewport (w/h) 46 | // zNear - The near clipping distance 47 | // zFar - The far clipping distance 48 | static void perspective (const GLdouble fovY, const GLdouble aspect, 49 | const GLdouble zNear, const GLdouble zFar) 50 | { 51 | //const GLdouble pi = 3.1415926535897932384626433832795; 52 | cauto fH = std::tan(0.5 * toRadians(fovY)) * zNear; 53 | cauto fW = fH * aspect; 54 | glFrustum(-fW, fW, -fH, fH, zNear, zFar); 55 | } 56 | 57 | static void pickMatrix(const GLdouble x, const GLdouble y, 58 | const GLdouble width, const GLdouble height, 59 | const GLint viewport[4]) 60 | { 61 | GLfloat m[16]; 62 | GLfloat sx, sy; 63 | GLfloat tx, ty; 64 | 65 | sx = viewport[2] / width; 66 | sy = viewport[3] / height; 67 | tx = (viewport[2] + 2.0 * (viewport[0] - x)) / width; 68 | ty = (viewport[3] + 2.0 * (viewport[1] - y)) / height; 69 | 70 | #define M(row, col) m[col*4+row] 71 | M(0, 0) = sx; 72 | M(0, 1) = 0.0; 73 | M(0, 2) = 0.0; 74 | M(0, 3) = tx; 75 | M(1, 0) = 0.0; 76 | M(1, 1) = sy; 77 | M(1, 2) = 0.0; 78 | M(1, 3) = ty; 79 | M(2, 0) = 0.0; 80 | M(2, 1) = 0.0; 81 | M(2, 2) = 1.0; 82 | M(2, 3) = 0.0; 83 | M(3, 0) = 0.0; 84 | M(3, 1) = 0.0; 85 | M(3, 2) = 0.0; 86 | M(3, 3) = 1.0; 87 | #undef M 88 | 89 | glMultMatrixf(m); 90 | } 91 | 92 | // thank you datenwolf! http://stackoverflow.com/questions/5988686/creating-a-3d-sphere-in-opengl-using-visual-c 93 | class SolidSphere 94 | { 95 | private: 96 | std::vector vertices; 97 | std::vector normals; 98 | std::vector texcoords; 99 | std::vector indices; 100 | 101 | public: 102 | SolidSphere(float radius, unsigned int sectors, unsigned int rings) 103 | { 104 | cauto R = 1.0f / (float)(rings-1); 105 | cauto S = 1.0f / (float)(sectors-1); 106 | int r, s; 107 | 108 | vertices.resize(rings * sectors * 3); 109 | normals.resize(rings * sectors * 3); 110 | texcoords.resize(rings * sectors * 2); 111 | auto v = vertices.begin(); 112 | auto n = normals.begin(); 113 | auto t = texcoords.begin(); 114 | for (r = 0; r < rings; r++) { 115 | for (s = 0; s < sectors; s++) { 116 | cfloat y = std::sin(-M_PI_2 + M_PI * r * R); 117 | cfloat x = std::cos(2*M_PI * s * S) * std::sin(M_PI * r * R); 118 | cfloat z = std::sin(2*M_PI * s * S) * std::sin(M_PI * r * R); 119 | 120 | *t++ = s * S; 121 | *t++ = r * R; 122 | 123 | *v++ = x * radius; 124 | *v++ = y * radius; 125 | *v++ = z * radius; 126 | 127 | *n++ = x; 128 | *n++ = y; 129 | *n++ = z; 130 | } 131 | } 132 | 133 | indices.resize(rings * sectors * 4); 134 | auto i = indices.begin(); 135 | for (r = 0; r < rings-1; r++) { 136 | for (s = 0; s < sectors-1; s++) { 137 | *i++ = r * sectors + s; 138 | *i++ = r * sectors + (s+1); 139 | *i++ = (r+1) * sectors + (s+1); 140 | *i++ = (r+1) * sectors + s; 141 | } 142 | } 143 | } 144 | 145 | void draw(GLfloat x, GLfloat y, GLfloat z) const 146 | { 147 | glMatrixMode(GL_MODELVIEW); 148 | glPushMatrix(); 149 | glTranslatef(x, y, z); 150 | 151 | glEnableClientState(GL_VERTEX_ARRAY); 152 | glEnableClientState(GL_NORMAL_ARRAY); 153 | glEnableClientState(GL_TEXTURE_COORD_ARRAY); 154 | 155 | glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); 156 | glNormalPointer(GL_FLOAT, 0, &normals[0]); 157 | glTexCoordPointer(2, GL_FLOAT, 0, &texcoords[0]); 158 | glDrawElements(GL_QUADS, indices.size(), GL_UNSIGNED_SHORT, &indices[0]); 159 | 160 | glPopMatrix(); 161 | } 162 | }; 163 | 164 | //template 165 | //void perspective(const T fovy, const T aspect, const T zNear, const T zFar) 166 | //{ 167 | // assert(abs(aspect - std::numeric_limits::epsilon()) > static_cast(0)); 168 | // 169 | // const T tanHalfFovy = std::tan(fovy / static_cast(2)); 170 | // 171 | // //tmat4x4 Result(static_cast(0)); 172 | // GLfloat m[4][4] = {0}; 173 | // m[0][0] = static_cast(1) / (aspect * tanHalfFovy); 174 | // m[1][1] = static_cast(1) / (tanHalfFovy); 175 | // m[2][3] = - static_cast(1); 176 | // 177 | ////# if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE 178 | //// Result[2][2] = zFar / (zNear - zFar); 179 | //// Result[3][2] = -(zFar * zNear) / (zFar - zNear); 180 | ////# else 181 | // m[2][2] = - (zFar + zNear) / (zFar - zNear); 182 | // m[3][2] = - (static_cast(2) * zFar * zNear) / (zFar - zNear); 183 | ////# endif 184 | //// return Result; 185 | // glMultMatrixf(&m[0][0]); 186 | //} 187 | 188 | } 189 | #endif /* GLUT_h */ 190 | -------------------------------------------------------------------------------- /History.h: -------------------------------------------------------------------------------- 1 | // 2 | // History.h 3 | // 4 | // Created by Andrew Barker on 8/25/16. 5 | // 6 | // 7 | 8 | #ifndef History_h 9 | #define History_h 10 | 11 | #include 12 | #include 13 | 14 | // this class keeps track of a history of undo/redoable states. it also has timer so that you can group smaller transactions into a single one based on the timeout length 15 | template 16 | class History { 17 | 18 | private: 19 | std::vector history; 20 | int current = 0; 21 | float undoTimer = 0; // in sec 22 | float timeoutLength = 3; // in sec 23 | 24 | public: 25 | void pushBack(const State& state) 26 | { 27 | int i = 1; 28 | if (history.size() > 0 && history[current] == state) 29 | --i; // overwrite the current history with the state passed in 30 | if (current + i < history.size()) 31 | history.erase(history.begin() + current + i, history.end()); 32 | history.emplace_back(state); 33 | current = history.size() - 1; 34 | } 35 | 36 | const State* const undo() noexcept 37 | { 38 | if (history.size() > 0) { 39 | current = std::max(0, current - 1); 40 | return &history[current]; 41 | } else 42 | return nullptr; 43 | } 44 | 45 | const State* const redo() noexcept 46 | { 47 | if (history.size() > 0) { 48 | current = std::min((int)history.size() - 1, current + 1); 49 | return &history[current]; 50 | } else 51 | return nullptr; 52 | } 53 | 54 | void clear() 55 | { 56 | current = 0; 57 | history.clear(); 58 | } 59 | 60 | const State* const getCurrent() const noexcept 61 | { 62 | if (history.size() > 0) 63 | return &history[current]; 64 | else 65 | return nullptr; 66 | } 67 | 68 | void resetTimer(const float newTimeoutLength = -1) noexcept 69 | { 70 | undoTimer = 0; 71 | if (newTimeoutLength > 0) 72 | timeoutLength = newTimeoutLength; 73 | } 74 | 75 | void advanceTimer(const float timeToAdvance) noexcept 76 | { 77 | if (! timerExpired()) 78 | undoTimer += timeToAdvance; 79 | } 80 | 81 | bool timerExpired() const noexcept 82 | { 83 | return undoTimer >= timeoutLength; 84 | } 85 | 86 | int getSize() const noexcept 87 | { 88 | return history.size(); 89 | } 90 | }; 91 | 92 | #endif /* History_h */ 93 | -------------------------------------------------------------------------------- /ImageEffect.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEffect.cpp 3 | // 4 | // Created by Andrew Barker on 5/16/16. 5 | // 6 | // 7 | 8 | #include "ImageEffect.h" 9 | 10 | 11 | MyGlowEffect::MyGlowEffect (Colour color, 12 | float radius, 13 | float originalAlpha) noexcept 14 | : color(color), radius(radius), originalAlpha(originalAlpha) 15 | {} 16 | 17 | void MyGlowEffect::applyEffect (Image& sourceImage, 18 | Graphics& destContext, 19 | const float scaleFactor, 20 | const float alpha) const 21 | { 22 | Image temp (sourceImage.getFormat(), sourceImage.getWidth(), sourceImage.getHeight(), true); 23 | 24 | ImageConvolutionKernel blurKernel (roundToInt (radius * scaleFactor * 2.0f)); 25 | 26 | blurKernel.createGaussianBlur (radius); 27 | blurKernel.rescaleAllValues (radius); 28 | 29 | blurKernel.applyToImage (temp, sourceImage, sourceImage.getBounds()); 30 | 31 | // need to duplicate source image in order to redraw it on top of the glow b/c the Graphics context might be set to draw onto the source image 32 | Image source = sourceImage; 33 | source.duplicateIfShared(); 34 | 35 | destContext.setColour (color.withMultipliedAlpha (alpha)); 36 | destContext.drawImageAt (temp, 0, 0, true); 37 | 38 | destContext.setOpacity (alpha * originalAlpha); 39 | destContext.drawImageAt (source, 0, 0); 40 | } 41 | 42 | std::unique_ptr MyGlowEffect::blendedTo (const MyImageEffectFilter* end, 43 | const float alpha) const 44 | { 45 | if (end == nullptr) { 46 | return std::make_unique 47 | (color.withMultipliedAlpha(1-alpha), radius * (1-alpha), originalAlpha); 48 | } else { 49 | auto temp = dynamic_cast(end); 50 | return std::make_unique 51 | (color.interpolatedWith(temp->color, alpha), 52 | radius + alpha * (temp->radius - radius), 53 | originalAlpha + alpha * (temp->originalAlpha - originalAlpha)); 54 | } 55 | } 56 | 57 | bool MyGlowEffect::operator == (const MyImageEffectFilter& other) const noexcept 58 | { 59 | if (getType() == (&other)->getType()) { 60 | auto x = dynamic_cast(other); 61 | return color == x.color && radius == x.radius && originalAlpha == x.originalAlpha; 62 | } 63 | return false; 64 | } 65 | 66 | bool MyGlowEffect::operator != (const MyImageEffectFilter& other) const noexcept 67 | { 68 | if (getType() == (&other)->getType()) { 69 | auto x = dynamic_cast(other); 70 | return color != x.color || radius != x.radius || originalAlpha != x.originalAlpha; 71 | } 72 | return false; 73 | } 74 | 75 | MyImageEffectFilterType MyGlowEffect::getType() const noexcept 76 | { 77 | return MyImageEffectFilterType::GLOW_EFFECT; 78 | } 79 | 80 | 81 | 82 | std::vector blend (const std::vector& begin, 83 | const std::vector& end, 84 | const float alpha) 85 | { 86 | std::vector blendeds;// (std::max(begin.size(), end.size())); 87 | blendeds.reserve(std::max(begin.size(), end.size())); 88 | int i = 0; 89 | for (; i < std::min(begin.size(), end.size()); ++i) 90 | blendeds.emplace_back(begin[i].blendedTo(end[i], alpha)); 91 | if (begin.size() > end.size()) { 92 | for (; i < begin.size(); ++i) 93 | blendeds.emplace_back(begin[i].blendedTo(ImageEffect(), alpha)); 94 | } else { 95 | for (; i < end.size(); ++i) 96 | blendeds.emplace_back(end[i].blendedTo(ImageEffect(), 1-alpha)); 97 | } 98 | return blendeds; 99 | } 100 | -------------------------------------------------------------------------------- /ImageEffect.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEffect.h 3 | // 4 | // Created by Andrew Barker on 5/16/16. 5 | // 6 | // 7 | 8 | #ifndef ImageEffect_h 9 | #define ImageEffect_h 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include 13 | 14 | enum class MyImageEffectFilterType { GLOW_EFFECT }; 15 | 16 | class MyImageEffectFilter 17 | { 18 | public: 19 | virtual void applyEffect (Image& sourceImage, 20 | Graphics& destContext, 21 | float scaleFactor, 22 | float alpha) const = 0; 23 | 24 | virtual std::unique_ptr blendedTo (const MyImageEffectFilter* end, 25 | float alpha) const = 0; 26 | 27 | virtual ~MyImageEffectFilter() = default; 28 | 29 | virtual bool operator == (const MyImageEffectFilter& other) const noexcept = 0; 30 | virtual bool operator != (const MyImageEffectFilter& other) const noexcept = 0; 31 | virtual MyImageEffectFilterType getType() const noexcept = 0; 32 | }; 33 | 34 | class MyGlowEffect : public MyImageEffectFilter 35 | { 36 | public: 37 | MyGlowEffect (Colour color = Colours::white, 38 | float radius = 2, 39 | float originalAlpha = 1) noexcept; 40 | 41 | void applyEffect (Image& sourceImage, 42 | Graphics& destContext, 43 | float scaleFactor, 44 | float alpha) const override; 45 | 46 | std::unique_ptr blendedTo (const MyImageEffectFilter* end, 47 | float alpha) const override; 48 | 49 | bool operator == (const MyImageEffectFilter& other) const noexcept override; 50 | bool operator != (const MyImageEffectFilter& other) const noexcept override; 51 | MyImageEffectFilterType getType() const noexcept override; 52 | 53 | Colour color; 54 | float radius; 55 | float originalAlpha; 56 | }; 57 | 58 | class ImageEffect 59 | { 60 | public: 61 | ImageEffect() : self_(nullptr) {} 62 | 63 | template 64 | ImageEffect(T x) : self_(std::make_shared>(std::move(x))) {} 65 | 66 | void apply(Image& source, Graphics& dest, float scale, float alpha) const 67 | { if (self_) self_->applyEffect(source, dest, scale, alpha); } 68 | 69 | ImageEffect blendedTo(const ImageEffect& end, float alpha) const 70 | { return self_ ? self_->blendedTo(end.self_.get(), alpha) : ImageEffect(); } 71 | 72 | bool operator == (const ImageEffect& other) const noexcept 73 | { return *self_ == *other.self_; } 74 | 75 | bool operator != (const ImageEffect& other) const noexcept 76 | { return *self_ != *other.self_; } 77 | 78 | private: 79 | ImageEffect(std::unique_ptr&& x) : self_(std::move(x)) {} 80 | 81 | template 82 | struct model : MyImageEffectFilter { 83 | model(T x) : data_(std::move(x)) {} 84 | 85 | void applyEffect(Image& source, Graphics& dest, float scale, float alpha) const override 86 | { (&data_)->applyEffect(source, dest, scale, alpha); } 87 | 88 | std::unique_ptr blendedTo(const MyImageEffectFilter* end, float alpha) const override 89 | { return (&data_)->blendedTo(end ? &dynamic_cast*>(end)->data_ : nullptr, alpha); } 90 | 91 | bool operator == (const MyImageEffectFilter& other) const noexcept override 92 | { return (&data_)->operator==(other); } 93 | 94 | bool operator != (const MyImageEffectFilter& other) const noexcept override 95 | { return (&data_)->operator!=(other); } 96 | 97 | MyImageEffectFilterType getType() const noexcept override 98 | { return (&data_)->getType(); } 99 | 100 | T data_; 101 | }; 102 | 103 | std::shared_ptr self_; 104 | }; 105 | 106 | // the two effects vectors better have the same ordering of ImageEffect subclasses for the effects that both share, otherwise not good stuff may happen 107 | std::vector blend (const std::vector& begin, 108 | const std::vector& end, 109 | const float alpha); 110 | 111 | #endif /* ImageEffect_h */ 112 | -------------------------------------------------------------------------------- /Multi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Multi.h 3 | // 4 | // Created by Andrew Barker on 4/15/16. 5 | // 6 | // 7 | 8 | #ifndef Multi_h 9 | #define Multi_h 10 | 11 | #include 12 | 13 | // holds multiple, uniquely-IDed things (like a std::map, but underlying container is a vector) 14 | template 15 | class Multi 16 | { 17 | private: 18 | // can't use POD's with inheritance 19 | // class IDedThing : public Thing 20 | // { 21 | // public: 22 | // template 23 | // IDedThing(std::size_t idNumber, Args&&... args) : Thing(std::forward(args)...), idNum(idNumber) {} 24 | // Thing thing; 25 | // std::size_t idNum = 0; 26 | // }; 27 | class IDedThing 28 | { 29 | public: 30 | //IDedThing(std::size_t idNumber, Thing thing) : thing(thing), idNum(idNumber) {} 31 | template 32 | IDedThing(const std::size_t idNumber, Args&&... args) : thing(std::forward(args)...), idNum(idNumber) {} 33 | Thing thing; 34 | std::size_t idNum = 0; 35 | }; 36 | std::vector things; 37 | 38 | public: 39 | Thing* get(const std::size_t idNumber) noexcept 40 | { 41 | auto index = std::find_if(std::begin(things), std::end(things), 42 | [&](const auto& thing){return thing.idNum == idNumber;}) 43 | - std::begin(things); 44 | if (index < things.size()) 45 | return &things[index].thing; 46 | else 47 | return nullptr; 48 | } 49 | 50 | const Thing* get(const std::size_t idNumber) const noexcept 51 | { 52 | return get(idNumber); 53 | } 54 | 55 | std::vector& getVector() noexcept 56 | { 57 | // std::vector justThings; 58 | // justThings.reserve(things.size()); 59 | // for (const auto& thing : things) 60 | // justThings.emplace_back(thing); 61 | // return justThings; 62 | return things; 63 | } 64 | 65 | const std::vector& getVector() const noexcept 66 | { 67 | return things; 68 | } 69 | 70 | template 71 | void add(const std::size_t idNumber, Args&&... args) 72 | { 73 | remove(idNumber); 74 | things.emplace_back(idNumber, std::forward(args)...); 75 | } 76 | 77 | void remove(const std::size_t idNumber) 78 | { 79 | things.erase(std::remove_if(std::begin(things), std::end(things), 80 | [&](const auto& thing){return thing.idNum == idNumber;}), 81 | std::end(things)); 82 | } 83 | 84 | void clear() noexcept 85 | { 86 | things.clear(); 87 | } 88 | 89 | std::size_t size() 90 | { 91 | return things.size(); 92 | } 93 | }; 94 | 95 | #endif /* Multi_h */ 96 | -------------------------------------------------------------------------------- /NonlinearInterpolator.h: -------------------------------------------------------------------------------- 1 | // 2 | // NonlinearInterpolator.h 3 | // 4 | // Created by Andrew Barker on 9/8/16. 5 | // 6 | // 7 | 8 | #ifndef NonlinearInterpolator_h 9 | #define NonlinearInterpolator_h 10 | 11 | #include 12 | 13 | template 14 | class NonlinearInterpolator 15 | { 16 | public: 17 | NonlinearInterpolator() {} 18 | virtual ~NonlinearInterpolator() {} 19 | virtual NonlinearInterpolator* clone() const = 0; 20 | // given a linear value between min and max, return a nonlinearly interpolated value between min and max 21 | virtual FloatingPointType getValue(FloatingPointType linearValue, 22 | FloatingPointType min, 23 | FloatingPointType max) const = 0; 24 | virtual FloatingPointType getInverseValue(FloatingPointType linearValue, 25 | FloatingPointType min, 26 | FloatingPointType max) const = 0; 27 | }; 28 | 29 | template 30 | class LogarithmicInterpolator : public NonlinearInterpolator 31 | { 32 | public: 33 | LogarithmicInterpolator(const FloatingPointType base = 2) noexcept : base(base) {} 34 | 35 | NonlinearInterpolator* clone() const override 36 | { 37 | return new LogarithmicInterpolator(*this); 38 | } 39 | 40 | FloatingPointType getValue(const FloatingPointType linearValue, 41 | const FloatingPointType min, 42 | const FloatingPointType max) const noexcept override 43 | { 44 | const auto range = max - min; 45 | // if (base > 1) 46 | const auto normalizedValue = (linearValue - min) / range * (base - 1) + 1; // [1, base] 47 | return min + std::log(normalizedValue) / std::log(base) * range; // [min, max] 48 | } 49 | 50 | FloatingPointType getInverseValue(const FloatingPointType linearValue, 51 | const FloatingPointType min, 52 | const FloatingPointType max) const noexcept override 53 | { 54 | const auto range = max - min; 55 | const auto normalizedValue = (linearValue - min) / range; // [0, 1] 56 | return min + (std::pow(base, normalizedValue) - 1) / (base - 1) * range; // [min, max] 57 | } 58 | 59 | private: 60 | FloatingPointType base; 61 | }; 62 | #endif /* NonlinearInterpolator_h */ -------------------------------------------------------------------------------- /OpenGL.h: -------------------------------------------------------------------------------- 1 | // 2 | // OpenGL.h 3 | // 4 | // Created by Andrew Barker on 9/19/16. 5 | // 6 | // 7 | 8 | #ifndef OpenGL_h 9 | #define OpenGL_h 10 | 11 | #ifdef __APPLE__ 12 | #include 13 | #else // windows or linux 14 | #include 15 | #endif 16 | 17 | //#include "../JuceLibraryCode/JuceHeader.h" 18 | // 19 | //void glColour(const Colour& color) 20 | //{ 21 | // glColor4f(color.getFloatRed(), color.getFloatGreen(), color.getFloatBlue(), color.getFloatAlpha()); 22 | //} 23 | 24 | #endif /* OpenGL_h */ 25 | -------------------------------------------------------------------------------- /OpenGLWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // OpenGLWindow.h 3 | // 4 | // Created by Andrew Barker on 9/20/16. 5 | // 6 | // 7 | 8 | #ifndef OpenGLWindow_h 9 | #define OpenGLWindow_h 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | 13 | class OpenGLWindow 14 | { 15 | public: 16 | OpenGLWindow(OpenGLContext* context) noexcept : context(context) {} 17 | void checkResized(int w, int h) noexcept 18 | { 19 | if (w != width || h != height) { 20 | resized = true; 21 | width = w; 22 | height = h; 23 | } 24 | } 25 | void saveResized() noexcept 26 | { 27 | resized = false; 28 | } 29 | float getAspect() const noexcept 30 | { 31 | return ((float)height) / width; 32 | } 33 | OpenGLContext* context = nullptr; 34 | int width = 0, height = 0; 35 | float frameRate = 30; // lets be artsy and get that "film" look, haha 36 | bool resized = true; 37 | }; 38 | 39 | #endif /* OpenGLWindow_h */ 40 | -------------------------------------------------------------------------------- /PluginProcessor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PluginProcessor.h 3 | // 4 | // Created by Andrew Barker on 4/26/14. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef __PluginProcessor__ 28 | #define __PluginProcessor__ 29 | 30 | // uncomment to build demo version instead of full version 31 | //#define DEMO 1 32 | 33 | #include "../JuceLibraryCode/JuceHeader.h" 34 | #include "DrewLib.h" 35 | 36 | #include "SoundSource.h" 37 | #include "Resampler.h" 38 | #include "ConcurrentResource.h" 39 | 40 | // keeps track of the number of plugin instances so we can only use one copy of the HRIR data 41 | static int numRefs = 0; 42 | // possible states for GUI display 43 | enum class DisplayState { MAIN, PATH_AUTOMATION, SETTINGS, NUM_DISPLAY_STATES }; 44 | // realtime is lightest on cpu and will not glitch, offline is expensive on cpu and may glitch, auto-detect assumes the processing mode from the host 45 | enum class ProcessingMode { REALTIME, OFFLINE, AUTO_DETECT }; 46 | // max number of sound sources 47 | static constexpr auto maxNumSources = 8; 48 | // making life easier 49 | using Sources = std::vector; 50 | using Locker = std::lock_guard; 51 | 52 | class ThreeDAudioProcessor : public AudioProcessor, public UndoManager 53 | #ifdef DEMO // demo version only 54 | , public Timer 55 | #endif 56 | { 57 | public: 58 | ThreeDAudioProcessor(); 59 | ~ThreeDAudioProcessor(); 60 | #ifdef DEMO // demo version only 61 | void timerCallback() override; 62 | #endif 63 | // the methods to for JUCE's AudioProcessor interface 64 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 65 | void releaseResources() override; 66 | void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override; 67 | AudioProcessorEditor* createEditor() override; 68 | bool hasEditor() const override; 69 | const String getName() const override; 70 | // no need to override the ones in AudioProcessor 71 | // int getNumParameters(); 72 | // float getParameter (int index); 73 | // void setParameter (int index, float newValue); 74 | // const String getParameterName (int index); 75 | // const String getParameterText (int index); 76 | const String getInputChannelName (int channelIndex) const override; 77 | const String getOutputChannelName (int channelIndex) const override; 78 | bool isInputChannelStereoPair (int index) const override; 79 | bool isOutputChannelStereoPair (int index) const override; 80 | bool acceptsMidi() const override; 81 | bool producesMidi() const override; 82 | bool silenceInProducesSilenceOut() const override; 83 | double getTailLengthSeconds() const override; 84 | int getNumPrograms() override; 85 | int getCurrentProgram() override; 86 | void setCurrentProgram (int index) override; 87 | const String getProgramName (int index) override; 88 | void changeProgramName (int index, const String& newName) override; 89 | void getStateInformation (MemoryBlock& destData) override; 90 | void setStateInformation (const void* data, int sizeInBytes) override; 91 | // methods for source interaction 92 | void saveCurrentState(int beforeOrAfter); 93 | void getSourcePosXYZ(int sourceIndex, float (&xyz)[3]) const; 94 | bool addSourceAtXYZ(const float (&xyz)[3]); 95 | void copySelectedSources(); 96 | bool getSourceSelected(std::size_t sourceIndex) const; 97 | void setSourceSelected(int sourceIndex, bool newSelectedState); 98 | void selectAllSources(bool newSelectedState); 99 | void deleteSelectedSources(); 100 | void toggleLockSourcesToPaths(); 101 | bool getLockSourcesToPaths() const; 102 | void toggleDoppler(); 103 | int moveSelectedSourcesXYZ(float dx, float dy, float dz, bool moveSource = false); 104 | int moveSelectedSourcesRAE(float dr, float da, float de, bool moveSource = false); 105 | void toggleSelectedSourcesPathType(); 106 | Array* getSources(); 107 | //void setSources(const Lockable& newSources); 108 | void setSources(const Sources& newSources); 109 | // path point interaction 110 | void dropPathPoint(); 111 | bool dropPathPoint(const float (&xyz)[3]); 112 | void togglePathPointSelected(int sourceIndex, int ptIndex); 113 | void setPathPointSelectedState(int sourceIndex, int ptIndex, bool newSelectedState); 114 | //void markPathAsUpdated(int sourceIndex); 115 | std::vector> getPathPoints(int sourceIndex) const; 116 | std::vector getPathPointsSelected(int sourceIndex) const; 117 | bool getPathPointSelected(std::size_t sourceIndex, 118 | std::size_t pathPointIndex) const; 119 | void setSelectedPathPointIndecies(int sourceIndex, 120 | int pathPointIndex, 121 | int newIndex); 122 | // path automation point interaction 123 | void togglePathAutomationPointSelected(int sourceIndex, int ptIndex); 124 | void selectAllPathAutomationView(bool newSelectedState); 125 | void setPathAutomationPointSelectedState(int sourceIndex, int ptIndex, bool newSelectedState); 126 | void deselectAllPathAutomationPoints(); 127 | int moveSelectedPathAutomationPoints(float dx, float dy); 128 | //std::vector moveSelectedPathAutomationPointsWithReorderingInfo(float dx, float dy, int sourceIndexOfInterest); 129 | void moveSelectedPathAutomationPointsTo(int referencePtSourceIndex, 130 | int& referencePtIndex, 131 | int referencePtIndexAmongSelecteds, 132 | float x, float y); 133 | void addPathAutomationPtAtXY(const float (&xy)[2]); 134 | void deleteSelectedAutomationPoints(); 135 | void setSelectedPathAutomationPointsSegmentType(int newSegType); 136 | //void markPathPosAsUpdated(int sourceIndex); 137 | void copySelectedPathAutomationPoints(); 138 | std::vector> getPathAutomationPoints(int sourceIndex) const; 139 | //std::vector> getSelectedPathAutomationPoints(int sourceIndex); 140 | //std::vector getPathAutomationPointsSelected(int sourceIndex); 141 | int getPathAutomationPointIndexAmongSelectedPoints(int sourceIndex, int pointIndex) const; 142 | bool areAnySelectedSourcesPathAutomationPointsSelected() const; 143 | void makeSourcesVisibleForPathAutomationView(); 144 | // resets the playing state if processBlock() has not been called in a while, needed because of the logic for moving selected sources 145 | void resetPlaying(float frameRate) noexcept; 146 | // for looping 147 | void toggleLooping(float defaultBegin, float defaultEnd); 148 | void defineLoopingRegionUsingSelectedPathAutomationPoints(); 149 | 150 | std::atomic presetJustLoaded {true}; // to communicate to the editor when preset is loaded, editor resets to false after doing what it needs 151 | std::atomic loopRegionBegin {-1}; 152 | std::atomic loopRegionEnd {-1}; 153 | std::atomic loopingEnabled {false}; 154 | // for the doppler effect 155 | void setSpeedOfSound(float newSpeedOfSound); 156 | bool dopplerOn = false; 157 | float speedOfSound = defaultSpeedOfSound; 158 | float maxSpeedOfSound = 500.0f; 159 | float minSpeedOfSound = 0.1f; 160 | // plugin window size 161 | int lastUIWidth = 700; 162 | int lastUIHeight = 600; 163 | // current time position, buffer size, sample rate, bpm, and time signature 164 | std::atomic posSEC {0}; 165 | std::atomic N; 166 | std::atomic fs; 167 | std::atomic bpm {120}; 168 | std::atomic timeSigNum {4}; 169 | std::atomic timeSigDen {4}; 170 | std::string getCurrentTimeString(int opt) const; 171 | std::tuple getMeasuresBeatsFrac(float sec) const; 172 | // eye position 173 | float upDir = 1.0f; // y component of eyeUp 174 | float eyePos[3]; // x,y,z 175 | float eyeUp[3] = {0.0f, 1.0f, 0.0f}; 176 | float eyeRad = 3.3f; 177 | float eyeAzi = 9*M_PI/8; 178 | float eyeEle = M_PI/2.2f; 179 | // layout for the path automation view 180 | float automationViewWidth = 60.0f; 181 | float automationViewOffset = automationViewWidth/2.0f; 182 | // which view is displayed in the plugin window 183 | std::atomic displayState {DisplayState::MAIN}; 184 | // determines audio rendering quality and realtime processing performance 185 | void setProcessingMode(ProcessingMode newMode) noexcept; 186 | std::atomic processingMode {ProcessingMode::AUTO_DETECT}; 187 | std::atomic realTime {true}; 188 | std::atomic isHostRealTime {false}; 189 | // show the controls for that view 190 | //bool showHelp = false; 191 | // for letting the GL know when its display lists for drawing the path and pathPos interps for each source are updated 192 | std::atomic pathChanged {false}; 193 | std::atomic pathPosChanged {false}; 194 | //std::array, maxNumSources> pathChangeds; 195 | //std::array, maxNumSources> pathPosChangeds; 196 | // the visual representation of sound sources along with temporary copies to support undo/redos 197 | RealtimeConcurrent sources; 198 | //AudioPlayHead::CurrentPositionInfo gPositionInfo; 199 | std::array, maxNumSources> sourcePathPositionsFromDAW; // for source position automation from DAW 200 | std::atomic wetOutputVolume {1.0f}; 201 | std::atomic dryOutputVolume {0.0f}; 202 | float savedMixValue = wetOutputVolume / (wetOutputVolume + dryOutputVolume); 203 | 204 | private: 205 | #ifdef DEMO // Demo version only 206 | DialogWindow::LaunchOptions buyMeWindowLauncher; 207 | DialogWindow* buyMeWindow = nullptr; 208 | #endif 209 | float prevWetOutputVolume = wetOutputVolume; 210 | float prevDryOutputVolume = dryOutputVolume; 211 | int maxBufferSizePreparedFor = -1; 212 | // version of sources that can be used to process audio, only updated in processBlock() and is therefore thread-safe to use for processing 213 | std::vector playableSources; 214 | int prevSourcesSize = 0; // see processBlock() for useage 215 | // temporary SoundSource copies to support undo/redos 216 | Sources beforeUndo; 217 | Sources currentUndo; 218 | // Lockable beforeUndo; 219 | // Lockable currentUndo; 220 | // objects for sample rate conversion 221 | Resampler resampler; 222 | Resampler unsamplerCh1; 223 | Resampler unsamplerCh2; 224 | // previous buffer's time position from this plugin's perspective 225 | float posSECprev = 0; 226 | // prev buf time position from host's perspective 227 | float posSECPrevHost = 0; 228 | float prevBufferDuration = 0; 229 | // are we ready to process audio with sound sources? 230 | std::atomic inited {false}; 231 | // during playback sources can be locked to move on their paths, or moved about freely by the user 232 | std::atomic lockSourcesToPaths {true}; 233 | // are we playing back audio now? 234 | std::atomic playing {false}; 235 | std::atomic resetPlayingCount {0}; 236 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreeDAudioProcessor) 237 | }; 238 | 239 | class EditSources : public UndoableAction 240 | { 241 | public: 242 | // EditSources(const Lockable& prevSourcesIn, const Lockable& nextSourcesIn, ThreeDAudioProcessor* ownerIn) 243 | // { 244 | // const Locker lockPrev (prevSources.getLock()); 245 | // const Locker lockPrevIn (prevSourcesIn.getLock()); 246 | // const Locker lockNext (nextSources.getLock()); 247 | // const Locker lockNextIn (nextSourcesIn.getLock()); 248 | // Sources& prev = prevSources.getResource(); 249 | // for (const auto& source : prevSourcesIn.getResource()) 250 | // prev.emplace_back(source); 251 | // Sources& next = nextSources.getResource(); 252 | // for (const auto& source : nextSourcesIn.getResource()) 253 | // next.emplace_back(source); 254 | // owner = ownerIn; 255 | // } 256 | EditSources(const Sources& prevSourcesIn, const Sources& nextSourcesIn, ThreeDAudioProcessor* ownerIn) 257 | { 258 | for (const auto& source : prevSourcesIn) 259 | prevSources.emplace_back(source); 260 | for (const auto& source : nextSourcesIn) 261 | nextSources.emplace_back(source); 262 | owner = ownerIn; 263 | } 264 | bool perform() override 265 | { 266 | owner->setSources(nextSources); 267 | return true; 268 | } 269 | bool undo() override 270 | { 271 | owner->setSources(prevSources); 272 | return true; 273 | } 274 | int getSizeInUnits() override 275 | { 276 | return 10; 277 | } 278 | UndoableAction* createCoalescedAction (UndoableAction* nextAction) override 279 | { 280 | UndoableAction* coalescedAction = new EditSources(prevSources, ((EditSources*)nextAction)->nextSources, ((EditSources*)nextAction)->owner); 281 | return coalescedAction; 282 | } 283 | private: 284 | // Lockable prevSources; 285 | // Lockable nextSources; 286 | Sources prevSources; 287 | Sources nextSources; 288 | ThreeDAudioProcessor* owner; 289 | }; 290 | 291 | #endif /* defined(__PluginProcessor__) */ 292 | -------------------------------------------------------------------------------- /Points.h: -------------------------------------------------------------------------------- 1 | // 2 | // Points.h 3 | // Represents all sorts of point types and associated convenience methods. 4 | // 5 | // Created by Andrew Barker on 11/13/16. 6 | // 7 | // 8 | 9 | #ifndef Points_h 10 | #define Points_h 11 | 12 | #include "OpenGL.h" 13 | 14 | template 15 | class PointXYZ { 16 | public: 17 | T x, y, z; 18 | 19 | PointXYZ addX (const T add) const noexcept { return {x + add, y, z}; } 20 | PointXYZ addY (const T add) const noexcept { return {x, y + add, z}; } 21 | PointXYZ addZ (const T add) const noexcept { return {x, y, z + add}; } 22 | PointXYZ subX (const T sub) const noexcept { return {x - sub, y, z}; } 23 | PointXYZ subY (const T sub) const noexcept { return {x, y - sub, z}; } 24 | PointXYZ subZ (const T sub) const noexcept { return {x, y, z - sub}; } 25 | //void normalize() noexcept { 26 | 27 | }; 28 | 29 | template 30 | PointXYZ operator+ (const PointXYZ& l, const PointXYZ& r) noexcept { return {l.x + r.x, l.y + r.y, l.z + r.z}; } 31 | 32 | template 33 | PointXYZ operator- (const PointXYZ& l, const PointXYZ& r) noexcept { return {l.x - r.x, l.y - r.y, l.z - r.z}; } 34 | 35 | template 36 | PointXYZ operator* (const PointXYZ& l, const T r) noexcept { return {r * l.x, r * l.y, r * l.z}; } 37 | 38 | template 39 | PointXYZ operator* (const T l, const PointXYZ& r) noexcept { return operator* (r, l); } 40 | 41 | template 42 | PointXYZ operator/ (const PointXYZ& l, const T r) noexcept { return {l.x / r, l.y / r, l.z / r}; } 43 | 44 | template 45 | PointXYZ normalized (const PointXYZ& p) noexcept 46 | { 47 | return p / std::hypot(p.x, p.y, p.z); 48 | } 49 | 50 | // to perform cross product between 2 vectors in myGluLookAt 51 | template 52 | PointXYZ crossProduct(const PointXYZ& a, const PointXYZ& b) 53 | { 54 | return {a.y * b.z - b.y * a.z, 55 | b.x * a.z - a.x * b.z, 56 | a.x * b.y - b.x * a.y}; 57 | } 58 | 59 | template 60 | T dotProduct(const PointXYZ& a, const PointXYZ& b) 61 | { 62 | return a.x * b.x + a.y * b.y + a.z * b.z; 63 | } 64 | 65 | static void glVertex(const PointXYZ& pt) 66 | { 67 | glVertex3f(pt.x, pt.y, pt.z); 68 | } 69 | 70 | #endif /* Points_h */ 71 | -------------------------------------------------------------------------------- /PolyPtr.h: -------------------------------------------------------------------------------- 1 | /* 2 | PolyPtr.h: a polymorphic smart pointer that deep copies 3 | Copyright (C) 2016 Andrew Barker 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 3 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, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU 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, see . 17 | 18 | The author can be contacted via email at andrew.barker.12345@gmail.com. 19 | */ 20 | 21 | #ifndef PolyPtr_h 22 | #define PolyPtr_h 23 | 24 | #include 25 | #include 26 | 27 | template 28 | class PolyPtr 29 | { 30 | public: 31 | // default ctor sets up a nullptr 32 | constexpr PolyPtr(std::nullptr_t n = nullptr) noexcept : ptr(nullptr) {} 33 | 34 | // destructor frees the memory so you can rely on RAII to do the cleanup 35 | ~PolyPtr() { delete ptr; } 36 | 37 | // ctor for creating a PolyPtr that assumes the ownership of some other raw pointer 38 | explicit PolyPtr(T* p) noexcept : ptr(std::move(p)) {} 39 | 40 | // copy ctor that implements the polymporhic deep copying 41 | PolyPtr(const PolyPtr& p) : ptr(p ? p.ptr->clone() : nullptr) {} 42 | 43 | // copy ctor that allows you to create a parent PolyPtr from a child PolyPtr 44 | template 45 | PolyPtr(const PolyPtr& p) : ptr(p ? new Other(*p) : nullptr) {} 46 | 47 | // move ctor that swaps the underlying pointers 48 | PolyPtr(PolyPtr&& p) noexcept : PolyPtr() { swap(p); } 49 | 50 | // move ctor that allows parent / child pointer swaps 51 | template 52 | PolyPtr(PolyPtr&& p) noexcept : PolyPtr() { swap(p); } 53 | 54 | // copy assignment that is implemented with the copy and swap idiom 55 | PolyPtr& operator=(PolyPtr p) noexcept 56 | { 57 | swap(p); 58 | return *this; 59 | } 60 | 61 | // copy assigment for parentPtr = childPtr type assignments 62 | template 63 | PolyPtr& operator=(PolyPtr p) noexcept 64 | { 65 | swap(p); 66 | return *this; 67 | } 68 | 69 | // raw pointer assignment 70 | PolyPtr& operator=(T* p) noexcept 71 | { 72 | reset(p); 73 | return *this; 74 | } 75 | 76 | // swap this PolyPtr with another 77 | void swap(PolyPtr& p) noexcept 78 | { 79 | using std::swap; 80 | swap(ptr, p.ptr); 81 | /* "using std::swap` then calling swap (without std::) allows for Koenig look up of a type specific swap first. If that does not exist then the std::swap version will be used by the compiler. */ 82 | } 83 | 84 | // allows internal access to PolyPtr::ptr 85 | template friend class PolyPtr; 86 | 87 | // swap that allows parent/child PolyPtr swapping 88 | template 89 | void swap(PolyPtr& p) noexcept 90 | { 91 | const auto tmp = ptr; 92 | ptr = std::move(dynamic_cast(p.get())); // shouldn't produce exceptions because we're casting a pointer type, not a reference 93 | p.ptr = std::move(dynamic_cast(tmp)); 94 | } 95 | 96 | // take over ownership of a raw pointer, or just delete the currently owned one if it exists 97 | void reset(T* newPtr = nullptr) noexcept 98 | { 99 | if (ptr) 100 | delete ptr; 101 | ptr = newPtr; 102 | } 103 | 104 | // const / non-const access to underlying raw pointer 105 | T* get() noexcept { return ptr; } 106 | const T* get() const noexcept { return ptr; } 107 | 108 | // pointer access operators 109 | T* operator->() noexcept { return ptr; } 110 | const T* operator->() const noexcept { return ptr; } 111 | 112 | // dereference operators 113 | T& operator*() noexcept { return *ptr; } 114 | const T& operator*() const noexcept { return *ptr; } 115 | 116 | // allows a quick "if (somePolyPtr)" to check for nullptr 117 | explicit operator bool() const noexcept { return ptr; } 118 | 119 | // implicit conversion operator so you don't have to use get() to convert a PolyPtr to T* 120 | operator T*() const noexcept { return ptr; } 121 | 122 | private: 123 | // the underlying raw pointer is encapsulated and protected from unecessary harm 124 | T* ptr; 125 | }; 126 | 127 | // a swap free function to swap two different types (parent/child) of PolyPtrs 128 | template 129 | void swap(PolyPtr& p1, PolyPtr& p2) noexcept { 130 | p1.swap(p2); 131 | } 132 | 133 | // the recommended, easy, std::make_unique-like way of creating a PolyPtr 134 | template 135 | PolyPtr makePolyPtr(Args&&... args) { 136 | return PolyPtr(new T(std::forward(args)...)); 137 | } 138 | 139 | #endif /* PolyPtr_h */ -------------------------------------------------------------------------------- /Polynomial.h: -------------------------------------------------------------------------------- 1 | // 2 | // Polynomial.h 3 | // 4 | // Created by Andrew Barker on 9/25/15. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | 28 | #ifndef __Polynomial__ 29 | #define __Polynomial__ 30 | 31 | #include // for speed and sexy 32 | 33 | // A class for storing a polynomial by the coefficients and exponants of its terms and providing a simple, efficient way to get its value at any input. 34 | template 35 | class Polynomial 36 | { 37 | public: 38 | // need a default constructor to compile (has to do with using this class in stl containers) 39 | Polynomial() {}; 40 | // outer dim should only be size two 41 | Polynomial(const std::valarray>& new_terms) 42 | { 43 | terms = new_terms; 44 | }; 45 | 46 | T operator()(const T& value) const noexcept 47 | { 48 | return (terms[0] * pow(value, terms[1])).sum(); 49 | }; 50 | 51 | void allocate(const int numTerms) 52 | { 53 | terms.resize(2); 54 | terms[0].resize(numTerms); 55 | terms[1].resize(numTerms); 56 | }; 57 | 58 | void fill(const T* coeffs, const T* exps, const int numTerms) noexcept 59 | { 60 | for (int i = 0; i < numTerms; ++i) 61 | { 62 | terms[0][i] = coeffs[i]; 63 | terms[1][i] = exps[i]; 64 | } 65 | }; 66 | 67 | private: 68 | std::valarray> terms; // coeffs in [0][*], exponents in [1][*] 69 | }; 70 | 71 | #endif /* defined __Polynomial__ */ 72 | -------------------------------------------------------------------------------- /PolynomialSpline.h: -------------------------------------------------------------------------------- 1 | // 2 | // PolynomialSpline.h 3 | // 4 | // Created by Andrew Barker on 9/26/15. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef __PolynomialSpline_h__ 28 | #define __PolynomialSpline_h__ 29 | 30 | #include "Spline.h" 31 | #include "Polynomial.h" 32 | 33 | // virtual class that stores the N polynomials needed for an N-dimensional polynomial spline 34 | template 35 | class PolynomialSpline : public virtual Spline 36 | { 37 | public: 38 | virtual std::vector pointAt(const T& val) override; 39 | virtual void pointAt(const T& val, T** point) const override; 40 | protected: 41 | std::vector> spline; 42 | }; 43 | 44 | // Class for cubic functional splines, a.k.a. f(x) = [y, z, ...] 45 | template 46 | class CubicFunctionalSpline : public PolynomialSpline, NPointSpline 47 | { 48 | public: 49 | CubicFunctionalSpline() {} 50 | // load a set of points like: 0 -PREVIOUS SPLINE- 1 -THIS SPLINE- 2 -NEXT SPLINE- 3 51 | CubicFunctionalSpline(const std::vector& p0, const std::vector& p1, 52 | const std::vector& p2, const std::vector& p3); 53 | void calc() override; 54 | std::vector pointAt(const T& val) override; 55 | //void pointAt(const T& val, T* point) override; 56 | virtual void pointAt(const T& val, T** point) const override; 57 | std::unique_ptr> clone() override { return std::make_unique>(*this);} 58 | SplineShape getShape() const noexcept override { return SplineShape::CUBIC; } 59 | private: 60 | using PolynomialSpline::spline; 61 | using Spline::points; 62 | }; 63 | // used in doppler effect to support pre-allocation for to real time use 64 | template 65 | class LightweightCubicFunctionalSpline 66 | { 67 | public: 68 | void allocate() 69 | { 70 | spline.resize(N - 1); 71 | spline.shrink_to_fit(); 72 | for (auto& poly : spline) 73 | poly.allocate(4); 74 | }; 75 | void calc(const T (&points)[4][N]) noexcept 76 | { 77 | splineStart = points[1][0]; // necessary for computing pointAt() 78 | 79 | T h_k, h_km1, h_kp1; // first dim segment lengths surrounding the kth segment 80 | T delta_k, delta_km1, delta_kp1; // linear slopes of surrounding segments 81 | T d_k, d_kp1; // spline slopes at segment endpoints 82 | T a0, a1, a2, a3; // cubic polynomial coefficients 83 | 84 | h_k = 1.0 / (points[2][0]-points[1][0]); 85 | h_km1 = 1.0 / (points[1][0]-points[0][0]); 86 | h_kp1 = 1.0 / (points[3][0]-points[2][0]); 87 | 88 | for (int i = 1; i < N; ++i) 89 | { 90 | delta_k = (points[2][i]-points[1][i]) * h_k; 91 | delta_km1 = (points[1][i]-points[0][i]) * h_km1; 92 | delta_kp1 = (points[3][i]-points[2][i]) * h_kp1; 93 | // check for NaNs 94 | if (delta_k != delta_k) 95 | delta_k = 0; 96 | if (delta_km1 != delta_km1) 97 | delta_km1 = 0; 98 | if (delta_kp1 != delta_kp1) 99 | delta_kp1 = 0; 100 | // if there is a change in sign between segment slopes or either slope is zero, make the boundary point a local min/max by setting the endpoint slope to 0, 101 | // otherwise the slopes at the points are the geometric mean of the linear slopes of the surrounding segments 102 | if (delta_k == 0 || delta_km1 == 0 || (delta_k < 0 && delta_km1 > 0) || (delta_k > 0 && delta_km1 < 0)) 103 | d_k = 0; 104 | else 105 | d_k = 2.0 / (1.0/delta_km1 + 1.0/delta_k); 106 | 107 | if (delta_kp1 == 0 || delta_k == 0 || (delta_kp1 < 0 && delta_k > 0) || (delta_kp1 > 0 && delta_k < 0)) 108 | d_kp1 = 0; 109 | else 110 | d_kp1 = 2.0 / (1.0/delta_k + 1.0/delta_kp1); 111 | 112 | a0 = points[1][i]; 113 | a1 = d_k; 114 | a2 = (3.0*delta_k - 2.0*d_k - d_kp1) * h_k; 115 | a3 = (d_k - 2.0*delta_k + d_kp1) * (h_k * h_k); 116 | 117 | const T coeffs[4] = {a0,a1,a2,a3}; 118 | const T exps[4] = {0,1,2,3}; 119 | spline[i-1].fill(coeffs, exps, 4); 120 | } 121 | }; 122 | void pointAt(const T& val, T* point) const noexcept 123 | { 124 | const int numDimensions = spline.size(); 125 | const T adjustedVal = val - splineStart; 126 | for (int i = 0; i < numDimensions; ++i) 127 | point[i] = spline[i](adjustedVal); 128 | }; 129 | private: 130 | std::vector> spline; 131 | T splineStart; 132 | }; 133 | 134 | // Class for cubic parametric splines, a.k.a. f(t) = [x, y, z, ...] 135 | template 136 | class CubicParametricSpline : public PolynomialSpline, NPointSpline 137 | { 138 | public: 139 | CubicParametricSpline() {} 140 | // load a set of points like: 0 -PREVIOUS SPLINE- 1 -THIS SPLINE- 2 -NEXT SPLINE- 3 141 | CubicParametricSpline(const std::vector& p0, const std::vector& p1, 142 | const std::vector& p2, const std::vector& p3); 143 | void calc() override; 144 | std::unique_ptr> clone() override { return std::make_unique>(*this); } 145 | SplineShape getShape() const noexcept override { return SplineShape::CUBIC; } 146 | private: 147 | using PolynomialSpline::spline; 148 | using Spline::points; 149 | }; 150 | 151 | // Class for linear functional splines, a.k.a. f(x) = [y, z, ...] 152 | template 153 | class LinearFunctionalSpline : public PolynomialSpline, NPointSpline 154 | { 155 | public: 156 | LinearFunctionalSpline() {} 157 | // spline goes from p0 to p1 158 | LinearFunctionalSpline(const std::vector& p0, const std::vector& p1); 159 | void calc() override; 160 | std::unique_ptr> clone() override { return std::make_unique>(*this); } 161 | SplineShape getShape() const noexcept override { return SplineShape::LINEAR; } 162 | private: 163 | using PolynomialSpline::spline; 164 | using Spline::points; 165 | }; 166 | 167 | // Class for linear parametric splines, a.k.a. f(t) = [x, y, z, ...] 168 | template 169 | class LinearParametricSpline : public PolynomialSpline, NPointSpline 170 | { 171 | public: 172 | LinearParametricSpline() {} 173 | // spline goes from p0 to p1 in a parametric distance of para_intrvl 174 | LinearParametricSpline(const std::vector& p0, const std::vector& p1); 175 | void calc() override; 176 | std::unique_ptr> clone() override { return std::make_unique>(*this); } 177 | SplineShape getShape() const noexcept override { return SplineShape::LINEAR; } 178 | private: 179 | using PolynomialSpline::spline; 180 | using Spline::points; 181 | }; 182 | 183 | template 184 | std::unique_ptr> SplineFactory(const SplineShape shape, 185 | const SplineBehavior behavior) 186 | { 187 | if (shape == SplineShape::EMPTY) 188 | return std::make_unique>(); 189 | else if (shape == SplineShape::CUBIC) { 190 | if (behavior == SplineBehavior::FUNCTIONAL) 191 | return std::make_unique>(); 192 | else if (behavior == SplineBehavior::PARAMETRIC) 193 | return std::make_unique>(); 194 | } 195 | else if (shape == SplineShape::LINEAR) { 196 | if (behavior == SplineBehavior::FUNCTIONAL) 197 | return std::make_unique>(); 198 | else if (behavior == SplineBehavior::PARAMETRIC) 199 | return std::make_unique>(); 200 | } 201 | return nullptr; 202 | } 203 | 204 | // the implementations 205 | template 206 | std::vector PolynomialSpline::pointAt(const T& val) 207 | { 208 | std::vector point (spline.size()); 209 | for (int i = 0; i < point.size(); ++i) 210 | point[i] = spline[i](val); 211 | return point; 212 | } 213 | template 214 | void PolynomialSpline::pointAt(const T& val, T** point) const 215 | { 216 | const int numDimensions = spline.size(); 217 | for (int i = 0; i < numDimensions; ++i) 218 | (*point)[i] = spline[i](val); 219 | } 220 | 221 | // cubic functional spline equation uses a substitution of s = x-x_k so adjust for that here 222 | template 223 | std::vector CubicFunctionalSpline::pointAt(const T& val) 224 | { 225 | std::vector point (spline.size()); 226 | const T adjustedVal = val - points[1][0]; 227 | for (int i = 0; i < point.size(); ++i) 228 | point[i] = spline[i](adjustedVal); 229 | return point; 230 | } 231 | template 232 | void CubicFunctionalSpline::pointAt(const T& val, T** point) const 233 | { 234 | const int numDimensions = spline.size(); 235 | const T adjustedVal = val - points[1][0]; 236 | for (int i = 0; i < numDimensions; ++i) 237 | (*point)[i] = spline[i](adjustedVal); 238 | } 239 | 240 | template 241 | CubicFunctionalSpline::CubicFunctionalSpline(const std::vector& p0, 242 | const std::vector& p1, 243 | const std::vector& p2, 244 | const std::vector& p3) 245 | { 246 | points = {p0, p1, p2, p3}; 247 | calc(); 248 | } 249 | 250 | template 251 | void CubicFunctionalSpline::calc() 252 | { 253 | T h_k, h_km1, h_kp1; // first dim segment lengths surrounding the kth segment 254 | T delta_k, delta_km1, delta_kp1; // linear slopes of surrounding segments 255 | T d_k, d_kp1; // spline slopes at segment endpoints 256 | T a0, a1, a2, a3; // cubic polynomial coefficients 257 | 258 | h_k = 1.0 / (points[2][0]-points[1][0]); 259 | h_km1 = 1.0 / (points[1][0]-points[0][0]); 260 | h_kp1 = 1.0 / (points[3][0]-points[2][0]); 261 | 262 | // check for NaNs 263 | if (h_k != h_k) 264 | h_k = 0; 265 | if (h_km1 != h_km1) 266 | h_km1 = 0; 267 | if (h_kp1 != h_kp1) 268 | h_kp1 = 0; 269 | 270 | // request size once before loop as opposed to push_back() in each iteration to be fast 271 | spline.resize(points[0].size()-1); 272 | for (int i = 1; i < points[0].size(); ++i) 273 | { 274 | delta_k = (points[2][i]-points[1][i]) * h_k; 275 | delta_km1 = (points[1][i]-points[0][i]) * h_km1; 276 | delta_kp1 = (points[3][i]-points[2][i]) * h_kp1; 277 | 278 | // if there is a change in sign between segment slopes or either slope is zero, make the boundary point a local min/max by setting the endpoint slope to 0, 279 | // otherwise the slopes at the points are the geometric mean of the linear slopes of the surrounding segments 280 | if (delta_k == 0 || delta_km1 == 0 || (delta_k < 0 && delta_km1 > 0) || (delta_k > 0 && delta_km1 < 0)) 281 | d_k = 0; 282 | else 283 | d_k = 2.0 / (1.0/delta_km1 + 1.0/delta_k); 284 | 285 | if (delta_kp1 == 0 || delta_k == 0 || (delta_kp1 < 0 && delta_k > 0) || (delta_kp1 > 0 && delta_k < 0)) 286 | d_kp1 = 0; 287 | else 288 | d_kp1 = 2.0 / (1.0/delta_k + 1.0/delta_kp1); 289 | 290 | a0 = points[1][i]; 291 | a1 = d_k; 292 | a2 = (3.0*delta_k - 2.0*d_k - d_kp1) * h_k; 293 | a3 = (d_k - 2.0*delta_k + d_kp1) * (h_k * h_k); 294 | 295 | spline[i-1] = Polynomial({{a0,a1,a2,a3},{0,1,2,3}}); 296 | } 297 | } 298 | 299 | 300 | template 301 | CubicParametricSpline::CubicParametricSpline(const std::vector& p0, 302 | const std::vector& p1, 303 | const std::vector& p2, 304 | const std::vector& p3) 305 | { 306 | points = {p0, p1, p2, p3}; 307 | calc(); 308 | } 309 | 310 | template 311 | void CubicParametricSpline::calc() 312 | { 313 | T m0, m1, m2; // linear slopes of surrounding segments 314 | T t1, t2; // spline slopes at segment entpoints 315 | T a0, a1, a2, a3; // cubic polynomial coefficients 316 | 317 | spline.resize(points[0].size()); 318 | for (int i = 0; i < points[0].size(); ++i) 319 | { 320 | // compute segment slopes 321 | m2 = (points[3][i]-points[2][i]); 322 | m1 = (points[2][i]-points[1][i]); 323 | m0 = (points[1][i]-points[0][i]); 324 | 325 | // compute interp slopes at segment boundaries by the arithmetic mean of surrounding segment slopes 326 | t1 = 0.5*(m0+m1); 327 | t2 = 0.5*(m1+m2); 328 | 329 | a0 = points[1][i]; 330 | a1 = t1; 331 | a2 = (3.0*m1 - 2.0*t1 - t2); 332 | a3 = (t1 + t2 - 2.0*m1); 333 | 334 | spline[i] = Polynomial({{a0,a1,a2,a3},{0,1,2,3}}); 335 | } 336 | } 337 | 338 | 339 | template 340 | LinearFunctionalSpline::LinearFunctionalSpline(const std::vector& p0, 341 | const std::vector& p1) 342 | { 343 | points = {p0, p1}; 344 | calc(); 345 | } 346 | 347 | template 348 | void LinearFunctionalSpline::calc() 349 | { 350 | // polynomial coefficients 351 | T m; // slope for each dimension between the two points 352 | T b; // the b in y = mx + b 353 | // first dim distance between points 354 | T dx = 1.0 / (points[1][0]-points[0][0]); 355 | spline.resize(points[0].size()-1); 356 | for (int i = 1; i < points[0].size(); ++i) { 357 | m = (points[1][i]-points[0][i]) * dx; 358 | b = points[1][i] - m*points[1][0]; 359 | spline[i-1] = Polynomial({{b,m},{0,1}}); 360 | } 361 | } 362 | 363 | 364 | template 365 | LinearParametricSpline::LinearParametricSpline(const std::vector& p0, 366 | const std::vector& p1) 367 | { 368 | points = {p0, p1}; 369 | calc(); 370 | } 371 | 372 | template 373 | void LinearParametricSpline::calc() 374 | { 375 | // polynomial coefficients 376 | T m; // slope for each dimension between the two points 377 | T b; // the b in y = mx + b 378 | spline.resize(points[0].size()); 379 | for (int i = 0; i < points[0].size(); i++) { 380 | m = (points[1][i]-points[0][i]); 381 | b = points[0][i]; 382 | spline[i] = Polynomial({{b,m},{0,1}}); 383 | } 384 | } 385 | 386 | 387 | #endif /* defined __PolynomialSpline_h__ */ 388 | -------------------------------------------------------------------------------- /PrintToFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // PrintToFile.h 3 | // ThreeDAudio 4 | // 5 | // Created by Andrew Barker on 9/7/16. 6 | // 7 | // 8 | 9 | #ifndef PrintToFile_h 10 | #define PrintToFile_h 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define NAMEANDVALUE(var) std::string( #var ) + ": " + std::to_string( (var) ) 18 | #define NAMEANDVALUESTR(var) std::string( #var ) + ": " + var 19 | enum class PrintToFileMode { OVERWRITE, APPEND }; 20 | // ex: debug({"bob", std::to_string(3.5f), ...}); 21 | 22 | //static void debug(const std::string& text, 23 | // const PrintToFileMode mode = PrintToFileMode::OVERWRITE, // append if false 24 | // const std::string& fileName = "/Users/AndrewBarker/Desktop/debug.txt") 25 | //{ 26 | // std::stringstream tmp; 27 | // if (mode == PrintToFileMode::APPEND) { 28 | // std::ofstream f (fileName, std::ios::in); 29 | // tmp << f.rdbuf(); 30 | // } 31 | // std::ofstream f (fileName); 32 | // if (mode == PrintToFileMode::APPEND) 33 | // f << tmp.rdbuf(); 34 | // f << text << std::endl; 35 | //} 36 | 37 | static void debug(const std::vector& textByLine, 38 | const PrintToFileMode mode = PrintToFileMode::OVERWRITE, // append if false 39 | const std::string& fileName = "/Users/AndrewBarker/Desktop/debug.txt") 40 | { 41 | std::stringstream tmp; 42 | if (mode == PrintToFileMode::APPEND) { 43 | std::ofstream f (fileName, std::ios::in); 44 | tmp << f.rdbuf(); 45 | //f.close(); 46 | } 47 | std::ofstream f (fileName); 48 | if (mode == PrintToFileMode::APPEND) 49 | f << tmp.rdbuf(); 50 | for (const auto& l : textByLine) 51 | f << l << std::endl; 52 | //f.close(); 53 | } 54 | 55 | //// single line of text version 56 | //static void debug(const std::string& text, 57 | // const PrintToFileMode mode = PrintToFileMode::OVERWRITE, // append if false 58 | // const std::string& fileName = "/Users/AndrewBarker/Desktop/debug.txt") 59 | //{ 60 | // debug({text}, mode, fileName); 61 | //} 62 | 63 | //template 64 | //static void debug(const Args&... args, 65 | // const std::string& fileName = "Debugger.txt", 66 | // const bool overwrite = true) // append if false 67 | //{ 68 | // std::ofstream f (fileName); 69 | // //debug(args..., f); 70 | // f.close(); 71 | //} 72 | // 73 | ////template 74 | ////static void debug(const Arg& arg, 75 | //// const Args&... args, 76 | //// const std::ofstream& f) 77 | ////{ 78 | //// f << std::to_string(arg) << std::endl; 79 | //// debug(args..., f); 80 | ////} 81 | // 82 | //template 83 | //static void bobfunc(std::ofstream& f, Arg& arg) 84 | //{ 85 | // f << std::to_string(arg) << std::endl; 86 | //} 87 | // 88 | //template 89 | //static void bobfunc(std::ofstream& f, Arg& arg, Args&... args) 90 | //{ 91 | // bobfunc(f, arg, args...); 92 | //} 93 | // 94 | //template 95 | //static void bobfunc(Arg& arg, Args&... args, const std::string& fileName = "Debugger.txt") 96 | //{ 97 | // std::ofstream f (fileName); 98 | // //arg = -1; 99 | // bobfunc(f, arg, args...); 100 | // //bobfunc(args...); 101 | // f.close(); 102 | //} 103 | 104 | 105 | #endif /* PrintToFile_h */ 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3DAudio 2 | An audio effects plugin that simulates moving surround sound audio over headphones. 3 | 4 | To compile this code you will also need the JUCE library(www.juce.com). I have most recently built this with JUCE 5.4.3 (and VST SDK 3.6.12) on Mac and JUCE 4.3.0 (with VST3 SDK 3.6.0) on Windows. Once you have JUCE installed, you can use the Introjucer/Projucer to set up an audio plugin application project and copy all these files into it. From there you will be able to configure Xcode/Visual Studio projects or Linux makefiles to compile on whatever platform you have. With JUCE, you can compile the code into a variety of plugin formats: Audio Unit, VST, VST3, RTAS, or AAX. In order to use the plugin to process audio you will need to have the binary data file that contains all the spatial impulse responses. The data file can be obtained by purchasing a copy of the software from http://freedomaudioplugins.com. 5 | -------------------------------------------------------------------------------- /Resampler.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Resampler.cpp 3 | // 4 | // Created by Andrew Barker on 9/30/14. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #include "Resampler.h" 28 | 29 | Resampler::Resampler(double new_fs_in, int new_N_in_out, double new_fs_out, bool direction) noexcept 30 | : fs_in(new_fs_in), fs_out(new_fs_out), dir(direction) 31 | { 32 | if (dir) { 33 | // 2nd param is N_in for the resampler 34 | N_in = new_N_in_out; 35 | N_out = fs_out * ((double)N_in) / fs_in; 36 | } else { 37 | // 2nd param is N_out for the unsampler 38 | N_out = new_N_in_out; 39 | N_in = fs_in * N_out / fs_out; 40 | N_in += 1; 41 | } 42 | } 43 | 44 | // linearly resamples N_in samples in x at fs_in and spits out ceil(N_out) samples in y at fs_out 45 | void Resampler::resampleLinear(const float* x, float *y) noexcept 46 | { 47 | int Nout = N_out;//ceil(N_out); 48 | const double this_fs_in = fs_in; 49 | const double this_fs_out = fs_out; 50 | const double Ts_in = 1.0 / this_fs_in; 51 | const double Ts_out = 1.0 / this_fs_out; 52 | const double this_offset = offset; 53 | const double thisPrevSample = prevSample; 54 | if (((double)Nout) * Ts_out + this_offset >= ((double)N_in) * Ts_in) { 55 | y[Nout] = 0; // set last output sample to 0 since it is not technically in this output buffer 56 | shortBuffer = true; 57 | } else { 58 | Nout += 1; 59 | shortBuffer = false; 60 | } 61 | const int thisNout = Nout; 62 | int k_low; 63 | float x_low, x_high; 64 | double t_out, t_in; 65 | for (int n = 0; n < thisNout; ++n) { 66 | t_out = ((double)n) * Ts_out + this_offset; 67 | k_low = t_out * this_fs_in; 68 | if (k_low == 0) { 69 | x_low = thisPrevSample; 70 | } else { 71 | x_low = x[k_low-1]; 72 | } 73 | x_high = x[k_low]; 74 | t_in = ((double)k_low) * Ts_in; 75 | // y = b + m * x 76 | y[n] = x_low + ((x_high - x_low) * this_fs_in) * (t_out - t_in); 77 | } 78 | offset = (((double)thisNout) * Ts_out + this_offset) - ((double)N_in) * Ts_in; 79 | prevSample = x[N_in-1]; 80 | } 81 | 82 | void Resampler::unsampleLinear(const float* x, const int Nin, float* y) noexcept 83 | { 84 | const double this_fs_in = fs_in; 85 | const double this_fs_out = fs_out; 86 | const double Ts_in = 1.0 / this_fs_in; 87 | const double Ts_out = 1.0 / this_fs_out; 88 | const double this_offset = offset; 89 | const double thisPrevSample = prevSample; 90 | const int thisNout = N_out; 91 | int k_low; 92 | float x_low, x_high; 93 | double t_out, t_in; 94 | for (int n = 0; n < thisNout; ++n) { 95 | t_out = ((double)n) * Ts_out + this_offset; 96 | k_low = t_out * this_fs_in; 97 | if (k_low == 0) { 98 | x_low = thisPrevSample; 99 | } else { 100 | x_low = x[k_low-1]; 101 | } 102 | x_high = x[k_low]; 103 | t_in = ((double)k_low) * Ts_in; 104 | // y = b + m * x 105 | y[n] = x_low + (x_high - x_low) * this_fs_in * (t_out - t_in); 106 | } 107 | offset = (((double)thisNout) * Ts_out + this_offset) - ((double)Nin) * Ts_in; 108 | prevSample = x[Nin-1]; 109 | } 110 | 111 | int Resampler::getNout() const noexcept 112 | { 113 | if (dir) { 114 | // resampling, resamplers have variable buffer size due to the fractional nature of fs_in/fs_out 115 | if (shortBuffer) { 116 | return N_out; 117 | } else { 118 | return N_out + 1; 119 | } 120 | } else { 121 | // unsampling, unsamplers should always output the same number of samples per buffer as was initially fed to the associated resampler 122 | return N_out; 123 | } 124 | } 125 | 126 | // returns the maximum size of the output buffer (used for allocating the output buffer's memory) 127 | int Resampler::getNoutMax() const noexcept 128 | { 129 | if (dir) 130 | return N_out + 1; 131 | else 132 | return N_out; 133 | } 134 | 135 | //int Resampler::getNumSamplesLatency() 136 | //{ 137 | // if (dir) 138 | // return 1; 139 | // else 140 | // return 0; 141 | //} 142 | 143 | //#include 144 | //// default construtor 145 | //Resampler::Resampler() 146 | //{ 147 | //} 148 | // 149 | //// the useful construtor 150 | //Resampler::Resampler(double new_fs_in, int new_N_in_out, double new_fs_out, int numBuffersDelay, int direction) 151 | //{ 152 | // fs_in = new_fs_in; 153 | // fs_out = new_fs_out; 154 | // dir = direction; 155 | // if (dir == 0) { 156 | // // 2nd param is N_in for the resampler 157 | // N_in = new_N_in_out; 158 | // N_out = fs_out*((double)N_in)/fs_in; 159 | // } else { 160 | // // 2nd param is N_out for the unsampler 161 | // N_out = new_N_in_out; 162 | // N_in = ceil( fs_in*N_out/fs_out ); 163 | // } 164 | // quality = numBuffersDelay; 165 | // 166 | // // allocate and zero init array of previous input buffers 167 | // inputs = new float* [2*quality+1]; 168 | // for (int i = 0; i < 2*quality+1; i++) { 169 | // inputs[i] = new float[N_in]; 170 | // for (int j = 0; j < N_in; j++) { 171 | // inputs[i][j] = 0; 172 | // } 173 | // } 174 | // 175 | // if (dir == 0) { 176 | // // resampler just needs to keep track if the one buffer length currently being output 177 | // shortBuffer[0] = false; 178 | // } else { 179 | // delete[] shortBuffer; 180 | // // unsampler needs to know if nearby buffers to the one currently being processed are short/normal length 181 | // shortBuffer = new bool[2*quality+1]; 182 | // for (int i = 0; i < 2*quality+1; i++) { 183 | // shortBuffer[i] = false; 184 | // } 185 | // } 186 | // 187 | // inited = true; 188 | //} 189 | // 190 | //// destructor 191 | //Resampler::~Resampler() 192 | //{ 193 | //} 194 | // 195 | //// linearly resamples N_in samples in x at fs_in and spits out ceil(N_out) samples in y at fs_out 196 | //void Resampler::resampleLinear(float *x, float *y) 197 | //{ 198 | // if (inited) { 199 | // // shift the prevs inputs 200 | // for (int i = 2*quality; i >= 1; i--) { 201 | // // not sure if we can get away with this... 202 | // //inputs[i] = inputs[i-1]; 203 | // for (int j = 0; j < N_in; j++) { 204 | // inputs[i][j] = inputs[i-1][j]; 205 | // } 206 | // } 207 | // // ... and add the current input 208 | // for (int n = 0; n < N_in; n++) { 209 | // inputs[0][n] = x[n]; 210 | // } 211 | // 212 | // // number of output samples for y 213 | // int Nout = ceil(N_out); 214 | // 215 | // if (numBuffersProcessed >= quality) { 216 | // // in this case we have passed the required processing latency so we can compute samples 217 | // 218 | // // last sample in this output buffer will be in the next input buffer's time region so this current output buffer needs to be one sample short to correct this, otherwise the time frame of our output buffers will eventually (perhaps very relatively soon) drift into future time that we don't have input samples for yet (due to the sample rate ratio being fractional in nature and hince the input/output buffer size ratio need be fractional so some output buffers are Nout-1 in length rather than Nout). 219 | // // NOTE: this should never be true for the unsampler (dir = 1), if so we have problems 220 | // // ALSO NOTE: the unsampler's frame will potentially not pass the negative version of this test and the offset (negative) magnitude will be more than one output sample period, but that is ok as Nin changes and is specified for this buffer by being passed in as a parameter 221 | // if (((double)(Nout-1))/fs_out + offset >= ((double)N_in)/fs_in) { 222 | // // set last output sample to 0 since it is not technically in this output buffer 223 | // y[Nout-1] = 0; 224 | // // don't compute the last output sample in the for loop below 225 | // Nout -= 1; 226 | // shortBuffer[0] = true; 227 | // } else { 228 | // shortBuffer[0] = false; 229 | // } 230 | // 231 | // // sample indecies of input buffer samples that boarder each output sample 232 | // int k_low, k_high; 233 | // // current time of each output/input sample, relative time for sinc function displacement 234 | // double t_out, t_in; 235 | // // localized slope for linear interpolation 236 | // double m; 237 | // // more variables, for indexing into inputs array 238 | // int i_low, i_high, j_low, j_high; 239 | // 240 | // for (int n = 0; n < Nout; n++) { 241 | // // compute shit 242 | // t_out = ((double)n)/fs_out + offset; 243 | // k_low = floor(t_out*fs_in); 244 | // k_high = k_low + 1; 245 | // t_in = ((double)k_low)/fs_in; 246 | // 247 | // // i indexes into which prev buffer we're needing, set it to the middle and adjust 248 | // i_low = quality; 249 | // i_high = quality; 250 | // // j is the indexing variable for the samples, so put it in range [0, N_in-1] 251 | // j_low = k_low; 252 | // j_high = k_high; 253 | // // adjust to proper indexing 254 | // while (j_low < 0) { 255 | // j_low += N_in; 256 | // i_low ++; 257 | // } 258 | // while (j_low >= N_in) { 259 | // j_low -= N_in; 260 | // i_low --; 261 | // } 262 | // while (j_high < 0) { 263 | // j_high += N_in; 264 | // i_high ++; 265 | // } 266 | // while (j_high >= N_in) { 267 | // j_high -= N_in; 268 | // i_high --; 269 | // } 270 | // 271 | // // y = mx + b 272 | // m = (inputs[i_high][j_high] - inputs[i_low][j_low]) * fs_in; 273 | // y[n] = inputs[i_low][j_low] + m*(t_out - t_in); 274 | // } 275 | // 276 | // offset = (((double)Nout)/fs_out + offset) - ((double)N_in)/fs_in; 277 | // 278 | // } else { 279 | // // just return zero samples here as processing cannot be done yet 280 | // for (int n = 0; n < Nout; n++) { 281 | // y[n] = 0; 282 | // } 283 | // } 284 | // 285 | // if (numBuffersProcessed < 2*quality+1) { 286 | // // no need to keep track of the number of buffers processed once it is past this because the resampler will be fully loaded with all relevent previous input buffers, and we probably don't want to overflow numBuffersProcessed if we're doing lots of resample() calls in a row 287 | // numBuffersProcessed++; 288 | // } 289 | // } // end if inited 290 | //} 291 | // 292 | //void Resampler::unsampleLinear(float *x, int Nin, float *y) 293 | //{ 294 | // if (inited) { 295 | // // shift the prevs inputs and buffer length states 296 | // for (int i = 2*quality; i >= 1; i--) { 297 | // //inputs[i] = inputs[i-1]; 298 | // for (int j = 0; j < N_in; j++) { 299 | // inputs[i][j] = inputs[i-1][j]; 300 | // } 301 | // shortBuffer[i] = shortBuffer[i-1]; 302 | // } 303 | // // ... and add the current input 304 | // for (int n = 0; n < Nin; n++) { 305 | // inputs[0][n] = x[n]; 306 | // } 307 | // // mark the new input buffer as short/normal length 308 | // if (Nin < N_in) { 309 | // shortBuffer[0] = true; 310 | // // fill potentially remaining sample with zeros (for when there is a short buffer) 311 | // for (int n = Nin; n < N_in; n++) { 312 | // inputs[0][n] = 0; 313 | // } 314 | // } else { 315 | // shortBuffer[0] = false; 316 | // } 317 | // 318 | // // number of output samples for y 319 | // int Nout = N_out; 320 | // 321 | // if (numBuffersProcessed >= quality) { 322 | // // in this case we have passed the required processing latency so we can compute samples 323 | // 324 | // // sample indecies of input buffer samples that boarder each output sample 325 | // int k_low, k_high; 326 | // // current time of each output/input sample, relative time for sinc function displacement 327 | // double t_out, t_in; 328 | // // localized slope for linear interpolation 329 | // double m; 330 | // // more variables, for indexing into inputs array 331 | // int i_low, i_high, j_low, j_high; 332 | // 333 | // for (int n = 0; n < Nout; n++) { 334 | // // compute shit 335 | // t_out = ((double)n)/fs_out + offset; 336 | // k_low = floor(t_out*fs_in); 337 | // k_high = k_low + 1; 338 | // t_in = ((double)k_low)/fs_in; 339 | // 340 | // // i indexes into which prev buffer we're needing, set it to the middle and adjust 341 | // i_low = quality; 342 | // i_high = quality; 343 | // // j is the indexing variable for the samples, so put it in range [0, Nin-1] 344 | // j_low = k_low; 345 | // j_high = k_high; 346 | // // adjust to proper indexing 347 | // while (j_low < 0) { 348 | // i_low++; 349 | // shortBuffer[i_low] ? (j_low += N_in-1) : (j_low += N_in); 350 | // } 351 | // while (shortBuffer[i_low] ? (j_low >= N_in-1) : (j_low >= N_in)) { 352 | // shortBuffer[i_low] ? (j_low -= N_in-1) : (j_low -= N_in); 353 | // i_low--; 354 | // } 355 | // while (j_high < 0) { 356 | // i_high++; 357 | // shortBuffer[i_high] ? (j_high += N_in-1) : (j_high += N_in); 358 | // } 359 | // while (shortBuffer[i_high] ? (j_high >= N_in-1) : (j_high >= N_in)) { 360 | // shortBuffer[i_high] ? (j_high -= N_in-1) : (j_high -= N_in); 361 | // i_high--; 362 | // } 363 | // 364 | // // y = mx + b 365 | // m = (inputs[i_high][j_high] - inputs[i_low][j_low]) * fs_in; 366 | // y[n] = inputs[i_low][j_low] + m*(t_out - t_in); 367 | // } 368 | // 369 | // // need to update offset after processing the buffer 370 | // if (shortBuffer[quality]) { 371 | // offset = (((double)Nout)/fs_out + offset) - ((double)(N_in-1))/fs_in; 372 | // } else { 373 | // offset = (((double)Nout)/fs_out + offset) - ((double)N_in)/fs_in; 374 | // } 375 | // 376 | // } else { 377 | // // just return zero samples here as processing cannot be done yet 378 | // for (int n = 0; n < Nout; n++) { 379 | // y[n] = 0; 380 | // } 381 | // } 382 | // 383 | // if (numBuffersProcessed < 2*quality+1) { 384 | // // no need to keep track of the number of buffers processed once it is past this because the resampler will be fully loaded with all relevent previous input buffers, and we probably don't want to overflow numBuffersProcessed if we're doing lots of resample() calls in a row 385 | // numBuffersProcessed++; 386 | // } 387 | // } // end if inited 388 | //} 389 | // 390 | //double Resampler::getFsIn() 391 | //{ 392 | // return fs_in; 393 | //} 394 | // 395 | //double Resampler::getFsOut() 396 | //{ 397 | // return fs_out; 398 | //} 399 | // 400 | //int Resampler::getNin() 401 | //{ 402 | // return N_in; 403 | //} 404 | // 405 | //int Resampler::getNout() 406 | //{ 407 | // if (dir == 0) { 408 | // // resampling, resamplers have variable buffer size due to the fractional nature of fs_in/fs_out 409 | // if (shortBuffer[0]) { 410 | // return floor(N_out); 411 | // } else { 412 | // return ceil(N_out); 413 | // } 414 | // } else { 415 | // // unsampling, unsamplers should always output the same number of samples per buffer as was initially fed to the associated resampler 416 | // return ((int)N_out); 417 | // } 418 | //} 419 | // 420 | //// returns the maximum size of the output buffer (used for allocating the output buffer's memory) 421 | //int Resampler::getNoutMax() 422 | //{ 423 | // if (dir == 0) { 424 | // return ceil(N_out); 425 | // } else { 426 | // return ((int)N_out); 427 | // } 428 | //} 429 | // 430 | //int Resampler::getNumBuffersLatency() 431 | //{ 432 | // return quality; 433 | //} 434 | 435 | //// not working... resamples N_in samples in x at fs_in and spits out floor(N_out) samples in y at fs_out 436 | //void Resampler::resample(float *x, float *y) 437 | //{ 438 | // if (inited) { 439 | // // shift the prevs inputs 440 | // for (int i = 2*quality; i >= 1; i--) { 441 | // // not sure if we can get away with this... 442 | // inputs[i] = inputs[i-1]; 443 | // // for (int j = 0; N_in; j++) { 444 | // // inputs[i][j] = inputs[i-1][j]; 445 | // // } 446 | // } 447 | // // ... and add the current input 448 | // for (int n = 0; n < N_in; n++) { 449 | // inputs[0][n] = x[n]; 450 | // } 451 | // 452 | // // number of output samples for y 453 | // int Nout; 454 | // if (dir == 0) { 455 | // // resampling 456 | // Nout = ceil(N_out); 457 | // } else { 458 | // // unsampling 459 | // Nout = floor(N_out); 460 | // } 461 | // 462 | // if (numBuffersProcessed >= quality) { 463 | // // in this case we have passed the required processing latency so we can compute samples 464 | // 465 | // // sample indecies of input buffer samples that boarder each output sample 466 | // int k_low, k_high; 467 | // // begining and ending indicies of input buffer for the summation when computing a particular output sample 468 | // int k_begin, k_end; 469 | // // current time of each output/input sample, relative time for sinc function displacement 470 | // double t_out, t_in, t; 471 | // // the minimum of the two sample rates that determines the cuttoff for the low pass filter 472 | // double fs_min = fmin(fs_in, fs_out); 473 | // // a thing 474 | // double thing; 475 | // // more variables, for indexing into inputs array 476 | // int i, j; 477 | // 478 | // for (int n = 0; n < Nout; n++) { 479 | // // zero each output sample before summing 480 | // y[n] = 0; 481 | // 482 | // // compute shit 483 | // t_out = ((double)n)/fs_out + offset; 484 | // k_low = floor(t_out*fs_in); 485 | // k_high = k_low + 1; 486 | // // // we will get a seg fault later if this is not fixed... 487 | // // if (k_high > N_in-1) { 488 | // // k_high = N_in-1; 489 | // // } 490 | // k_begin = k_low - 30;//quality*N_in; 491 | // k_end = k_high + 30;//quality*N_in; 492 | // 493 | // for (int k = k_begin; k <= k_end; k++) { 494 | // t_in = ((double)k)/fs_in; 495 | // t = t_in - t_out; 496 | // thing = M_PI*fs_min*t; 497 | // 498 | // // i indexes into which prev buffer we're needing, set it to the middle and adjust 499 | // i = quality; 500 | // // j is the indexing variable for the samples, so put it in range [0, N_in-1] 501 | // j = k; 502 | // while (j < 0) { 503 | // j += N_in; 504 | // i++; 505 | // } 506 | // while (j >= N_in) { 507 | // j -= N_in; 508 | // i--; 509 | // } 510 | // 511 | // if (thing == 0) { 512 | // // divide by zero, bad (sinc is just 1 here anyways) 513 | // y[n] += inputs[i][j]; 514 | // } else { 515 | // // NOTE: this is lacking an explicit window function (so rectangular window essentially) 516 | // // otherwise it is safe to evaluate the sinc function 517 | // y[n] += sin(thing)/thing * inputs[i][j]; 518 | // } 519 | // } 520 | // } 521 | // // need to update offset after processing the buffer 522 | // //offset = 1.0/fs_out - (((double)N_in)/fs_in - t_out); 523 | // //offset += 1.0/fs_out * (1.0-(N_out-Nout)); 524 | // offset += 1.0/fs_out * (((double)Nout)-N_out); 525 | // 526 | // while (offset >= 1.0/fs_out) { 527 | // offset -= 1.0/fs_out; 528 | // } 529 | // while (offset <= -1.0/fs_out) { 530 | // offset += 1.0/fs_out; 531 | // } 532 | // 533 | // } else { 534 | // // just return zero samples here as processing cannot be done yet 535 | // for (int n = 0; n < Nout; n++) { 536 | // y[n] = 0; 537 | // } 538 | // } 539 | // 540 | // if (numBuffersProcessed < 2*quality+1) { 541 | // // no need to keep track of the number of buffers processed once it is past this because the resampler will be fully loaded with all relevent previous input buffers, and we probably don't want to overflow numBuffersProcessed if we're doing lots of resample() calls in a row 542 | // numBuffersProcessed++; 543 | // } 544 | // } // end if inited 545 | //} -------------------------------------------------------------------------------- /Resampler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Resampler.h 3 | // 4 | // Created by Andrew Barker on 9/30/14. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef __Resampler__ 28 | #define __Resampler__ 29 | 30 | // NOTE: this whole setup assumes that 1/fs_out !> N_in / fs_in. that is that there is not less than one sample per output buffer on average. 31 | class Resampler 32 | { 33 | public: 34 | Resampler() noexcept {}; 35 | Resampler(double new_fs_in, int new_N_in, double new_fs_out, bool direction) noexcept; 36 | ~Resampler() {}; 37 | void resampleLinear(const float* x, float* y) noexcept; 38 | void unsampleLinear(const float* x, int Nin, float* y) noexcept; 39 | int getNout() const noexcept; 40 | int getNoutMax() const noexcept ; 41 | //int getNumSamplesLatency(); 42 | private: 43 | // input sample rate 44 | double fs_in = 44100; 45 | // output sample rate 46 | double fs_out = 44100; 47 | // length of input buffer 48 | int N_in = 0; 49 | // length of output buffer (fraction due to sample rate change, however resample only outputs floor(N_out) samples per input buffer and the appropriate time shifting is taken care of with the computation of those samples) 50 | double N_out = 0; 51 | // direction of resampling: true = resample, false = unsample 52 | bool dir = true; 53 | // last sample of the previous input 54 | double prevSample = 0; 55 | // the time offset of the first resampled sample from the actual begin time of the input buffer, due to the fact that N_out is fractional in nature 56 | double offset = 0; 57 | // state variable(s) to indicate when we are outputing the occasional "short" buffer from the resampler and when we are indexing into "short" buffers when unsampling 58 | bool shortBuffer = false; 59 | }; 60 | 61 | ////#include 62 | // 63 | //// NOTE: this whole setup assumes that 1/fs_out !> N_in / fs_in. that is that there is not less than one sample per output buffer on average. 64 | //class Resampler 65 | //{ 66 | //public: 67 | // // constructors / destructor 68 | // Resampler(); 69 | // Resampler(double new_fs_in, int new_N_in, double new_fs_out, int numBuffersDelay, int direction); 70 | // ~Resampler(); 71 | // 72 | // // the processing routines 73 | // //void resample(float *x, float *y); // resampling w/ sincs, not working, not nessesary 74 | // void resampleLinear(float *x, float *y); 75 | // void unsampleLinear(float *x, int Nin, float *y); 76 | // 77 | // // getters / setters 78 | // double getFsIn(); 79 | // double getFsOut(); 80 | // int getNin(); 81 | // int getNout(); 82 | // int getNoutMax(); 83 | // int getNumBuffersLatency(); 84 | // 85 | //private: 86 | // 87 | // // are we initialized yet? 88 | // bool inited = false; 89 | // // input sample rate 90 | // double fs_in = 44100; 91 | // // output sample rate 92 | // double fs_out = 44100; 93 | // // length of input buffer 94 | // int N_in = 0; 95 | // // length of output buffer (fraction due to sample rate change, however resample only outputs floor(N_out) samples per input buffer and the appropriate time shifting is taken care of with the computation of those samples) 96 | // double N_out = 0; 97 | // // determines the resampling quality, specifically by setting the processing delay in number of buffers 98 | // int quality = 0; 99 | // // the number of buffers processed since this resampler was created/reset 100 | // int numBuffersProcessed = 0; 101 | // // direction of resampling: 0 = resample, else = undo 102 | // int dir = 0; 103 | // // inputs needed to compute one output buffer at a given resampling quality/latency 104 | // float **inputs; 105 | // // the time offset of the first resampled sample from the actual begin time of the input buffer, due to the fact that N_out is fractional in nature 106 | // double offset = 0; 107 | // // state variable(s) to indicate when we are outputing the occasional "short" buffer from the resampler and when we are indexing into "short" buffers when unsampling 108 | // bool *shortBuffer = new bool[1]; 109 | //}; 110 | 111 | #endif /* defined(__Resampler__) */ 112 | -------------------------------------------------------------------------------- /SelectionBox.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionBox.cpp 3 | // 4 | // Created by Andrew Barker on 9/20/16. 5 | // 6 | // 7 | 8 | #include "SelectionBox.h" 9 | #include "OpenGL.h" 10 | 11 | SelectionBox::SelectionBox() noexcept 12 | : animation(0.5f) 13 | { 14 | } 15 | 16 | SelectionBox::SelectionBox(const float top, 17 | const float bottom, 18 | const float left, 19 | const float right) noexcept 20 | : Box(top, bottom, left, right), 21 | animation(0.5f) 22 | { 23 | } 24 | 25 | const Animation& SelectionBox::getAnimation() const noexcept 26 | { 27 | return animation; 28 | } 29 | 30 | Animation& SelectionBox::getAnimation() noexcept 31 | { 32 | return animation; 33 | } 34 | //void SelectionBox::setAnimation(const Animation& newAnimation) noexcept 35 | //{ 36 | // animation = newAnimation; 37 | //} 38 | 39 | bool SelectionBox::isActive() const noexcept 40 | { 41 | return active; 42 | } 43 | 44 | void SelectionBox::setActive(const bool newActive) noexcept 45 | { 46 | active = newActive; 47 | } 48 | 49 | 50 | 51 | void draw (SelectionBox& box, 52 | const OpenGLWindow& window) 53 | { 54 | Animation& animation = box.getAnimation(); 55 | const bool active = box.isActive(); 56 | if (active || animation.isPlaying()) { 57 | const float alphaFactor = active ? 1 : 1 - animation.getProgress(); 58 | animation.advance(window.frameRate); 59 | glColor4f(1, 1, 1, 1 * alphaFactor); 60 | box.drawOutline();// drawOutline(box); 61 | glColor4f(0.6f, 0.6f, 0.6f, 0.2f * alphaFactor); 62 | box.drawFill();// drawFill(box); 63 | } 64 | } 65 | 66 | void mouseDragged (SelectionBox& box, 67 | const Point mouseDown, 68 | const Point mouseCurrent) noexcept 69 | { 70 | box = {mouseCurrent.y, mouseDown.y, mouseCurrent.x, mouseDown.x}; 71 | box.setActive(true); 72 | } 73 | 74 | //bool isDragging (const SelectionBox& box) noexcept 75 | //{ 76 | // return area(box) != 0 && box.getAnimation().getProgress() == 0; 77 | //} 78 | 79 | -------------------------------------------------------------------------------- /SelectionBox.h: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionBox.h 3 | // 4 | // Created by Andrew Barker on 9/20/16. 5 | // 6 | // 7 | 8 | #ifndef SelectionBox_h 9 | #define SelectionBox_h 10 | 11 | #include "Animation.h" 12 | #include "Box.h" 13 | #include "OpenGLWindow.h" 14 | 15 | class SelectionBox : public Box 16 | { 17 | public: 18 | SelectionBox() noexcept; 19 | SelectionBox(float top, float bottom, float left, float right) noexcept; 20 | 21 | const Animation& getAnimation() const noexcept; 22 | Animation& getAnimation() noexcept; 23 | // void setAnimation(const Animation& newAnimation) noexcept; 24 | 25 | bool isActive() const noexcept; 26 | void setActive(bool active) noexcept; 27 | 28 | private: 29 | bool active; 30 | Animation animation; 31 | }; 32 | 33 | // can SelectionBox be used with contain(Box, Point)? 34 | void draw (SelectionBox& box, 35 | const OpenGLWindow& window); 36 | void mouseDragged (SelectionBox& box, 37 | const Point mouseDown, 38 | const Point mouseCurrent) noexcept; 39 | //bool isDragging (const SelectionBox& box) noexcept; 40 | 41 | #endif /* SelectionBox_h */ 42 | -------------------------------------------------------------------------------- /Spline.h: -------------------------------------------------------------------------------- 1 | // 2 | // Spline.h 3 | // 4 | // Created by Andrew Barker on 9/26/15. 5 | // 6 | // 7 | /* 8 | 3DAudio: simulates surround sound audio for headphones 9 | Copyright (C) 2016 Andrew Barker 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The author can be contacted via email at andrew.barker.12345@gmail.com. 25 | */ 26 | 27 | #ifndef __Spline_h__ 28 | #define __Spline_h__ 29 | 30 | #include 31 | #include 32 | 33 | enum class SplineShape 34 | { 35 | INVALID = -1, 36 | LINEAR, // linearly interpolates 37 | CUBIC, // cubicly interpolates 38 | EMPTY // a non existant spline 39 | }; 40 | 41 | enum class SplineBehavior 42 | { 43 | FUNCTIONAL, 44 | PARAMETRIC 45 | }; 46 | 47 | // the virtual base class to define the interface for an N-dimensional interpolating spline using N (a different N) surrounding points to determine its shape 48 | template 49 | class Spline 50 | { 51 | public: 52 | // for polymorphic copying of splines, default copy constructors should be fine for splines as we have no pointers... 53 | virtual std::unique_ptr> clone() = 0; 54 | virtual ~Spline() {} 55 | // a spline is pretty much an N-dimensional function f(val) = [y,z,a,...] 56 | virtual std::vector pointAt(const T& val) = 0; 57 | //virtual void pointAt(const T& val, T* point) = 0; 58 | virtual void pointAt(const T& val, T** point) const = 0; // need this wackiness to be able to set the external pointer to nullptr for an empty spline, could just check spline type though... 59 | // calc spline from loaded points 60 | virtual void calc() = 0; 61 | // load from new interpolator point range centered about the spline and recalc spline 62 | virtual void calc(const std::vector>& new_points) = 0; 63 | virtual SplineShape getShape() const noexcept = 0; 64 | protected: 65 | // points that are used to construct it 66 | std::vector> points; 67 | }; 68 | 69 | // class to represent a nonexistent spline 70 | template 71 | class EmptySpline : public Spline 72 | { 73 | public: 74 | std::unique_ptr> clone() override { return std::make_unique>(*this); } 75 | // return an empty vector to signal an open spline segment 76 | std::vector pointAt(const T& val) override { return std::vector(); } 77 | //void pointAt(const T& val, T* point) { /*point = nullptr;*/ }; // this don't set the external pointer 78 | void pointAt(const T& val, T** point) const override { *point = nullptr; } // this do 79 | void calc() override {} 80 | void calc(const std::vector>& new_points) override {} 81 | SplineShape getShape() const noexcept override { return SplineShape::EMPTY; } 82 | }; 83 | 84 | // virtual class for recomputing an N-point spline from a set of N points from an interpolator 85 | template 86 | class NPointSpline : public virtual Spline 87 | { 88 | public: 89 | void calc(const std::vector>& new_points) override 90 | { 91 | // example case for N = 4 pts: 92 | //points = {new_points[middle-2], new_points[middle-1], new_points[middle], new_points[middle+1]}; 93 | const int dim = new_points[0].size(); 94 | const int middle = (new_points.size()>>1); 95 | points.resize(N); 96 | int i = -1*(N>>1); 97 | for (int j = 0; j < N; ++j) 98 | { 99 | if (0 <= middle+i && middle+i < new_points.size()) 100 | points[j] = new_points[middle+i]; 101 | else // need to fill in some not yet needed points so that calc() below doesn't have a bad access crash when the interpolator (and spline) only has one specified point 102 | points[j] = std::vector(dim, 0); 103 | ++i; 104 | } 105 | calc(); 106 | } 107 | private: 108 | using Spline::calc; 109 | using Spline::points; 110 | }; 111 | 112 | #endif /* defined __Spline_h__ */ 113 | -------------------------------------------------------------------------------- /StackArray.h: -------------------------------------------------------------------------------- 1 | // 2 | // StackArray.h 3 | // 4 | // Created by Andrew Barker on 9/14/16. 5 | // 6 | // 7 | 8 | #ifndef StackArray_h 9 | #define StackArray_h 10 | 11 | // windows compatible stack array with size determined at runtime 12 | #ifdef WIN32 13 | #define STACK_ARRAY(type, name, size) type *name = static_cast(alloca((size) * sizeof(type))); 14 | #else 15 | #define STACK_ARRAY(type, name, size) type name[size]; 16 | #endif 17 | 18 | #endif /* StackArray_h */ 19 | -------------------------------------------------------------------------------- /StringFunctions.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // StringFunctions.cpp 3 | // 4 | // Created by Andrew Barker on 10/6/16. 5 | // 6 | // 7 | 8 | #include "StringFunctions.h" 9 | 10 | #include 11 | #include 12 | 13 | //std::string StrFuncs::roundedFloatString (const float num, 14 | // const int numDecimalPlaces) noexcept 15 | //{ 16 | // auto str = std::to_string(num); 17 | // if (numDecimalPlaces > 5 || numDecimalPlaces < 0) 18 | // return str; 19 | // const int decimalIndex = (int)(std::find(str.begin(), str.end(), '.') - str.begin()); 20 | // const int truncIndex = numDecimalPlaces > 0 ? decimalIndex + numDecimalPlaces + 2 : decimalIndex + 2; 21 | // str = str.substr(0, truncIndex); 22 | // const auto roundingDigit = std::stoi(str.substr(str.length() - 1)); 23 | // str.pop_back(); 24 | // if (roundingDigit > 4) { // need to round up 25 | // auto digitToRoundIndex = str.length() - 1; 26 | // while (digitToRoundIndex > 0 && (str[digitToRoundIndex] == '9' || str[digitToRoundIndex] == '.')) 27 | // --digitToRoundIndex; 28 | // const auto roundedDigit = std::to_string(std::stoi(str.substr(digitToRoundIndex, 1)) + 1); 29 | // str.replace(str.begin() + digitToRoundIndex, str.begin() + digitToRoundIndex + 1, roundedDigit); 30 | // ++digitToRoundIndex; 31 | // while (digitToRoundIndex < str.length()) { // fill in zeros if need be 32 | // if (str[digitToRoundIndex] != '.') 33 | // str[digitToRoundIndex] = '0'; 34 | // ++digitToRoundIndex; 35 | // } 36 | // } //else // round down, just truncate 37 | // if (numDecimalPlaces == 0) // get rid of decimal point if no decimal places 38 | // str.pop_back(); 39 | // return str; 40 | //} 41 | 42 | std::string StrFuncs::hrMinSecFromSec (cfloat sec, 43 | cint numDecimalPlaces) 44 | { 45 | std::string str; 46 | cint secs = std::floor(sec); 47 | cint mins = secs / 60 % 60; 48 | cint hours = secs / 3600; 49 | cauto secStr = roundedFloatString(std::fmod(sec, 60), numDecimalPlaces); 50 | // std::string secStr = std::to_string(std::fmod(sec, 60)); // 6 decimal places 51 | // // was trying to fix rounding, but said screw it. 52 | //// const int roundIndex = secStr.length() - std::max(6 - numDecimalPlaces, 0); 53 | //// int roundDigit = -1; 54 | //// int digitToRound = -1; 55 | //// try { 56 | //// roundDigit = stoi(secStr.substr(roundIndex, roundIndex + 1)); 57 | //// digitToRound = stoi(secStr.substr(roundIndex - 1, roundIndex)); 58 | //// } catch (...) {} 59 | //// if (roundDigit > 4 && digitToRound > -1) { 60 | //// secStr = secStr.substr(0, roundIndex - 1); 61 | //// secStr += std::to_string(digitToRound + 1).substr(0, 1); 62 | //// } else 63 | //// secStr = secStr.substr(0, roundIndex); 64 | // secStr = secStr.substr(0, secStr.length() 65 | // - std::max(6 - numDecimalPlaces, 0)); 66 | if (hours != 0) 67 | str = std::to_string(hours) + ":" 68 | + std::to_string(mins) + ":" + secStr; 69 | // if (std::fmod(sec, 60) < 10) 70 | // str = std::to_string(hours) + ":" + 71 | // std::to_string(mins) + ":" + 72 | // secStr;//std::sprintf(str, "%d:%.2d:0%.2f", hours, mins, std::fmod(sec, 60)); 73 | // else 74 | // std::sprintf(str, "%d:%.2d:%.2f", hours, mins, std::fmod(sec, 60)); 75 | else if (mins != 0) 76 | str = std::to_string(mins) + ":" + secStr; 77 | // if (std::fmod(sec, 60) < 10) 78 | // std::sprintf(str, "%d:0%.2f", mins, std::fmod(sec, 60)); 79 | // else 80 | // std::sprintf(str, "%d:%.2f", mins, std::fmod(sec, 60)); 81 | else 82 | str = secStr; 83 | //std::sprintf(str, "%.2f", sec); 84 | return str; 85 | } 86 | 87 | // returns the time value in seconds of a HR:MIN::SEC formatted time string 88 | float StrFuncs::secFromHrMinSec (const std::string& hrMinSec) noexcept 89 | { 90 | float seconds = 0; 91 | auto txt = hrMinSec; 92 | std::string hr, min, sec; 93 | int i, count = 0; 94 | while ((i = txt.rfind(':')) >= 0) { 95 | count++; 96 | if (count == 1) { 97 | sec = txt.substr(i + 1, hrMinSec.npos); 98 | min = txt.substr(0, i); 99 | txt = min; 100 | } else if (count == 2) { 101 | min = txt.substr(i + 1, txt.npos); 102 | hr = txt.substr(0, i); 103 | break; 104 | } 105 | } 106 | if (count == 0) 107 | sec = txt; 108 | try { 109 | if (! hr.empty()) 110 | seconds += std::stoi(hr) * 3600; 111 | if (! min.empty()) 112 | seconds += std::stoi(min) * 60; 113 | if (! sec.empty()) 114 | seconds += std::stof(sec); 115 | } catch (...) { seconds = -1; } 116 | return seconds; 117 | } 118 | 119 | int StrFuncs::castToInt(const std::string& str) noexcept 120 | { 121 | int i = 0; 122 | if (str.empty()) 123 | i = -1; 124 | else { 125 | try { 126 | int k = str.length() - 1; 127 | for (unsigned char c : str) 128 | i += c + 256 * k--; 129 | } catch (...) { i = -2; } 130 | } 131 | return i; 132 | } 133 | -------------------------------------------------------------------------------- /StringFunctions.h: -------------------------------------------------------------------------------- 1 | // 2 | // StringFunctions.h 3 | // 4 | // Created by Andrew Barker on 10/5/16. 5 | // 6 | // 7 | 8 | #ifndef StringFunctions_h 9 | #define StringFunctions_h 10 | 11 | #include "DrewLib.h" 12 | #include 13 | 14 | namespace StrFuncs { 15 | /** returns a string with up to 6 decimals of precision and rounding applied from a float */ 16 | //std::string roundedFloatString(float num, int numDecimalPlaces = 2) noexcept; 17 | /** Takes a floating point type and returns a string that is rounded to a number of decimal places (default is 2). 18 | Negative decimal places translate to rounding to digits left of the decimal ex: -1 rounds to tens, -2 rounds to hundreds, etc. */ 19 | template 20 | std::string roundedFloatString (T num, int numDecimalPlaces = 2) noexcept; 21 | std::string hrMinSecFromSec (float sec, int numDecimalPlaces = 2); 22 | float secFromHrMinSec (const std::string& hrMinSec) noexcept; 23 | int castToInt(const std::string& str) noexcept; 24 | } 25 | 26 | #include 27 | /** Takes a floating point type and returns a string that is rounded to a number of decimal places (default is 2). 28 | Negative decimal places translate to rounding to digits left of the decimal ex: -1 rounds to tens, -2 rounds to hundreds, etc. */ 29 | template 30 | std::string StrFuncs::roundedFloatString (const T num, 31 | const int numDecimalPlaces) noexcept 32 | { 33 | std::stringstream stream; 34 | std::fixed(stream); 35 | const auto precision = (numDecimalPlaces >= 0) ? numDecimalPlaces : 1; // don't want rounding done by stream for numDecimalPlaces < 0 36 | stream.precision(precision); 37 | stream << num; 38 | if (numDecimalPlaces >= 0) 39 | return stream.str(); 40 | else { 41 | auto str = stream.str(); 42 | str.erase(str.end() - 2, str.end()); // get rid of ".(tens)" that was kept to prevent rounding 43 | if ((int)str.length() + numDecimalPlaces - 1 < 0) 44 | str.insert(str.begin(), '0'); // pad front with zero for rounding up to extra digit if need be 45 | if ((int)str.length() + numDecimalPlaces - 1 < 0) 46 | return "0"; // answer will be rounded down to zero 47 | const auto roundingDigitIndex = (int)str.length() + numDecimalPlaces; 48 | const auto roundingDigit = std::stoi(str.substr(roundingDigitIndex, 1)); 49 | auto digitToRoundIndex = roundingDigitIndex - 1; 50 | if (roundingDigit > 4) { // need to round up 51 | while (digitToRoundIndex > 0 && str[digitToRoundIndex] == '9') 52 | --digitToRoundIndex; 53 | const auto roundedDigit = std::to_string(std::stoi(str.substr(digitToRoundIndex, 1)) + 1); 54 | str.replace(str.begin() + digitToRoundIndex, str.begin() + digitToRoundIndex + 1, roundedDigit); 55 | while (++digitToRoundIndex < str.length()) // fill in zeros if need be 56 | str[digitToRoundIndex] = '0'; 57 | } else { // rounding down, just truncate and fill in zeros if need be 58 | while (++digitToRoundIndex < str.length()) 59 | str[digitToRoundIndex] = '0'; 60 | if (std::count(str.begin(), str.end(), '0') == str.length()) 61 | str = "0"; // replace potential full string of zeros with just one 62 | } 63 | return str; 64 | } 65 | } 66 | 67 | #endif /* StringFunctions_h */ 68 | -------------------------------------------------------------------------------- /View2D.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // View2D.cpp 3 | // 4 | // Created by Andrew Barker on 9/21/16. 5 | // 6 | // 7 | 8 | #include "View2D.h" 9 | 10 | #include 11 | 12 | View2D::View2D () noexcept 13 | : _holder (nullptr), 14 | _boundary (), 15 | _width (), 16 | _height (), 17 | _xPosition (), 18 | _yPosition () 19 | {} 20 | 21 | View2D::View2D (Box* holder, 22 | const Box& boundary, 23 | const BoundedValue& width, 24 | const BoundedValue& height, 25 | float xPosition, float xMin, float xMax, 26 | float yPosition, float yMin, float yMax) noexcept 27 | // const BoundedValue& xOffset, 28 | // const BoundedValue& yOffset) noexcept 29 | : _holder (holder), 30 | _boundary (boundary), 31 | _width (width), 32 | _height (height), 33 | _xPosition (xPosition, std::min(xMin, xMax) + width * 0.5f, std::max(xMin, xMax) - width * 0.5f), 34 | _yPosition (yPosition, std::min(yMin, yMax) + height * 0.5f, std::max(yMin, yMax) - height * 0.5f) 35 | //({xOffset, xOffset.getMin() + width * 0.5f, xOffset.getMax() - width * 0.5f}), 36 | //({yOffset, yOffset.getMin() + height * 0.5f, yOffset.getMax() - height * 0.5f}) 37 | {} 38 | 39 | const Box* View2D::getHolder () const noexcept { return _holder; } 40 | void View2D::setHolder (Box* holder) noexcept { _holder = holder; } 41 | 42 | Box View2D::getBoundary () const noexcept { return _boundary; } 43 | void View2D::setBoundary (const Box& boundary) noexcept { _boundary = boundary; } 44 | 45 | BoundedValue View2D::getWidth () const noexcept { return _width; } 46 | void View2D::setWidth (const float width) noexcept { setWidthInternal(width); } 47 | void View2D::setWidth (const BoundedValue& width) noexcept { setWidthInternal(width); } 48 | template 49 | void View2D::setWidthInternal (const T& width) noexcept 50 | { 51 | const auto absoluteMinX = getXMin(); 52 | const auto absoluteMaxX = getXMax(); 53 | _width = width; 54 | _xPosition.setMin(absoluteMinX + _width * 0.5f); 55 | _xPosition.setMax(absoluteMaxX - _width * 0.5f); 56 | } 57 | 58 | BoundedValue View2D::getHeight () const noexcept { return _height; } 59 | void View2D::setHeight (const float height) noexcept { setHeightInternal(height); } 60 | void View2D::setHeight (const BoundedValue& height) noexcept { setHeightInternal(height); } 61 | template 62 | void View2D::setHeightInternal (const T& height) noexcept 63 | { 64 | const auto absoluteMinY = getYMin(); 65 | const auto absoluteMaxY = getYMax(); 66 | _height = height; 67 | _yPosition.setMin(absoluteMinY + _height * 0.5f); 68 | _yPosition.setMax(absoluteMaxY - _height * 0.5f); 69 | } 70 | 71 | float View2D::getXPosition () const noexcept { return _xPosition; } 72 | void View2D::setXPosition (const float xPosition) noexcept { _xPosition = xPosition; } 73 | 74 | float View2D::getYPosition () const noexcept { return _yPosition; } 75 | void View2D::setYPosition (const float yPosition) noexcept { _yPosition = yPosition; } 76 | 77 | float View2D::getXMin () const noexcept { return _xPosition.getMin() - getWidth() * 0.5f; } 78 | void View2D::setXMin (const float xMin) noexcept { _xPosition.setMin(xMin + getWidth() * 0.5f); } 79 | 80 | float View2D::getXMax () const noexcept { return _xPosition.getMax() + getWidth() * 0.5f; } 81 | void View2D::setXMax (const float xMax) noexcept { return _xPosition.setMax(xMax - getWidth() * 0.5f); } 82 | 83 | float View2D::getYMin () const noexcept { return _yPosition.getMin() - getHeight() * 0.5f; } 84 | void View2D::setYMin (const float yMin) noexcept { _yPosition.setMin(yMin + getHeight() * 0.5f); } 85 | 86 | float View2D::getYMax () const noexcept { return _yPosition.getMax() + getHeight() * 0.5f; } 87 | void View2D::setYMax (const float yMax) noexcept { _yPosition.setMax(yMax - getHeight() * 0.5f); } 88 | 89 | // getters/setters interface over. only use the getters/setters, not the impl variables from here on!!! 90 | float View2D::xMinCurrent () const noexcept { return getXPosition() - getWidth() * 0.5f; } 91 | float View2D::xMaxCurrent () const noexcept { return getXPosition() + getWidth() * 0.5f; } 92 | float View2D::yMinCurrent () const noexcept { return getYPosition() - getHeight() * 0.5f; } 93 | float View2D::yMaxCurrent () const noexcept { return getYPosition() + getHeight() * 0.5f; } 94 | 95 | float View2D::holderToViewX (float holderX) const noexcept { return (holderX - getBoundary().getLeft()) / getBoundary().width() * getWidth() + xMinCurrent()/*+ getXMin()*/; } 96 | float View2D::holderToViewY (float holderY) const noexcept { return (holderY - getBoundary().getBottom()) / getBoundary().height() * getHeight() + yMinCurrent() /*+ getYMin()*/; } 97 | Point View2D::holderToView(const Point& pt) const noexcept { return { holderToViewX(pt.x), holderToViewY(pt.y) }; } 98 | Box View2D::holderToView(const Box& b) const noexcept 99 | { 100 | return { holderToViewY(b.getTop()), holderToViewY(b.getBottom()), 101 | holderToViewX(b.getLeft()), holderToViewX(b.getRight()) }; 102 | } 103 | 104 | float View2D::viewToHolderX (float viewX) const noexcept { return (viewX - xMinCurrent()) / getWidth() * getBoundary().width() + getBoundary().getLeft(); } 105 | float View2D::viewToHolderY (float viewY) const noexcept { return (viewY - yMinCurrent()) / getHeight() * getBoundary().height() + getBoundary().getBottom(); } 106 | Point View2D::viewToHolder(const Point& pt) const noexcept { return { viewToHolderX(pt.x), viewToHolderY(pt.y) }; } 107 | Box View2D::viewToHolder(const Box& b) const noexcept 108 | { 109 | return { viewToHolderY(b.getTop()), viewToHolderY(b.getBottom()), 110 | viewToHolderX(b.getLeft()), viewToHolderX(b.getRight()) }; 111 | } 112 | 113 | //namespace View2DFuncs { 114 | // 115 | //float getX (const View2D& view, 116 | // const float x) noexcept 117 | //{ 118 | // return x * 0.5f * view.width / view.xScale + view.xOffset; 119 | //} 120 | // 121 | //float getY (const View2D& view, 122 | // const float y) noexcept 123 | //{ 124 | // return y * 0.5f * view.height / view.yScale + view.yOffset + view.height * 0.5f; 125 | //} 126 | // 127 | //Point getPoint (const View2D& view, 128 | // const Point& normalizedPoint) noexcept 129 | //{ 130 | // return { getX(view, normalizedPoint.x), getY(view, normalizedPoint.y) }; 131 | //} 132 | // 133 | //Box getBox (const View2D& view, 134 | // const Box& b) noexcept 135 | //{ 136 | // 137 | // return { getY(view, b.getTop()), getY(view, b.getBottom()), 138 | // getX(view, b.getLeft()), getX(view, b.getRight()) }; 139 | //} 140 | // 141 | //} -------------------------------------------------------------------------------- /View2D.h: -------------------------------------------------------------------------------- 1 | // 2 | // View2D.h 3 | // 4 | // Created by Andrew Barker on 9/21/16. 5 | // 6 | // 7 | 8 | #ifndef View2D_h 9 | #define View2D_h 10 | 11 | #include "../JuceLibraryCode/JuceHeader.h" 12 | #include "Box.h" 13 | #include "BoundedValue.h" 14 | 15 | //class View2DOld 16 | //{ 17 | //public: 18 | // float width = 1, height = 1; 19 | // float xOffset = 0, yOffset = 0; 20 | // float xScale = 1, yScale = 1; 21 | //}; 22 | 23 | /** represents a 2D, vertically/horizontally scrollable/zoomable view positioned within a holding box */ 24 | class View2D 25 | { 26 | public: 27 | /** initializes a zero-sized view with no holding box */ 28 | View2D () noexcept; 29 | /** initializes a view of a specfied size and position within a holding box 30 | holder: the holding box 31 | boundary: the view's boundary within the holding box in terms of the holding box's coordinates 32 | width: the horizontal width of the view including its bounds in terms of the view's coordinates 33 | height: the vertical height of the view including its bounds in terms of the view's coordinates 34 | xPosition: the horizontal position of the view, defined as the x coordinate (view's coordinates) of the view's center 35 | xMin/xMax: the minimum and maximum x coordinates (view's coordinates) visible through the view 36 | yPosition: the vertical position of the view, defined as the y coordinate (view's coordinates) of the view's center 37 | yMin/yMax: the minimum and maximum y coordinates (view's coordinates) visible through the view 38 | */ 39 | View2D (Box* holder, 40 | const Box& boundary, 41 | const BoundedValue& width, 42 | const BoundedValue& height, 43 | float xPosition, float xMin, float xMax, 44 | float yPosition, float yMin, float yMax) noexcept; 45 | 46 | /** returns a pointer to the holding box for this view */ 47 | const Box* getHolder () const noexcept; 48 | /** sets the holding box for this view to some external box 49 | NOTE: the holding box is NOT required to contain the entire boundary for the view */ 50 | void setHolder (Box* holder) noexcept; 51 | 52 | /** returns the boundary box in terms of the holding box's coordinates for this view */ 53 | Box getBoundary () const noexcept; 54 | /** sets the boundary box in terms of the holding box's coordinates for this view 55 | NOTE: the holding box is NOT required to contain the entire boundary for the view */ 56 | void setBoundary (const Box& boundary) noexcept; 57 | 58 | /** gets the width of the view (in the view's coordinates) along with the bounds for the width */ 59 | BoundedValue getWidth () const noexcept; 60 | /** sets the width of the view (in the view's coordinates) with bounds checking */ 61 | void setWidth (float width) noexcept; 62 | /** sets the width and it's bounds for the view (in the view's coordinates) */ 63 | void setWidth (const BoundedValue& width) noexcept; 64 | 65 | /** gets the height of the view (in the view's coordinates) along with the bounds for the height */ 66 | BoundedValue getHeight () const noexcept; 67 | /** sets the height of the view (in the view's coordinates) with bounds checking */ 68 | void setHeight (float height) noexcept; 69 | /** sets the height and it's bounds for the view (in the view's coordinates) */ 70 | void setHeight (const BoundedValue& height) noexcept; 71 | 72 | /** returns the view's horizontal position, defined as the x view coordinate of the view's center */ 73 | float getXPosition () const noexcept; 74 | /** sets the view's horizontal position, defined as the x view coordinate of the view's center, with bounds checking */ 75 | void setXPosition (float xPosition) noexcept; 76 | /** returns the view's vertical position, defined as the y view coordinate of the view's center */ 77 | float getYPosition () const noexcept; 78 | /** sets the view's horizontal position, defined as the y view coordinate of the view's center, with bounds checking */ 79 | void setYPosition (float yPosition) noexcept; 80 | 81 | /** returns the minimum x view coordinate visible by the view */ 82 | float getXMin () const noexcept; 83 | /** sets the minimum x view coordinate visible by the view */ 84 | void setXMin (float xMin) noexcept; 85 | /** returns the maximum x view coordinate visible by the view */ 86 | float getXMax () const noexcept; 87 | /** sets the maximum x view coordinate visible by the view */ 88 | void setXMax (float xMax) noexcept; 89 | /** returns the minimum y view coordinate visible by the view */ 90 | float getYMin () const noexcept; 91 | /** sets the minimum y view coordinate visible by the view */ 92 | void setYMin (float yMin) noexcept; 93 | /** returns the maximum y view coordinate visible by the view */ 94 | float getYMax () const noexcept; 95 | /** sets the maximum y view coordinate visible by the view */ 96 | void setYMax (float yMax) noexcept; 97 | 98 | /** returns the minimum x view coordinate visible by the view at the view's current position */ 99 | float xMinCurrent () const noexcept; 100 | /** returns the maximum x view coordinate visible by the view at the view's current position */ 101 | float xMaxCurrent () const noexcept; 102 | /** returns the minimum y view coordinate visible by the view at the view's current position */ 103 | float yMinCurrent () const noexcept; 104 | /** returns the maximum y view coordinate visible by the view at the view's current position */ 105 | float yMaxCurrent () const noexcept; 106 | 107 | /** returns a view x coordinate from a holder x coordinate */ 108 | float holderToViewX (float holderX) const noexcept; 109 | /** returns a view y coordinate from a holder y coordinate */ 110 | float holderToViewY (float holderY) const noexcept; 111 | /** returns a point in view coordinates from a holder coordinate point */ 112 | Point holderToView (const Point& pt) const noexcept; 113 | /** returns a box in view coordinates from a holder coordinate box */ 114 | Box holderToView (const Box& box) const noexcept; 115 | 116 | /** returns a holder x coordinate from a view x coordinate */ 117 | float viewToHolderX (float viewX) const noexcept; 118 | /** returns a holder y coordinate from a view y coordinate */ 119 | float viewToHolderY (float viewY) const noexcept; 120 | /** returns a point in holder coordinates from a view coordinate point */ 121 | Point viewToHolder (const Point& pt) const noexcept; 122 | /** returns a box in holder coordinates from a view coordinate box */ 123 | Box viewToHolder (const Box& box) const noexcept; 124 | 125 | private: 126 | /** these are just a nice way to reduce code duplication for the implementation */ 127 | template 128 | void setWidthInternal (const T& width) noexcept; 129 | template 130 | void setHeightInternal (const T& height) noexcept; 131 | 132 | Box *_holder, _boundary; 133 | BoundedValue _width, _height, _xPosition, _yPosition; 134 | }; 135 | 136 | 137 | //namespace View2DFuncs { 138 | // 139 | //float viewX (const View2D& view, 140 | // float normalizedX) noexcept; 141 | // 142 | //float viewY (const View2D& view, 143 | // float normalizedY) noexcept; 144 | // 145 | //Point viewPoint (const View2D& view, 146 | // const Point& normalizedPoint) noexcept; 147 | // 148 | //Box viewBox (const View2D& view, 149 | // const Box& normalizedBox) noexcept; 150 | // 151 | //float normalizedX (const View2D& view, 152 | // float viewX) noexcept; 153 | // 154 | //float normalizedY (const View2D& view, 155 | // float viewY) noexcept; 156 | // 157 | //Point normalizedPoint (const View2D& view, 158 | // const Point& viewPoint) noexcept; 159 | // 160 | //Box normalizedBox (const View2D& view, 161 | // const Box& viewBox) noexcept; 162 | // 163 | //} 164 | 165 | #endif /* View2D_h */ 166 | --------------------------------------------------------------------------------