forked from PlummersSoftwareLLC/NightDriverStrip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaudio.cpp
330 lines (273 loc) · 10.3 KB
/
audio.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//+--------------------------------------------------------------------------
//
// File: audio.cpp
//
// NightDriverStrip - (c) 2018 Plummer's Software LLC. All Rights Reserved.
//
// This file is part of the NightDriver software project.
//
// NightDriver is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NightDriver is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nightdriver. It is normally found in copying.txt
// If not, see <https://www.gnu.org/licenses/>.
//
//
// Description:
//
// Source files for NightDriverStrip's audio processing
//
// History: Apr-13-2019 Davepl Created for NightDriverStrip
//
//---------------------------------------------------------------------------
#include "globals.h"
#if ENABLE_AUDIO
#include <esp_task_wdt.h>
#include "soundanalyzer.h"
#if ENABLE_VICE_SERVER
#include "network.h"
#endif
// AudioSamplerTaskEntry
// A background task that samples audio, computes the VU, stores it for effect use, etc.
void IRAM_ATTR AudioSamplerTaskEntry(void *)
{
debugI(">>> Sampler Task Started");
// Enable microphone input
pinMode(INPUT_PIN, INPUT);
g_Analyzer.SampleBufferInitI2S();
for (;;)
{
uint64_t lastFrame = millis();
g_Analyzer.RunSamplerPass();
g_Analyzer.UpdatePeakData();
g_Analyzer.DecayPeaks();
// VURatio with a fadeout
static float lastVU = 0.0;
constexpr auto VU_DECAY_PER_SECOND = 4.0;
if (g_Analyzer._VURatio > lastVU)
lastVU = g_Analyzer._VURatio;
else
lastVU -= (millis() - lastFrame) / 1000.0 * VU_DECAY_PER_SECOND;
lastVU = std::max(lastVU, 0.0f);
lastVU = std::min(lastVU, 2.0f);
g_Analyzer._VURatioFade = lastVU;
// Instantaneous VURatio
assert(g_Analyzer._PeakVU >= g_Analyzer._MinVU);
g_Analyzer._VURatio = (g_Analyzer._PeakVU == g_Analyzer._MinVU) ?
0.0 :
(g_Analyzer._VU - g_Analyzer._MinVU) / std::max(g_Analyzer._PeakVU - g_Analyzer._MinVU, (float) MIN_VU) * 2.0f;
debugV("VU: %f\n", g_Analyzer._VU);
debugV("PeakVU: %f\n", g_Analyzer._PeakVU);
debugV("MinVU: %f\n", g_Analyzer._MinVU);
debugV("VURatio: %f\n", g_Analyzer._VURatio);
// Delay enough time to yield 60fps max
// We wait a minimum even if busy so we don't Bogart the CPU
unsigned long elapsed = millis() - lastFrame;
constexpr auto kMaxFPS = 60;
const auto targetDelay = PERIOD_FROM_FREQ(kMaxFPS) * MILLIS_PER_SECOND / MICROS_PER_SECOND;
delay(max(1.0, targetDelay - elapsed));
g_Analyzer._AudioFPS = FPS(lastFrame, millis());
}
}
#if ENABLE_AUDIOSERIAL
// AudioSerial
//
// There is a project at https://github.com/PlummersSoftwareLLC/PETRock which allows you to connect the ESP to the USERPORT
// of a Commodore 64 or PET (40 or 80 cols) and display the spectrum visualization on the computer's screen in assembly
// language.
//
// To support this, when enabled, this task repeatedly sends out copies of the latest data peaks, scaled to 20, which is
// the max height of the PET/C64 spectrum bar. This should manage around 24 fps at 2400baud.
//
// The VICESocketServer acts as a server that sends serial data to the socket on the emulator machine to emulate serial data.
#include <fcntl.h>
class VICESocketServer
{
private:
int _port;
int _server_fd;
struct sockaddr_in _address;
std::unique_ptr<uint8_t[]> _pBuffer;
std::unique_ptr<uint8_t[]> _abOutputBuffer;
const int BUFFER_SIZE = 255;
public:
VICESocketServer(int port) : _port(port),
_server_fd(0)
{
_abOutputBuffer = std::make_unique<uint8_t[]>(BUFFER_SIZE);
memset(&_address, 0, sizeof(_address));
}
void release()
{
_pBuffer.release();
if (_server_fd)
{
close(_server_fd);
_server_fd = 0;
}
}
bool begin()
{
_pBuffer = std::make_unique<uint8_t[]>(BUFFER_SIZE);
// Creating socket file descriptor
if ((_server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
debugW("socket error\n");
release();
return false;
}
SetSocketBlockingEnabled(_server_fd, false);
memset(&_address, 0, sizeof(_address));
_address.sin_family = AF_INET;
_address.sin_addr.s_addr = INADDR_ANY;
_address.sin_port = htons(_port);
if (bind(_server_fd, (struct sockaddr *)&_address, sizeof(_address)) < 0) // Bind socket to port
{
debugW("bind failed\n");
release();
return false;
}
if (listen(_server_fd, 6) < 0) // Start listening for connections
{
debugW("listen failed\n");
release();
return false;
}
return true;
}
int CheckForConnection()
{
int new_socket = -1;
// Accept a new incoming connnection
int addrlen = sizeof(_address);
struct timeval to;
to.tv_sec = 1;
to.tv_usec = 0;
if ((new_socket = accept(_server_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen)) < 0)
{
return -1;
}
if (setsockopt(new_socket, SOL_SOCKET, SO_SNDTIMEO, &to, sizeof(to)) < 0)
{
debugW("Unable to set send timeout on socket!");
close(new_socket);
return false;
}
if (setsockopt(new_socket, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)) < 0)
{
debugW("Unable to set receive timeout on socket!");
close(new_socket);
return false;
}
Serial.println("Accepted new VICE Client!");
return new_socket;
}
bool SendPacketToVICE(int socket, void *pData, size_t cbSize)
{
// Send data to the emulator's virtual serial port
if (cbSize != write(socket, pData, cbSize))
{
debugW("Could not write to socket\n");
return false;
}
return true;
}
};
struct SerialData
{
uint8_t header[1]; // 'DP' in the high and low nibbles
uint8_t vu; // VU meter, 0-31
uint8_t peaks[8]; // 16 4-bit values representing the band peaks, 0-31 (constrained to 0-19)
uint8_t tail; // (0x0D)
};
void IRAM_ATTR AudioSerialTaskEntry(void *)
{
// SoftwareSerial Serial64(SERIAL_PINRX, SERIAL_PINTX);
debugI(">>> Sampler Task Started");
SoundAnalyzer Analyzer;
#if ENABLE_VICE_SERVER
VICESocketServer socketServer(NetworkPort::VICESocketServer);
if (!socketServer.begin())
{
debugE("Unable to start socket server on port %u for VICE!", NetworkPort::VICESocketServer);
}
else
{
debugW("Started socket server for VICE on port %u!", NetworkPort::VICESocketServer);
}
#endif
Serial2.begin(2400, SERIAL_8N1, SERIAL_PINRX, SERIAL_PINTX);
debugI(" Opened Serial2 on pins %d,%d\n", SERIAL_PINRX, SERIAL_PINTX);
int socket = -1;
for (;;)
{
unsigned long startTime = millis();
SerialData data;
const int MAXPET = 16; // Highest value that the PET can display in a bar
data.header[0] = ((3 << 4) + 15);
// Change the 0-2 range of the VURatioFade to 0-16 for the PET
data.vu = (byte)((g_Analyzer._VURatioFade / 2.0f) * (float)MAXPET);
// We treat 0 as a NUL terminator and so we don't want to send it in-band. Since a band has to be 2 before
// it is displayed, this has no effect on the display
for (int i = 0; i < 8; i++)
{
int iBand = map(i, 0, 7, 0, NUM_BANDS - 2);
uint8_t low = g_Analyzer._peak2Decay[iBand] * MAXPET;
uint8_t high = g_Analyzer._peak2Decay[iBand + 1] * MAXPET;
data.peaks[i] = (high << 4) + low;
}
data.tail = 00;
if (Serial2.availableForWrite())
{
Serial2.write((uint8_t *)&data, sizeof(data));
// Serial2.flush(true);
static int lastFrame = millis();
g_Analyzer._serialFPS = FPS(lastFrame, millis());
lastFrame = millis();
}
/* PETROCK no longer sends these confirmation stars, but we could add it back...
// When the CBM processes a packet, it sends us a * to ACK. We count those to determine the number
// of packets per second being processed
while (Serial2.available() > 0)
{
char read = Serial2.read();
Serial.print(read);
if (read == '*')
{
}
}
*/
#if ENABLE_VICE_SERVER
// If we have a socket open, send our packet to its virtual serial port now as well.
if (socket < 0)
socket = socketServer.CheckForConnection();
if (socket >= 0)
{
if (!socketServer.SendPacketToVICE(socket, (uint8_t *)&data, sizeof(data)))
{
// If anything goes wrong, we close the socket so it can accept new incoming attempts
debugI("Error on socket, so closing");
close(socket);
socket = -1;
}
}
#endif
auto targetFPS = 480.0 / sizeof(data);
auto targetElapsed = 1.0 / targetFPS * 1000;
auto elapsed = millis() - startTime;
if (targetElapsed > elapsed)
delay(targetElapsed - elapsed);
else
delay(1);
}
}
#endif // ENABLE_AUDIOSERIAL
#endif