chris@543
|
1 /*
|
chris@543
|
2 ____ _____ _ _
|
chris@543
|
3 | __ )| ____| | / \
|
chris@543
|
4 | _ \| _| | | / _ \
|
chris@543
|
5 | |_) | |___| |___ / ___ \
|
chris@543
|
6 |____/|_____|_____/_/ \_\
|
chris@543
|
7
|
chris@543
|
8 The platform for ultra-low latency audio and sensor processing
|
chris@543
|
9
|
chris@543
|
10 http://bela.io
|
chris@543
|
11
|
chris@543
|
12 A project of the Augmented Instruments Laboratory within the
|
chris@543
|
13 Centre for Digital Music at Queen Mary University of London.
|
chris@543
|
14 http://www.eecs.qmul.ac.uk/~andrewm
|
chris@543
|
15
|
chris@543
|
16 (c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
|
chris@543
|
17 Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
|
chris@543
|
18 Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
|
chris@543
|
19
|
chris@543
|
20 The Bela software is distributed under the GNU Lesser General Public License
|
chris@543
|
21 (LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
|
chris@543
|
22 */
|
chris@543
|
23
|
chris@543
|
24
|
robert@464
|
25 /*
|
robert@464
|
26 * AIR-HARP
|
robert@464
|
27 * Physically modelled strings using waveguide junctions and mass-spring-dampers
|
chris@543
|
28 * controllable using an accelerometer
|
robert@464
|
29 *
|
robert@464
|
30 * render.cpp
|
robert@464
|
31 *
|
robert@464
|
32 * Christian Heinrichs 04/2015
|
robert@464
|
33 *
|
robert@464
|
34 */
|
robert@464
|
35
|
robert@464
|
36
|
robert@464
|
37 #include "MassSpringDamper.h"
|
robert@464
|
38 #include "String.h"
|
robert@464
|
39 #include "Plectrum.h"
|
robert@464
|
40
|
robert@464
|
41 #include <Bela.h>
|
robert@464
|
42 #include <cmath>
|
robert@464
|
43 #include <stdio.h>
|
robert@464
|
44 #include <cstdlib>
|
robert@464
|
45 #include <rtdk.h>
|
robert@464
|
46
|
robert@464
|
47 #define ACCEL_BUF_SIZE 8
|
robert@464
|
48 #define NUMBER_OF_STRINGS 9
|
robert@464
|
49
|
robert@464
|
50 // PENTATONIC SCALE
|
robert@464
|
51 float gMidinotes[NUMBER_OF_STRINGS] = {40,45,50,55,57,60,62,64,67};
|
robert@464
|
52
|
robert@464
|
53 float gInverseSampleRate;
|
robert@464
|
54
|
robert@464
|
55 float out_gain = 5.0;
|
robert@464
|
56
|
robert@464
|
57 int accelPin_x = 0;
|
robert@464
|
58 int accelPin_y = 1;
|
robert@464
|
59 int accelPin_z = 2;
|
robert@464
|
60
|
robert@464
|
61 MassSpringDamper msd = MassSpringDamper(1,0.1,10);// (10,0.001,10);
|
robert@464
|
62 String strings[NUMBER_OF_STRINGS];
|
robert@464
|
63 Plectrum plectrums[NUMBER_OF_STRINGS];
|
robert@464
|
64
|
robert@464
|
65 float gPlectrumDisplacement = 0;
|
robert@464
|
66
|
robert@464
|
67 float gAccel_x[ACCEL_BUF_SIZE] = {0};
|
robert@464
|
68 int gAccelReadPtr = 0;
|
robert@464
|
69
|
robert@464
|
70 // DC BLOCK BUTTERWORTH
|
robert@464
|
71
|
chris@543
|
72 // Coefficients for 100hz high-pass centre frequency
|
robert@464
|
73 float a0_l = 0.9899759179893742;
|
robert@464
|
74 float a1_l = -1.9799518359787485;
|
robert@464
|
75 float a2_l = 0.9899759179893742;
|
robert@464
|
76 float a3_l = -1.979851353142371;
|
robert@464
|
77 float a4_l = 0.9800523188151258;
|
robert@464
|
78
|
robert@464
|
79 float a0_r = a0_l;
|
robert@464
|
80 float a1_r = a1_l;
|
robert@464
|
81 float a2_r = a2_l;
|
robert@464
|
82 float a3_r = a3_l;
|
robert@464
|
83 float a4_r = a4_l;
|
robert@464
|
84
|
robert@464
|
85 float x1_l = 0;
|
robert@464
|
86 float x2_l = 0;
|
robert@464
|
87 float y1_l = 0;
|
robert@464
|
88 float y2_l = 0;
|
robert@464
|
89
|
robert@464
|
90 float x1_r = 0;
|
robert@464
|
91 float x2_r = 0;
|
robert@464
|
92 float y1_r = 0;
|
robert@464
|
93 float y2_r = 0;
|
robert@464
|
94
|
robert@464
|
95
|
robert@464
|
96 bool setup(BelaContext *context, void *userData)
|
robert@464
|
97 {
|
robert@464
|
98
|
robert@464
|
99 gInverseSampleRate = 1.0 / context->audioSampleRate;
|
robert@464
|
100
|
robert@464
|
101 // initialise strings & plectrums
|
robert@464
|
102 for(int i=0;i<NUMBER_OF_STRINGS;i++) {
|
robert@464
|
103
|
robert@464
|
104 plectrums[i] = Plectrum();
|
robert@464
|
105 plectrums[i].setup(250,0.25,0.05);
|
robert@464
|
106
|
robert@464
|
107 strings[i] = String();
|
robert@464
|
108 strings[i].setMidinote(gMidinotes[i]);
|
robert@464
|
109
|
robert@464
|
110 float spacing = 2.0 / (NUMBER_OF_STRINGS+1);
|
robert@464
|
111
|
robert@464
|
112 strings[i].setGlobalPosition( -1 + spacing*(i+1) );
|
robert@464
|
113
|
robert@464
|
114 rt_printf("STRING %d // midinote: %f position: %f\n",i,gMidinotes[i],( -1 + spacing*(i+1) ));
|
robert@464
|
115
|
robert@464
|
116 }
|
robert@464
|
117
|
robert@464
|
118 return true;
|
robert@464
|
119 }
|
robert@464
|
120
|
robert@464
|
121 void render(BelaContext *context, void *userData)
|
robert@464
|
122 {
|
robert@464
|
123
|
robert@464
|
124 float lastAccel = 0;
|
robert@464
|
125
|
robert@464
|
126 for(int n = 0; n < context->audioFrames; n++) {
|
robert@464
|
127
|
robert@464
|
128 /*
|
robert@464
|
129 *
|
robert@464
|
130 * ACCELEROMETER DATA
|
robert@464
|
131 *
|
robert@464
|
132 */
|
robert@464
|
133
|
robert@464
|
134 // Read accelerometer data from analog input
|
robert@464
|
135 float accel_x = 0;
|
robert@464
|
136 if(n%2) {
|
robert@464
|
137 accel_x = (float)context->analogIn[(n/2)*8+accelPin_x] * 2 - 1; // 15800 - 28300 - 41500
|
robert@464
|
138 lastAccel = accel_x;
|
robert@464
|
139 } else {
|
robert@464
|
140 // grab previous value if !n%2
|
robert@464
|
141 accel_x = lastAccel;
|
robert@464
|
142 }
|
robert@464
|
143
|
robert@464
|
144 // Dead-zone avoids noise when box is lying horizontally on a surface
|
robert@464
|
145
|
robert@464
|
146 float accelDeadZone = 0.1;
|
robert@464
|
147
|
robert@464
|
148 if(accel_x <= accelDeadZone && accel_x >= -accelDeadZone)
|
robert@464
|
149 accel_x = 0;
|
robert@464
|
150
|
robert@464
|
151 // Perform smoothing (moving average) on acceleration value
|
robert@464
|
152 if(++gAccelReadPtr >= ACCEL_BUF_SIZE)
|
robert@464
|
153 gAccelReadPtr = 0;
|
robert@464
|
154 gAccel_x[gAccelReadPtr] = accel_x;
|
robert@464
|
155 float gravity = 0;
|
robert@464
|
156 for(int i=0;i<ACCEL_BUF_SIZE;i++) {
|
robert@464
|
157 gravity = gAccel_x[(gAccelReadPtr-i+ACCEL_BUF_SIZE)%ACCEL_BUF_SIZE];
|
robert@464
|
158 }
|
robert@464
|
159 gravity /= ACCEL_BUF_SIZE;
|
robert@464
|
160
|
robert@464
|
161 /*
|
robert@464
|
162 *
|
robert@464
|
163 * PHYSICS SIMULATION
|
robert@464
|
164 *
|
robert@464
|
165 */
|
robert@464
|
166
|
robert@464
|
167 // The horizontal force (which can be gravity if box is tipped on its side)
|
robert@464
|
168 // is used as the input to a Mass-Spring-Damper model
|
robert@464
|
169 // Plectrum displacement (i.e. when interacting with string) is included
|
robert@464
|
170 float massPosition = (float)msd.update(gravity - gPlectrumDisplacement);
|
robert@464
|
171
|
robert@464
|
172 float out_l = 0;
|
robert@464
|
173 float out_r = 0;
|
robert@464
|
174 // Use this parameter to quickly adjust output gain
|
robert@464
|
175 float gain = 0.0015; // 0.0015 is a good value or 12 strings
|
robert@464
|
176 gPlectrumDisplacement = 0;
|
robert@464
|
177
|
robert@464
|
178 for(int s=0;s<NUMBER_OF_STRINGS;s++) {
|
robert@464
|
179
|
robert@464
|
180 float stringPosition = strings[s].getGlobalPosition();
|
robert@464
|
181
|
robert@464
|
182 float plectrumForce = plectrums[s].update(massPosition, stringPosition);
|
robert@464
|
183 gPlectrumDisplacement += strings[s].getPlectrumDisplacement();
|
robert@464
|
184
|
robert@464
|
185 // calculate panning based on string position (-1->left / 1->right)
|
robert@464
|
186 float panRight = map(stringPosition,1,-1,0.1,1);
|
robert@464
|
187 float panLeft = map(stringPosition,-1,1,0.1,1);
|
robert@464
|
188 panRight *= panRight;
|
robert@464
|
189 panLeft *= panLeft;
|
robert@464
|
190
|
robert@464
|
191 float out = strings[s].update(plectrumForce)*gain;
|
robert@464
|
192
|
robert@464
|
193 out_l += out*panLeft;
|
robert@464
|
194 out_r += out*panRight;
|
robert@464
|
195
|
robert@464
|
196 }
|
robert@464
|
197
|
robert@464
|
198 // APPLY DC-BLOCK FILTER TO OUTPUTS
|
robert@464
|
199
|
robert@464
|
200 // LEFT CHANNEL
|
robert@464
|
201 float temp_in = out_l;
|
robert@464
|
202 /* compute result */
|
robert@464
|
203 out_l = a0_l * out_l + a1_l * x1_l + a2_l * x2_l - a3_l * y1_l - a4_l * y2_l;
|
robert@464
|
204 /* shift x1 to x2, sample to x1 */
|
robert@464
|
205 x2_l = x1_l;
|
robert@464
|
206 x1_l = temp_in;
|
robert@464
|
207 /* shift y1 to y2, result to y1 */
|
robert@464
|
208 y2_l = y1_l;
|
robert@464
|
209 y1_l = out_l;
|
robert@464
|
210
|
robert@464
|
211 // RIGHT CHANNEL
|
robert@464
|
212 temp_in = out_r;
|
robert@464
|
213 /* compute result */
|
robert@464
|
214 out_r = a0_r * out_r + a1_r * x1_r + a2_r * x2_r - a3_r * y1_r - a4_r * y2_r;
|
robert@464
|
215 /* shift x1 to x2, sample to x1 */
|
robert@464
|
216 x2_r = x1_r;
|
robert@464
|
217 x1_r = temp_in;
|
robert@464
|
218 /* shift y1 to y2, result to y1 */
|
robert@464
|
219 y2_r = y1_r;
|
robert@464
|
220 y1_r = out_r;
|
robert@464
|
221
|
chris@543
|
222 context->audioOut[n * context->audioOutChannels + 1] = out_l * out_gain;
|
chris@543
|
223 context->audioOut[n * context->audioOutChannels + 0] = out_r * out_gain;
|
robert@464
|
224
|
robert@464
|
225 }
|
robert@464
|
226
|
robert@464
|
227 }
|
robert@464
|
228
|
robert@464
|
229
|
robert@464
|
230 // cleanup_render() is called once at the end, after the audio has stopped.
|
robert@464
|
231 // Release any resources that were allocated in initialise_render().
|
robert@464
|
232
|
robert@464
|
233 void cleanup(BelaContext *context, void *userData)
|
robert@464
|
234 {
|
robert@464
|
235
|
robert@464
|
236 }
|