-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcursedAudioPlayer.cpp
More file actions
239 lines (209 loc) · 8.1 KB
/
Copy pathcursedAudioPlayer.cpp
File metadata and controls
239 lines (209 loc) · 8.1 KB
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
#include <cstring>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include <deque>
#include <complex>
#include <chrono>
#include <filesystem>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <sstream>
#include "AudioQueue.h"
#include "AudioSystem.h"
#include "CursedLayout.h"
#include "features/FFT.h"
#include "features/CQT.h"
#include <mutex>
using namespace std;
// --- Utility ---
std::string formatDuration(const char* prefix, double seconds) {
const auto time = static_cast<long long>(seconds);
std::ostringstream out;
out << prefix << " " << time / 60 << ':' << std::setfill('0') << std::setw(2) << time % 60;
return out.str();
}
// --- Main ---
int runPlayer(int argc, char **argv) {
if (argc < 2 || std::string(argv[1]) == "--help") {
std::cout << "Usage: cursedap <audio_file> [chunk_size]\n"
<< " <audio_file> Path to the audio file (required)\n"
<< " [chunk_size] Optional: override default chunk size (default = 1024)\n";
return 0;
}
std::string filename = argv[1];
if (!std::filesystem::exists(filename)) {
std::cerr << "Error: File does not exist -> " << filename << std::endl;
return 1;
}
size_t chunkSize = 1024; // Default chunk size
if (argc >= 3) {
try {
const std::string value = argv[2];
size_t parsed = 0;
if (value.empty() || value.front() == '-') throw std::invalid_argument("Invalid chunk size");
chunkSize = std::stoul(value, &parsed);
if (parsed != value.size()) throw std::invalid_argument("Unexpected characters in chunk size");
if (chunkSize < 64 || chunkSize > (1U << 20) || (chunkSize & (chunkSize - 1)) != 0) {
throw std::invalid_argument("Chunk size must be a power of 2 between 64 and 1048576");
}
} catch (const std::exception& e) {
std::cerr << "Invalid chunk size: " << argv[2] << "\n"
<< "Reason: " << e.what() << "\n";
return 1;
}
}
AudioSystem audioSystem(filename, chunkSize);
FFT<float> fft(audioSystem.getChunkSize());
CQT cqt(audioSystem.getInfo().sample_rate, std::min(55.0f, audioSystem.getInfo().sample_rate / 8.0f));
std::vector<float> cqtData(12, 0.0f);
std::mutex cqtMutex;
// Topics and Queues
std::vector<float> spectrum(audioSystem.getChunkSize() / 2);
std::vector<float> window(audioSystem.getChunkSize());
Topic<float*, size_t> ampTopic("amplitude");
Topic<std::complex<float>*, size_t> specTopic("spectrum");
Topic<float*, size_t> cqtTopic("constant-q");
AudioQueue<float> ampQueue(32, audioSystem.getChunkSize() / 32);
AudioQueue<float> specQueue(32, audioSystem.getChunkSize() / 64);
AudioQueue<float> hexQueue(12, audioSystem.getChunkSize() / 12);
struct PlaybackGuard {
AudioSystem& system;
~PlaybackGuard() { system.setIsPlaying(false); }
} playbackGuard{audioSystem};
// --- Data Flow Setup ---
ampTopic.subscribe([&](float* buffer, size_t frames) {
ampQueue.pushBuffer(buffer, frames);
hexQueue.pushBuffer(buffer, frames);
for (size_t i = 0; i < window.size(); ++i) {
const float sample = i < frames ? buffer[i] : 0.0f;
window[i] = sample * 0.5f * (1.0f - std::cos(2.0f * std::numbers::pi_v<float> * i / (window.size() - 1)));
}
fft.fill(window.data(), window.size());
fft.compute();
fft.publishResult();
cqt.process(buffer, frames);
});
specTopic.subscribe([&](std::complex<float>* buffer, size_t frames) {
const size_t bins = std::min(spectrum.size(), frames / 2);
for (size_t i = 0; i < bins; ++i)
spectrum[i] = std::abs(buffer[i]) * 4.0f / audioSystem.getChunkSize();
specQueue.pushBuffer(spectrum.data(), bins);
});
cqtTopic.subscribe([&](float* data, size_t bins) {
std::lock_guard lock(cqtMutex);
CQT::foldPitchClasses(data, bins, cqt.getMinFrequency(), cqtData.data(), cqt.getBinsPerOctave());
});
cqt.subscribe(cqtTopic);
audioSystem.subscribe(ampTopic);
fft.subscribe(specTopic);
auto resetVisuals = [&]() {
cqt.reset();
ampQueue.reset();
specQueue.reset();
hexQueue.reset();
std::lock_guard lock(cqtMutex);
std::fill(cqtData.begin(), cqtData.end(), 0.0f);
};
// --- UI Callback Setup ---
auto playbackToggle = [&](bool play) {
if (play && audioSystem.getCurrentFrame() == audioSystem.getTotalFrames()) resetVisuals();
audioSystem.setIsPlaying(play);
};
auto setSeekRatio = [&](double ratio) {
auto totalMs = audioSystem.getTotalDurationMs().count();
audioSystem.seek(std::chrono::milliseconds(static_cast<long long>(totalMs * ratio)));
resetVisuals();
};
auto getProgressRatio = [&]() {
const auto total = audioSystem.getTotalFrames();
return total ? static_cast<double>(audioSystem.getCurrentFrame()) / total : 0.0;
};
auto seekRelative = [&](int seconds, bool forward) {
auto currentMs = audioSystem.getElapsedTimeMs().count();
auto totalMs = audioSystem.getTotalDurationMs().count();
long long target = forward
? std::min(currentMs + seconds * 1000L, totalMs)
: std::max(currentMs - seconds * 1000L, 0L);
audioSystem.seek(std::chrono::milliseconds(target));
resetVisuals();
};
// Volume and Playback Controls
auto setVolume = [&](double v) { audioSystem.setVolume(v); };
auto getVolume = [&]() { return audioSystem.getVolume(); };
auto setMute = [&](bool mute) { audioSystem.setIsMute(mute); };
auto getMute = [&]() { return audioSystem.getIsMute(); };
auto isPlaying = [&]() { return audioSystem.getIsPlaying(); };
// Time Display
auto getElapsedStr = [&]() {
return formatDuration("POS", audioSystem.getElapsedTimeMs().count() / 1000.0);
};
auto getTotalStr = [&]() {
return formatDuration("LEN", audioSystem.getTotalDurationMs().count() / 1000.0);
};
// Spectrum and Amplitude Data
auto acquireAmplitude = [&](float* out) {
ampQueue.fullPeek(out);
};
auto acquireSpectrum = [&](float* out) {
specQueue.fullPeek(out);
for (size_t i = 0; i < specQueue.size(); ++i)
out[i] = 20.0f * std::log10(std::max(out[i], 0.0001f));
};
auto acquireCqt = [&](float* out) {
std::lock_guard lock(cqtMutex);
for (size_t i = 0; i < cqtData.size(); ++i)
out[i] = 20.0f * std::log10(std::max(cqtData[i], 0.0001f));
};
auto audioInfo = [&]() -> AudioSystemInfo {
return audioSystem.getInfo();
};
// Hex Stream Viewer
auto hexStreamData = [&]() -> std::deque<std::pair<size_t, uint32_t>> {
std::deque<std::pair<size_t, uint32_t>> buffer;
const auto samples = hexQueue.peek();
const size_t current = audioSystem.getCurrentFrame();
const size_t stride = audioSystem.getChunkSize() / 12;
const size_t first = current > samples.size() * stride ? current - samples.size() * stride : 0;
size_t index = 0;
for (const auto sample : samples) {
uint32_t asInt;
std::memcpy(&asInt, &sample, sizeof(float));
buffer.emplace_back(first + index++ * stride, asInt);
}
return buffer;
};
// --- Launch UI ---
CursedLayout layout{
setSeekRatio,
playbackToggle,
getProgressRatio,
isPlaying,
[&](int sec) { seekRelative(sec, false); },
[&](int sec) { seekRelative(sec, true); },
setVolume,
getVolume,
setMute,
getMute,
getElapsedStr,
getTotalStr,
acquireAmplitude,
acquireSpectrum,
acquireCqt,
audioInfo,
hexStreamData
};
layout.mount();
layout.run();
return 0;
}
int main(int argc, char **argv) {
try {
return runPlayer(argc, argv);
} catch (const std::exception& e) {
std::cerr << "cursedap: " << e.what() << '\n';
return 1;
}
}